Skip to content

Commit 6e48824

Browse files
committed
Add dump-effective-configuration subcommand
closes #667 Implement the subcommand `dump-effective-configuration`, which dumps the configuration that would be used if `swift-format` was executed from the current working directory (cwd), incorporating configuration files found in the cwd or its parents, or input from the `--configuration` option. This helps when composing a configuration or with configuration debugging/verification activities.
1 parent 2f71242 commit 6e48824

16 files changed

+198
-75
lines changed

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,19 @@ settings in the default configuration can be viewed by running
210210
`swift-format dump-configuration`, which will dump it to standard
211211
output.
212212

213-
If the `--configuration <file>` option is passed to `swift-format`, then that
214-
configuration will be used unconditionally and the file system will not be
215-
searched.
213+
If the `--configuration <configuration>` option is passed to `swift-format`,
214+
then that configuration will be used unconditionally and the file system will
215+
not be searched.
216216

217217
See [Documentation/Configuration.md](Documentation/Configuration.md) for a
218-
description of the configuration file format and the settings that are
219-
available.
218+
description of the configuration format and the settings that are available.
219+
220+
#### Viewing the Effective Configuration
221+
222+
The `dump-effective-configuration` subcommand dumps the configuration that
223+
would be used if `swift-format` was executed from the current working directory,
224+
and accounts for `.swift-format` files or `--configuration` options as outlined
225+
above.
220226

221227
### Miscellaneous
222228

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
extension Configuration {
16+
/// Return the configuration as a JSON string.
17+
public func asJsonString() throws -> String {
18+
let data: Data
19+
20+
do {
21+
let encoder = JSONEncoder()
22+
encoder.outputFormatting = [.prettyPrinted]
23+
if #available(macOS 10.13, *) {
24+
encoder.outputFormatting.insert(.sortedKeys)
25+
}
26+
27+
data = try encoder.encode(self)
28+
} catch {
29+
throw SwiftFormatError.configurationDumpFailed("\(error)")
30+
}
31+
32+
guard let jsonString = String(data: data, encoding: .utf8) else {
33+
// This should never happen, but let's make sure we fail more gracefully than crashing, just in case.
34+
throw SwiftFormatError.configurationDumpFailed("The JSON was not valid UTF-8")
35+
}
36+
37+
return jsonString
38+
}
39+
}

Sources/SwiftFormat/API/SwiftFormatError.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public enum SwiftFormatError: LocalizedError {
2828
/// The requested experimental feature name was not recognized by the parser.
2929
case unrecognizedExperimentalFeature(String)
3030

31+
/// An error happened while dumping the tool's configuration.
32+
case configurationDumpFailed(String)
33+
3134
public var errorDescription: String? {
3235
switch self {
3336
case .fileNotReadable:
@@ -38,6 +41,8 @@ public enum SwiftFormatError: LocalizedError {
3841
return "file contains invalid Swift syntax"
3942
case .unrecognizedExperimentalFeature(let name):
4043
return "experimental feature '\(name)' was not recognized by the Swift parser"
44+
case .configurationDumpFailed(let message):
45+
return "dumping configuration failed: \(message)"
4146
}
4247
}
4348
}

Sources/SwiftFormat/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
#[[
22
This source file is part of the swift-format open source project
33
4-
Copyright (c) 2024 Apple Inc. and the swift-format project authors
4+
Copyright (c) 2024 - 2025 Apple Inc. and the swift-format project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66
77
See https://swift.org/LICENSE.txt for license information
88
#]]
99

1010
add_library(SwiftFormat
1111
API/Configuration+Default.swift
12+
API/Configuration+Dump.swift
1213
API/Configuration.swift
1314
API/DebugOptions.swift
1415
API/Finding.swift

Sources/swift-format/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#[[
22
This source file is part of the swift-format open source project
33
4-
Copyright (c) 2024 Apple Inc. and the swift-format project authors
4+
Copyright (c) 2024 - 2025 Apple Inc. and the swift-format project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66
77
See https://swift.org/LICENSE.txt for license information
@@ -12,18 +12,20 @@ add_executable(swift-format
1212
SwiftFormatCommand.swift
1313
VersionOptions.swift
1414
Frontend/ConfigurationLoader.swift
15+
Frontend/DumpEffectiveConfigurationFrontend.swift
1516
Frontend/FormatFrontend.swift
1617
Frontend/Frontend.swift
1718
Frontend/LintFrontend.swift
19+
Subcommands/ConfigurationOptions.swift
1820
Subcommands/DumpConfiguration.swift
21+
Subcommands/DumpEffectiveConfiguration.swift
1922
Subcommands/Format.swift
2023
Subcommands/Lint.swift
2124
Subcommands/LintFormatOptions.swift
2225
Subcommands/PerformanceMeasurement.swift
2326
Utilities/Diagnostic.swift
2427
Utilities/DiagnosticsEngine.swift
2528
Utilities/FileHandleTextOutputStream.swift
26-
Utilities/FormatError.swift
2729
Utilities/StderrDiagnosticPrinter.swift
2830
Utilities/TTY.swift)
2931
target_link_libraries(swift-format PRIVATE
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import SwiftFormat
15+
16+
/// The frontend for dumping the effective configuration.
17+
class DumpEffectiveConfigurationFrontend: Frontend {
18+
private(set) var dumpResult: Result<String, Error> = .failure(
19+
SwiftFormatError.configurationDumpFailed("Configuration not resolved yet")
20+
)
21+
22+
override func processFile(_ fileToProcess: FileToProcess) {
23+
dumpResult = Result.init(catching: fileToProcess.configuration.asJsonString)
24+
}
25+
}

Sources/swift-format/Frontend/FormatFrontend.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -20,9 +20,9 @@ class FormatFrontend: Frontend {
2020
/// Whether or not to format the Swift file in-place.
2121
private let inPlace: Bool
2222

23-
init(lintFormatOptions: LintFormatOptions, inPlace: Bool) {
23+
init(configurationOptions: ConfigurationOptions, lintFormatOptions: LintFormatOptions, inPlace: Bool) {
2424
self.inPlace = inPlace
25-
super.init(lintFormatOptions: lintFormatOptions)
25+
super.init(configurationOptions: configurationOptions, lintFormatOptions: lintFormatOptions)
2626
}
2727

2828
override func processFile(_ fileToProcess: FileToProcess) {

Sources/swift-format/Frontend/Frontend.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -67,6 +67,9 @@ class Frontend {
6767
/// The diagnostic engine to which warnings and errors will be emitted.
6868
final let diagnosticsEngine: DiagnosticsEngine
6969

70+
/// Options that control the tool's configuration.
71+
final let configurationOptions: ConfigurationOptions
72+
7073
/// Options that apply during formatting or linting.
7174
final let lintFormatOptions: LintFormatOptions
7275

@@ -85,7 +88,8 @@ class Frontend {
8588
/// Creates a new frontend with the given options.
8689
///
8790
/// - Parameter lintFormatOptions: Options that apply during formatting or linting.
88-
init(lintFormatOptions: LintFormatOptions) {
91+
init(configurationOptions: ConfigurationOptions, lintFormatOptions: LintFormatOptions) {
92+
self.configurationOptions = configurationOptions
8993
self.lintFormatOptions = lintFormatOptions
9094

9195
self.diagnosticPrinter = StderrDiagnosticPrinter(
@@ -139,7 +143,7 @@ class Frontend {
139143

140144
guard
141145
let configuration = configuration(
142-
fromPathOrString: lintFormatOptions.configuration,
146+
fromPathOrString: configurationOptions.configuration,
143147
orInferredFromSwiftFileAt: assumedUrl
144148
)
145149
else {
@@ -190,7 +194,7 @@ class Frontend {
190194

191195
guard
192196
let configuration = configuration(
193-
fromPathOrString: lintFormatOptions.configuration,
197+
fromPathOrString: configurationOptions.configuration,
194198
orInferredFromSwiftFileAt: url
195199
)
196200
else {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import ArgumentParser
14+
15+
/// Common arguments used by the `lint`, `format` and `dump-effective-configuration` subcommands.
16+
struct ConfigurationOptions: ParsableArguments {
17+
/// The path to the JSON configuration file that should be loaded.
18+
///
19+
/// If not specified, the default configuration will be used.
20+
@Option(
21+
name: .customLong("configuration"),
22+
help: """
23+
The path to a JSON file containing the configuration of the linter/formatter or a JSON string containing the \
24+
configuration directly.
25+
"""
26+
)
27+
var configuration: String?
28+
}

Sources/swift-format/Subcommands/DumpConfiguration.swift

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,9 @@ extension SwiftFormatCommand {
2222
)
2323

2424
func run() throws {
25-
let configuration = Configuration()
26-
do {
27-
let encoder = JSONEncoder()
28-
encoder.outputFormatting = [.prettyPrinted]
29-
if #available(macOS 10.13, *) {
30-
encoder.outputFormatting.insert(.sortedKeys)
31-
}
25+
let configuration = try Configuration().asJsonString()
3226

33-
let data = try encoder.encode(configuration)
34-
guard let jsonString = String(data: data, encoding: .utf8) else {
35-
// This should never happen, but let's make sure we fail more gracefully than crashing, just
36-
// in case.
37-
throw FormatError(
38-
message: "Could not dump the default configuration: the JSON was not valid UTF-8"
39-
)
40-
}
41-
print(jsonString)
42-
} catch {
43-
throw FormatError(message: "Could not dump the default configuration: \(error)")
44-
}
27+
print(configuration)
4528
}
4629
}
4730
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import ArgumentParser
14+
import Foundation
15+
import SwiftFormat
16+
17+
extension SwiftFormatCommand {
18+
/// Dumps the tool's effective configuration in JSON format to standard output.
19+
struct DumpEffectiveConfiguration: ParsableCommand {
20+
static var configuration = CommandConfiguration(
21+
abstract: "Dump the effective configuration in JSON format to standard output",
22+
discussion: """
23+
Dumps the configuration that would be used if swift-format was executed from the current working \
24+
directory (cwd), incorporating configuration files found in the cwd or its parents, or input from the \
25+
--configuration option.
26+
"""
27+
)
28+
29+
@OptionGroup()
30+
var configurationOptions: ConfigurationOptions
31+
32+
func run() throws {
33+
// Pretend to use stdin, so that the configuration loading machinery in the Frontend base class can be used in the
34+
// next step. This produces the same results as if "format" or "lint" subcommands were called.
35+
let lintFormatOptions = try LintFormatOptions.parse(["-"])
36+
37+
let frontend = DumpEffectiveConfigurationFrontend(
38+
configurationOptions: configurationOptions,
39+
lintFormatOptions: lintFormatOptions
40+
)
41+
frontend.run()
42+
if frontend.diagnosticsEngine.hasErrors {
43+
throw ExitCode.failure
44+
}
45+
46+
switch frontend.dumpResult {
47+
case .success(let configuration):
48+
print(configuration)
49+
case .failure(let error):
50+
throw error
51+
}
52+
}
53+
}
54+
}

Sources/swift-format/Subcommands/Format.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -29,6 +29,9 @@ extension SwiftFormatCommand {
2929
)
3030
var inPlace: Bool = false
3131

32+
@OptionGroup()
33+
var configurationOptions: ConfigurationOptions
34+
3235
@OptionGroup()
3336
var formatOptions: LintFormatOptions
3437

@@ -43,7 +46,11 @@ extension SwiftFormatCommand {
4346

4447
func run() throws {
4548
try performanceMeasurementOptions.printingInstructionCountIfRequested() {
46-
let frontend = FormatFrontend(lintFormatOptions: formatOptions, inPlace: inPlace)
49+
let frontend = FormatFrontend(
50+
configurationOptions: configurationOptions,
51+
lintFormatOptions: formatOptions,
52+
inPlace: inPlace
53+
)
4754
frontend.run()
4855
if frontend.diagnosticsEngine.hasErrors { throw ExitCode.failure }
4956
}

Sources/swift-format/Subcommands/Lint.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -20,6 +20,9 @@ extension SwiftFormatCommand {
2020
discussion: "When no files are specified, it expects the source from standard input."
2121
)
2222

23+
@OptionGroup()
24+
var configurationOptions: ConfigurationOptions
25+
2326
@OptionGroup()
2427
var lintOptions: LintFormatOptions
2528

@@ -34,7 +37,7 @@ extension SwiftFormatCommand {
3437

3538
func run() throws {
3639
try performanceMeasurementOptions.printingInstructionCountIfRequested {
37-
let frontend = LintFrontend(lintFormatOptions: lintOptions)
40+
let frontend = LintFrontend(configurationOptions: configurationOptions, lintFormatOptions: lintOptions)
3841
frontend.run()
3942

4043
if frontend.diagnosticsEngine.hasErrors || strict && frontend.diagnosticsEngine.hasWarnings {

0 commit comments

Comments
 (0)