Links

TreeVisitingPrinter

When you first begin to look into Lossless Semantic Trees (LSTs), it can be difficult to understand what code corresponds to what LST. You could use a debugger to step through the tree, but that can take a lot of time and it's easy to get lost in irrelevant elements.
Fortunately, in OpenRewrite 7.35.0, a new option was created: the TreeVisitingPrinter. Utilizing this, you can inject a snippet of code into your Java recipe and quickly see how certain code translates to specific LSTs.
This guide will walk through the process of adding this to a recipe and running it to get a visual representation of the code.

Prerequisites

This guide assumes that you:

Adding TreeVisitingPrinter to a recipe

To use the TreeVisitingPrinter to examine some code, the first thing you will need to do is add this code to a visitor in a Java recipe:
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) {
System.out.println("------LST Start------");
System.out.println(TreeVisitingPrinter.printTree(cu));
System.out.println("------LST End------");
return super.visitCompilationUnit(cu, executionContext);
}
A simple recipe that does nothing other than utilize this printer would be:
package com.yourorg;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.TreeVisitingPrinter;
import org.openrewrite.java.tree.J;
public class SomeRecipe extends Recipe {
@Override
public String getDisplayName() {
return "Some display name";
}
@Override
public JavaIsoVisitor<ExecutionContext> getVisitor() {
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) {
System.out.println("------LST Start------");
System.out.println(TreeVisitingPrinter.printTree(cu));
System.out.println("------LST End------");
return super.visitCompilationUnit(cu, executionContext);
}
};
}
}

Using tests to see the tree for some code

Once you've added the above snippet to your recipe, you can see a visual LST for any code by writing a test with the code you are interested in.
For example, if you wanted to see what the LST looks like for this code:
class A {
void test() {
int a;
a = 0;
}
}
You could write this test:
@Test
void someTest() {
rewriteRun(
java(
"""
class A {
void test() {
int a;
a = 0;
}
}
"""
)
);
}
And, if you ran the test, you would see this in your console:
------LST Start------
----J.CompilationUnit
\---J.ClassDeclaration
|---J.Identifier | "A"
\---J.Block
\-------J.MethodDeclaration | "MethodDeclaration{A{name=test,return=void,parameters=[]}}"
|---J.Primitive | "void"
|---J.Identifier | "test"
|-----------J.Empty
\---J.Block
|-------J.VariableDeclarations | "int a"
| |---J.Primitive | "int"
| \-------J.VariableDeclarations.NamedVariable | "a"
| \---J.Identifier | "a"
\-------J.Assignment | "a = 0"
|---J.Identifier | "a"
\-------J.Literal
------LST End------
The TreeVisitingPrinter skips unvisited elements such as JRightPadded or JLeftPadded that you might see if you traced through the tree yourself.
You could then use this tree to help make key decisions about your recipe and what LSTs it should handle. For instance, if you saw int l = ~k; but were unsure what ~ was, you could use this printer to find out that it's a J.Unary and adjust your recipe accordingly.