Skip to content
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

### Enhancements

* Add a `--parent-config` option to the `lint` command, allowing a parent
configuration to be supplied without modifying the child configuration file.
[LizunovSergey](https://github.com/LizunovSergey)
[#5421](https://github.com/realm/SwiftLint/issues/5421)

* Speed up the `collection_alignment` rule, which read the whole file's
source lines once per element of a dictionary literal.
[Brett-Best](https://github.com/Brett-Best)
Expand Down
3 changes: 2 additions & 1 deletion Source/SwiftLintFramework/Configuration+CommandLine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,12 @@ extension Configuration {

init(options: LintOrAnalyzeOptions) {
self.init(
configurationFiles: options.configurationFiles,
configurationFiles: options.effectiveConfigurationFiles,
enableAllRules: options.enableAllRules,
onlyRule: options.onlyRule,
cachePath: options.cachePath
)
basedOnCustomConfigurationFiles = options.configurationFiles.isNotEmpty
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public struct Configuration {
/// This value is `true` iff the `--config` parameter was used to specify (a) configuration file(s)
/// In particular, this means that the value is also `true` if the `--config` parameter
/// was used to explicitly specify the default `.swiftlint.yml` as the configuration file
public private(set) var basedOnCustomConfigurationFiles = false
public internal(set) var basedOnCustomConfigurationFiles = false

// MARK: Public Computed
/// All rules enabled in this configuration
Expand Down
19 changes: 19 additions & 0 deletions Source/SwiftLintFramework/LintOrAnalyzeCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ package struct LintOrAnalyzeOptions {
let paths: [URL]
let useSTDIN: Bool
let configurationFiles: [URL]
let parentConfigurationFile: URL?
let strict: Bool
let lenient: Bool
let forceExclude: Bool
Expand Down Expand Up @@ -63,6 +64,7 @@ package struct LintOrAnalyzeOptions {
paths: [URL],
useSTDIN: Bool,
configurationFiles: [URL],
parentConfigurationFile: URL?,
strict: Bool,
lenient: Bool,
forceExclude: Bool,
Expand Down Expand Up @@ -91,6 +93,7 @@ package struct LintOrAnalyzeOptions {
self.paths = paths
self.useSTDIN = useSTDIN
self.configurationFiles = configurationFiles
self.parentConfigurationFile = parentConfigurationFile
self.strict = strict
self.lenient = lenient
self.forceExclude = forceExclude
Expand Down Expand Up @@ -124,6 +127,22 @@ package struct LintOrAnalyzeOptions {
var capitalizedVerb: String {
verb.capitalized
}

var effectiveConfigurationFiles: [URL] {
guard let parentConfigurationFile else {
return configurationFiles
}

let defaultConfigurationFile = Configuration.defaultFileName.url()
let childConfigurationFiles: [URL] = if configurationFiles.isNotEmpty {
configurationFiles
} else if defaultConfigurationFile.exists {
[defaultConfigurationFile]
} else {
[]
}
return [parentConfigurationFile] + childConfigurationFiles
}
}

package struct LintOrAnalyzeCommand {
Expand Down
1 change: 1 addition & 0 deletions Source/swiftlint/Commands/Analyze.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ extension SwiftLint {
paths: allPaths,
useSTDIN: false,
configurationFiles: common.config,
parentConfigurationFile: nil,
strict: common.leniency == .strict,
lenient: common.leniency == .lenient,
forceExclude: common.forceExclude,
Expand Down
3 changes: 3 additions & 0 deletions Source/swiftlint/Commands/Lint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ extension SwiftLint {

@OptionGroup
var common: LintOrAnalyzeArguments
@Option(help: "The path to a parent SwiftLint configuration file.")
var parentConfig: URL?
@Flag(help: "Lint standard input.")
var useSTDIN = false
@Flag(help: quietOptionDescription(for: .lint))
Expand Down Expand Up @@ -42,6 +44,7 @@ extension SwiftLint {
paths: allPaths,
useSTDIN: useSTDIN,
configurationFiles: common.config,
parentConfigurationFile: parentConfig,
strict: common.leniency == .strict,
lenient: common.leniency == .lenient,
forceExclude: common.forceExclude,
Expand Down
88 changes: 86 additions & 2 deletions Tests/FrameworkTests/LintOrAnalyzeOptionsTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Foundation
@testable import SwiftLintFramework
import TestHelpers
import Testing

@Suite
Expand Down Expand Up @@ -38,14 +40,96 @@ struct LintOrAnalyzeOptionsTests {
}
}
}

@Test
func parentConfigurationFilePrecedesExplicitConfigurationFiles() {
let options = LintOrAnalyzeOptions(
configurationFiles: ["child-1.yml".url(), "child-2.yml".url()],
parentConfigurationFile: "parent.yml".url()
)

#expect(options.effectiveConfigurationFiles == [
"parent.yml".url(),
"child-1.yml".url(),
"child-2.yml".url(),
])
}

@Test(.temporaryDirectory)
func parentConfigurationFilePrecedesDefaultConfigurationFile() throws {
try "reporter: csv".write(
to: Configuration.defaultFileName.url(),
atomically: true,
encoding: .utf8
)
let options = LintOrAnalyzeOptions(
configurationFiles: [],
parentConfigurationFile: "parent.yml".url()
)

#expect(options.effectiveConfigurationFiles == [
"parent.yml".url(),
Configuration.defaultFileName.url(),
])
}

@Test(.temporaryDirectory)
func parentConfigurationFileWorksWithoutDefaultConfigurationFile() {
let options = LintOrAnalyzeOptions(
configurationFiles: [],
parentConfigurationFile: "parent.yml".url()
)

#expect(options.effectiveConfigurationFiles == ["parent.yml".url()])
}

@Test(.rulesRegistered, .temporaryDirectory)
func parentConfigurationFilePreservesNestedConfigurations() throws {
let parentConfigurationFile = "parent.yml".url()
try "disabled_rules: []".write(to: parentConfigurationFile, atomically: true, encoding: .utf8)
try "disabled_rules: []".write(
to: Configuration.defaultFileName.url(),
atomically: true,
encoding: .utf8
)

let nestedDirectory = URL.cwd.appending(path: "Nested", directoryHint: .isDirectory)
try FileManager.default.createDirectory(at: nestedDirectory, withIntermediateDirectories: true)
try "disabled_rules: [line_length]".write(
to: nestedDirectory.appending(path: Configuration.defaultFileName),
atomically: true,
encoding: .utf8
)

let options = LintOrAnalyzeOptions(
configurationFiles: [],
parentConfigurationFile: parentConfigurationFile
)
let configuration = Configuration(options: options)
let nestedFile = SwiftLintFile(
pathDeferringReading: nestedDirectory.appending(path: "Example.swift")
)

#expect(!configuration.basedOnCustomConfigurationFiles)
#expect(!configuration.rulesWrapper.disabledRuleIdentifiers.contains("line_length"))
#expect(
configuration.configuration(for: nestedFile)
.rulesWrapper.disabledRuleIdentifiers.contains("line_length")
)
}
}

private extension LintOrAnalyzeOptions {
init(leniency: Leniency) {
init(
leniency: Leniency = (strict: false, lenient: false),
configurationFiles: [URL] = [],
parentConfigurationFile: URL? = nil
) {
self.init(mode: .lint,
paths: [],
useSTDIN: true,
configurationFiles: [],
configurationFiles: configurationFiles,
parentConfigurationFile: parentConfigurationFile,
strict: leniency.strict,
lenient: leniency.lenient,
forceExclude: false,
Expand Down