From 4eefa53c83edcbf6d5bbab567462687a05170dde Mon Sep 17 00:00:00 2001 From: WuerfelDev Date: Wed, 5 Jun 2024 13:51:07 +0200 Subject: [PATCH 1/6] [config] Search in OS config directories Searches for: - ~/Library/Application Support/swift-format/config.json - $XDG_CONFIG_HOME/swift-format/config.json - ~/.config/swift-format/config.json --- Sources/swift-format/Frontend/Frontend.swift | 55 +++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 99ecef090..5ef514c0e 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -181,7 +181,9 @@ class Frontend { /// it was provided, or by searching in paths inferred by `swiftFilePath` if one exists, or the /// default configuration otherwise. If an error occurred when reading the configuration, a /// diagnostic is emitted and `nil` is returned. If neither `pathOrString` nor `swiftFilePath` - /// were provided, a default `Configuration()` will be returned. + /// were provided, a configuration is searched at the current working directory or upwards the + /// path. Next the configuration is searched for at the OS default config locations as + /// swift-format/config.json. Finally the default `Configuration()` will be returned. private func configuration( fromPathOrString pathOrString: String?, orInferredFromSwiftFileAt swiftFileURL: URL? @@ -241,6 +243,57 @@ class Frontend { } } + // Load global configuration file + // First URLs are created, then they are queried. First match is loaded + var configLocations: [URL] = [] + + if #available(macOS 13.0, iOS 16.0, *) { + // From "~/Library/Application Support/" directory + configLocations.append(URL.applicationSupportDirectory) + // From $XDG_CONFIG_HOME directory + if let xdgConfig: String = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { + configLocations.append(URL(filePath: xdgConfig, directoryHint: .isDirectory)) + } + // From "~/.config/" directory + var dotconfig: URL = URL.homeDirectory + dotconfig.append(component: ".config", directoryHint: .isDirectory) + configLocations.append(dotconfig) + } else { + // From "~/Library/Application Support/" directory + var appSupport: URL = FileManager.default.homeDirectoryForCurrentUser + appSupport.appendPathComponent("Library", isDirectory: true) + appSupport.appendPathComponent("Application Support", isDirectory: true) + configLocations.append(appSupport) + // From $XDG_CONFIG_HOME directory + if let xdgConfig: String = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { + configLocations.append(URL(fileURLWithPath: xdgConfig)) + } + // From "~/.config/" directory + var dotconfig: URL = FileManager.default.homeDirectoryForCurrentUser + dotconfig.appendPathComponent(".config") + configLocations.append(dotconfig) + } + + for var location: URL in configLocations { + if #available(macOS 13.0, iOS 16.0, *) { + location.append(components: "swift-format", "config.json") + } else { + location.appendPathComponent("swift-format", isDirectory: true) + location.appendPathComponent("config.json", isDirectory: false) + } + if FileManager.default.fileExists(atPath: location.path) { + do { + let configuration = try configurationLoader.configuration(at: location) + self.checkForUnrecognizedRules(in: configuration) + return configuration + } catch { + diagnosticsEngine.emitError( + "Unable to read configuration for \(location.path): \(error.localizedDescription)") + return nil + } + } + } + // An explicit configuration has not been given, and one cannot be found. // Return the default configuration. return Configuration() From 46b1d475fd4cb038e2c836b2b390a92120dbefaa Mon Sep 17 00:00:00 2001 From: WuerfelDev Date: Wed, 5 Jun 2024 14:30:26 +0200 Subject: [PATCH 2/6] Documentation for global configuration --- Documentation/Configuration.md | 16 ++++++++++++++-- README.md | 18 ++++++++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 1035286ce..6de4c3b5e 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -5,7 +5,7 @@ used as a command line tool or as an API. ## Command Line Configuration -A `swift-format` configuration file is a JSON file with the following +A `.swift-format` configuration file is a JSON file with the following top-level keys and values: * `version` _(number)_: The version of the configuration file. For now, this @@ -92,7 +92,7 @@ top-level keys and values: An example `.swift-format` configuration file is shown below. -```javascript +```json { "version": 1, "lineLength": 100, @@ -118,6 +118,18 @@ You can also run this command to see the list of rules in the default $ swift-format dump-configuration +## Global Configuration + +If no `.swift-format` can be found for the current project/file, the configuration directories +are searched for a `swift-format/config.json` file. While the filename is different,the +configuration format stays the same. + +Locations that are searched, in this order: + +- `~/Library/Application Support/swift-format/config.json` +- `$XDG_CONFIG_HOME/swift-format/config.json` +- `~/.config/swift-format/config.json` + ## API Configuration The `SwiftConfiguration` module contains a `Configuration` type that is diff --git a/README.md b/README.md index 6b29d73eb..54615583b 100644 --- a/README.md +++ b/README.md @@ -167,10 +167,12 @@ subcommands: or off respectively, regardless of whether the output is going to a terminal. -* `--configuration `: The path to a JSON file that contains +* `--configuration `: The path to a JSON file that contains [configurable settings](#configuring-the-command-line-tool) for - `swift-format`. If omitted, a default configuration is use (which - can be seen by running `swift-format dump-configuration`). + `swift-format`. If no file is found, `swift-format` tries to load the json + data as a string, if valid. If the parameter is omitted, a default + configuration is used (which can be seen by running + `swift-format dump-configuration`). * `--ignore-unparsable-files`: If this option is specified and a source file contains syntax errors or can otherwise not be parsed successfully by the @@ -202,14 +204,18 @@ JSON-formatted file named `.swift-format` in the same directory. If one is found, then that file is loaded to determine the tool's configuration. If the file is not found, then it looks in the parent directory, and so on. +If there is no project specific configuration file, the +[config directories](Documentation/Configuration.md#Global-Configuration) +are checked for a `swift-format/config.json` configuration file. + If no configuration file is found, a default configuration is used. The settings in the default configuration can be viewed by running `swift-format dump-configuration`, which will dump it to standard output. -If the `--configuration ` option is passed to `swift-format`, then that -configuration will be used unconditionally and the file system will not be -searched. +If the `--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 From 7b9586c5ab5e890af5af4456ba14e5aa8515efa2 Mon Sep 17 00:00:00 2001 From: WuerfelDev Date: Fri, 8 Nov 2024 11:05:30 +0100 Subject: [PATCH 3/6] adjust order of searching config directories --- Documentation/Configuration.md | 12 +++- Sources/swift-format/Frontend/Frontend.swift | 63 +++++++++++--------- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 6de4c3b5e..198406679 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -121,14 +121,20 @@ You can also run this command to see the list of rules in the default ## Global Configuration If no `.swift-format` can be found for the current project/file, the configuration directories -are searched for a `swift-format/config.json` file. While the filename is different,the +are searched for a `swift-format/config.json` file. While the filename is different, the configuration format stays the same. Locations that are searched, in this order: -- `~/Library/Application Support/swift-format/config.json` - `$XDG_CONFIG_HOME/swift-format/config.json` -- `~/.config/swift-format/config.json` +- `$HOME/Library/Application Support/swift-format/config.json` +- each path in `$XDG_CONFIG_DIRS` (system wide configuration) +- `/Library/Application Support/swift-format/config.json` (system wide configuration) + +or on windows: + +- `%LOCALAPPDATA%/swift-format/config.json` +- `%PROGRAMDATA%/swift-format/config.json` (system wide configuration) ## API Configuration diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 5ef514c0e..7e1943de5 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -12,8 +12,8 @@ import Foundation @_spi(Internal) import SwiftFormat -import SwiftSyntax import SwiftParser +import SwiftSyntax class Frontend { /// Represents a file to be processed by the frontend and any file-specific options associated @@ -245,36 +245,40 @@ class Frontend { // Load global configuration file // First URLs are created, then they are queried. First match is loaded - var configLocations: [URL] = [] - - if #available(macOS 13.0, iOS 16.0, *) { - // From "~/Library/Application Support/" directory - configLocations.append(URL.applicationSupportDirectory) - // From $XDG_CONFIG_HOME directory - if let xdgConfig: String = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { - configLocations.append(URL(filePath: xdgConfig, directoryHint: .isDirectory)) + var configLocations: [URL?] = [] + + #if os(Windows) + if let localAppData = ProcessInfo.processInfo.environment["LOCALAPPDATA"] { + configLocations.append(URL(fileURLWithPath: localAppData)) } - // From "~/.config/" directory - var dotconfig: URL = URL.homeDirectory - dotconfig.append(component: ".config", directoryHint: .isDirectory) - configLocations.append(dotconfig) - } else { - // From "~/Library/Application Support/" directory - var appSupport: URL = FileManager.default.homeDirectoryForCurrentUser - appSupport.appendPathComponent("Library", isDirectory: true) - appSupport.appendPathComponent("Application Support", isDirectory: true) - configLocations.append(appSupport) - // From $XDG_CONFIG_HOME directory - if let xdgConfig: String = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { - configLocations.append(URL(fileURLWithPath: xdgConfig)) + if let programData = ProcessInfo.processInfo.environment["PROGRAMDATA"] { + configLocations.append(URL(fileURLWithPath: programData)) } - // From "~/.config/" directory - var dotconfig: URL = FileManager.default.homeDirectoryForCurrentUser - dotconfig.appendPathComponent(".config") - configLocations.append(dotconfig) - } + #else + if let xdgConfigHome = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { + configLocations.append(URL(fileURLWithPath: xdgConfigHome)) + } + + if let libraryUrl = FileManager.default.urls( + for: .applicationSupportDirectory, in: .userDomainMask + ).first { + configLocations.append(libraryUrl) + } + + if let xdgConfigDirs = ProcessInfo.processInfo.environment["XDG_CONFIG_DIRS"] { + configLocations += xdgConfigDirs.split(separator: ":").map { xdgConfigDir in + URL(fileURLWithPath: String(xdgConfigDir)) + } + } + + if let libraryUrl = FileManager.default.urls( + for: .applicationSupportDirectory, in: .systemDomainMask + ).first { + configLocations.append(libraryUrl) + } + #endif - for var location: URL in configLocations { + for case var location? in configLocations { if #available(macOS 13.0, iOS 16.0, *) { location.append(components: "swift-format", "config.json") } else { @@ -307,7 +311,8 @@ class Frontend { // That way they will be printed out, but we'll continue execution on the valid rules. let invalidRules = configuration.rules.filter { !RuleRegistry.rules.keys.contains($0.key) } for rule in invalidRules { - diagnosticsEngine.emitWarning("Configuration contains an unrecognized rule: \(rule.key)", location: nil) + diagnosticsEngine.emitWarning( + "Configuration contains an unrecognized rule: \(rule.key)", location: nil) } } } From 9d598bd44bd264578631423950e152058619038c Mon Sep 17 00:00:00 2001 From: WuerfelDev Date: Wed, 13 Nov 2024 17:48:53 +0100 Subject: [PATCH 4/6] config loading cleaned --- Sources/swift-format/Frontend/Frontend.swift | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 7e1943de5..c888fc03d 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -259,10 +259,14 @@ class Frontend { configLocations.append(URL(fileURLWithPath: xdgConfigHome)) } - if let libraryUrl = FileManager.default.urls( - for: .applicationSupportDirectory, in: .userDomainMask - ).first { - configLocations.append(libraryUrl) + if let homeLocation = ProcessInfo.processInfo.environment["HOME"] { + configLocations.append(URL(fileURLWithPath: homeLocation + "/.config/")) + } + + for supportDirectoryUrl in FileManager.default.urls( + for: .applicationSupportDirectory, in: .userDomainMask) + { + configLocations.append(supportDirectoryUrl) } if let xdgConfigDirs = ProcessInfo.processInfo.environment["XDG_CONFIG_DIRS"] { @@ -279,12 +283,8 @@ class Frontend { #endif for case var location? in configLocations { - if #available(macOS 13.0, iOS 16.0, *) { - location.append(components: "swift-format", "config.json") - } else { - location.appendPathComponent("swift-format", isDirectory: true) - location.appendPathComponent("config.json", isDirectory: false) - } + location.appendPathComponent("swift-format", isDirectory: true) + location.appendPathComponent("config.json", isDirectory: false) if FileManager.default.fileExists(atPath: location.path) { do { let configuration = try configurationLoader.configuration(at: location) From 3a27b02c2bea63ca79b42df1e4ecf41dd4e25559 Mon Sep 17 00:00:00 2001 From: WuerfelDev Date: Fri, 15 Nov 2024 15:28:00 +0100 Subject: [PATCH 5/6] Configuration fallback ~/.config/ --- Sources/swift-format/Frontend/Frontend.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index c888fc03d..0b513f76a 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -257,10 +257,10 @@ class Frontend { #else if let xdgConfigHome = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { configLocations.append(URL(fileURLWithPath: xdgConfigHome)) - } - - if let homeLocation = ProcessInfo.processInfo.environment["HOME"] { - configLocations.append(URL(fileURLWithPath: homeLocation + "/.config/")) + }else if let homeLocation = ProcessInfo.processInfo.environment["HOME"] { + let dotConfigUrl = URL(fileURLWithPath: homeLocation) + .appendingPathComponent(".config", isDirectory: true) + configLocations.append(dotConfigUrl) } for supportDirectoryUrl in FileManager.default.urls( From fa16f61da98ef289a02538e6a98dac101907bbd8 Mon Sep 17 00:00:00 2001 From: WuerfelDev Date: Fri, 15 Nov 2024 15:49:38 +0100 Subject: [PATCH 6/6] apply .swift-format --- Sources/swift-format/Frontend/Frontend.swift | 63 +++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/Sources/swift-format/Frontend/Frontend.swift b/Sources/swift-format/Frontend/Frontend.swift index 2750ca06e..4c260d7ce 100644 --- a/Sources/swift-format/Frontend/Frontend.swift +++ b/Sources/swift-format/Frontend/Frontend.swift @@ -275,38 +275,40 @@ class Frontend { var configLocations: [URL?] = [] #if os(Windows) - if let localAppData = ProcessInfo.processInfo.environment["LOCALAPPDATA"] { - configLocations.append(URL(fileURLWithPath: localAppData)) - } - if let programData = ProcessInfo.processInfo.environment["PROGRAMDATA"] { - configLocations.append(URL(fileURLWithPath: programData)) - } + if let localAppData = ProcessInfo.processInfo.environment["LOCALAPPDATA"] { + configLocations.append(URL(fileURLWithPath: localAppData)) + } + if let programData = ProcessInfo.processInfo.environment["PROGRAMDATA"] { + configLocations.append(URL(fileURLWithPath: programData)) + } #else - if let xdgConfigHome = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { - configLocations.append(URL(fileURLWithPath: xdgConfigHome)) - }else if let homeLocation = ProcessInfo.processInfo.environment["HOME"] { - let dotConfigUrl = URL(fileURLWithPath: homeLocation) - .appendingPathComponent(".config", isDirectory: true) - configLocations.append(dotConfigUrl) - } + if let xdgConfigHome = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { + configLocations.append(URL(fileURLWithPath: xdgConfigHome)) + } else if let homeLocation = ProcessInfo.processInfo.environment["HOME"] { + let dotConfigUrl = URL(fileURLWithPath: homeLocation) + .appendingPathComponent(".config", isDirectory: true) + configLocations.append(dotConfigUrl) + } - for supportDirectoryUrl in FileManager.default.urls( - for: .applicationSupportDirectory, in: .userDomainMask) - { - configLocations.append(supportDirectoryUrl) - } + for supportDirectoryUrl in FileManager.default.urls( + for: .applicationSupportDirectory, + in: .userDomainMask + ) { + configLocations.append(supportDirectoryUrl) + } - if let xdgConfigDirs = ProcessInfo.processInfo.environment["XDG_CONFIG_DIRS"] { - configLocations += xdgConfigDirs.split(separator: ":").map { xdgConfigDir in - URL(fileURLWithPath: String(xdgConfigDir)) - } + if let xdgConfigDirs = ProcessInfo.processInfo.environment["XDG_CONFIG_DIRS"] { + configLocations += xdgConfigDirs.split(separator: ":").map { xdgConfigDir in + URL(fileURLWithPath: String(xdgConfigDir)) } + } - if let libraryUrl = FileManager.default.urls( - for: .applicationSupportDirectory, in: .systemDomainMask - ).first { - configLocations.append(libraryUrl) - } + if let libraryUrl = FileManager.default.urls( + for: .applicationSupportDirectory, + in: .systemDomainMask + ).first { + configLocations.append(libraryUrl) + } #endif for case var location? in configLocations { @@ -319,7 +321,8 @@ class Frontend { return configuration } catch { diagnosticsEngine.emitError( - "Unable to read configuration for \(location.path): \(error.localizedDescription)") + "Unable to read configuration for \(location.path): \(error.localizedDescription)" + ) return nil } } @@ -339,7 +342,9 @@ class Frontend { let invalidRules = configuration.rules.filter { !RuleRegistry.rules.keys.contains($0.key) } for rule in invalidRules { diagnosticsEngine.emitWarning( - "Configuration contains an unrecognized rule: \(rule.key)", location: nil) + "Configuration contains an unrecognized rule: \(rule.key)", + location: nil + ) } } }