Skip to content

Commit

Permalink
Add dump-effective-configuration subcommand
Browse files Browse the repository at this point in the history
closes swiftlang#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.
  • Loading branch information
andre-richter committed Feb 6, 2025
1 parent 2f71242 commit e267558
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 75 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,19 @@ settings in the default configuration can be viewed by running
`swift-format dump-configuration`, which will dump it to standard
output.

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

See [Documentation/Configuration.md](Documentation/Configuration.md) for a
description of the configuration file format and the settings that are
available.
description of the configuration format and the settings that are available.

#### Viewing the Effective Configuration

The `dump-effective-configuration` subcommand dumps the configuration that
would be used if `swift-format` was executed from the current working directory,
and accounts for `.swift-format` files or `--configuration` options as outlined
above.

### Miscellaneous

Expand Down
39 changes: 39 additions & 0 deletions Sources/SwiftFormat/API/Configuration+Dump.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation

extension Configuration {
/// Return the configuration as a JSON string.
public func asJsonString() throws -> String {
let data: Data

do {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
if #available(macOS 10.13, *) {
encoder.outputFormatting.insert(.sortedKeys)
}

data = try encoder.encode(self)
} catch {
throw SwiftFormatError.configurationDumpFailed("\(error)")
}

guard let jsonString = String(data: data, encoding: .utf8) else {
// This should never happen, but let's make sure we fail more gracefully than crashing, just in case.
throw SwiftFormatError.configurationDumpFailed("The JSON was not valid UTF-8")
}

return jsonString
}
}
5 changes: 5 additions & 0 deletions Sources/SwiftFormat/API/SwiftFormatError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public enum SwiftFormatError: LocalizedError {
/// The requested experimental feature name was not recognized by the parser.
case unrecognizedExperimentalFeature(String)

/// An error happened while dumping the tool's configuration.
case configurationDumpFailed(String)

public var errorDescription: String? {
switch self {
case .fileNotReadable:
Expand All @@ -38,6 +41,8 @@ public enum SwiftFormatError: LocalizedError {
return "file contains invalid Swift syntax"
case .unrecognizedExperimentalFeature(let name):
return "experimental feature '\(name)' was not recognized by the Swift parser"
case .configurationDumpFailed(let message):
return "dumping configuration failed: \(message)"
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/SwiftFormat/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#[[
This source file is part of the swift-format open source project
Copyright (c) 2024 Apple Inc. and the swift-format project authors
Copyright (c) 2024 - 2025 Apple Inc. and the swift-format project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
#]]

add_library(SwiftFormat
API/Configuration+Default.swift
API/Configuration+Dump.swift
API/Configuration.swift
API/DebugOptions.swift
API/Finding.swift
Expand Down
6 changes: 4 additions & 2 deletions Sources/swift-format/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[[
This source file is part of the swift-format open source project
Copyright (c) 2024 Apple Inc. and the swift-format project authors
Copyright (c) 2024 - 2025 Apple Inc. and the swift-format project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
Expand All @@ -12,18 +12,20 @@ add_executable(swift-format
SwiftFormatCommand.swift
VersionOptions.swift
Frontend/ConfigurationLoader.swift
Frontend/DumpEffectiveConfigurationFrontend.swift
Frontend/FormatFrontend.swift
Frontend/Frontend.swift
Frontend/LintFrontend.swift
Subcommands/ConfigurationOptions.swift
Subcommands/DumpConfiguration.swift
Subcommands/DumpEffectiveConfiguration.swift
Subcommands/Format.swift
Subcommands/Lint.swift
Subcommands/LintFormatOptions.swift
Subcommands/PerformanceMeasurement.swift
Utilities/Diagnostic.swift
Utilities/DiagnosticsEngine.swift
Utilities/FileHandleTextOutputStream.swift
Utilities/FormatError.swift
Utilities/StderrDiagnosticPrinter.swift
Utilities/TTY.swift)
target_link_libraries(swift-format PRIVATE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation
import SwiftFormat

/// The frontend for dumping the effective configuration.
class DumpEffectiveConfigurationFrontend: Frontend {
private(set) var dumpResult: Result<String, Error> = .failure(
SwiftFormatError.configurationDumpFailed("Configuration not resolved yet")
)

override func processFile(_ fileToProcess: FileToProcess) {
dumpResult = Result.init(catching: fileToProcess.configuration.asJsonString)
}
}
6 changes: 3 additions & 3 deletions Sources/swift-format/Frontend/FormatFrontend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand All @@ -20,9 +20,9 @@ class FormatFrontend: Frontend {
/// Whether or not to format the Swift file in-place.
private let inPlace: Bool

init(lintFormatOptions: LintFormatOptions, inPlace: Bool) {
init(configurationOptions: ConfigurationOptions, lintFormatOptions: LintFormatOptions, inPlace: Bool) {
self.inPlace = inPlace
super.init(lintFormatOptions: lintFormatOptions)
super.init(configurationOptions: configurationOptions, lintFormatOptions: lintFormatOptions)
}

override func processFile(_ fileToProcess: FileToProcess) {
Expand Down
12 changes: 8 additions & 4 deletions Sources/swift-format/Frontend/Frontend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -67,6 +67,9 @@ class Frontend {
/// The diagnostic engine to which warnings and errors will be emitted.
final let diagnosticsEngine: DiagnosticsEngine

/// Options that control the tool's configuration.
final let configurationOptions: ConfigurationOptions

/// Options that apply during formatting or linting.
final let lintFormatOptions: LintFormatOptions

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

self.diagnosticPrinter = StderrDiagnosticPrinter(
Expand Down Expand Up @@ -139,7 +143,7 @@ class Frontend {

guard
let configuration = configuration(
fromPathOrString: lintFormatOptions.configuration,
fromPathOrString: configurationOptions.configuration,
orInferredFromSwiftFileAt: assumedUrl
)
else {
Expand Down Expand Up @@ -190,7 +194,7 @@ class Frontend {

guard
let configuration = configuration(
fromPathOrString: lintFormatOptions.configuration,
fromPathOrString: configurationOptions.configuration,
orInferredFromSwiftFileAt: url
)
else {
Expand Down
28 changes: 28 additions & 0 deletions Sources/swift-format/Subcommands/ConfigurationOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import ArgumentParser

/// Common arguments used by the `lint`, `format` and `dump-effective-configuration` subcommands.
struct ConfigurationOptions: ParsableArguments {
/// The path to the JSON configuration file that should be loaded.
///
/// If not specified, the default configuration will be used.
@Option(
name: .customLong("configuration"),
help: """
The path to a JSON file containing the configuration of the linter/formatter or a JSON string containing the \
configuration directly.
"""
)
var configuration: String?
}
21 changes: 2 additions & 19 deletions Sources/swift-format/Subcommands/DumpConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,9 @@ extension SwiftFormatCommand {
)

func run() throws {
let configuration = Configuration()
do {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
if #available(macOS 10.13, *) {
encoder.outputFormatting.insert(.sortedKeys)
}
let configuration = try Configuration().asJsonString()

let data = try encoder.encode(configuration)
guard let jsonString = String(data: data, encoding: .utf8) else {
// This should never happen, but let's make sure we fail more gracefully than crashing, just
// in case.
throw FormatError(
message: "Could not dump the default configuration: the JSON was not valid UTF-8"
)
}
print(jsonString)
} catch {
throw FormatError(message: "Could not dump the default configuration: \(error)")
}
print(configuration)
}
}
}
54 changes: 54 additions & 0 deletions Sources/swift-format/Subcommands/DumpEffectiveConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import ArgumentParser
import Foundation
import SwiftFormat

extension SwiftFormatCommand {
/// Dumps the tool's effective configuration in JSON format to standard output.
struct DumpEffectiveConfiguration: ParsableCommand {
static var configuration = CommandConfiguration(
abstract: "Dump the effective configuration in JSON format to standard output",
discussion: """
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.
"""
)

@OptionGroup()
var configurationOptions: ConfigurationOptions

func run() throws {
// Pretend to use stdin, so that the configuration loading machinery in the Frontend base class can be used in the
// next step. This produces the same results as if "format" or "lint" subcommands were called.
let lintFormatOptions = try LintFormatOptions.parse(["-"])

let frontend = DumpEffectiveConfigurationFrontend(
configurationOptions: configurationOptions,
lintFormatOptions: lintFormatOptions
)
frontend.run()
if frontend.diagnosticsEngine.hasErrors {
throw ExitCode.failure
}

switch frontend.dumpResult {
case .success(let configuration):
print(configuration)
case .failure(let error):
throw error
}
}
}
}
11 changes: 9 additions & 2 deletions Sources/swift-format/Subcommands/Format.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand All @@ -29,6 +29,9 @@ extension SwiftFormatCommand {
)
var inPlace: Bool = false

@OptionGroup()
var configurationOptions: ConfigurationOptions

@OptionGroup()
var formatOptions: LintFormatOptions

Expand All @@ -43,7 +46,11 @@ extension SwiftFormatCommand {

func run() throws {
try performanceMeasurementOptions.printingInstructionCountIfRequested() {
let frontend = FormatFrontend(lintFormatOptions: formatOptions, inPlace: inPlace)
let frontend = FormatFrontend(
configurationOptions: configurationOptions,
lintFormatOptions: formatOptions,
inPlace: inPlace
)
frontend.run()
if frontend.diagnosticsEngine.hasErrors { throw ExitCode.failure }
}
Expand Down
7 changes: 5 additions & 2 deletions Sources/swift-format/Subcommands/Lint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand All @@ -20,6 +20,9 @@ extension SwiftFormatCommand {
discussion: "When no files are specified, it expects the source from standard input."
)

@OptionGroup()
var configurationOptions: ConfigurationOptions

@OptionGroup()
var lintOptions: LintFormatOptions

Expand All @@ -34,7 +37,7 @@ extension SwiftFormatCommand {

func run() throws {
try performanceMeasurementOptions.printingInstructionCountIfRequested {
let frontend = LintFrontend(lintFormatOptions: lintOptions)
let frontend = LintFrontend(configurationOptions: configurationOptions, lintFormatOptions: lintOptions)
frontend.run()

if frontend.diagnosticsEngine.hasErrors || strict && frontend.diagnosticsEngine.hasWarnings {
Expand Down
Loading

0 comments on commit e267558

Please sign in to comment.