diff --git a/CHANGELOG.md b/CHANGELOG.md index 6538b218b..8430f2828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,10 @@ #### Bug Fixes +* Ignore TipKit's `#Rule` macro in `empty_count` rule. + [Ueeek](https://github.com/Ueeek) + [#5883](https://github.com/realm/SwiftLint/issues/5883) + * Ignore super calls with trailing closures in `unneeded_override` rule. [SimplyDanny](https://github.com/SimplyDanny) [#5886](ttps://github.com/realm/SwiftLint/issues/5886) diff --git a/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift b/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift index 3901237f2..c1166ee47 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Performance/EmptyCountRule.swift @@ -20,6 +20,8 @@ struct EmptyCountRule: Rule { Example("[Int]().count == 0o07"), Example("discount == 0"), Example("order.discount == 0"), + Example("let rule = #Rule(Tips.Event(id: \"someTips\")) { $0.donations.count == 0 }"), + Example("#Rule(param1: \"param1\")", excludeFromDocumentation: true), ], triggeringExamples: [ Example("[Int]().↓count == 0"), @@ -32,6 +34,23 @@ struct EmptyCountRule: Rule { Example("[Int]().↓count == 0b00"), Example("[Int]().↓count == 0o00"), Example("↓count == 0"), + Example("#ExampleMacro { $0.list.↓count == 0 }"), + Example("#Rule { $0.donations.↓count == 0 }", excludeFromDocumentation: true), + Example( + "#Rule(param1: \"param1\", param2: \"param2\") { $0.donations.↓count == 0 }", + excludeFromDocumentation: true + ), + Example( + "#Rule(param1: \"param1\") { $0.donations.↓count == 0 } closure2: { doSomething() }", + excludeFromDocumentation: true + ), + Example("#Rule(param1: \"param1\") { return $0.donations.↓count == 0 }", excludeFromDocumentation: true), + Example(""" + #Rule(param1: "param1") { + doSomething() + return $0.donations.↓count == 0 + } + """, excludeFromDocumentation: true), ], corrections: [ Example("[].↓count == 0"): @@ -62,6 +81,10 @@ struct EmptyCountRule: Rule { Example("isEmpty && [Int]().isEmpty"), Example("[Int]().count != 3 && [Int]().↓count != 0 || ↓count == 0 && [Int]().count > 2"): Example("[Int]().count != 3 && ![Int]().isEmpty || isEmpty && [Int]().count > 2"), + Example("#ExampleMacro { $0.list.↓count == 0 }"): + Example("#ExampleMacro { $0.list.isEmpty }"), + Example("#Rule(param1: \"param1\") { return $0.donations.↓count == 0 }"): + Example("#Rule(param1: \"param1\") { return $0.donations.isEmpty }"), ] ) } @@ -77,6 +100,10 @@ private extension EmptyCountRule { violations.append(position) } } + + override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { + node.isTipsRuleMacro ? .skipChildren : .visitChildren + } } final class Rewriter: ViolationsSyntaxRewriter { @@ -105,6 +132,14 @@ private extension EmptyCountRule { } return super.visit(node) } + + override func visit(_ node: MacroExpansionExprSyntax) -> ExprSyntax { + if node.isTipsRuleMacro { + ExprSyntax(node) + } else { + super.visit(node) + } + } } } @@ -137,6 +172,15 @@ private extension TokenSyntax { } } +private extension MacroExpansionExprSyntax { + var isTipsRuleMacro: Bool { + macroName.text == "Rule" && + additionalTrailingClosures.isEmpty && + arguments.count == 1 && + trailingClosure.map { $0.statements.onlyElement?.item.is(ReturnStmtSyntax.self) == false } ?? false + } +} + private extension ExprSyntaxProtocol { var negated: ExprSyntax { ExprSyntax(PrefixOperatorExprSyntax(operator: .prefixOperator("!"), expression: self)) diff --git a/Tests/BuiltInRulesTests/EmptyCountRuleTests.swift b/Tests/BuiltInRulesTests/EmptyCountRuleTests.swift index 7df9e4910..75d50cf45 100644 --- a/Tests/BuiltInRulesTests/EmptyCountRuleTests.swift +++ b/Tests/BuiltInRulesTests/EmptyCountRuleTests.swift @@ -15,6 +15,7 @@ final class EmptyCountRuleTests: SwiftLintTestCase { Example("discount == 0\n"), Example("order.discount == 0\n"), Example("count == 0\n"), + Example("let rule = #Rule(Tips.Event(id: \"someTips\")) { $0.donations.isEmpty }"), ] let triggeringExamples = [ Example("[Int]().↓count == 0\n"), @@ -24,6 +25,7 @@ final class EmptyCountRuleTests: SwiftLintTestCase { Example("[Int]().↓count == 0x00_00\n"), Example("[Int]().↓count == 0b00\n"), Example("[Int]().↓count == 0o00\n"), + Example("#ExampleMacro { $0.list.↓count == 0 }"), ] let corrections = [ @@ -55,6 +57,8 @@ final class EmptyCountRuleTests: SwiftLintTestCase { Example("count == 0 && [Int]().isEmpty"), Example("[Int]().count != 3 && [Int]().↓count != 0 || count == 0 && [Int]().count > 2"): Example("[Int]().count != 3 && ![Int]().isEmpty || count == 0 && [Int]().count > 2"), + Example("#ExampleMacro { $0.list.↓count == 0 }"): + Example("#ExampleMacro { $0.list.isEmpty }"), ] let description = EmptyCountRule.description