@@ -16,6 +16,118 @@ import SwiftParser
1616import SwiftSyntax
1717
1818class 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+
19131 /// Represents a file to be processed by the frontend and any file-specific options associated
20132 /// with it.
21133 final class FileToProcess {
@@ -73,8 +185,8 @@ class Frontend {
73185 /// Options that apply during formatting or linting.
74186 final let lintFormatOptions : LintFormatOptions
75187
76- /// Loads formatter configuration files .
77- final var configurationLoader = ConfigurationLoader ( )
188+ /// The provider for formatter configurations .
189+ final var configurationProvider : ConfigurationProvider
78190
79191 /// Advanced options that are useful for developing/debugging but otherwise not meant for general
80192 /// use.
@@ -95,8 +207,8 @@ class Frontend {
95207 self . diagnosticPrinter = StderrDiagnosticPrinter (
96208 colorMode: lintFormatOptions. colorDiagnostics. map { $0 ? . on : . off } ?? . auto
97209 )
98- self . diagnosticsEngine =
99- DiagnosticsEngine ( diagnosticsHandlers : [ diagnosticPrinter . printDiagnostic ] )
210+ self . diagnosticsEngine = DiagnosticsEngine ( diagnosticsHandlers : [ diagnosticPrinter . printDiagnostic ] )
211+ self . configurationProvider = ConfigurationProvider ( diagnosticsEngine : self . diagnosticsEngine )
100212 }
101213
102214 /// Runs the linter or formatter over the inputs.
@@ -142,9 +254,9 @@ class Frontend {
142254 let assumedUrl = lintFormatOptions. assumeFilename. map ( URL . init ( fileURLWithPath: ) )
143255
144256 guard
145- let configuration = configuration (
146- fromPathOrString : configurationOptions. configuration,
147- orInferredFromSwiftFileAt : assumedUrl
257+ let configuration = configurationProvider . provide (
258+ forConfigPathOrString : configurationOptions. configuration,
259+ orForSwiftFileAt : assumedUrl
148260 )
149261 else {
150262 // Already diagnosed in the called method.
@@ -193,9 +305,9 @@ class Frontend {
193305 }
194306
195307 guard
196- let configuration = configuration (
197- fromPathOrString : configurationOptions. configuration,
198- orInferredFromSwiftFileAt : url
308+ let configuration = configurationProvider . provide (
309+ forConfigPathOrString : configurationOptions. configuration,
310+ orForSwiftFileAt : url
199311 )
200312 else {
201313 // Already diagnosed in the called method.
@@ -210,98 +322,4 @@ class Frontend {
210322 )
211323 }
212324
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- }
307325}
0 commit comments