From 2d5c06aae4cdcac8f354150b6f162b04cad84d9a Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sun, 17 May 2026 22:56:39 +0800 Subject: [PATCH] feat(empty_enum_arguments): add excluded_members configuration Allow opting out specific member names from empty-enum-argument checks and autocorrect, which avoids breaking APIs such as HealthKit static functions. Closes #5269 --- CHANGELOG.md | 5 +++ .../EmptyEnumArgumentsConfiguration.swift | 9 +++++ .../Rules/Style/EmptyEnumArgumentsRule.swift | 37 ++++++++++++++++--- 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/EmptyEnumArgumentsConfiguration.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index be786e3de4..b307c1876a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ ### Enhancements +* Add `excluded_members` configuration to `empty_enum_arguments` to skip members + that require empty parentheses (e.g. HealthKit static functions). + [leno23](https://github.com/leno23) + [#5269](https://github.com/realm/SwiftLint/issues/5269) + * 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/SwiftLintBuiltInRules/Rules/RuleConfigurations/EmptyEnumArgumentsConfiguration.swift b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/EmptyEnumArgumentsConfiguration.swift new file mode 100644 index 0000000000..e332c42eb5 --- /dev/null +++ b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/EmptyEnumArgumentsConfiguration.swift @@ -0,0 +1,9 @@ +import SwiftLintCore + +@AutoConfigParser +struct EmptyEnumArgumentsConfiguration: SeverityBasedRuleConfiguration { + @ConfigurationElement(key: "severity") + private(set) var severityConfiguration = SeverityConfiguration(.warning) + @ConfigurationElement(key: "excluded_members") + private(set) var excludedMembers = Set() +} diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/EmptyEnumArgumentsRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/EmptyEnumArgumentsRule.swift index 331a97c34f..98250ee087 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/EmptyEnumArgumentsRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/EmptyEnumArgumentsRule.swift @@ -26,7 +26,7 @@ private func wrapInFunc(_ str: String, file: StaticString = #filePath, line: UIn @SwiftSyntaxRule(explicitRewriter: true) struct EmptyEnumArgumentsRule: Rule { - var configuration = SeverityConfiguration(.warning) + var configuration = EmptyEnumArgumentsConfiguration() static let description = RuleDescription( identifier: "empty_enum_arguments", @@ -61,6 +61,7 @@ struct EmptyEnumArgumentsRule: Rule { return true } """), + Example("if case .gram() = foo {}", configuration: ["excluded_members": ["gram"]]), ], triggeringExamples: [ wrapInSwitch("case .bar↓(_)"), @@ -117,13 +118,19 @@ struct EmptyEnumArgumentsRule: Rule { private extension EmptyEnumArgumentsRule { final class Visitor: ViolationsSyntaxVisitor { override func visitPost(_ node: SwitchCaseItemSyntax) { - if let violationPosition = node.pattern.emptyEnumArgumentsViolation(rewrite: false)?.position { + if let violationPosition = node.pattern.emptyEnumArgumentsViolation( + rewrite: false, + excludedMembers: configuration.excludedMembers + )?.position { violations.append(violationPosition) } } override func visitPost(_ node: MatchingPatternConditionSyntax) { - if let violationPosition = node.pattern.emptyEnumArgumentsViolation(rewrite: false)?.position { + if let violationPosition = node.pattern.emptyEnumArgumentsViolation( + rewrite: false, + excludedMembers: configuration.excludedMembers + )?.position { violations.append(violationPosition) } } @@ -131,7 +138,10 @@ private extension EmptyEnumArgumentsRule { final class Rewriter: ViolationsSyntaxRewriter { override func visit(_ node: SwitchCaseItemSyntax) -> SwitchCaseItemSyntax { - guard let (_, newPattern) = node.pattern.emptyEnumArgumentsViolation(rewrite: true) else { + guard let (_, newPattern) = node.pattern.emptyEnumArgumentsViolation( + rewrite: true, + excludedMembers: configuration.excludedMembers + ) else { return super.visit(node) } numberOfCorrections += 1 @@ -139,7 +149,10 @@ private extension EmptyEnumArgumentsRule { } override func visit(_ node: MatchingPatternConditionSyntax) -> MatchingPatternConditionSyntax { - guard let (_, newPattern) = node.pattern.emptyEnumArgumentsViolation(rewrite: true) else { + guard let (_, newPattern) = node.pattern.emptyEnumArgumentsViolation( + rewrite: true, + excludedMembers: configuration.excludedMembers + ) else { return super.visit(node) } numberOfCorrections += 1 @@ -149,7 +162,10 @@ private extension EmptyEnumArgumentsRule { } private extension PatternSyntax { - func emptyEnumArgumentsViolation(rewrite: Bool) -> (position: AbsolutePosition, pattern: PatternSyntax)? { + func emptyEnumArgumentsViolation( + rewrite: Bool, + excludedMembers: Set + ) -> (position: AbsolutePosition, pattern: PatternSyntax)? { guard var pattern = `as`(ExpressionPatternSyntax.self), let expression = pattern.expression.as(FunctionCallExprSyntax.self), @@ -161,6 +177,15 @@ private extension PatternSyntax { return nil } + let memberName = expression.innermostFunctionCall.calledExpression + .as(MemberAccessExprSyntax.self)? + .declName + .baseName + .text + if let memberName, excludedMembers.contains(memberName) { + return nil + } + if rewrite { pattern.expression = expression.removingInnermostDiscardArguments }