diff --git a/.swiftlint.yml b/.swiftlint.yml index 115c0f7115..93d6ba5998 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -43,6 +43,7 @@ disabled_rules: - prefer_nimble - prefer_self_in_static_references - prefixed_toplevel_constant + - redundant_extension - redundant_self_in_closure - required_deinit - self_binding diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1c654055..59205c9879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ #### Enhancements +* Add new `redundant_extension` rule that detects redundant extensions. + An extension is considered redundant if it does not define any + members, but only conformances. + [Muhammad Zeeshan](https://github.com/mzeeshanid) + [#5359](https://github.com/realm/SwiftLint/issues/5359) + * Prevent from compiling `SwiftLint` target when only using `SwiftLintPlugin` on macOS. [Julien Baillon](https://github.com/julien-baillon) [#5372](https://github.com/realm/SwiftLint/issues/5372) @@ -18,6 +24,11 @@ rule to ignore switch statements written in a single line. [tonell-m](https://github.com/tonell-m) [#5373](https://github.com/realm/SwiftLint/issues/5373) +* Add new `redundant_extension` rule that detects redundant extensions. + An extension is considered redundant if it does not define any + members, but only conformances. + [Muhammad Zeeshan](https://github.com/mzeeshanid) + [#5359](https://github.com/realm/SwiftLint/issues/5359) * Add new `one_declaration_per_file` rule that allows only a single class/struct/enum/protocol declaration per file. diff --git a/Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift b/Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift index 8788d3c056..fe93a776fb 100644 --- a/Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift +++ b/Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift @@ -167,6 +167,7 @@ public let builtInRules: [any Rule.Type] = [ ReduceBooleanRule.self, ReduceIntoRule.self, RedundantDiscardableLetRule.self, + RedundantExtensionRule.self, RedundantNilCoalescingRule.self, RedundantObjcAttributeRule.self, RedundantOptionalInitializationRule.self, diff --git a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/RedundantExtensionRule.swift b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/RedundantExtensionRule.swift new file mode 100644 index 0000000000..81c5bb64e7 --- /dev/null +++ b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/RedundantExtensionRule.swift @@ -0,0 +1,50 @@ +import SwiftSyntax + +@SwiftSyntaxRule +struct RedundantExtensionRule: OptInRule { + var configuration = SeverityConfiguration(.warning) + + static let description = RuleDescription( + identifier: "redundant_extension", + name: "Redundant Extension", + description: "Avoid redundant extensions", + kind: .idiomatic, + nonTriggeringExamples: [ + Example(""" + extension Foo { + func something() {} + } + """), + Example(""" + extension Foo { + var a: Int { 1 } + } + """), + Example(""" + extension Foo { + final class Bar {} + } + """), + Example(""" + extension Foo { + struct Bar {} + } + """) + ], + triggeringExamples: [ + Example(""" + ↓extension Bar {} + """) + ] + ) +} + +private extension RedundantExtensionRule { + final class Visitor: ViolationsSyntaxVisitor { + override func visitPost(_ node: ExtensionDeclSyntax) { + if node.memberBlock.members.isEmpty { + violations.append(node.extensionKeyword.positionAfterSkippingLeadingTrivia) + } + } + } +} diff --git a/Tests/GeneratedTests/GeneratedTests.swift b/Tests/GeneratedTests/GeneratedTests.swift index 78003f5f18..256d8877cd 100644 --- a/Tests/GeneratedTests/GeneratedTests.swift +++ b/Tests/GeneratedTests/GeneratedTests.swift @@ -992,6 +992,12 @@ class RedundantDiscardableLetRuleGeneratedTests: SwiftLintTestCase { } } +class RedundantExtensionRuleGeneratedTests: SwiftLintTestCase { + func testWithDefaultConfiguration() { + verifyRule(RedundantExtensionRule.description) + } +} + class RedundantNilCoalescingRuleGeneratedTests: SwiftLintTestCase { func testWithDefaultConfiguration() { verifyRule(RedundantNilCoalescingRule.description)