Skip to content

Commit 32408b9

Browse files
committed
Extend redundant_self_in_closure to find all redundant selfs
1 parent f8d448d commit 32408b9

File tree

4 files changed

+77
-73
lines changed

4 files changed

+77
-73
lines changed

.swiftlint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ disabled_rules:
4040
- one_declaration_per_file
4141
- prefer_nimble
4242
- prefixed_toplevel_constant
43+
- redundant_self_in_closure
4344
- required_deinit
4445
- sorted_enum_cases
4546
- strict_fileprivate
Lines changed: 48 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
@_spi(Diagnostics)
2+
import SwiftParser
3+
@_spi(RawSyntax)
14
import SwiftSyntax
25

36
@SwiftSyntaxRule(correctable: true, optIn: true)
@@ -16,19 +19,15 @@ struct RedundantSelfInClosureRule: Rule {
1619
}
1720

1821
private enum TypeDeclarationKind {
19-
case likeStruct
20-
case likeClass
22+
case likeStruct, likeClass
2123
}
2224

2325
private enum FunctionCallType {
24-
case anonymousClosure
25-
case function
26+
case anonymousClosure, function
2627
}
2728

2829
private enum SelfCaptureKind {
29-
case strong
30-
case weak
31-
case uncaptured
30+
case strong, weak, uncaptured
3231
}
3332

3433
private extension RedundantSelfInClosureRule {
@@ -63,25 +62,20 @@ private extension RedundantSelfInClosureRule {
6362
} else {
6463
selfCaptures.push(.uncaptured)
6564
}
65+
if node.keyPathInParent == \FunctionCallExprSyntax.calledExpression {
66+
functionCalls.push(.anonymousClosure)
67+
} else if [.functionCallExpr, .multipleTrailingClosureElement].contains(node.parent?.kind) {
68+
functionCalls.push(.function)
69+
}
6670
return .visitChildren
6771
}
6872

6973
override func visitPost(_ node: ClosureExprSyntax) {
70-
guard let activeTypeDeclarationKind = typeDeclarations.peek(),
71-
let activeFunctionCallType = functionCalls.peek(),
72-
let activeSelfCaptureKind = selfCaptures.peek() else {
73-
return
74-
}
75-
let localViolationCorrections = ExplicitSelfVisitor(
76-
configuration: configuration,
77-
file: file,
78-
typeDeclarationKind: activeTypeDeclarationKind,
79-
functionCallType: activeFunctionCallType,
80-
selfCaptureKind: activeSelfCaptureKind,
81-
scope: scope
82-
).walk(tree: node.statements, handler: \.violations)
83-
violations.append(contentsOf: localViolationCorrections)
8474
selfCaptures.pop()
75+
if node.keyPathInParent == \FunctionCallExprSyntax.calledExpression
76+
|| [.functionCallExpr, .multipleTrailingClosureElement].contains(node.parent?.kind) {
77+
functionCalls.pop()
78+
}
8579
}
8680

8781
override func visit(_: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
@@ -93,17 +87,26 @@ private extension RedundantSelfInClosureRule {
9387
typeDeclarations.pop()
9488
}
9589

96-
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
97-
if node.calledExpression.is(ClosureExprSyntax.self) {
98-
functionCalls.push(.anonymousClosure)
99-
} else {
100-
functionCalls.push(.function)
90+
override func visitPost(_ node: MemberAccessExprSyntax) {
91+
if selfCaptures.isNotEmpty {
92+
// In closure ...
93+
guard typeDeclarations.isNotEmpty, functionCalls.isNotEmpty, isSelfRedundant else {
94+
return
95+
}
96+
}
97+
let declName = node.declName.baseName.text
98+
if !hasSeenDeclaration(for: declName), node.isBaseSelf, declName != "init" {
99+
violations.append(
100+
at: node.positionAfterSkippingLeadingTrivia,
101+
correction: .init(
102+
start: node.positionAfterSkippingLeadingTrivia,
103+
end: node.endPositionBeforeTrailingTrivia,
104+
replacement: node.declName.baseName.needsEscaping
105+
? "`\(declName)`"
106+
: declName
107+
)
108+
)
101109
}
102-
return .visitChildren
103-
}
104-
105-
override func visitPost(_: FunctionCallExprSyntax) {
106-
functionCalls.pop()
107110
}
108111

109112
override func visit(_: StructDeclSyntax) -> SyntaxVisitorContinueKind {
@@ -114,48 +117,23 @@ private extension RedundantSelfInClosureRule {
114117
override func visitPost(_: StructDeclSyntax) {
115118
typeDeclarations.pop()
116119
}
117-
}
118-
}
119120

120-
private class ExplicitSelfVisitor<Configuration: RuleConfiguration>: DeclaredIdentifiersTrackingVisitor<Configuration> {
121-
private let typeDeclKind: TypeDeclarationKind
122-
private let functionCallType: FunctionCallType
123-
private let selfCaptureKind: SelfCaptureKind
124-
125-
init(configuration: Configuration,
126-
file: SwiftLintFile,
127-
typeDeclarationKind: TypeDeclarationKind,
128-
functionCallType: FunctionCallType,
129-
selfCaptureKind: SelfCaptureKind,
130-
scope: Scope) {
131-
self.typeDeclKind = typeDeclarationKind
132-
self.functionCallType = functionCallType
133-
self.selfCaptureKind = selfCaptureKind
134-
super.init(configuration: configuration, file: file, scope: scope)
135-
}
136-
137-
override func visitPost(_ node: MemberAccessExprSyntax) {
138-
if !hasSeenDeclaration(for: node.declName.baseName.text), node.isBaseSelf, isSelfRedundant {
139-
violations.append(
140-
at: node.positionAfterSkippingLeadingTrivia,
141-
correction: .init(
142-
start: node.positionAfterSkippingLeadingTrivia,
143-
end: node.period.endPositionBeforeTrailingTrivia,
144-
replacement: ""
145-
)
146-
)
121+
private var isSelfRedundant: Bool {
122+
typeDeclarations.peek() == .likeStruct
123+
|| functionCalls.peek() == .anonymousClosure
124+
|| selfCaptures.peek() == .strong && SwiftVersion.current >= .fiveDotThree
125+
|| selfCaptures.peek() == .weak && SwiftVersion.current >= .fiveDotEight
147126
}
148127
}
128+
}
149129

150-
override func visit(_: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
151-
// Will be handled separately by the parent visitor.
152-
.skipChildren
153-
}
154-
155-
var isSelfRedundant: Bool {
156-
typeDeclKind == .likeStruct
157-
|| functionCallType == .anonymousClosure
158-
|| selfCaptureKind == .strong && SwiftVersion.current >= .fiveDotThree
159-
|| selfCaptureKind == .weak && SwiftVersion.current >= .fiveDotEight
130+
private extension TokenSyntax {
131+
var needsEscaping: Bool {
132+
[UInt8](text.utf8).withUnsafeBufferPointer {
133+
if let keyword = Keyword(SyntaxText(baseAddress: $0.baseAddress, count: text.count)) {
134+
return TokenKind.keyword(keyword).isLexerClassifiedKeyword
135+
}
136+
return false
137+
}
160138
}
161139
}

Source/SwiftLintCore/Helpers/Stack.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,21 @@ public struct Stack<Element> {
4949
}
5050
}
5151

52-
extension Stack: Sequence {
53-
public func makeIterator() -> [Element].Iterator {
54-
elements.makeIterator()
52+
extension Stack: Collection {
53+
public var startIndex: Int {
54+
elements.startIndex
55+
}
56+
57+
public var endIndex: Int {
58+
elements.endIndex
59+
}
60+
61+
public subscript(position: Int) -> Element {
62+
elements[position]
63+
}
64+
65+
public func index(after index: Int) -> Int {
66+
elements.index(after: index)
5567
}
5668
}
5769

Source/SwiftLintCore/Visitors/DeclaredIdentifiersTrackingVisitor.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,19 @@ open class DeclaredIdentifiersTrackingVisitor<Configuration: RuleConfiguration>:
132132
}
133133
}
134134

135+
override open func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
136+
if node.parent?.is(MemberBlockItemSyntax.self) != true {
137+
scope.addToCurrentScope(.localVariable(name: node.name))
138+
}
139+
return .visitChildren
140+
}
141+
142+
override open func visitPost(_ node: FunctionDeclSyntax) {
143+
if node.parent?.is(MemberBlockItemSyntax.self) != true {
144+
scope.pop()
145+
}
146+
}
147+
135148
// MARK: Private methods
136149

137150
private func collectIdentifiers(from parameters: FunctionParameterListSyntax) {

0 commit comments

Comments
 (0)