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

Command Line Options for Interacting with the Configuration Merging Process #1748

Closed
wants to merge 7 commits into from
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
[JP Simard](https://github.com/jpsim)
[#676](https://github.com/realm/SwiftLint/issues/676)

* Several command line options allow interaction with the merging process.
(See `swiftlint help lint`.)
[Jeremy David Giesbrecht](https://github.com/SDGGiesbrecht)
[##1693](https://github.com/realm/SwiftLint/issues/1693)

##### Enhancements

* Add `xctfail_message` rule to enforce XCTFail
Expand Down
48 changes: 38 additions & 10 deletions Source/SwiftLintFramework/Extensions/Configuration+Merging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,57 @@ extension Configuration {
}

private func configuration(forPath path: String) -> Configuration {
if path == rootPath {
if ignoreNested {
if let commandLineDefaults = defaults?.value {
// --config-defaults is lowest in the tree
return commandLineDefaults
} else {
// when no --config-defaults, the root directory is lowest in the tree
return self
}
}

var merged = mergeConfigurationFiles(upTo: path)
if let commandLineDefaults = defaults?.value {
// add --config-defaults underneath
merged = commandLineDefaults.merge(with: merged)
}
if let commandLineOverrides = overrides?.value {
// add --config-overrides on top
merged = merged.merge(with: commandLineOverrides)
}
return merged
}

private func mergeConfigurationFiles(upTo path: String) -> Configuration {
if path == rootPath || path == "/" {
return self
}

let pathNSString = path.bridge()
let configurationSearchPath = pathNSString.appendingPathComponent(Configuration.fileName)
if configurationSearchPath == configurationPath {
// We are the configuration, no need to read from the disk again, just return self
return self
}

// We are not at the root path, so get the parent configuration
let parent = mergeConfigurationFiles(upTo: path.bridge().deletingLastPathComponent)

// If a configuration exists and it isn't us, load and merge the configurations
// If a configuration exists, load it
if configurationSearchPath != configurationPath &&
FileManager.default.fileExists(atPath: configurationSearchPath) {
let fullPath = pathNSString.absolutePathRepresentation()
let config = Configuration.getCached(atPath: fullPath) ??
Configuration(path: configurationSearchPath, rootPath: fullPath, optional: false, quiet: true)
return merge(with: config)
}

// If we are not at the root path, continue down the tree
if path != rootPath && path != "/" {
return configuration(forPath: pathNSString.deletingLastPathComponent)
}
// merge the changes specified in this directory on top of the parent
return parent.merge(with: config)

// If nothing else, return self
return self
} else {
// no changes specified in this directory, so just return the parent
return parent
}
}

private struct HashableRule: Hashable {
Expand Down
40 changes: 37 additions & 3 deletions Source/SwiftLintFramework/Models/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public struct Configuration: Equatable {
public var warningThreshold: Int? // warning threshold
public var rootPath: String? // the root path to search for nested configurations
public var configurationPath: String? // if successfully loaded from a path
public var defaults: Ref<Configuration>? // --config-defaults
public var overrides: Ref<Configuration>? // --config-overrides
public var ignoreNested: Bool = false // --ignore-nested-configs
public let cachePath: String?

// MARK: Rules Properties
Expand Down Expand Up @@ -136,8 +139,28 @@ public struct Configuration: Equatable {
rootPath = configuration.rootPath
}

public init(path: String = Configuration.fileName, rootPath: String? = nil,
optional: Bool = true, quiet: Bool = false, enableAllRules: Bool = false, cachePath: String? = nil) {
// swiftlint:disable:next function_body_length
public init(path: String = Configuration.fileName, rootPath: String? = nil, optional: Bool = true,
defaults: String? = nil, overrides: String? = nil, ignoreNested: Bool = false,
quiet: Bool = false, enableAllRules: Bool = false, cachePath: String? = nil) {

defer {
if let defaultsPath = defaults {
// The defaults must inherit all but the path
// (i.e. quiet, enableAllRules & cachePath)
// because the root will not be merged into the defaults
// if merging is prevented by --ignore-nested-configs.
self.defaults = Ref(Configuration(path: defaultsPath, optional: false,
overrides: overrides, ignoreNested: ignoreNested,
quiet: quiet, enableAllRules: enableAllRules,
cachePath: cachePath))
}
if let overridesPath = overrides {
self.overrides = Ref(Configuration(path: overridesPath, optional: false))
}
self.ignoreNested = ignoreNested
}

let fullPath: String
if let rootPath = rootPath, rootPath.isDirectory() {
fullPath = path.bridge().absolutePathRepresentation(rootDirectory: rootPath)
Expand Down Expand Up @@ -190,7 +213,8 @@ public struct Configuration: Equatable {
(lhs.reporter == rhs.reporter) &&
(lhs.configurationPath == rhs.configurationPath) &&
(lhs.rootPath == lhs.rootPath) &&
(lhs.rules == rhs.rules)
(lhs.rules.sorted(by: { String(describing: $0) < String(describing: $1) })
== rhs.rules.sorted(by: { String(describing: $0) < String(describing: $1) }))
}
}

Expand Down Expand Up @@ -242,3 +266,13 @@ private extension String {
return false
}
}

public class Ref<T> {
// Indirection to allow one configuration to store another as a property

public let value: T

public init(_ value: T) {
self.value = value
}
}
14 changes: 10 additions & 4 deletions Source/swiftlint/Commands/AutoCorrectCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ struct AutoCorrectCommand: CommandProtocol {
struct AutoCorrectOptions: OptionsProtocol {
let path: String
let configurationFile: String
let configurationDefaults: String?
let configurationOverrides: String?
let ignoreNestedConfigurations: Bool
let useScriptInputFiles: Bool
let quiet: Bool
let format: Bool
Expand All @@ -53,17 +56,20 @@ struct AutoCorrectOptions: OptionsProtocol {
let useTabs: Bool

// swiftlint:disable line_length
static func create(_ path: String) -> (_ configurationFile: String) -> (_ useScriptInputFiles: Bool) -> (_ quiet: Bool) -> (_ format: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ useTabs: Bool) -> AutoCorrectOptions {
return { configurationFile in { useScriptInputFiles in { quiet in { format in { cachePath in { ignoreCache in { useTabs in
self.init(path: path, configurationFile: configurationFile, useScriptInputFiles: useScriptInputFiles, quiet: quiet, format: format, cachePath: cachePath, ignoreCache: ignoreCache, useTabs: useTabs)
}}}}}}}
static func create(_ path: String) -> (_ configurationFile: String) -> (_ configurationDefaults: String?) -> (_ configurationOverrides: String?) -> (_ ignoreNestedConfigurations: Bool) -> (_ useScriptInputFiles: Bool) -> (_ quiet: Bool) -> (_ format: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ useTabs: Bool) -> AutoCorrectOptions {
return { configurationFile in { configurationDefaults in { configurationOverrides in { ignoreNestedConfigurations in { useScriptInputFiles in { quiet in { format in { cachePath in { ignoreCache in { useTabs in
self.init(path: path, configurationFile: configurationFile, configurationDefaults: configurationDefaults, configurationOverrides: configurationOverrides, ignoreNestedConfigurations: ignoreNestedConfigurations, useScriptInputFiles: useScriptInputFiles, quiet: quiet, format: format, cachePath: cachePath, ignoreCache: ignoreCache, useTabs: useTabs)
}}}}}}}}}}
}

static func evaluate(_ mode: CommandMode) -> Result<AutoCorrectOptions, CommandantError<CommandantError<()>>> {
// swiftlint:enable line_length
return create
<*> mode <| pathOption(action: "correct")
<*> mode <| configOption
<*> mode <| configDefaultsOption
<*> mode <| configOverridesOption
<*> mode <| ignoreNestedConfigsOption
<*> mode <| useScriptInputFilesOption
<*> mode <| quietOption(action: "correcting")
<*> mode <| Option(key: "format",
Expand Down
14 changes: 10 additions & 4 deletions Source/swiftlint/Commands/LintCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ struct LintOptions: OptionsProtocol {
let path: String
let useSTDIN: Bool
let configurationFile: String
let configurationDefaults: String?
let configurationOverrides: String?
let ignoreNestedConfigurations: Bool
let strict: Bool
let lenient: Bool
let useScriptInputFiles: Bool
Expand All @@ -139,10 +142,10 @@ struct LintOptions: OptionsProtocol {
let enableAllRules: Bool

// swiftlint:disable line_length
static func create(_ path: String) -> (_ useSTDIN: Bool) -> (_ configurationFile: String) -> (_ strict: Bool) -> (_ lenient: Bool) -> (_ useScriptInputFiles: Bool) -> (_ benchmark: Bool) -> (_ reporter: String) -> (_ quiet: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ enableAllRules: Bool) -> LintOptions {
return { useSTDIN in { configurationFile in { strict in { lenient in { useScriptInputFiles in { benchmark in { reporter in { quiet in { cachePath in { ignoreCache in { enableAllRules in
self.init(path: path, useSTDIN: useSTDIN, configurationFile: configurationFile, strict: strict, lenient: lenient, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, cachePath: cachePath, ignoreCache: ignoreCache, enableAllRules: enableAllRules)
}}}}}}}}}}}
static func create(_ path: String) -> (_ useSTDIN: Bool) -> (_ configurationFile: String) -> (_ configurationDefaults: String?) -> (_ configurationOverrides: String?) -> (_ ignoreNestedConfigurations: Bool) -> (_ strict: Bool) -> (_ lenient: Bool) -> (_ useScriptInputFiles: Bool) -> (_ benchmark: Bool) -> (_ reporter: String) -> (_ quiet: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ enableAllRules: Bool) -> LintOptions {
return { useSTDIN in { configurationFile in { configurationDefaults in { configurationOverrides in { ignoreNestedConfigurations in { strict in { lenient in { useScriptInputFiles in { benchmark in { reporter in { quiet in { cachePath in { ignoreCache in { enableAllRules in
self.init(path: path, useSTDIN: useSTDIN, configurationFile: configurationFile, configurationDefaults: configurationDefaults, configurationOverrides: configurationOverrides, ignoreNestedConfigurations: ignoreNestedConfigurations, strict: strict, lenient: lenient, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, cachePath: cachePath, ignoreCache: ignoreCache, enableAllRules: enableAllRules)
}}}}}}}}}}}}}}
}

static func evaluate(_ mode: CommandMode) -> Result<LintOptions, CommandantError<CommandantError<()>>> {
Expand All @@ -152,6 +155,9 @@ struct LintOptions: OptionsProtocol {
<*> mode <| Option(key: "use-stdin", defaultValue: false,
usage: "lint standard input")
<*> mode <| configOption
<*> mode <| configDefaultsOption
<*> mode <| configOverridesOption
<*> mode <| ignoreNestedConfigsOption
<*> mode <| Option(key: "strict", defaultValue: false,
usage: "fail on warnings")
<*> mode <| Option(key: "lenient", defaultValue: false,
Expand Down
12 changes: 9 additions & 3 deletions Source/swiftlint/Commands/RulesCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,22 +78,28 @@ struct RulesCommand: CommandProtocol {
struct RulesOptions: OptionsProtocol {
fileprivate let ruleID: String?
let configurationFile: String
let configurationDefaults: String?
let configurationOverrides: String?
fileprivate let onlyEnabledRules: Bool
fileprivate let onlyDisabledRules: Bool

// swiftlint:disable line_length
static func create(_ configurationFile: String) -> (_ ruleID: String) -> (_ onlyEnabledRules: Bool) -> (_ onlyDisabledRules: Bool) -> RulesOptions {
return { ruleID in { onlyEnabledRules in { onlyDisabledRules in
static func create(_ configurationFile: String) -> (_ configurationDefaults: String?) -> (_ configurationOverrides: String?) -> (_ ruleID: String) -> (_ onlyEnabledRules: Bool) -> (_ onlyDisabledRules: Bool) -> RulesOptions {
return { configurationDefaults in { configurationOverrides in { ruleID in { onlyEnabledRules in { onlyDisabledRules in
self.init(ruleID: (ruleID.isEmpty ? nil : ruleID),
configurationFile: configurationFile,
configurationDefaults: configurationDefaults,
configurationOverrides: configurationOverrides,
onlyEnabledRules: onlyEnabledRules,
onlyDisabledRules: onlyDisabledRules)
}}}
}}}}}
}

static func evaluate(_ mode: CommandMode) -> Result<RulesOptions, CommandantError<CommandantError<()>>> {
return create
<*> mode <| configOption
<*> mode <| configDefaultsOption
<*> mode <| configOverridesOption
<*> mode <| Argument(defaultValue: "",
usage: "the rule identifier to display description for")
<*> mode <| Switch(flag: "e",
Expand Down
12 changes: 9 additions & 3 deletions Source/swiftlint/Extensions/Configuration+CommandLine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ extension Configuration {
let cachePath = options.cachePath.isEmpty ? nil : options.cachePath
let optional = !CommandLine.arguments.contains("--config")
self.init(path: options.configurationFile, rootPath: options.path.absolutePathStandardized(),
optional: optional, quiet: options.quiet,
optional: optional,
defaults: options.configurationDefaults, overrides: options.configurationOverrides,
ignoreNested: options.ignoreNestedConfigurations, quiet: options.quiet,
enableAllRules: options.enableAllRules, cachePath: cachePath)
}

Expand All @@ -141,13 +143,17 @@ extension Configuration {
let cachePath = options.cachePath.isEmpty ? nil : options.cachePath
let optional = !CommandLine.arguments.contains("--config")
self.init(path: options.configurationFile, rootPath: options.path.absolutePathStandardized(),
optional: optional, quiet: options.quiet, cachePath: cachePath)
optional: optional,
defaults: options.configurationDefaults, overrides: options.configurationOverrides,
ignoreNested: options.ignoreNestedConfigurations,
quiet: options.quiet, cachePath: cachePath)
}

// MARK: Rules command

init(options: RulesOptions) {
let optional = !CommandLine.arguments.contains("--config")
self.init(path: options.configurationFile, optional: optional)
self.init(path: options.configurationFile, optional: optional,
defaults: options.configurationDefaults, overrides: options.configurationOverrides)
}
}
14 changes: 14 additions & 0 deletions Source/swiftlint/Helpers/CommonOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ let configOption = Option(key: "config",
defaultValue: Configuration.fileName,
usage: "the path to SwiftLint's configuration file")

let configDefaultsOption = Option<String?>(key: "config-defaults",
defaultValue: nil,
usage: "the path of an external configuration file " +
"to use as the root of the merge tree")

let configOverridesOption = Option<String?>(key: "config-overrides",
defaultValue: nil,
usage: "the path of an external configuration file " +
"to append to the end of each branch of the merge tree")

let ignoreNestedConfigsOption = Option(key: "ignore-nested-configs",
defaultValue: false,
usage: "ignores nested configuration files")

let useScriptInputFilesOption = Option(key: "use-script-input-files",
defaultValue: false,
usage: "read SCRIPT_INPUT_FILE* environment variables " +
Expand Down
Loading