Skip to content

Commit b2ee42a

Browse files
committed
Automatically remove unused arguments in switch cases as well
1 parent f130e0b commit b2ee42a

File tree

2 files changed

+210
-15
lines changed

2 files changed

+210
-15
lines changed

Sources/EnumeratorMacroImpl/Rewriter.swift

Lines changed: 134 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,105 @@ import SwiftSyntax
22

33
final class Rewriter: SyntaxRewriter {
44
override func visit(_ node: SwitchCaseSyntax) -> SwitchCaseSyntax {
5-
let newNode = self.removeUnusedLet(node)
6-
return newNode
5+
self.removeUnusedLet(
6+
self.removeUnusedArguments(
7+
node
8+
)
9+
)
10+
}
11+
12+
/// Rewrites and removed unused arguments in switch cases.
13+
/// For example, it rewrites:
14+
/// ```swift
15+
/// switch self {
16+
/// case let .testCase(x, y):
17+
/// return x
18+
/// ```
19+
/// to:
20+
/// ```swift
21+
/// switch self {
22+
/// case .testCase(x):
23+
/// ```
24+
/// because `b` is unused.
25+
private func removeUnusedArguments(_ node: SwitchCaseSyntax) -> SwitchCaseSyntax {
26+
guard let label = node.label.as(SwitchCaseLabelSyntax.self) else {
27+
return node
28+
}
29+
var items = label.caseItems
30+
31+
for (idx, item) in items.enumerated() {
32+
guard let pattern = item.pattern.as(ValueBindingPatternSyntax.self),
33+
let expr = pattern.pattern.as(ExpressionPatternSyntax.self),
34+
let functionCallSyntax = expr.expression.as(FunctionCallExprSyntax.self) else {
35+
continue
36+
}
37+
38+
var missingPresenceIndices: [Int] = []
39+
for (idx, argument) in functionCallSyntax.arguments.enumerated() {
40+
guard let patternExpr = argument.expression.as(PatternExprSyntax.self),
41+
let identifier = patternExpr.pattern.as(IdentifierPatternSyntax.self) else {
42+
continue
43+
}
44+
let presenceDetector = PresenceDetector(toDetect: identifier.identifier.tokenKind)
45+
presenceDetector.walk(node)
46+
if presenceDetector.detectCount < 2 {
47+
missingPresenceIndices.append(idx)
48+
}
49+
}
50+
51+
var arguments = functionCallSyntax.arguments
52+
53+
for (index, idxInArguments) in missingPresenceIndices.enumerated() {
54+
let idx = arguments.index(at: idxInArguments - index)
55+
arguments.remove(at: idx)
56+
}
57+
58+
if !missingPresenceIndices.isEmpty {
59+
let innerExpression: ExprSyntax = if arguments.isEmpty {
60+
functionCallSyntax.calledExpression
61+
} else {
62+
ExprSyntax(
63+
functionCallSyntax.with(
64+
\.arguments,
65+
arguments.with(
66+
/// Remove the trailing comma from the last arg, if it's there.
67+
\.[arguments.lastIndex(where: { _ in true })!],
68+
arguments.last!.with(
69+
\.trailingComma,
70+
nil
71+
)
72+
)
73+
)
74+
)
75+
}
76+
items = items.with(
77+
\.[items.index(at: idx)].pattern,
78+
PatternSyntax(
79+
pattern.with(
80+
\.pattern,
81+
PatternSyntax(
82+
expr.with(
83+
\.expression,
84+
innerExpression
85+
)
86+
)
87+
)
88+
)
89+
)
90+
}
91+
}
92+
93+
let node = node.with(
94+
\.label,
95+
SwitchCaseSyntax.Label(
96+
label.with(
97+
\.caseItems,
98+
items
99+
)
100+
)
101+
)
102+
103+
return node
7104
}
8105

9106
/// Rewrites and removed unused `let`s in switch cases.
@@ -18,7 +115,7 @@ final class Rewriter: SyntaxRewriter {
18115
/// case .testCase:
19116
/// ```
20117
private func removeUnusedLet(_ node: SwitchCaseSyntax) -> SwitchCaseSyntax {
21-
guard var label = node.label.as(SwitchCaseLabelSyntax.self) else {
118+
guard let label = node.label.as(SwitchCaseLabelSyntax.self) else {
22119
return node
23120
}
24121
var items = label.caseItems
@@ -30,15 +127,43 @@ final class Rewriter: SyntaxRewriter {
30127
expr.expression.is(MemberAccessExprSyntax.self) else {
31128
continue
32129
}
33-
let idx = items.index(at: idx)
34-
let newPattern = expr
35-
items[idx].pattern = PatternSyntax(newPattern)
130+
131+
items = items.with(
132+
\.[items.index(at: idx)].pattern,
133+
PatternSyntax(expr)
134+
)
36135
}
37136

38-
label.caseItems = items
39-
var node = node
40-
node.label = SwitchCaseSyntax.Label(label)
137+
let node = node.with(
138+
\.label,
139+
SwitchCaseSyntax.Label(
140+
label.with(
141+
\.caseItems,
142+
items
143+
)
144+
)
145+
)
41146

42147
return node
43148
}
44149
}
150+
151+
private final class PresenceDetector: SyntaxVisitor {
152+
var detectCount = 0
153+
var toDetect: TokenKind
154+
155+
init(
156+
viewMode: SyntaxTreeViewMode = .sourceAccurate,
157+
toDetect: TokenKind
158+
) {
159+
self.toDetect = toDetect
160+
super.init(viewMode: viewMode)
161+
}
162+
163+
override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
164+
if node.tokenKind == self.toDetect {
165+
self.detectCount += 1
166+
}
167+
return .visitChildren
168+
}
169+
}

Tests/EnumeratorMacroTests/EnumeratorMacroTests.swift

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -538,10 +538,6 @@ final class EnumeratorMacroTests: XCTestCase {
538538
switch self {
539539
case let .testCase:
540540
return true
541-
case let .a(a, b):
542-
return false
543-
case .b:
544-
return false
545541
default:
546542
return false
547543
}
@@ -563,10 +559,84 @@ final class EnumeratorMacroTests: XCTestCase {
563559
switch self {
564560
case .testCase:
565561
return true
566-
case let .a(a, b):
562+
default:
567563
return false
568-
case .b:
564+
}
565+
}
566+
}
567+
"""#,
568+
macros: EnumeratorMacroEntryPoint.macros
569+
)
570+
}
571+
572+
func testRemovesArgumentInSwitchStatements() throws {
573+
assertMacroExpansion(
574+
#"""
575+
@Enumerator("""
576+
var isTestCase: Bool {
577+
switch self {
578+
case let .testCase(asd):
579+
return true
580+
default:
581+
return false
582+
}
583+
}
584+
""")
585+
enum TestEnum {
586+
case a(val1: String, Int)
587+
case b
588+
case testCase(testValue: String)
589+
}
590+
"""#,
591+
expandedSource: #"""
592+
enum TestEnum {
593+
case a(val1: String, Int)
594+
case b
595+
case testCase(testValue: String)
596+
597+
var isTestCase: Bool {
598+
switch self {
599+
case .testCase:
600+
return true
601+
default:
569602
return false
603+
}
604+
}
605+
}
606+
"""#,
607+
macros: EnumeratorMacroEntryPoint.macros
608+
)
609+
}
610+
611+
func testRemovesArgumentInSwitchStatementsWithMultipleArgumentsWhereOneArgIsUsed() throws {
612+
assertMacroExpansion(
613+
#"""
614+
@Enumerator("""
615+
var isTestCase: Bool {
616+
switch self {
617+
case let .a(x, y):
618+
return x
619+
default:
620+
return false
621+
}
622+
}
623+
""")
624+
enum TestEnum {
625+
case a(val1: String, Int)
626+
case b
627+
case testCase(testValue: String)
628+
}
629+
"""#,
630+
expandedSource: #"""
631+
enum TestEnum {
632+
case a(val1: String, Int)
633+
case b
634+
case testCase(testValue: String)
635+
636+
var isTestCase: Bool {
637+
switch self {
638+
case let .a(x):
639+
return x
570640
default:
571641
return false
572642
}

0 commit comments

Comments
 (0)