Skip to content

Commit 599e339

Browse files
authored
Convert switch expressions to arrows where possible (#801)
* Convert switches to switch expressions with arrows where possible * Apply best practices * Minimize SwitchExpressionYieldToArrow * Convert yield to arrows after `SwitchCaseAssignmentsToSwitchExpression` * Separate out `SwitchCaseReturnsToSwitchExpression` * Verify we do not yet convert empty cases * Apply suggestions from code review
1 parent 5906a02 commit 599e339

File tree

6 files changed

+398
-63
lines changed

6 files changed

+398
-63
lines changed

src/main/java/org/openrewrite/java/migrate/lang/SwitchCaseAssignmentsToSwitchExpression.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public J.Block visitBlock(J.Block originalBlock, ExecutionContext ctx) {
7070
return block.withStatements(ListUtils.map(block.getStatements(), (index, statement) -> {
7171
if (statement == originalSwitch.getAndSet(null)) {
7272
doAfterVisit(new InlineVariable().getVisitor());
73+
doAfterVisit(new SwitchExpressionYieldToArrow().getVisitor());
7374
// We've already converted the switch/assignments to an assignment with a switch expression.
7475
return null;
7576
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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.openrewrite.ExecutionContext;
21+
import org.openrewrite.Preconditions;
22+
import org.openrewrite.Recipe;
23+
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.internal.ListUtils;
25+
import org.openrewrite.java.JavaIsoVisitor;
26+
import org.openrewrite.java.search.UsesJavaVersion;
27+
import org.openrewrite.java.tree.J;
28+
import org.openrewrite.java.tree.JContainer;
29+
import org.openrewrite.java.tree.Space;
30+
import org.openrewrite.java.tree.Statement;
31+
import org.openrewrite.staticanalysis.groovy.GroovyFileChecker;
32+
import org.openrewrite.staticanalysis.kotlin.KotlinFileChecker;
33+
34+
import static java.util.Objects.requireNonNull;
35+
36+
@Value
37+
@EqualsAndHashCode(callSuper = false)
38+
public class SwitchExpressionYieldToArrow extends Recipe {
39+
@Override
40+
public String getDisplayName() {
41+
return "Convert switch expression yield to arrow";
42+
}
43+
44+
@Override
45+
public String getDescription() {
46+
return "Convert switch expressions with colon cases and yield statements to arrow syntax.";
47+
}
48+
49+
@Override
50+
public TreeVisitor<?, ExecutionContext> getVisitor() {
51+
TreeVisitor<?, ExecutionContext> preconditions = Preconditions.and(
52+
new UsesJavaVersion<>(14),
53+
Preconditions.not(new KotlinFileChecker<>()),
54+
Preconditions.not(new GroovyFileChecker<>())
55+
);
56+
return Preconditions.check(preconditions, new JavaIsoVisitor<ExecutionContext>() {
57+
@Override
58+
public J.SwitchExpression visitSwitchExpression(J.SwitchExpression switchExpression, ExecutionContext ctx) {
59+
J.SwitchExpression se = super.visitSwitchExpression(switchExpression, ctx);
60+
61+
// Check if this is a complex case (more than just a yield)
62+
if (anythingOtherThanYield(se)) {
63+
return se;
64+
}
65+
66+
return se.withCases(se.getCases().withStatements(ListUtils.map(se.getCases().getStatements(), statement -> {
67+
J.Case caseStatement = (J.Case) requireNonNull(statement);
68+
J.Yield yieldStatement = (J.Yield) caseStatement.getStatements().get(0);
69+
70+
// Add space after the last case label to create space before arrow
71+
JContainer<J> caseLabels = caseStatement.getPadding().getCaseLabels();
72+
JContainer<J> updatedLabels = caseLabels.getPadding().withElements(
73+
ListUtils.mapLast(caseLabels.getPadding().getElements(),
74+
elem -> elem.withAfter(Space.SINGLE_SPACE))
75+
);
76+
77+
return caseStatement
78+
.withStatements(null)
79+
.withBody(yieldStatement.getValue().withPrefix(Space.SINGLE_SPACE))
80+
.withType(J.Case.Type.Rule)
81+
.getPadding()
82+
.withCaseLabels(updatedLabels);
83+
})));
84+
}
85+
86+
// For now, we only convert switch expressions that consist solely of yield statements
87+
private boolean anythingOtherThanYield(J.SwitchExpression se) {
88+
for (Statement statement : se.getCases().getStatements()) {
89+
if (!(statement instanceof J.Case)) {
90+
return true;
91+
}
92+
93+
J.Case caseStatement = (J.Case) statement;
94+
if (caseStatement.getType() != J.Case.Type.Statement ||
95+
caseStatement.getStatements().size() != 1 ||
96+
!(caseStatement.getStatements().get(0) instanceof J.Yield)) {
97+
return true;
98+
}
99+
}
100+
return false;
101+
}
102+
});
103+
}
104+
}

src/main/resources/META-INF/rewrite/examples.yml

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,31 +1025,34 @@ recipeName: org.openrewrite.java.migrate.UpgradePluginsForJava11
10251025
examples:
10261026
- description: ''
10271027
sources:
1028+
- before: project
1029+
language: mavenProject
10281030
- before: |
10291031
<project>
10301032
<groupId>com.mycompany.app</groupId>
10311033
<artifactId>my-app</artifactId>
10321034
<version>1</version>
1033-
<properties>
1034-
<wro4j.version>1.8.0</wro4j.version>
1035-
</properties>
10361035
<build>
10371036
<plugins>
10381037
<plugin>
1039-
<groupId>ro.isdc.wro4j</groupId>
1040-
<artifactId>wro4j-maven-plugin</artifactId>
1041-
<version>${wro4j.version}</version>
1038+
<groupId>org.codehaus.mojo</groupId>
1039+
<artifactId>jaxb2-maven-plugin</artifactId>
1040+
<version>2.3.1</version>
10421041
</plugin>
10431042
</plugins>
10441043
</build>
10451044
</project>
1046-
after: |
1045+
path: pom.xml
1046+
language: xml
1047+
- description: ''
1048+
sources:
1049+
- before: |
10471050
<project>
10481051
<groupId>com.mycompany.app</groupId>
10491052
<artifactId>my-app</artifactId>
10501053
<version>1</version>
10511054
<properties>
1052-
<wro4j.version>1.10.1</wro4j.version>
1055+
<wro4j.version>1.8.0</wro4j.version>
10531056
</properties>
10541057
<build>
10551058
<plugins>
@@ -1061,23 +1064,20 @@ examples:
10611064
</plugins>
10621065
</build>
10631066
</project>
1064-
path: pom.xml
1065-
language: xml
1066-
- description: ''
1067-
sources:
1068-
- before: project
1069-
language: mavenProject
1070-
- before: |
1067+
after: |
10711068
<project>
10721069
<groupId>com.mycompany.app</groupId>
10731070
<artifactId>my-app</artifactId>
10741071
<version>1</version>
1072+
<properties>
1073+
<wro4j.version>1.10.1</wro4j.version>
1074+
</properties>
10751075
<build>
10761076
<plugins>
10771077
<plugin>
1078-
<groupId>org.codehaus.mojo</groupId>
1079-
<artifactId>jaxb2-maven-plugin</artifactId>
1080-
<version>2.3.1</version>
1078+
<groupId>ro.isdc.wro4j</groupId>
1079+
<artifactId>wro4j-maven-plugin</artifactId>
1080+
<version>${wro4j.version}</version>
10811081
</plugin>
10821082
</plugins>
10831083
</build>
@@ -3338,25 +3338,6 @@ examples:
33383338
type: specs.openrewrite.org/v1beta/example
33393339
recipeName: org.openrewrite.java.migrate.guava.NoGuava
33403340
examples:
3341-
- description: ''
3342-
sources:
3343-
- before: |
3344-
import com.google.common.base.MoreObjects;
3345-
3346-
class A {
3347-
Object foo(Object obj) {
3348-
return MoreObjects.firstNonNull(obj, "default");
3349-
}
3350-
}
3351-
after: |
3352-
import java.util.Objects;
3353-
3354-
class A {
3355-
Object foo(Object obj) {
3356-
return Objects.requireNonNullElse(obj, "default");
3357-
}
3358-
}
3359-
language: java
33603341
- description: ''
33613342
sources:
33623343
- before: |
@@ -3403,6 +3384,25 @@ examples:
34033384
}
34043385
}
34053386
language: java
3387+
- description: ''
3388+
sources:
3389+
- before: |
3390+
import com.google.common.base.MoreObjects;
3391+
3392+
class A {
3393+
Object foo(Object obj) {
3394+
return MoreObjects.firstNonNull(obj, "default");
3395+
}
3396+
}
3397+
after: |
3398+
import java.util.Objects;
3399+
3400+
class A {
3401+
Object foo(Object obj) {
3402+
return Objects.requireNonNullElse(obj, "default");
3403+
}
3404+
}
3405+
language: java
34063406
---
34073407
type: specs.openrewrite.org/v1beta/example
34083408
recipeName: org.openrewrite.java.migrate.guava.NoGuavaAtomicsNewReference
@@ -6235,7 +6235,7 @@ examples:
62356235
language: java
62366236
---
62376237
type: specs.openrewrite.org/v1beta/example
6238-
recipeName: org.openrewrite.java.migrate.lang.SwitchCaseAssigningToSwitchExpression
6238+
recipeName: org.openrewrite.java.migrate.lang.SwitchCaseAssignmentsToSwitchExpression
62396239
examples:
62406240
- description: ''
62416241
sources:
@@ -6314,6 +6314,35 @@ examples:
63146314
language: java
63156315
---
63166316
type: specs.openrewrite.org/v1beta/example
6317+
recipeName: org.openrewrite.java.migrate.lang.SwitchExpressionYieldToArrow
6318+
examples:
6319+
- description: ''
6320+
sources:
6321+
- before: |
6322+
class Test {
6323+
String format(String str) {
6324+
String formatted = switch (str) {
6325+
case "foo": yield "Foo";
6326+
case "bar": yield "Bar";
6327+
case null, default: yield "unknown";
6328+
};
6329+
return formatted;
6330+
}
6331+
}
6332+
after: |
6333+
class Test {
6334+
String format(String str) {
6335+
String formatted = switch (str) {
6336+
case "foo" -> "Foo";
6337+
case "bar" -> "Bar";
6338+
case null, default -> "unknown";
6339+
};
6340+
return formatted;
6341+
}
6342+
}
6343+
language: java
6344+
---
6345+
type: specs.openrewrite.org/v1beta/example
63176346
recipeName: org.openrewrite.java.migrate.lang.ThreadStopUnsupported
63186347
examples:
63196348
- description: ''

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.SwitchExpressionYieldToArrow
6364

6465
---
6566
type: specs.openrewrite.org/v1beta/recipe

0 commit comments

Comments
 (0)