Skip to content

Commit ded268e

Browse files
authored
Fix context-sensitive template replacement for standalone expression statements in non-void methods (#7211)
When a JavaTemplate replaces a standalone method invocation expression statement inside a non-void method, the generated template stub was missing a semicolon after the expression. The semicolon was appended to the `after` buffer after the `}\nreturn ...;` from the non-void method wrapping, placing it outside the `if(true)` block instead of directly after the template content. This caused `parseBlockStatements` to return 0 results. Fix by using `insert(0, ';')` instead of `append(';')` so the semicolon is placed before the closing brace of the `if(true)` block.
1 parent 9e08fc3 commit ded268e

2 files changed

Lines changed: 80 additions & 2 deletions

File tree

rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest8Test.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,83 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex
257257
);
258258
}
259259

260+
@Test
261+
void replaceContextSensitiveMethodInvocationStandaloneStatement() {
262+
rewriteRun(
263+
spec -> spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() {
264+
@Override
265+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
266+
method = super.visitMethodInvocation(method, ctx);
267+
if (method.getSimpleName().equals("visitClassDeclaration") &&
268+
method.getSelect() != null &&
269+
!(method.getSelect() instanceof J.Identifier &&
270+
"super".equals(((J.Identifier) method.getSelect()).getSimpleName()))) {
271+
return JavaTemplate.builder("#{any()}.visit(#{any()}, #{any()}, getCursor().getParentTreeCursor())")
272+
.contextSensitive()
273+
.javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))
274+
.build()
275+
.apply(getCursor(), method.getCoordinates().replace(),
276+
method.getSelect(), method.getArguments().get(0), method.getArguments().get(1));
277+
}
278+
return method;
279+
}
280+
})),
281+
java(
282+
"""
283+
import org.openrewrite.*;
284+
import org.openrewrite.java.JavaIsoVisitor;
285+
import org.openrewrite.java.JavaVisitor;
286+
import org.openrewrite.java.tree.J;
287+
288+
class MyRecipe extends Recipe {
289+
@Override
290+
public String getDisplayName() { return ""; }
291+
@Override
292+
public String getDescription() { return ""; }
293+
294+
@Override
295+
public TreeVisitor<?, ExecutionContext> getVisitor() {
296+
return new JavaIsoVisitor<ExecutionContext>() {
297+
@Override
298+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
299+
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
300+
new JavaVisitor<ExecutionContext>() {}.visitClassDeclaration(cd, ctx);
301+
return cd;
302+
}
303+
};
304+
}
305+
}
306+
""",
307+
"""
308+
import org.openrewrite.*;
309+
import org.openrewrite.java.JavaIsoVisitor;
310+
import org.openrewrite.java.JavaVisitor;
311+
import org.openrewrite.java.tree.J;
312+
313+
class MyRecipe extends Recipe {
314+
@Override
315+
public String getDisplayName() { return ""; }
316+
@Override
317+
public String getDescription() { return ""; }
318+
319+
@Override
320+
public TreeVisitor<?, ExecutionContext> getVisitor() {
321+
return new JavaIsoVisitor<ExecutionContext>() {
322+
@Override
323+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
324+
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
325+
new JavaVisitor<ExecutionContext>() {
326+
}.visit(cd, ctx, getCursor().getParentTreeCursor());
327+
return cd;
328+
}
329+
};
330+
}
331+
}
332+
"""
333+
)
334+
);
335+
}
336+
260337
@DocumentExample
261338
@Test
262339
void parameterizedMatch() {

rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,9 @@ private void contextTemplate(Cursor cursor, J prior, StringBuilder before, Strin
321321
}
322322

323323
if (prior == insertionPoint && prior instanceof Expression) {
324-
// the template represents an expression, so we need to wrap it in a statement
325-
after.append(';');
324+
// the template represents an expression, so we need to wrap it in a statement.
325+
// Use insert(0, ...) so the semicolon comes before any "}\nreturn ...;" from non-void methods.
326+
after.insert(0, ';');
326327
}
327328
after.append('}');
328329
} else if (j instanceof J.Annotation) {

0 commit comments

Comments
 (0)