Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = []

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)
Expand Down Expand Up @@ -60,6 +81,14 @@ private extension PreferSelfInStaticReferencesRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
private var parentDeclScopes = Stack<ParentDeclBehavior>()
private var variableDeclScopes = Stack<VariableDeclBehavior>()
private var valueTypeNames: Set<String> = []

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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down