diff --git a/CHANGELOG.md b/CHANGELOG.md index 52cafe5b3e..e0f3f789b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,11 @@ ### Enhancements -* Print fixed code read from stdin to stdout. +* Add `--fail-on-unfixable` flag for single-pass fix and validation. + [omar-y-abdi](https://github.com/omar-y-abdi) + [#6450](https://github.com/realm/SwiftLint/issues/6450) + +* Print fixed code read from stdin to stdout. [SimplyDanny](https://github.com/SimplyDanny) [#6501](https://github.com/realm/SwiftLint/issues/6501) diff --git a/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift b/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift index 6444adc84e..062fb3b7fc 100644 --- a/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift +++ b/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift @@ -58,6 +58,7 @@ package struct LintOrAnalyzeOptions { let compilerLogPath: String? let compileCommands: String? let checkForUpdates: Bool + let failOnUnfixable: Bool package init(mode: LintOrAnalyzeMode, paths: [String], @@ -86,7 +87,8 @@ package struct LintOrAnalyzeOptions { disableSourceKit: Bool, compilerLogPath: String?, compileCommands: String?, - checkForUpdates: Bool) { + checkForUpdates: Bool, + failOnUnfixable: Bool = false) { self.mode = mode self.paths = paths self.useSTDIN = useSTDIN @@ -115,6 +117,42 @@ package struct LintOrAnalyzeOptions { self.compilerLogPath = compilerLogPath self.compileCommands = compileCommands self.checkForUpdates = checkForUpdates + self.failOnUnfixable = failOnUnfixable + } + + /// Returns a copy of these options with `autocorrect` set to `false` and `failOnUnfixable` set to `false`, + /// suitable for a re-lint pass after autocorrection. + func asLintOnly() -> Self { + Self( + mode: mode, + paths: paths, + useSTDIN: useSTDIN, + configurationFiles: configurationFiles, + strict: strict, + lenient: lenient, + forceExclude: forceExclude, + useExcludingByPrefix: useExcludingByPrefix, + useScriptInputFiles: useScriptInputFiles, + useScriptInputFileLists: useScriptInputFileLists, + benchmark: benchmark, + reporter: reporter, + baseline: baseline, + writeBaseline: writeBaseline, + workingDirectory: nil, // Already changed in run() + quiet: quiet, + output: output, + progress: progress, + cachePath: cachePath, + ignoreCache: true, // Must ignore cache since files were just modified + enableAllRules: enableAllRules, + onlyRule: onlyRule, + autocorrect: false, + format: false, + disableSourceKit: disableSourceKit, + compilerLogPath: compilerLogPath, + compileCommands: compileCommands, + checkForUpdates: false // Already checked in autocorrect pass + ) } var verb: String { @@ -140,7 +178,19 @@ package struct LintOrAnalyzeCommand { } } try await Signposts.record(name: "LintOrAnalyzeCommand.run") { - try await options.autocorrect ? autocorrect(options) : lintOrAnalyze(options) + if options.autocorrect { + try await autocorrect(options) + if options.failOnUnfixable { + try await lintOrAnalyze(options.asLintOnly()) + } + } else { + if options.failOnUnfixable { + queuedPrintError( + "warning: The option --fail-on-unfixable has no effect without --fix." + ) + } + try await lintOrAnalyze(options) + } } } diff --git a/Source/swiftlint/Commands/Analyze.swift b/Source/swiftlint/Commands/Analyze.swift index 968fb99e27..fb79b60f1c 100644 --- a/Source/swiftlint/Commands/Analyze.swift +++ b/Source/swiftlint/Commands/Analyze.swift @@ -47,7 +47,8 @@ extension SwiftLint { disableSourceKit: false, compilerLogPath: compilerLogPath, compileCommands: compileCommands, - checkForUpdates: common.checkForUpdates + checkForUpdates: common.checkForUpdates, + failOnUnfixable: common.failOnUnfixable ) try await LintOrAnalyzeCommand.run(options) diff --git a/Source/swiftlint/Commands/Lint.swift b/Source/swiftlint/Commands/Lint.swift index 56c8cca192..475a529448 100644 --- a/Source/swiftlint/Commands/Lint.swift +++ b/Source/swiftlint/Commands/Lint.swift @@ -30,7 +30,7 @@ extension SwiftLint { func run() async throws { Issue.printDeprecationWarnings = !silenceDeprecationWarnings - if common.fix, let leniency = common.leniency { + if common.fix, !common.failOnUnfixable, let leniency = common.leniency { Issue.genericWarning("The option --\(leniency) has no effect together with --fix.").print() } @@ -64,7 +64,8 @@ extension SwiftLint { disableSourceKit: disableSourceKit, compilerLogPath: nil, compileCommands: nil, - checkForUpdates: common.checkForUpdates + checkForUpdates: common.checkForUpdates, + failOnUnfixable: common.failOnUnfixable ) try await LintOrAnalyzeCommand.run(options) } diff --git a/Source/swiftlint/Common/LintOrAnalyzeArguments.swift b/Source/swiftlint/Common/LintOrAnalyzeArguments.swift index b47e4162a6..bfdc8249af 100644 --- a/Source/swiftlint/Common/LintOrAnalyzeArguments.swift +++ b/Source/swiftlint/Common/LintOrAnalyzeArguments.swift @@ -62,6 +62,8 @@ struct LintOrAnalyzeArguments: ParsableArguments { """ ) var onlyRule: [String] = [] + @Flag(help: "Fail with a non-zero exit code if there are remaining violations after fix.") + var failOnUnfixable = false } // MARK: - Common Argument Help