Skip to content

Commit 5202c07

Browse files
Convert switch cases with returns to returned switch expression (#802)
* Convert switch cases with returns to returned switch expression * Early bit of polish * Convert switch if it's the last statement, not only statement * Use a more concise `visitBlock` * Update src/main/java/org/openrewrite/java/migrate/lang/SwitchCaseReturnsToSwitchExpression.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Some simplifications * Condense further * Further condense and reuse --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 599e339 commit 5202c07

File tree

3 files changed

+514
-0
lines changed

3 files changed

+514
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.lang;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jspecify.annotations.Nullable;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.Preconditions;
23+
import org.openrewrite.Recipe;
24+
import org.openrewrite.TreeVisitor;
25+
import org.openrewrite.internal.ListUtils;
26+
import org.openrewrite.java.JavaIsoVisitor;
27+
import org.openrewrite.java.search.UsesJavaVersion;
28+
import org.openrewrite.java.tree.*;
29+
import org.openrewrite.marker.Markers;
30+
import org.openrewrite.staticanalysis.groovy.GroovyFileChecker;
31+
import org.openrewrite.staticanalysis.kotlin.KotlinFileChecker;
32+
33+
import java.util.List;
34+
35+
import static org.openrewrite.Tree.randomId;
36+
37+
@Value
38+
@EqualsAndHashCode(callSuper = false)
39+
public class SwitchCaseReturnsToSwitchExpression extends Recipe {
40+
@Override
41+
public String getDisplayName() {
42+
return "Convert switch cases where every case returns into a returned switch expression";
43+
}
44+
45+
@Override
46+
public String getDescription() {
47+
return "Switch statements where each case returns a value can be converted to a switch expression that returns the value directly.";
48+
}
49+
50+
@Override
51+
public TreeVisitor<?, ExecutionContext> getVisitor() {
52+
TreeVisitor<?, ExecutionContext> preconditions = Preconditions.and(
53+
new UsesJavaVersion<>(14),
54+
Preconditions.not(new KotlinFileChecker<>()),
55+
Preconditions.not(new GroovyFileChecker<>())
56+
);
57+
return Preconditions.check(preconditions, new JavaIsoVisitor<ExecutionContext>() {
58+
@Override
59+
public J.Block visitBlock(J.Block block, ExecutionContext ctx) {
60+
J.Block b = super.visitBlock(block, ctx);
61+
return b.withStatements(ListUtils.map(b.getStatements(), statement -> {
62+
if (statement instanceof J.Switch) {
63+
J.Switch sw = (J.Switch) statement;
64+
if (canConvertToSwitchExpression(sw)) {
65+
J.SwitchExpression switchExpression = convertToSwitchExpression(sw);
66+
return new J.Return(
67+
randomId(),
68+
sw.getPrefix(),
69+
Markers.EMPTY,
70+
switchExpression
71+
);
72+
}
73+
}
74+
return statement;
75+
}));
76+
}
77+
78+
private boolean canConvertToSwitchExpression(J.Switch switchStatement) {
79+
for (Statement statement : switchStatement.getCases().getStatements()) {
80+
if (!(statement instanceof J.Case)) {
81+
return false;
82+
}
83+
84+
J.Case caseStatement = (J.Case) statement;
85+
if (caseStatement.getBody() != null) {
86+
// Arrow case
87+
J body = caseStatement.getBody();
88+
if (body instanceof J.Block) {
89+
if (!isReturnCase(((J.Block) body).getStatements())) {
90+
return false;
91+
}
92+
} else if (!(body instanceof J.Return)) {
93+
return false;
94+
}
95+
} else {
96+
// Colon case
97+
if (!isReturnCase(caseStatement.getStatements())) {
98+
return false;
99+
}
100+
}
101+
}
102+
103+
// We need either a default case or the switch to cover all possible values
104+
return SwitchUtils.coversAllPossibleValues(switchStatement);
105+
}
106+
107+
private boolean isReturnCase(List<Statement> statements) {
108+
if (statements.size() != 1) {
109+
return false;
110+
}
111+
// Handle block containing a single return
112+
if (statements.get(0) instanceof J.Block) {
113+
return isReturnCase(((J.Block) statements.get(0)).getStatements());
114+
}
115+
// Direct return statement
116+
return statements.get(0) instanceof J.Return;
117+
}
118+
119+
private J.SwitchExpression convertToSwitchExpression(J.Switch switchStatement) {
120+
JavaType returnType = extractReturnType(switchStatement);
121+
122+
List<Statement> convertedCases = ListUtils.map(switchStatement.getCases().getStatements(), statement -> {
123+
J.Case caseStatement = (J.Case) statement;
124+
if (caseStatement.getBody() != null) {
125+
// Arrow case
126+
J body = caseStatement.getBody();
127+
if (body instanceof J.Block && ((J.Block) body).getStatements().size() == 1) {
128+
body = ((J.Block) body).getStatements().get(0);
129+
}
130+
if (body instanceof J.Return) {
131+
J.Return ret = (J.Return) body;
132+
if (ret.getExpression() != null) {
133+
return caseStatement.withBody(ret.getExpression());
134+
}
135+
}
136+
} else {
137+
// Colon case - convert to arrow case
138+
Expression returnExpression = extractReturnExpression(caseStatement.getStatements());
139+
if (returnExpression != null) {
140+
// When converting from colon to arrow syntax, we need to ensure proper spacing
141+
JContainer<J> caseLabels = caseStatement.getPadding().getCaseLabels();
142+
JContainer<J> updatedLabels = caseLabels.getPadding().withElements(
143+
ListUtils.mapLast(caseLabels.getPadding().getElements(),
144+
elem -> elem.withAfter(Space.SINGLE_SPACE)));
145+
return caseStatement
146+
.withStatements(null)
147+
.withBody(returnExpression.withPrefix(Space.SINGLE_SPACE))
148+
.withType(J.Case.Type.Rule)
149+
.getPadding()
150+
.withCaseLabels(updatedLabels);
151+
}
152+
}
153+
return caseStatement;
154+
});
155+
return new J.SwitchExpression(
156+
randomId(),
157+
Space.SINGLE_SPACE,
158+
Markers.EMPTY,
159+
switchStatement.getSelector(),
160+
switchStatement.getCases().withStatements(convertedCases),
161+
returnType
162+
);
163+
}
164+
165+
private @Nullable JavaType extractReturnType(J.Switch switchStatement) {
166+
for (Statement statement : switchStatement.getCases().getStatements()) {
167+
J.Case caseStatement = (J.Case) statement;
168+
if (caseStatement.getBody() != null) {
169+
J body = caseStatement.getBody();
170+
if (body instanceof J.Block && ((J.Block) body).getStatements().size() == 1) {
171+
body = ((J.Block) body).getStatements().get(0);
172+
}
173+
if (body instanceof J.Return) {
174+
J.Return ret = (J.Return) body;
175+
if (ret.getExpression() != null && ret.getExpression().getType() != null) {
176+
return ret.getExpression().getType();
177+
}
178+
}
179+
} else {
180+
Expression returnExpression = extractReturnExpression(caseStatement.getStatements());
181+
if (returnExpression != null && returnExpression.getType() != null) {
182+
return returnExpression.getType();
183+
}
184+
}
185+
}
186+
return null;
187+
}
188+
189+
190+
private @Nullable Expression extractReturnExpression(List<Statement> statements) {
191+
if (statements.size() != 1) {
192+
return null;
193+
}
194+
// Handle block containing a single return
195+
if (statements.get(0) instanceof J.Block) {
196+
J.Block block = (J.Block) statements.get(0);
197+
if (block.getStatements().size() == 1 && block.getStatements().get(0) instanceof J.Return) {
198+
return ((J.Return) block.getStatements().get(0)).getExpression();
199+
}
200+
}
201+
// Direct return statement
202+
if (statements.get(0) instanceof J.Return) {
203+
return ((J.Return) statements.get(0)).getExpression();
204+
}
205+
return null;
206+
}
207+
});
208+
}
209+
}

src/main/resources/META-INF/rewrite/java-version-17.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ recipeList:
6060
newVersion: 1.17.x
6161
- org.openrewrite.java.migrate.AddLombokMapstructBinding
6262
- org.openrewrite.java.migrate.lang.SwitchCaseAssignmentsToSwitchExpression
63+
- org.openrewrite.java.migrate.lang.SwitchCaseReturnsToSwitchExpression
6364
- org.openrewrite.java.migrate.lang.SwitchExpressionYieldToArrow
6465

6566
---

0 commit comments

Comments
 (0)