From 23d6a7c3f56f2d0815774415a12b5405dfd1d9b0 Mon Sep 17 00:00:00 2001 From: Martin Redington Date: Wed, 30 Oct 2024 22:03:54 +0000 Subject: [PATCH] Added `lenient` command line option (#5801) --- CHANGELOG.md | 5 ++ README.md | 3 + .../Extensions/Configuration+Merging.swift | 1 + .../Extensions/Configuration+Parsing.swift | 2 + .../SwiftLintCore/Models/Configuration.swift | 11 +++ .../LintOrAnalyzeCommand.swift | 26 +++++-- .../ConfigurationTests.swift | 6 ++ .../LintOrAnalyzeOptionsTests.swift | 72 +++++++++++++++++++ 8 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 Tests/SwiftLintFrameworkTests/LintOrAnalyzeOptionsTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f2100741..ffb00ebc19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,11 @@ argument `--use-script-input-file-lists`. [BlueVirusX](https://github.com/BlueVirusX) +* Adds a `lenient` configuration file setting, equivalent to the `--lenient` + command line option. + [Martin Redington](https://github.com/mildm8nnered) + [#5801](https://github.com/realm/SwiftLint/issues/5801) + #### Bug Fixes * Run command plugin in whole package if no targets are defined in the diff --git a/README.md b/README.md index 2d37e03326..39b7294f7e 100644 --- a/README.md +++ b/README.md @@ -723,6 +723,9 @@ allow_zero_lintable_files: false # If true, SwiftLint will treat all warnings as errors. strict: false +# If true, SwiftLint will treat all errors as warnings. +lenient: false + # The path to a baseline file, which will be used to filter out detected violations. baseline: Baseline.json diff --git a/Source/SwiftLintCore/Extensions/Configuration+Merging.swift b/Source/SwiftLintCore/Extensions/Configuration+Merging.swift index 05e5dfd490..82a11e342d 100644 --- a/Source/SwiftLintCore/Extensions/Configuration+Merging.swift +++ b/Source/SwiftLintCore/Extensions/Configuration+Merging.swift @@ -26,6 +26,7 @@ extension Configuration { cachePath: cachePath, allowZeroLintableFiles: childConfiguration.allowZeroLintableFiles, strict: childConfiguration.strict, + lenient: childConfiguration.lenient, baseline: childConfiguration.baseline, writeBaseline: childConfiguration.writeBaseline, checkForUpdates: childConfiguration.checkForUpdates diff --git a/Source/SwiftLintCore/Extensions/Configuration+Parsing.swift b/Source/SwiftLintCore/Extensions/Configuration+Parsing.swift index 54dcf399da..c8326c0776 100644 --- a/Source/SwiftLintCore/Extensions/Configuration+Parsing.swift +++ b/Source/SwiftLintCore/Extensions/Configuration+Parsing.swift @@ -15,6 +15,7 @@ extension Configuration { case analyzerRules = "analyzer_rules" case allowZeroLintableFiles = "allow_zero_lintable_files" case strict = "strict" + case lenient = "lenient" case baseline = "baseline" case writeBaseline = "write_baseline" case checkForUpdates = "check_for_updates" @@ -103,6 +104,7 @@ extension Configuration { pinnedVersion: dict[Key.swiftlintVersion.rawValue].map { ($0 as? String) ?? String(describing: $0) }, allowZeroLintableFiles: dict[Key.allowZeroLintableFiles.rawValue] as? Bool ?? false, strict: dict[Key.strict.rawValue] as? Bool ?? false, + lenient: dict[Key.lenient.rawValue] as? Bool ?? false, baseline: dict[Key.baseline.rawValue] as? String, writeBaseline: dict[Key.writeBaseline.rawValue] as? String, checkForUpdates: dict[Key.checkForUpdates.rawValue] as? Bool ?? false diff --git a/Source/SwiftLintCore/Models/Configuration.swift b/Source/SwiftLintCore/Models/Configuration.swift index b8169afe11..bf5d6d9bc1 100644 --- a/Source/SwiftLintCore/Models/Configuration.swift +++ b/Source/SwiftLintCore/Models/Configuration.swift @@ -38,6 +38,9 @@ public struct Configuration { /// Treat warnings as errors. public let strict: Bool + /// Treat errors as warnings. + public let lenient: Bool + /// The path to read a baseline from. public let baseline: String? @@ -83,6 +86,7 @@ public struct Configuration { cachePath: String?, allowZeroLintableFiles: Bool, strict: Bool, + lenient: Bool, baseline: String?, writeBaseline: String?, checkForUpdates: Bool @@ -97,6 +101,7 @@ public struct Configuration { self.cachePath = cachePath self.allowZeroLintableFiles = allowZeroLintableFiles self.strict = strict + self.lenient = lenient self.baseline = baseline self.writeBaseline = writeBaseline self.checkForUpdates = checkForUpdates @@ -117,6 +122,7 @@ public struct Configuration { cachePath = configuration.cachePath allowZeroLintableFiles = configuration.allowZeroLintableFiles strict = configuration.strict + lenient = configuration.lenient baseline = configuration.baseline writeBaseline = configuration.writeBaseline checkForUpdates = configuration.checkForUpdates @@ -143,6 +149,7 @@ public struct Configuration { /// - parameter allowZeroLintableFiles: Allow SwiftLint to exit successfully when passed ignored or unlintable /// files. /// - parameter strict: Treat warnings as errors. + /// - parameter lenient: Treat errors as warnings. /// - parameter baseline: The path to read a baseline from. /// - parameter writeBaseline: The path to write a baseline to. /// - parameter checkForUpdates: Check for updates to SwiftLint. @@ -160,6 +167,7 @@ public struct Configuration { pinnedVersion: String? = nil, allowZeroLintableFiles: Bool = false, strict: Bool = false, + lenient: Bool = false, baseline: String? = nil, writeBaseline: String? = nil, checkForUpdates: Bool = false @@ -189,6 +197,7 @@ public struct Configuration { cachePath: cachePath, allowZeroLintableFiles: allowZeroLintableFiles, strict: strict, + lenient: lenient, baseline: baseline, writeBaseline: writeBaseline, checkForUpdates: checkForUpdates @@ -304,6 +313,7 @@ extension Configuration: Hashable { hasher.combine(reporter) hasher.combine(allowZeroLintableFiles) hasher.combine(strict) + hasher.combine(lenient) hasher.combine(baseline) hasher.combine(writeBaseline) hasher.combine(checkForUpdates) @@ -325,6 +335,7 @@ extension Configuration: Hashable { lhs.fileGraph == rhs.fileGraph && lhs.allowZeroLintableFiles == rhs.allowZeroLintableFiles && lhs.strict == rhs.strict && + lhs.lenient == rhs.lenient && lhs.baseline == rhs.baseline && lhs.writeBaseline == rhs.writeBaseline && lhs.checkForUpdates == rhs.checkForUpdates && diff --git a/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift b/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift index f9e24151b4..746342c301 100644 --- a/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift +++ b/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift @@ -169,6 +169,7 @@ package struct LintOrAnalyzeCommand { currentViolations = applyLeniency( options: options, strict: builder.configuration.strict, + lenient: builder.configuration.lenient, violations: violationsBeforeLeniency ) visitorMutationQueue.sync { @@ -179,6 +180,7 @@ package struct LintOrAnalyzeCommand { currentViolations = applyLeniency( options: options, strict: builder.configuration.strict, + lenient: builder.configuration.lenient, violations: linter.styleViolations(using: builder.storage) ) } @@ -273,15 +275,16 @@ package struct LintOrAnalyzeCommand { private static func applyLeniency( options: LintOrAnalyzeOptions, strict: Bool, + lenient: Bool, violations: [StyleViolation] ) -> [StyleViolation] { - let strict = (strict && !options.lenient) || options.strict + let leniency = options.leniency(strict: strict, lenient: lenient) - switch (options.lenient, strict) { + switch leniency { case (false, false): return violations - case (true, false): + case (false, true): return violations.map { if $0.severity == .error { return $0.with(severity: .warning) @@ -289,7 +292,7 @@ package struct LintOrAnalyzeCommand { return $0 } - case (false, true): + case (true, false): return violations.map { if $0.severity == .warning { return $0.with(severity: .error) @@ -298,7 +301,7 @@ package struct LintOrAnalyzeCommand { } case (true, true): - queuedFatalError("Invalid command line options: 'lenient' and 'strict' are mutually exclusive.") + queuedFatalError("Invalid command line or config options: 'strict' and 'lenient' are mutually exclusive.") } } @@ -394,8 +397,8 @@ private class LintOrAnalyzeResultBuilder { } } -private extension LintOrAnalyzeOptions { - func writeToOutput(_ string: String) { +extension LintOrAnalyzeOptions { + fileprivate func writeToOutput(_ string: String) { guard let outFile = output else { queuedPrint(string) return @@ -411,6 +414,15 @@ private extension LintOrAnalyzeOptions { Issue.fileNotWritable(path: outFile).print() } } + + typealias Leniency = (strict: Bool, lenient: Bool) + + // Config file settings can be overridden by either `--strict` or `--lenient` command line options. + func leniency(strict configurationStrict: Bool, lenient configurationLenient: Bool) -> Leniency { + let strict = self.strict || (configurationStrict && !self.lenient) + let lenient = self.lenient || (configurationLenient && !self.strict) + return Leniency(strict: strict, lenient: lenient) + } } private actor CorrectionsBuilder { diff --git a/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift b/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift index ffca2d57ab..9fd493a068 100644 --- a/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift +++ b/Tests/SwiftLintFrameworkTests/ConfigurationTests.swift @@ -56,6 +56,7 @@ final class ConfigurationTests: SwiftLintTestCase { XCTAssertEqual(reporterFrom(identifier: config.reporter).identifier, "xcode") XCTAssertFalse(config.allowZeroLintableFiles) XCTAssertFalse(config.strict) + XCTAssertFalse(config.lenient) XCTAssertNil(config.baseline) XCTAssertNil(config.writeBaseline) XCTAssertFalse(config.checkForUpdates) @@ -458,6 +459,11 @@ final class ConfigurationTests: SwiftLintTestCase { XCTAssertTrue(configuration.strict) } + func testLenient() throws { + let configuration = try Configuration(dict: ["lenient": true]) + XCTAssertTrue(configuration.lenient) + } + func testBaseline() throws { let baselinePath = "Baseline.json" let configuration = try Configuration(dict: ["baseline": baselinePath]) diff --git a/Tests/SwiftLintFrameworkTests/LintOrAnalyzeOptionsTests.swift b/Tests/SwiftLintFrameworkTests/LintOrAnalyzeOptionsTests.swift new file mode 100644 index 0000000000..07ec490791 --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/LintOrAnalyzeOptionsTests.swift @@ -0,0 +1,72 @@ +@testable import SwiftLintFramework +import XCTest + +final class LintOrAnalyzeOptionsTests: XCTestCase { + private typealias Leniency = LintOrAnalyzeOptions.Leniency + + func testLeniency() { + let parameters = [ + Leniency(strict: false, lenient: false), + Leniency(strict: true, lenient: true), + Leniency(strict: true, lenient: false), + Leniency(strict: false, lenient: true), + ] + + for commandLine in parameters { + let options = LintOrAnalyzeOptions(leniency: commandLine) + for configuration in parameters { + let leniency = options.leniency(strict: configuration.strict, lenient: configuration.lenient) + if commandLine.strict { + // Command line takes precedence. + XCTAssertTrue(leniency.strict) + if !commandLine.lenient { + // `--strict` should disable configuration lenience. + XCTAssertFalse(leniency.lenient) + } + } else if commandLine.lenient { + // Command line takes precedence, and should override + // `strict` in the configuration. + XCTAssertTrue(leniency.lenient) + XCTAssertFalse(leniency.strict) + } else if configuration.strict { + XCTAssertTrue(leniency.strict) + } else if configuration.lenient { + XCTAssertTrue(leniency.lenient) + } + } + } + } +} + +private extension LintOrAnalyzeOptions { + init(leniency: Leniency) { + self.init(mode: .lint, + paths: [], + useSTDIN: true, + configurationFiles: [], + strict: leniency.strict, + lenient: leniency.lenient, + forceExclude: false, + useExcludingByPrefix: false, + useScriptInputFiles: false, + useScriptInputFileLists: false, + benchmark: false, + reporter: nil, + baseline: nil, + writeBaseline: nil, + workingDirectory: nil, + quiet: false, + output: nil, + progress: false, + cachePath: nil, + ignoreCache: false, + enableAllRules: false, + onlyRule: [], + autocorrect: false, + format: false, + compilerLogPath: nil, + compileCommands: nil, + checkForUpdates: false + ) + } +}