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 (or use the debugger to run a command) and quickly see how certain code translates to specific LSTs.
This guide will walk through the different ways you can use the TreeVisitingPrinter
to get a visual representation of the LST for your code.
Prerequisites
This guide assumes that you:
- Are familiar with writing Java
- Have already set up your Recipe Development Environment
- Are using at least OpenRewrite version
7.35.0
or Rewrite-Recipe-Bom version2.6.0
Using TreeVisitingPrinter.printTree
There are two ways you can access the visual representation of the LST for some code:
- Add a snippet of code to your recipe OR
- Add a breakpoint in your recipe and enter a manual command when the breakpoint is hit
Let's walk through both of these.
Adding code to your recipe
Inside of any visit<LST>
method in a visitor (such as visitVariableDeclarations
or visitCompilationUnit
), add this line of code:
System.out.println(TreeVisitingPrinter.printTree(getCursor()));
Then, when you run your tests, you will see the visual representation of the LST in the console.
If you don't have a recipe to use or if you want to grab a simple one to start with, check out the example below.
Using the debugger
- Inside of any
visit<LST>
method in a visitor (such asvisitVariableDeclarations
orvisitCompilationUnit
), add a breakpoint. - Trigger the debugger by debugging a test that will reach your breakpoint.
- Once your breakpoint is hit, type
TreeVisitingPrinter.printTree(getCursor())
into theEvaluate expression
prompt and press enter. - You should see a
result
appear. You can pressview
to get a pop-up of the LST in your IDE or you could copy it to another text editor for use in the future.
Example
Let's imagine that you wanted to see what the LST looked like for this code:
class A {
void test() {
int a;
a = 0;
}
}
To begin, you will need a recipe you can modify and run. Here is a simple recipe you can use that does nothing other than utilize the TreeVisitingPrinter
:
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 compUnit, ExecutionContext executionContext) {
// This next line could be omitted in favor of a breakpoint
// if you'd prefer to use the debugger instead.
System.out.println(TreeVisitingPrinter.printTree(getCursor()));
return super.visitCompilationUnit(compUnit, executionContext);
}
};
}
}
Once you have that recipe, you would then write a test that includes the snippet of code you care about:
@Test
void someTest() {
rewriteRun(
java(
"""
class A {
void test() {
int a;
a = 0;
}
}
"""
)
);
}
From there, if you ran that test, you would see this in your console:
----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
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;
in some code, but were unsure what ~
was, you could use this printer to find out that it's a J.Unary
.
Limitations
The TreeVisitingPrinter
skips unvisited elements such as JRightPadded
or JLeftPadded
that you might see if you traced through the tree yourself. This is done to make the visual easier to understand - but it's important to know that it will not perfectly match the actual OpenRewrite LST.