@@ -16,6 +16,118 @@ import SwiftParser
16
16
import SwiftSyntax
17
17
18
18
class Frontend {
19
+ /// Provides formatter configurations for given `.swift` source files, configuration files or configuration strings.
20
+ struct ConfigurationProvider {
21
+ /// Loads formatter configuration files and chaches them in memory.
22
+ private var configurationLoader : ConfigurationLoader = ConfigurationLoader ( )
23
+
24
+ /// The diagnostic engine to which warnings and errors will be emitted.
25
+ private let diagnosticsEngine : DiagnosticsEngine
26
+
27
+ /// Creates a new instance with the given options.
28
+ ///
29
+ /// - Parameter diagnosticsEngine: The diagnostic engine to which warnings and errors will be emitted.
30
+ init ( diagnosticsEngine: DiagnosticsEngine ) {
31
+ self . diagnosticsEngine = diagnosticsEngine
32
+ }
33
+
34
+ /// Checks if all the rules in the given configuration are supported by the registry.
35
+ ///
36
+ /// If there are any rules that are not supported, they are emitted as a warning.
37
+ private func checkForUnrecognizedRules( in configuration: Configuration ) {
38
+ // If any rules in the decoded configuration are not supported by the registry,
39
+ // emit them into the diagnosticsEngine as warnings.
40
+ // That way they will be printed out, but we'll continue execution on the valid rules.
41
+ let invalidRules = configuration. rules. filter { !RuleRegistry. rules. keys. contains ( $0. key) }
42
+ for rule in invalidRules {
43
+ diagnosticsEngine. emitWarning ( " Configuration contains an unrecognized rule: \( rule. key) " , location: nil )
44
+ }
45
+ }
46
+
47
+ /// Returns the configuration that applies to the given `.swift` source file, when an explicit
48
+ /// configuration path is also perhaps provided.
49
+ ///
50
+ /// This method also checks for unrecognized rules within the configuration.
51
+ ///
52
+ /// - Parameters:
53
+ /// - pathOrString: A string containing either the path to a configuration file that will be
54
+ /// loaded, JSON configuration data directly, or `nil` to try to infer it from
55
+ /// `swiftFileURL`.
56
+ /// - swiftFileURL: The path to a `.swift` file, which will be used to infer the path to the
57
+ /// configuration file if `configurationFilePath` is nil.
58
+ ///
59
+ /// - Returns: If successful, the returned configuration is the one loaded from `pathOrString` if
60
+ /// it was provided, or by searching in paths inferred by `swiftFileURL` if one exists, or the
61
+ /// default configuration otherwise. If an error occurred when reading the configuration, a
62
+ /// diagnostic is emitted and `nil` is returned. If neither `pathOrString` nor `swiftFileURL`
63
+ /// were provided, a default `Configuration()` will be returned.
64
+ mutating func provide(
65
+ forConfigPathOrString pathOrString: String ? ,
66
+ orForSwiftFileAt swiftFileURL: URL ?
67
+ ) -> Configuration ? {
68
+ if let pathOrString = pathOrString {
69
+ // If an explicit configuration file path was given, try to load it and fail if it cannot be
70
+ // loaded. (Do not try to fall back to a path inferred from the source file path.)
71
+ let configurationFileURL = URL ( fileURLWithPath: pathOrString)
72
+ do {
73
+ let configuration = try configurationLoader. configuration ( at: configurationFileURL)
74
+ self . checkForUnrecognizedRules ( in: configuration)
75
+ return configuration
76
+ } catch {
77
+ // If we failed to load this from the path, try interpreting the string as configuration
78
+ // data itself because the user might have written something like `--configuration '{...}'`,
79
+ let data = pathOrString. data ( using: . utf8) !
80
+ if let configuration = try ? Configuration ( data: data) {
81
+ return configuration
82
+ }
83
+
84
+ // Fail if the configuration flag was neither a valid file path nor valid configuration
85
+ // data.
86
+ diagnosticsEngine. emitError ( " Unable to read configuration: \( error. localizedDescription) " )
87
+ return nil
88
+ }
89
+ }
90
+
91
+ // If no explicit configuration file path was given but a `.swift` source file path was given,
92
+ // then try to load the configuration by inferring it based on the source file path.
93
+ if let swiftFileURL = swiftFileURL {
94
+ do {
95
+ if let configuration = try configurationLoader. configuration ( forPath: swiftFileURL) {
96
+ self . checkForUnrecognizedRules ( in: configuration)
97
+ return configuration
98
+ }
99
+ // Fall through to the default return at the end of the function.
100
+ } catch {
101
+ diagnosticsEngine. emitError (
102
+ " Unable to read configuration for \( swiftFileURL. path) : \( error. localizedDescription) "
103
+ )
104
+ return nil
105
+ }
106
+ } else {
107
+ // If reading from stdin and no explicit configuration file was given,
108
+ // walk up the file tree from the cwd to find a config.
109
+
110
+ let cwd = URL ( fileURLWithPath: FileManager . default. currentDirectoryPath)
111
+ // Definitely a Swift file. Definitely not a directory. Shhhhhh.
112
+ do {
113
+ if let configuration = try configurationLoader. configuration ( forPath: cwd) {
114
+ self . checkForUnrecognizedRules ( in: configuration)
115
+ return configuration
116
+ }
117
+ } catch {
118
+ diagnosticsEngine. emitError (
119
+ " Unable to read configuration for \( cwd) : \( error. localizedDescription) "
120
+ )
121
+ return nil
122
+ }
123
+ }
124
+
125
+ // An explicit configuration has not been given, and one cannot be found.
126
+ // Return the default configuration.
127
+ return Configuration ( )
128
+ }
129
+ }
130
+
19
131
/// Represents a file to be processed by the frontend and any file-specific options associated
20
132
/// with it.
21
133
final class FileToProcess {
@@ -73,8 +185,8 @@ class Frontend {
73
185
/// Options that apply during formatting or linting.
74
186
final let lintFormatOptions : LintFormatOptions
75
187
76
- /// Loads formatter configuration files .
77
- final var configurationLoader = ConfigurationLoader ( )
188
+ /// The provider for formatter configurations .
189
+ final var configurationProvider : ConfigurationProvider
78
190
79
191
/// Advanced options that are useful for developing/debugging but otherwise not meant for general
80
192
/// use.
@@ -95,8 +207,8 @@ class Frontend {
95
207
self . diagnosticPrinter = StderrDiagnosticPrinter (
96
208
colorMode: lintFormatOptions. colorDiagnostics. map { $0 ? . on : . off } ?? . auto
97
209
)
98
- self . diagnosticsEngine =
99
- DiagnosticsEngine ( diagnosticsHandlers : [ diagnosticPrinter . printDiagnostic ] )
210
+ self . diagnosticsEngine = DiagnosticsEngine ( diagnosticsHandlers : [ diagnosticPrinter . printDiagnostic ] )
211
+ self . configurationProvider = ConfigurationProvider ( diagnosticsEngine : self . diagnosticsEngine )
100
212
}
101
213
102
214
/// Runs the linter or formatter over the inputs.
@@ -142,9 +254,9 @@ class Frontend {
142
254
let assumedUrl = lintFormatOptions. assumeFilename. map ( URL . init ( fileURLWithPath: ) )
143
255
144
256
guard
145
- let configuration = configuration (
146
- fromPathOrString : configurationOptions. configuration,
147
- orInferredFromSwiftFileAt : assumedUrl
257
+ let configuration = configurationProvider . provide (
258
+ forConfigPathOrString : configurationOptions. configuration,
259
+ orForSwiftFileAt : assumedUrl
148
260
)
149
261
else {
150
262
// Already diagnosed in the called method.
@@ -193,9 +305,9 @@ class Frontend {
193
305
}
194
306
195
307
guard
196
- let configuration = configuration (
197
- fromPathOrString : configurationOptions. configuration,
198
- orInferredFromSwiftFileAt : url
308
+ let configuration = configurationProvider . provide (
309
+ forConfigPathOrString : configurationOptions. configuration,
310
+ orForSwiftFileAt : url
199
311
)
200
312
else {
201
313
// Already diagnosed in the called method.
@@ -210,98 +322,4 @@ class Frontend {
210
322
)
211
323
}
212
324
213
- /// Returns the configuration that applies to the given `.swift` source file, when an explicit
214
- /// configuration path is also perhaps provided.
215
- ///
216
- /// This method also checks for unrecognized rules within the configuration.
217
- ///
218
- /// - Parameters:
219
- /// - pathOrString: A string containing either the path to a configuration file that will be
220
- /// loaded, JSON configuration data directly, or `nil` to try to infer it from
221
- /// `swiftFilePath`.
222
- /// - swiftFilePath: The path to a `.swift` file, which will be used to infer the path to the
223
- /// configuration file if `configurationFilePath` is nil.
224
- ///
225
- /// - Returns: If successful, the returned configuration is the one loaded from `pathOrString` if
226
- /// it was provided, or by searching in paths inferred by `swiftFilePath` if one exists, or the
227
- /// default configuration otherwise. If an error occurred when reading the configuration, a
228
- /// diagnostic is emitted and `nil` is returned. If neither `pathOrString` nor `swiftFilePath`
229
- /// were provided, a default `Configuration()` will be returned.
230
- private func configuration(
231
- fromPathOrString pathOrString: String ? ,
232
- orInferredFromSwiftFileAt swiftFileURL: URL ?
233
- ) -> Configuration ? {
234
- if let pathOrString = pathOrString {
235
- // If an explicit configuration file path was given, try to load it and fail if it cannot be
236
- // loaded. (Do not try to fall back to a path inferred from the source file path.)
237
- let configurationFileURL = URL ( fileURLWithPath: pathOrString)
238
- do {
239
- let configuration = try configurationLoader. configuration ( at: configurationFileURL)
240
- self . checkForUnrecognizedRules ( in: configuration)
241
- return configuration
242
- } catch {
243
- // If we failed to load this from the path, try interpreting the string as configuration
244
- // data itself because the user might have written something like `--configuration '{...}'`,
245
- let data = pathOrString. data ( using: . utf8) !
246
- if let configuration = try ? Configuration ( data: data) {
247
- return configuration
248
- }
249
-
250
- // Fail if the configuration flag was neither a valid file path nor valid configuration
251
- // data.
252
- diagnosticsEngine. emitError ( " Unable to read configuration: \( error. localizedDescription) " )
253
- return nil
254
- }
255
- }
256
-
257
- // If no explicit configuration file path was given but a `.swift` source file path was given,
258
- // then try to load the configuration by inferring it based on the source file path.
259
- if let swiftFileURL = swiftFileURL {
260
- do {
261
- if let configuration = try configurationLoader. configuration ( forPath: swiftFileURL) {
262
- self . checkForUnrecognizedRules ( in: configuration)
263
- return configuration
264
- }
265
- // Fall through to the default return at the end of the function.
266
- } catch {
267
- diagnosticsEngine. emitError (
268
- " Unable to read configuration for \( swiftFileURL. path) : \( error. localizedDescription) "
269
- )
270
- return nil
271
- }
272
- } else {
273
- // If reading from stdin and no explicit configuration file was given,
274
- // walk up the file tree from the cwd to find a config.
275
-
276
- let cwd = URL ( fileURLWithPath: FileManager . default. currentDirectoryPath)
277
- // Definitely a Swift file. Definitely not a directory. Shhhhhh.
278
- do {
279
- if let configuration = try configurationLoader. configuration ( forPath: cwd) {
280
- self . checkForUnrecognizedRules ( in: configuration)
281
- return configuration
282
- }
283
- } catch {
284
- diagnosticsEngine. emitError (
285
- " Unable to read configuration for \( cwd) : \( error. localizedDescription) "
286
- )
287
- return nil
288
- }
289
- }
290
-
291
- // An explicit configuration has not been given, and one cannot be found.
292
- // Return the default configuration.
293
- return Configuration ( )
294
- }
295
-
296
- /// Checks if all the rules in the given configuration are supported by the registry.
297
- /// If there are any rules that are not supported, they are emitted as a warning.
298
- private func checkForUnrecognizedRules( in configuration: Configuration ) {
299
- // If any rules in the decoded configuration are not supported by the registry,
300
- // emit them into the diagnosticsEngine as warnings.
301
- // That way they will be printed out, but we'll continue execution on the valid rules.
302
- let invalidRules = configuration. rules. filter { !RuleRegistry. rules. keys. contains ( $0. key) }
303
- for rule in invalidRules {
304
- diagnosticsEngine. emitWarning ( " Configuration contains an unrecognized rule: \( rule. key) " , location: nil )
305
- }
306
- }
307
325
}
0 commit comments