Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search for configuration in config directories (dotfiles, etc) #749

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions Documentation/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -102,7 +102,7 @@ top-level keys and values:

An example `.swift-format` configuration file is shown below.

```javascript
```json
{
"version": 1,
"lineLength": 100,
Expand All @@ -128,6 +128,24 @@ 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:

- `$XDG_CONFIG_HOME/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

The `SwiftConfiguration` module contains a `Configuration` type that is
Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,12 @@ subcommands:
or off respectively, regardless of whether the output is going to a
terminal.

* `--configuration <file>`: The path to a JSON file that contains
* `--configuration <file|string>`: 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
Expand Down Expand Up @@ -205,14 +207,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 <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 <file|string>` 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
Expand Down
67 changes: 65 additions & 2 deletions Sources/swift-format/Frontend/Frontend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,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?
Expand Down Expand Up @@ -268,6 +270,64 @@ class Frontend {
}
}

// Load global configuration file
// First URLs are created, then they are queried. First match is loaded
var configLocations: [URL?] = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I only now noticed: Do we ever add nil to this array? I don’t think so and we wouldn’t need to do case var location? in the loop below if we don’t allow optionals in here.


#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))
}
#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)
}

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 libraryUrl = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .systemDomainMask
).first {
configLocations.append(libraryUrl)
}
#endif

for case var location? in configLocations {
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)
ahoppen marked this conversation as resolved.
Show resolved Hide resolved
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()
Expand All @@ -281,7 +341,10 @@ 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
)
}
}
}