Skip to content

Commit b20bf9c

Browse files
committed
Extend redundant_self_in_closure to find all redundant selfs
1 parent 2e92f75 commit b20bf9c

File tree

5 files changed

+88
-76
lines changed

5 files changed

+88
-76
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: 46 additions & 71 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,17 @@ private extension RedundantSelfInClosureRule {
6362
} else {
6463
selfCaptures.push(.uncaptured)
6564
}
65+
if node.keyPathInParent == \FunctionCallExprSyntax.calledExpression {
66+
functionCalls.push(.anonymousClosure)
67+
} else {
68+
functionCalls.push(.function)
69+
}
6670
return .visitChildren
6771
}
6872

69-
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)
73+
override func visitPost(_: ClosureExprSyntax) {
8474
selfCaptures.pop()
75+
functionCalls.pop()
8576
}
8677

8778
override func visit(_: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
@@ -93,17 +84,26 @@ private extension RedundantSelfInClosureRule {
9384
typeDeclarations.pop()
9485
}
9586

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

109109
override func visit(_: StructDeclSyntax) -> SyntaxVisitorContinueKind {
@@ -114,48 +114,23 @@ private extension RedundantSelfInClosureRule {
114114
override func visitPost(_: StructDeclSyntax) {
115115
typeDeclarations.pop()
116116
}
117-
}
118-
}
119-
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-
}
136117

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-
)
118+
private var isSelfRedundant: Bool {
119+
typeDeclarations.peek() == .likeStruct
120+
|| functionCalls.peek() == .anonymousClosure
121+
|| selfCaptures.peek() == .strong && SwiftVersion.current >= .fiveDotThree
122+
|| selfCaptures.peek() == .weak && SwiftVersion.current >= .fiveDotEight
147123
}
148124
}
125+
}
149126

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
127+
private extension TokenSyntax {
128+
var needsEscaping: Bool {
129+
[UInt8](text.utf8).withUnsafeBufferPointer {
130+
if let keyword = Keyword(SyntaxText(baseAddress: $0.baseAddress, count: text.count)) {
131+
return TokenKind.keyword(keyword).isLexerClassifiedKeyword
132+
}
133+
return false
134+
}
160135
}
161136
}

Source/SwiftLintBuiltInRules/Rules/Style/RedundantSelfInClosureRuleExamples.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,11 @@ struct RedundantSelfInClosureRuleExamples {
194194
var x = 0
195195
func f(_ work: @escaping () -> Void) { work() }
196196
func g() {
197-
f { [weak self] in
197+
f({ [weak self] in
198198
self?.x = 1
199199
guard let self else { return }
200200
↓self.x = 1
201-
}
201+
})
202202
f { [weak self] in
203203
self?.x = 1
204204
if let self = self { ↓self.x = 1 }
@@ -212,6 +212,23 @@ struct RedundantSelfInClosureRuleExamples {
212212
}
213213
}
214214
"""),
215+
Example("""
216+
class C {
217+
var x = 0
218+
private lazy var c1: Int = {
219+
↓self.x = 1
220+
let f = { self.x = 2 }
221+
let g = { [self] in ↓self.x = 3 }
222+
return 2
223+
}()
224+
private lazy var c2: Int = { [weak self] in
225+
guard let self else { return 0 }
226+
↓self.x = 1
227+
let f = { self.x = 2 }
228+
return 2
229+
}()
230+
}
231+
""", excludeFromDocumentation: true),
215232
]
216233

217234
static let corrections = [

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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ 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+
135142
// MARK: Private methods
136143

137144
private func collectIdentifiers(from parameters: FunctionParameterListSyntax) {

0 commit comments

Comments
 (0)