From 47d9dcddbbdc4f2fa50bb3ef76536212ccb64d19 Mon Sep 17 00:00:00 2001 From: "Aryan Singh K." <70511529+aryansk@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:31:50 +0530 Subject: [PATCH] feat: add command line rule exclusions --- CHANGELOG.md | 4 ++ README.md | 5 ++ .../Configuration+CommandLine.swift | 6 ++ .../Configuration/Configuration.swift | 7 +++ .../LintOrAnalyzeCommand.swift | 3 + Source/swiftlint/Commands/Analyze.swift | 1 + Source/swiftlint/Commands/Lint.swift | 1 + .../Common/LintOrAnalyzeArguments.swift | 6 ++ .../ConfigurationTests.swift | 59 +++++++++++++++++++ .../LintOrAnalyzeOptionsTests.swift | 1 + 10 files changed, 93 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d74d76a4554..935598148ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ ### Enhancements +* Add a repeatable `--disable-rule` command-line option for disabling rules for a single invocation. + [aryansk](https://github.com/aryansk) + [#6831](https://github.com/realm/SwiftLint/issues/6831) + * Add `#examples` and `#corrections` macros that expand lists and dictionaries of code strings into `Example`s, reducing boilerplate when defining a rule's triggering/non-triggering examples and corrections. Adopt diff --git a/README.md b/README.md index 615db35016a..f95a44bf94c 100644 --- a/README.md +++ b/README.md @@ -1041,6 +1041,11 @@ A simple example including just two configuration files looks like this: `swiftlint --config .swiftlint.yml --config .swiftlint_child.yml` +To disable rules for one invocation without changing a configuration file, use +`--disable-rule`. The option can be repeated: + +`swiftlint lint --disable-rule todo --disable-rule line_length` + ### Nested Configurations In addition to a main configuration (the `.swiftlint.yml` file in the root diff --git a/Source/SwiftLintFramework/Configuration+CommandLine.swift b/Source/SwiftLintFramework/Configuration+CommandLine.swift index 599c913b16e..f5561a96f4d 100644 --- a/Source/SwiftLintFramework/Configuration+CommandLine.swift +++ b/Source/SwiftLintFramework/Configuration+CommandLine.swift @@ -293,6 +293,12 @@ extension Configuration { onlyRule: options.onlyRule, cachePath: options.cachePath ) + + guard options.disabledRule.isNotEmpty else { + return + } + + disableRules(options.disabledRule) } } diff --git a/Source/SwiftLintFramework/Configuration/Configuration.swift b/Source/SwiftLintFramework/Configuration/Configuration.swift index 7a29c524362..9695da29a82 100644 --- a/Source/SwiftLintFramework/Configuration/Configuration.swift +++ b/Source/SwiftLintFramework/Configuration/Configuration.swift @@ -283,6 +283,13 @@ public struct Configuration { } // MARK: - Methods: Internal + internal mutating func disableRules(_ ruleIdentifiers: [String]) { + let disabledRulesConfiguration = Self( + rulesMode: .defaultConfiguration(disabled: Set(ruleIdentifiers), optIn: []) + ) + rulesWrapper = rulesWrapper.merged(with: disabledRulesConfiguration.rulesWrapper) + } + mutating func makeIncludedAndExcludedPaths(relativeTo newBasePath: URL) { includedPaths = includedPaths.map { $0.relative(to: newBasePath) } excludedPaths = excludedPaths.map { $0.relative(to: newBasePath) } diff --git a/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift b/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift index a6214bb8c62..1add5d62c87 100644 --- a/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift +++ b/Source/SwiftLintFramework/LintOrAnalyzeCommand.swift @@ -52,6 +52,7 @@ package struct LintOrAnalyzeOptions { let ignoreCache: Bool let enableAllRules: Bool let onlyRule: [String] + let disabledRule: [String] let autocorrect: Bool let format: Bool let disableSourceKit: Bool @@ -81,6 +82,7 @@ package struct LintOrAnalyzeOptions { ignoreCache: Bool, enableAllRules: Bool, onlyRule: [String], + disabledRule: [String], autocorrect: Bool, format: Bool, disableSourceKit: Bool, @@ -109,6 +111,7 @@ package struct LintOrAnalyzeOptions { self.ignoreCache = ignoreCache self.enableAllRules = enableAllRules self.onlyRule = onlyRule + self.disabledRule = disabledRule self.autocorrect = autocorrect self.format = format self.disableSourceKit = disableSourceKit diff --git a/Source/swiftlint/Commands/Analyze.swift b/Source/swiftlint/Commands/Analyze.swift index 0e81aa88248..ab2983b975e 100644 --- a/Source/swiftlint/Commands/Analyze.swift +++ b/Source/swiftlint/Commands/Analyze.swift @@ -43,6 +43,7 @@ extension SwiftLint { ignoreCache: true, enableAllRules: false, onlyRule: common.onlyRule, + disabledRule: common.disabledRule, autocorrect: common.fix, format: common.format, disableSourceKit: false, diff --git a/Source/swiftlint/Commands/Lint.swift b/Source/swiftlint/Commands/Lint.swift index d67a22625ec..ef7c72a8cc4 100644 --- a/Source/swiftlint/Commands/Lint.swift +++ b/Source/swiftlint/Commands/Lint.swift @@ -60,6 +60,7 @@ extension SwiftLint { ignoreCache: noCache, enableAllRules: enableAllRules, onlyRule: common.onlyRule, + disabledRule: common.disabledRule, autocorrect: common.fix, format: common.format, disableSourceKit: disableSourceKit, diff --git a/Source/swiftlint/Common/LintOrAnalyzeArguments.swift b/Source/swiftlint/Common/LintOrAnalyzeArguments.swift index b316b0aaef7..8adfb1f7c39 100644 --- a/Source/swiftlint/Common/LintOrAnalyzeArguments.swift +++ b/Source/swiftlint/Common/LintOrAnalyzeArguments.swift @@ -62,6 +62,12 @@ struct LintOrAnalyzeArguments: ParsableArguments { """ ) var onlyRule: [String] = [] + @Option( + name: .customLong("disable-rule"), + parsing: .singleValue, + help: "Disable the specified rule for this invocation. Can be specified repeatedly." + ) + var disabledRule: [String] = [] } // MARK: - Common Argument Help diff --git a/Tests/FileSystemAccessTests/ConfigurationTests.swift b/Tests/FileSystemAccessTests/ConfigurationTests.swift index 83c648a40d3..c6ef10c0959 100644 --- a/Tests/FileSystemAccessTests/ConfigurationTests.swift +++ b/Tests/FileSystemAccessTests/ConfigurationTests.swift @@ -100,6 +100,25 @@ struct ConfigurationTests { // swiftlint:disable:this type_body_length #expect(onlyRuleIdentifiers.dropLast() == mergedConfiguration.enabledRuleIdentifiers) } + @Test + func commandLineDisabledRules() { + Configuration.resetCache() + + let allRulesConfiguration = Configuration( + options: .init(enableAllRules: true, disabledRule: ["todo"]) + ) + #expect(!allRulesConfiguration.enabledRuleIdentifiers.contains("todo")) + #expect( + allRulesConfiguration.rules.count == RuleRegistry.shared.list.list.count - 1 + ) + + Configuration.resetCache() + let onlyRulesConfiguration = Configuration( + options: .init(onlyRule: ["line_length", "todo"], disabledRule: ["todo"]) + ) + #expect(onlyRulesConfiguration.enabledRuleIdentifiers == ["line_length"]) + } + @Test func onlyRules() throws { let only = ["nesting", "todo"] @@ -679,3 +698,43 @@ private extension Configuration { rules.map { type(of: $0).identifier }.sorted() } } + +private extension LintOrAnalyzeOptions { + init( + enableAllRules: Bool = false, + onlyRule: [String] = [], + disabledRule: [String] = [] + ) { + self.init( + mode: .lint, + paths: [], + useSTDIN: false, + configurationFiles: [], + strict: false, + lenient: false, + forceExclude: false, + useExcludingByPrefix: false, + useScriptInputFiles: false, + useScriptInputFileLists: false, + benchmark: false, + reporter: nil, + baseline: nil, + writeBaseline: nil, + workingDirectory: nil, + quiet: true, + output: nil, + progress: false, + cachePath: nil, + ignoreCache: true, + enableAllRules: enableAllRules, + onlyRule: onlyRule, + disabledRule: disabledRule, + autocorrect: false, + format: false, + disableSourceKit: false, + compilerLogPath: nil, + compileCommands: nil, + checkForUpdates: false + ) + } +} diff --git a/Tests/FrameworkTests/LintOrAnalyzeOptionsTests.swift b/Tests/FrameworkTests/LintOrAnalyzeOptionsTests.swift index 27c1b30bda5..cbcb0246b94 100644 --- a/Tests/FrameworkTests/LintOrAnalyzeOptionsTests.swift +++ b/Tests/FrameworkTests/LintOrAnalyzeOptionsTests.swift @@ -64,6 +64,7 @@ private extension LintOrAnalyzeOptions { ignoreCache: false, enableAllRules: false, onlyRule: [], + disabledRule: [], autocorrect: false, format: false, disableSourceKit: false,