@@ -11,5 +11,136 @@ import SwiftSyntax
11
11
///
12
12
/// - SeeAlso: https://google.github.io/swift#fallthrough-in-switch-statements
13
13
public final class NoCasesWithOnlyFallthrough : SyntaxFormatRule {
14
+
15
+ public override func visit( _ node: SwitchStmtSyntax ) -> StmtSyntax {
16
+ var newCases : [ SwitchCaseSyntax ] = [ ]
17
+ var violations : [ SwitchCaseLabelSyntax ] = [ ]
18
+
19
+ for switchCase in node. cases {
20
+ guard let switchCase = switchCase as? SwitchCaseSyntax else { continue }
21
+ guard let label = switchCase. label as? SwitchCaseLabelSyntax else {
22
+ newCases. append ( switchCase)
23
+ continue
24
+ }
25
+
26
+ if switchCase. statements. count == 1 ,
27
+ let only = switchCase. statements. first,
28
+ only. item is FallthroughStmtSyntax {
29
+ diagnose ( . collapseCase( name: " \( label) " ) , on: switchCase)
30
+ violations. append ( label)
31
+ } else {
32
+ guard violations. count > 0 else {
33
+ newCases. append ( switchCase)
34
+ continue
35
+ }
14
36
37
+ if retrieveNumericCaseValue ( caseLabel: label) != nil {
38
+ let newCase = collapseIntegerCases ( violations: violations,
39
+ validCaseLabel: label,
40
+ validCase: switchCase)
41
+ newCases. append ( newCase)
42
+ } else {
43
+ let newCase = collapseNonIntegerCases ( violations: violations,
44
+ validCaseLabel: label,
45
+ validCase: switchCase)
46
+ newCases. append ( newCase)
47
+ }
48
+ violations = [ ]
49
+ }
50
+ }
51
+ return node. withCases ( SyntaxFactory . makeSwitchCaseList ( newCases) )
52
+ }
53
+
54
+ // Puts all given cases on one line with range operator or commas
55
+ func collapseIntegerCases( violations: [ SwitchCaseLabelSyntax ] ,
56
+ validCaseLabel: SwitchCaseLabelSyntax , validCase: SwitchCaseSyntax ) -> SwitchCaseSyntax {
57
+ var isConsecutive = true
58
+ var index = 0
59
+ var caseNums : [ Int ] = [ ]
60
+
61
+ for item in violations {
62
+ guard let caseNum = retrieveNumericCaseValue ( caseLabel: item) else { continue }
63
+ caseNums. append ( caseNum)
64
+ }
65
+
66
+ guard let validCaseNum = retrieveNumericCaseValue ( caseLabel: validCaseLabel) else {
67
+ return validCase
68
+ }
69
+ caseNums. append ( validCaseNum)
70
+
71
+ while index <= caseNums. count - 2 , isConsecutive {
72
+ isConsecutive = caseNums [ index] + 1 == caseNums [ index + 1 ]
73
+ index += 1
74
+ }
75
+
76
+ var newCaseItems : [ CaseItemSyntax ] = [ ]
77
+ let first = caseNums [ 0 ]
78
+ let last = caseNums [ caseNums. count - 1 ]
79
+ if isConsecutive {
80
+ // Create a case with a sequence expression based on the new range
81
+ let start = SyntaxFactory . makeIntegerLiteralExpr (
82
+ digits: SyntaxFactory . makeIntegerLiteral ( " \( first) " ) )
83
+ let end = SyntaxFactory . makeIntegerLiteralExpr (
84
+ digits: SyntaxFactory . makeIntegerLiteral ( " \( last) " ) )
85
+ let newExpList = SyntaxFactory . makeExprList (
86
+ [ start,
87
+ SyntaxFactory . makeBinaryOperatorExpr ( operatorToken:
88
+ SyntaxFactory . makeUnspacedBinaryOperator ( " ... " ) ) ,
89
+ end] )
90
+ let newExpPat = SyntaxFactory . makeExpressionPattern (
91
+ expression: SyntaxFactory . makeSequenceExpr ( elements: newExpList) )
92
+ newCaseItems. append (
93
+ SyntaxFactory . makeCaseItem ( pattern: newExpPat, whereClause: nil , trailingComma: nil ) )
94
+ } else {
95
+ // Add each case item separated by a comma
96
+ for num in caseNums {
97
+ let newExpPat = SyntaxFactory . makeExpressionPattern (
98
+ expression: SyntaxFactory . makeIntegerLiteralExpr (
99
+ digits: SyntaxFactory . makeIntegerLiteral ( " \( num) " ) ) )
100
+ let trailingComma = SyntaxFactory . makeCommaToken ( trailingTrivia: . spaces( 1 ) )
101
+ let newCaseItem = SyntaxFactory . makeCaseItem (
102
+ pattern: newExpPat,
103
+ whereClause: nil ,
104
+ trailingComma: num == last ? nil : trailingComma
105
+ )
106
+ newCaseItems. append ( newCaseItem)
107
+ }
108
+ }
109
+ let caseItemList = SyntaxFactory . makeCaseItemList ( newCaseItems)
110
+ return validCase. withLabel ( validCaseLabel. withCaseItems ( caseItemList) )
111
+ }
112
+
113
+ // Gets integer value from case label, if possible
114
+ func retrieveNumericCaseValue( caseLabel: SwitchCaseLabelSyntax ) -> Int ? {
115
+ if let firstTok = caseLabel. caseItems. firstToken,
116
+ let num = Int ( firstTok. text) {
117
+ return num
118
+ }
119
+ return nil
120
+ }
121
+
122
+ // Puts all given cases on one line separated by commas
123
+ func collapseNonIntegerCases( violations: [ SwitchCaseLabelSyntax ] ,
124
+ validCaseLabel: SwitchCaseLabelSyntax , validCase: SwitchCaseSyntax ) -> SwitchCaseSyntax {
125
+ var newCaseItems : [ CaseItemSyntax ] = [ ]
126
+ for violation in violations {
127
+ for item in violation. caseItems {
128
+ let newCaseItem = item. withTrailingComma (
129
+ SyntaxFactory . makeCommaToken ( trailingTrivia: . spaces( 1 ) ) )
130
+ newCaseItems. append ( newCaseItem)
131
+ }
132
+ }
133
+ for item in validCaseLabel. caseItems {
134
+ newCaseItems. append ( item)
135
+ }
136
+ let caseItemList = SyntaxFactory . makeCaseItemList ( newCaseItems)
137
+ return validCase. withLabel ( validCaseLabel. withCaseItems ( caseItemList) )
138
+ }
139
+ }
140
+
141
+ extension Diagnostic . Message {
142
+ static func collapseCase( name: String ) -> Diagnostic . Message {
143
+ return . init( . warning,
144
+ " \( name) only contains 'fallthrough' and can be combined with a following case " )
145
+ }
15
146
}
0 commit comments