Skip to content

Commit dbabed5

Browse files
MoveConditionsToWhile recipe (#698)
* Basic implementation * Basic implementation * Revert common-static-analysis.yml change * Check for break label * Remove invalid Sonar reference
1 parent a5a709c commit dbabed5

File tree

2 files changed

+421
-0
lines changed

2 files changed

+421
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.staticanalysis;
17+
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Recipe;
20+
import org.openrewrite.TreeVisitor;
21+
import org.openrewrite.java.JavaTemplate;
22+
import org.openrewrite.java.JavaVisitor;
23+
import org.openrewrite.java.tree.J;
24+
import org.openrewrite.java.tree.Statement;
25+
26+
import java.util.List;
27+
28+
public class MoveConditionsToWhile extends Recipe {
29+
30+
@Override
31+
public String getDisplayName() {
32+
return "Convert `while (true)` with initial `if` break to loop condition";
33+
}
34+
35+
@Override
36+
public String getDescription() {
37+
return "Simplifies `while (true)` loops where the first statement is an `if` statement that only contains a `break`. " +
38+
"The condition is inverted and moved to the loop condition for better readability.";
39+
}
40+
41+
@Override
42+
public TreeVisitor<?, ExecutionContext> getVisitor() {
43+
return new JavaVisitor<ExecutionContext>() {
44+
@Override
45+
public J visitWhileLoop(J.WhileLoop whileLoop, ExecutionContext ctx) {
46+
J.WhileLoop wl = (J.WhileLoop) super.visitWhileLoop(whileLoop, ctx);
47+
48+
if (!(wl.getCondition().getTree() instanceof J.Literal)) {
49+
return wl;
50+
}
51+
52+
J.Literal condition = (J.Literal) wl.getCondition().getTree();
53+
if (!Boolean.TRUE.equals(condition.getValue())) {
54+
return wl;
55+
}
56+
57+
if (!(wl.getBody() instanceof J.Block)) {
58+
return wl;
59+
}
60+
61+
J.Block body = (J.Block) wl.getBody();
62+
List<Statement> statements = body.getStatements();
63+
64+
if (statements.isEmpty() || !(statements.get(0) instanceof J.If)) {
65+
return wl;
66+
}
67+
68+
J.If ifStatement = (J.If) statements.get(0);
69+
70+
if (ifStatement.getElsePart() != null) {
71+
// Actually in some cases it would be safe to proceed, but let's skip for now. Can be amended later.
72+
return wl;
73+
}
74+
75+
Statement thenBody = ifStatement.getThenPart();
76+
J.Break breakStatement = null;
77+
if (thenBody instanceof J.Block) {
78+
J.Block thenBlock = (J.Block) thenBody;
79+
if (thenBlock.getStatements().size() != 1 || !(thenBlock.getStatements().get(0) instanceof J.Break)) {
80+
return wl;
81+
}
82+
breakStatement = (J.Break) thenBlock.getStatements().get(0);
83+
} else if (thenBody instanceof J.Break) {
84+
breakStatement = (J.Break) thenBody;
85+
}
86+
87+
// Check that the break has no label
88+
if (breakStatement == null || breakStatement.getLabel() != null) {
89+
return wl;
90+
}
91+
92+
JavaTemplate whileTemplate = JavaTemplate.builder("while (!(#{any()})) #{}")
93+
.build();
94+
95+
List<Statement> remainingStatements = statements.subList(1, statements.size());
96+
J.Block newBody = body.withStatements(remainingStatements);
97+
98+
return whileTemplate.apply(getCursor(), wl.getCoordinates().replace(),
99+
ifStatement.getIfCondition().getTree(), newBody);
100+
}
101+
};
102+
}
103+
}

0 commit comments

Comments
 (0)