From f17d158da03d7d2cf7b2f68977e11270171cdc06 Mon Sep 17 00:00:00 2001 From: theamodhshetty Date: Wed, 25 Mar 2026 08:14:13 +0530 Subject: [PATCH 1/3] fix(prefer_self): skip shadowed nested type refs --- CHANGELOG.md | 4 ++ .../PreferSelfInStaticReferencesRule.swift | 68 +++++++++++++++---- ...erSelfInStaticReferencesRuleExamples.swift | 6 ++ 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f8bc7a12..a08d686138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,10 @@ * `multiline_call_arguments` no longer reports violations for enum-case patterns in pattern matching (e.g. if case, switch case, for case, catch). [GandaLF2006](https://github.com/GandaLF2006) +* Avoid false positives in `prefer_self_in_static_references` when a nested type + shadows its enclosing type name. + [theamodhshetty](https://github.com/theamodhshetty) + [#5917](https://github.com/realm/SwiftLint/issues/5917) ## 0.63.2: High-Speed Extraction diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift index fd65e1e32b..2b2aee5241 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift @@ -37,15 +37,16 @@ private extension PreferSelfInStaticReferencesRule { final class Visitor: ViolationsSyntaxVisitor { private var parentDeclScopes = Stack() + private var shadowingNestedTypeScopes = Stack() private var variableDeclScopes = Stack() override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { - parentDeclScopes.push(.likeClass(name: node.name.text)) + pushParentDeclScope(.likeClass(name: node.name.text), for: node.name.text, memberBlock: node.memberBlock) return .skipChildren } override func visitPost(_: ActorDeclSyntax) { - parentDeclScopes.pop() + popParentDeclScope() } override func visit(_: AttributeSyntax) -> SyntaxVisitorContinueKind { @@ -56,12 +57,12 @@ private extension PreferSelfInStaticReferencesRule { } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - parentDeclScopes.push(.likeClass(name: node.name.text)) + pushParentDeclScope(.likeClass(name: node.name.text), for: node.name.text, memberBlock: node.memberBlock) return .visitChildren } override func visitPost(_: ClassDeclSyntax) { - parentDeclScopes.pop() + popParentDeclScope() } override func visit(_: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind { @@ -74,21 +75,21 @@ private extension PreferSelfInStaticReferencesRule { } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - parentDeclScopes.push(.likeStruct(node.name.text)) + pushParentDeclScope(.likeStruct(node.name.text), for: node.name.text, memberBlock: node.memberBlock) return .visitChildren } override func visitPost(_: EnumDeclSyntax) { - parentDeclScopes.pop() + popParentDeclScope() } override func visit(_: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - parentDeclScopes.push(.skipReferences) + pushParentDeclScope(.skipReferences) return .visitChildren } override func visitPost(_: ExtensionDeclSyntax) { - parentDeclScopes.pop() + popParentDeclScope() } override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind { @@ -146,12 +147,12 @@ private extension PreferSelfInStaticReferencesRule { } override func visit(_: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - parentDeclScopes.push(.skipReferences) + pushParentDeclScope(.skipReferences) return .skipChildren } override func visitPost(_: ProtocolDeclSyntax) { - parentDeclScopes.pop() + popParentDeclScope() } override func visit(_: ReturnClauseSyntax) -> SyntaxVisitorContinueKind { @@ -162,12 +163,12 @@ private extension PreferSelfInStaticReferencesRule { } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - parentDeclScopes.push(.likeStruct(node.name.text)) + pushParentDeclScope(.likeStruct(node.name.text), for: node.name.text, memberBlock: node.memberBlock) return .visitChildren } override func visitPost(_: StructDeclSyntax) { - parentDeclScopes.pop() + popParentDeclScope() } override func visit(_: GenericArgumentListSyntax) -> SyntaxVisitorContinueKind { @@ -213,7 +214,9 @@ private extension PreferSelfInStaticReferencesRule { } private func addViolation(on node: TokenSyntax) { - if let parentName = parentDeclScopes.peek()?.parentName, node.tokenKind == .identifier(parentName) { + if shadowingNestedTypeScopes.peek() != true, + let parentName = parentDeclScopes.peek()?.parentName, + node.tokenKind == .identifier(parentName) { violations.append( at: node.positionAfterSkippingLeadingTrivia, correction: .init( @@ -224,5 +227,44 @@ private extension PreferSelfInStaticReferencesRule { ) } } + + private func pushParentDeclScope( + _ behavior: ParentDeclBehavior, + for name: String? = nil, + memberBlock: MemberBlockSyntax? = nil + ) { + parentDeclScopes.push(behavior) + let hasShadowingNestedType: Bool + if let name, let memberBlock { + hasShadowingNestedType = containsSameNamedNestedType(named: name, in: memberBlock) + } else { + hasShadowingNestedType = false + } + shadowingNestedTypeScopes.push(hasShadowingNestedType) + } + + private func popParentDeclScope() { + parentDeclScopes.pop() + shadowingNestedTypeScopes.pop() + } + + private func containsSameNamedNestedType(named name: String, in memberBlock: MemberBlockSyntax) -> Bool { + memberBlock.members.contains { member in + if let actor = member.decl.as(ActorDeclSyntax.self) { + return actor.name.text == name + } + if let classDecl = member.decl.as(ClassDeclSyntax.self) { + return classDecl.name.text == name + } + if let enumDecl = member.decl.as(EnumDeclSyntax.self) { + return enumDecl.name.text == name + } + if let structDecl = member.decl.as(StructDeclSyntax.self) { + return structDecl.name.text == name + } + + return false + } + } } } diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift index 668e3e5627..7e04e7464c 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift @@ -101,6 +101,12 @@ enum PreferSelfInStaticReferencesRuleExamples { } } """, excludeFromDocumentation: true), + Example(""" + struct S1 { + struct S1 {} + var s = S1() + } + """, excludeFromDocumentation: true), ] static let triggeringExamples = [ From 29eaa453a9a82eb16a62dd4494c75596adc2cc27 Mon Sep 17 00:00:00 2001 From: theamodhshetty Date: Fri, 17 Apr 2026 10:53:23 +0530 Subject: [PATCH 2/3] refactor(prefer_self): reuse parent scope for shadowed types --- .../PreferSelfInStaticReferencesRule.swift | 67 +++++++------------ ...erSelfInStaticReferencesRuleExamples.swift | 6 ++ 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift index 2b2aee5241..ea26f110c5 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift @@ -37,16 +37,15 @@ private extension PreferSelfInStaticReferencesRule { final class Visitor: ViolationsSyntaxVisitor { private var parentDeclScopes = Stack() - private var shadowingNestedTypeScopes = Stack() private var variableDeclScopes = Stack() override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { - pushParentDeclScope(.likeClass(name: node.name.text), for: node.name.text, memberBlock: node.memberBlock) + pushParentDeclScope(.likeClass(name: node.name.text), memberBlock: node.memberBlock) return .skipChildren } override func visitPost(_: ActorDeclSyntax) { - popParentDeclScope() + parentDeclScopes.pop() } override func visit(_: AttributeSyntax) -> SyntaxVisitorContinueKind { @@ -57,12 +56,12 @@ private extension PreferSelfInStaticReferencesRule { } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - pushParentDeclScope(.likeClass(name: node.name.text), for: node.name.text, memberBlock: node.memberBlock) + pushParentDeclScope(.likeClass(name: node.name.text), memberBlock: node.memberBlock) return .visitChildren } override func visitPost(_: ClassDeclSyntax) { - popParentDeclScope() + parentDeclScopes.pop() } override func visit(_: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind { @@ -75,21 +74,21 @@ private extension PreferSelfInStaticReferencesRule { } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - pushParentDeclScope(.likeStruct(node.name.text), for: node.name.text, memberBlock: node.memberBlock) + pushParentDeclScope(.likeStruct(node.name.text), memberBlock: node.memberBlock) return .visitChildren } override func visitPost(_: EnumDeclSyntax) { - popParentDeclScope() + parentDeclScopes.pop() } override func visit(_: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - pushParentDeclScope(.skipReferences) + parentDeclScopes.push(.skipReferences) return .visitChildren } override func visitPost(_: ExtensionDeclSyntax) { - popParentDeclScope() + parentDeclScopes.pop() } override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind { @@ -147,12 +146,12 @@ private extension PreferSelfInStaticReferencesRule { } override func visit(_: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - pushParentDeclScope(.skipReferences) + parentDeclScopes.push(.skipReferences) return .skipChildren } override func visitPost(_: ProtocolDeclSyntax) { - popParentDeclScope() + parentDeclScopes.pop() } override func visit(_: ReturnClauseSyntax) -> SyntaxVisitorContinueKind { @@ -163,12 +162,12 @@ private extension PreferSelfInStaticReferencesRule { } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - pushParentDeclScope(.likeStruct(node.name.text), for: node.name.text, memberBlock: node.memberBlock) + pushParentDeclScope(.likeStruct(node.name.text), memberBlock: node.memberBlock) return .visitChildren } override func visitPost(_: StructDeclSyntax) { - popParentDeclScope() + parentDeclScopes.pop() } override func visit(_: GenericArgumentListSyntax) -> SyntaxVisitorContinueKind { @@ -214,8 +213,7 @@ private extension PreferSelfInStaticReferencesRule { } private func addViolation(on node: TokenSyntax) { - if shadowingNestedTypeScopes.peek() != true, - let parentName = parentDeclScopes.peek()?.parentName, + if let parentName = parentDeclScopes.peek()?.parentName, node.tokenKind == .identifier(parentName) { violations.append( at: node.positionAfterSkippingLeadingTrivia, @@ -228,39 +226,20 @@ private extension PreferSelfInStaticReferencesRule { } } - private func pushParentDeclScope( - _ behavior: ParentDeclBehavior, - for name: String? = nil, - memberBlock: MemberBlockSyntax? = nil - ) { - parentDeclScopes.push(behavior) - let hasShadowingNestedType: Bool - if let name, let memberBlock { - hasShadowingNestedType = containsSameNamedNestedType(named: name, in: memberBlock) - } else { - hasShadowingNestedType = false - } - shadowingNestedTypeScopes.push(hasShadowingNestedType) - } - - private func popParentDeclScope() { - parentDeclScopes.pop() - shadowingNestedTypeScopes.pop() + private func pushParentDeclScope(_ behavior: ParentDeclBehavior, memberBlock: MemberBlockSyntax) { + let hasShadowingNestedType = + if let name = behavior.parentName { + containsSameNamedNestedType(named: name, in: memberBlock) + } else { + false + } + parentDeclScopes.push(hasShadowingNestedType ? .skipReferences : behavior) } private func containsSameNamedNestedType(named name: String, in memberBlock: MemberBlockSyntax) -> Bool { memberBlock.members.contains { member in - if let actor = member.decl.as(ActorDeclSyntax.self) { - return actor.name.text == name - } - if let classDecl = member.decl.as(ClassDeclSyntax.self) { - return classDecl.name.text == name - } - if let enumDecl = member.decl.as(EnumDeclSyntax.self) { - return enumDecl.name.text == name - } - if let structDecl = member.decl.as(StructDeclSyntax.self) { - return structDecl.name.text == name + if member.decl.isProtocol((any DeclGroupSyntax).self) || member.decl.is(TypeAliasDeclSyntax.self) { + return member.decl.asProtocol((any NamedDeclSyntax).self)?.name.text == name } return false diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift index 7e04e7464c..734cd8b210 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift @@ -107,6 +107,12 @@ enum PreferSelfInStaticReferencesRuleExamples { var s = S1() } """, excludeFromDocumentation: true), + Example(""" + struct S1 { + var s = S1() + struct S1 {} + } + """, excludeFromDocumentation: true), ] static let triggeringExamples = [ From c7238f1f5f35e6a96333eeb035f22f938996d4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danny=20M=C3=B6sch?= Date: Sun, 19 Apr 2026 14:26:25 +0200 Subject: [PATCH 3/3] Add empty line separator --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a08d686138..e9e161fb06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ * `multiline_call_arguments` no longer reports violations for enum-case patterns in pattern matching (e.g. if case, switch case, for case, catch). [GandaLF2006](https://github.com/GandaLF2006) + * Avoid false positives in `prefer_self_in_static_references` when a nested type shadows its enclosing type name. [theamodhshetty](https://github.com/theamodhshetty)