Skip to content

Commit 805bdec

Browse files
committed
Do not use dummy frontend for effective config printing
1 parent caa6f78 commit 805bdec

File tree

5 files changed

+147
-155
lines changed

5 files changed

+147
-155
lines changed

Sources/SwiftFormat/API/Configuration+Dump.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ extension Configuration {
1919

2020
do {
2121
let encoder = JSONEncoder()
22-
encoder.outputFormatting = [.prettyPrinted]
23-
if #available(macOS 10.13, *) {
24-
encoder.outputFormatting.insert(.sortedKeys)
25-
}
26-
22+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
2723
data = try encoder.encode(self)
2824
} catch {
2925
throw SwiftFormatError.configurationDumpFailed("\(error)")

Sources/swift-format/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ add_executable(swift-format
1212
SwiftFormatCommand.swift
1313
VersionOptions.swift
1414
Frontend/ConfigurationLoader.swift
15-
Frontend/DumpConfigurationFrontend.swift
1615
Frontend/FormatFrontend.swift
1716
Frontend/Frontend.swift
1817
Frontend/LintFrontend.swift

Sources/swift-format/Frontend/DumpConfigurationFrontend.swift

Lines changed: 0 additions & 25 deletions
This file was deleted.

Sources/swift-format/Frontend/Frontend.swift

Lines changed: 122 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,118 @@ import SwiftParser
1616
import SwiftSyntax
1717

1818
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+
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
}

Sources/swift-format/Subcommands/DumpConfiguration.swift

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,34 @@ extension SwiftFormatCommand {
4040
}
4141

4242
func run() throws {
43-
if !effective {
44-
let configuration = try Configuration().asJsonString()
45-
print(configuration)
46-
return
47-
}
43+
let diagnosticPrinter = StderrDiagnosticPrinter(colorMode: .auto)
44+
let diagnosticsEngine = DiagnosticsEngine(diagnosticsHandlers: [diagnosticPrinter.printDiagnostic])
4845

49-
// Pretend to use stdin, so that the configuration loading machinery in the Frontend base class can be used in the
50-
// next step. This produces the same results as if "format" or "lint" subcommands were called.
51-
let lintFormatOptions = try LintFormatOptions.parse(["-"])
46+
let configuration: Configuration
47+
if effective {
48+
var configurationProvider = Frontend.ConfigurationProvider(diagnosticsEngine: diagnosticsEngine)
5249

53-
let frontend = DumpConfigurationFrontend(
54-
configurationOptions: configurationOptions,
55-
lintFormatOptions: lintFormatOptions
56-
)
57-
frontend.run()
58-
if frontend.diagnosticsEngine.hasErrors {
59-
throw ExitCode.failure
50+
guard
51+
let effectiveConfiguration = configurationProvider.provide(
52+
forConfigPathOrString: configurationOptions.configuration,
53+
orForSwiftFileAt: nil
54+
)
55+
else {
56+
// Already diagnosed in the called method through the diagnosticsEngine.
57+
throw ExitCode.failure
58+
}
59+
60+
configuration = effectiveConfiguration
61+
} else {
62+
configuration = Configuration()
6063
}
6164

62-
switch frontend.dumpedConfiguration {
63-
case .success(let configuration):
64-
print(configuration)
65-
case .failure(let error):
66-
throw error
65+
do {
66+
let configurationAsJson = try configuration.asJsonString()
67+
print(configurationAsJson)
68+
} catch {
69+
diagnosticsEngine.emitError("\(error.localizedDescription)")
70+
throw ExitCode.failure
6771
}
6872
}
6973
}

0 commit comments

Comments
 (0)