-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathImplicitReturnRule.swift
More file actions
176 lines (148 loc) · 6.89 KB
/
ImplicitReturnRule.swift
File metadata and controls
176 lines (148 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import SwiftSyntax
@SwiftSyntaxRule(correctable: true, optIn: true)
struct ImplicitReturnRule: Rule {
var configuration = ImplicitReturnConfiguration()
static let description = RuleDescription(
identifier: "implicit_return",
name: "Implicit Return",
description: "Prefer implicit returns in closures, functions and getters",
kind: .style,
nonTriggeringExamples: ImplicitReturnRuleExamples.nonTriggeringExamples,
triggeringExamples: ImplicitReturnRuleExamples.triggeringExamples,
corrections: ImplicitReturnRuleExamples.corrections
)
}
private extension ImplicitReturnRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override var skippableDeclarations: [any DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visitPost(_ node: AccessorDeclSyntax) {
if configuration.isKindIncluded(.getter),
node.accessorSpecifier.tokenKind == .keyword(.get),
let body = node.body {
collectViolation(in: body.statements)
}
}
override func visitPost(_ node: ClosureExprSyntax) {
if configuration.isKindIncluded(.closure) {
collectViolation(in: node.statements)
}
}
override func visitPost(_ node: FunctionDeclSyntax) {
if configuration.isKindIncluded(.function),
let body = node.body {
collectViolation(in: body.statements)
}
}
override func visitPost(_ node: InitializerDeclSyntax) {
if configuration.isKindIncluded(.initializer),
let body = node.body {
collectViolation(in: body.statements)
}
}
override func visitPost(_ node: PatternBindingSyntax) {
if configuration.isKindIncluded(.getter),
case let .getter(itemList) = node.accessorBlock?.accessors {
collectViolation(in: itemList)
}
}
override func visitPost(_ node: SubscriptDeclSyntax) {
if configuration.isKindIncluded(.subscript),
case let .getter(itemList) = node.accessorBlock?.accessors {
collectViolation(in: itemList)
}
}
private func collectViolation(in itemList: CodeBlockItemListSyntax) {
guard let onlyItem = itemList.onlyElement else {
return
}
// Case 1: Direct return statement
if let returnStmt = onlyItem.item.as(ReturnStmtSyntax.self) {
addViolation(for: returnStmt)
return
}
// Case 2: Expression statement containing if or switch
if let exprStmt = onlyItem.item.as(ExpressionStmtSyntax.self) {
analyzeExpressionForImplicitReturns(exprStmt.expression)
}
}
private func analyzeExpressionForImplicitReturns(_ expr: ExprSyntax) {
if let ifExpr = expr.as(IfExprSyntax.self) {
analyzeIfExpression(ifExpr)
} else if let switchExpr = expr.as(SwitchExprSyntax.self) {
analyzeSwitchExpression(switchExpr)
}
}
private func analyzeIfExpression(_ ifExpr: IfExprSyntax) {
guard checkIfAllBranchesCanUseImplicitReturn(ifExpr) else { return }
let returnStatements = extractAllReturnStatements(from: ifExpr)
returnStatements.forEach { addViolation(for: $0) }
}
private func analyzeSwitchExpression(_ switchExpr: SwitchExprSyntax) {
guard checkIfAllCasesCanUseImplicitReturn(switchExpr) else { return }
let returnStatements = extractAllReturnStatements(from: switchExpr)
returnStatements.forEach { addViolation(for: $0) }
}
private func extractAllReturnStatements(from ifExpr: IfExprSyntax) -> [ReturnStmtSyntax] {
var statements: [ReturnStmtSyntax] = []
// Extract from main if body
if let returnStmt = getSingleReturnStatement(from: ifExpr.body.statements) {
statements.append(returnStmt)
}
// Extract from else body (recursively handle nested if-else)
if let elseBody = ifExpr.elseBody {
switch elseBody {
case .codeBlock(let codeBlock):
if let returnStmt = getSingleReturnStatement(from: codeBlock.statements) {
statements.append(returnStmt)
}
case .ifExpr(let nestedIfExpr):
statements.append(contentsOf: extractAllReturnStatements(from: nestedIfExpr))
}
}
return statements
}
private func extractAllReturnStatements(from switchExpr: SwitchExprSyntax) -> [ReturnStmtSyntax] {
switchExpr.cases.compactMap { caseItem in
guard let switchCase = caseItem.as(SwitchCaseSyntax.self) else { return nil }
return getSingleReturnStatement(from: switchCase.statements)
}
}
private func checkIfAllBranchesCanUseImplicitReturn(_ ifExpr: IfExprSyntax) -> Bool {
guard isSingleReturnStatement(ifExpr.body.statements),
let elseBody = ifExpr.elseBody else {
return false
}
switch elseBody {
case .codeBlock(let codeBlock):
return isSingleReturnStatement(codeBlock.statements)
case .ifExpr(let nestedIfExpr):
return checkIfAllBranchesCanUseImplicitReturn(nestedIfExpr)
}
}
private func checkIfAllCasesCanUseImplicitReturn(_ switchExpr: SwitchExprSyntax) -> Bool {
!switchExpr.cases.isEmpty &&
switchExpr.cases.allSatisfy { caseItem in
guard let switchCase = caseItem.as(SwitchCaseSyntax.self) else { return false }
return isSingleReturnStatement(switchCase.statements)
}
}
private func isSingleReturnStatement(_ statements: CodeBlockItemListSyntax) -> Bool {
getSingleReturnStatement(from: statements) != nil
}
private func getSingleReturnStatement(from statements: CodeBlockItemListSyntax) -> ReturnStmtSyntax? {
statements.onlyElement?.item.as(ReturnStmtSyntax.self)
}
private func addViolation(for returnStmt: ReturnStmtSyntax) {
let returnKeyword = returnStmt.returnKeyword
violations.append(
at: returnKeyword.positionAfterSkippingLeadingTrivia,
correction: .init(
start: returnKeyword.positionAfterSkippingLeadingTrivia,
end: returnKeyword.endPositionBeforeTrailingTrivia
.advanced(by: returnStmt.expression == nil ? 0 : 1),
replacement: ""
)
)
}
}
}