diff --git a/CHANGELOG.md b/CHANGELOG.md index d74d76a455..cf712fbae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,11 @@ ### Bug Fixes +* Report `prefer_self_in_static_references` violations in return types of static + functions in extensions whose value type is declared in the same file. + [aryansk](https://github.com/aryansk) + [#6828](https://github.com/realm/SwiftLint/issues/6828) + * Add an opt-in `allow_explicit_unsafe_unowned` option to let the `unowned_variable_capture` rule accept explicit `unowned(unsafe)` captures. [Yurii Bakurov](https://github.com/Yurii201811) diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift index ecb9aa0ac5..0017e15bca 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRule.swift @@ -18,6 +18,27 @@ struct PreferSelfInStaticReferencesRule: Rule { } private extension PreferSelfInStaticReferencesRule { + // An extension does not encode whether its target is a class or a value type. + // Collect declarations from this file so return types for known value types + // can be handled without relaxing the class-like extension behavior. + private final class ValueTypeNameCollector: SyntaxVisitor { + private(set) var names: Set = [] + + init() { + super.init(viewMode: .sourceAccurate) + } + + override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + names.insert(node.name.text) + return .visitChildren + } + + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + names.insert(node.name.text) + return .visitChildren + } + } + private enum ExtendedType { /// A type named by a plain identifier, e.g. `extension Foo`. case identifier(String) @@ -60,6 +81,14 @@ private extension PreferSelfInStaticReferencesRule { final class Visitor: ViolationsSyntaxVisitor { private var parentDeclScopes = Stack() private var variableDeclScopes = Stack() + private var valueTypeNames: Set = [] + + override init(configuration: ConfigurationType, file: SwiftLintFile) { + super.init(configuration: configuration, file: file) + + let collector = ValueTypeNameCollector() + valueTypeNames = collector.walk(tree: file.syntaxTree) { $0.names } + } override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { pushParentDeclScope(.likeClass(.identifier(node.name.text)), memberBlock: node.memberBlock) @@ -210,13 +239,39 @@ private extension PreferSelfInStaticReferencesRule { parentDeclScopes.pop() } - override func visit(_: ReturnClauseSyntax) -> SyntaxVisitorContinueKind { + override func visit(_ node: ReturnClauseSyntax) -> SyntaxVisitorContinueKind { if case .likeStruct = parentDeclScopes.peek() { return .visitChildren } + if isKnownValueTypeExtension(node) { + return .visitChildren + } return .skipChildren } + private func isKnownValueTypeExtension(_ node: ReturnClauseSyntax) -> Bool { + var isStaticFunction = false + var ancestor = node.parent + while let current = ancestor { + if let functionDecl = current.as(FunctionDeclSyntax.self) { + isStaticFunction = functionDecl.modifiers.contains(keyword: .static) + } + if let extensionDecl = current.as(ExtensionDeclSyntax.self), + let identifier = extensionDecl.extendedType.as(IdentifierTypeSyntax.self) { + return isStaticFunction && valueTypeNames.contains(identifier.name.text) + } + if current.is(ClassDeclSyntax.self) + || current.is(StructDeclSyntax.self) + || current.is(EnumDeclSyntax.self) + || current.is(ActorDeclSyntax.self) + || current.is(ProtocolDeclSyntax.self) { + return false + } + ancestor = current.parent + } + return false + } + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { pushParentDeclScope(.likeStruct(node.name.text), memberBlock: node.memberBlock) return .visitChildren diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift index 074dbc9487..c01394ee94 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/PreferSelfInStaticReferencesRuleExamples.swift @@ -22,6 +22,20 @@ enum PreferSelfInStaticReferencesRuleExamples { static let k = { T.j }() } """, + """ + class ClassExtension { + required init() {} + } + extension ClassExtension { + static func make() -> ClassExtension { .init() } + } + """.asExample(excludeFromDocumentation: true), + """ + struct InstanceExtension {} + extension InstanceExtension { + func make() -> InstanceExtension { self } + } + """.asExample(excludeFromDocumentation: true), """ class `Self` { static let i = 0 @@ -348,6 +362,16 @@ enum PreferSelfInStaticReferencesRuleExamples { var v: Int { ↓C.i } } """.asExample(excludeFromDocumentation: true), + """ + struct Example { + var value = 1 + } + extension Example { + static func example() -> ↓Example { + .init() + } + } + """.asExample(excludeFromDocumentation: true), """ class Outer { class Inner { @@ -427,6 +451,25 @@ enum PreferSelfInStaticReferencesRuleExamples { func f() -> Int { Self.i } } """, + """ + struct Example { + var value = 1 + } + extension Example { + static func example() -> ↓Example { + .init() + } + } + """: """ + struct Example { + var value = 1 + } + extension Example { + static func example() -> Self { + .init() + } + } + """, """ class Outer { class Inner {