This guide helps you move from Java code to a formatter DSL rule when it is not clear what JavaParser calls the required construct or which fields it has.
- Take the smallest possible Java code sample that contains the required construct.
- Parse it with JavaParser.
- Print the AST tree: node class names and their
.toString()output. - Find the node that corresponds to the required construct.
- Print the properties of this node.
- Use the node class name in the DSL pattern.
- Use property names as field names in the DSL.
Paste this code:
JavaParser parser = new JavaParser(
new ParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_25)
);
String code = """
class Sample {
void run() {
while (i < limit) {
i++;
}
}
}
""";
CompilationUnit compilationUnit = parser.parse(code)
.getResult()
.orElseThrow();
compilationUnit.findAll(Node.class).forEach(node -> {
String source = node.toString().replace('\n', ' ');
System.out.println(node.getClass().getSimpleName() + " -> " + source);
});The output will be:
CompilationUnit -> class Sample { void run() { while (i < limit) { i++; } } }
ClassOrInterfaceDeclaration -> class Sample { void run() { while (i < limit) { i++; } } }
SimpleName -> Sample
MethodDeclaration -> void run() { while (i < limit) { i++; } }
SimpleName -> run
VoidType -> void
BlockStmt -> { while (i < limit) { i++; } }
WhileStmt -> while (i < limit) { i++; }
BinaryExpr -> i < limit
NameExpr -> i
SimpleName -> i
NameExpr -> limit
SimpleName -> limit
BlockStmt -> { i++; }
ExpressionStmt -> i++;
UnaryExpr -> i++
NameExpr -> i
SimpleName -> i
So the JavaParser node name for while is:
WhileStmt
Once the node name is found, you need to understand which fields can be used in the DSL. Add this to the previous code (example for WhileStmt):
Node node = compilationUnit.findFirst(WhileStmt.class).orElseThrow();
for (PropertyMetaModel property : node.getMetaModel().getAllPropertyMetaModels()) {
String name = property.getName();
if (List.of(
"metaModel", // description of the node model itself
"range", // position in the source file
"tokenRange", // token range
"parsed", // whether the node was produced by the parser or created programmatically
"comment", // comment attached to the node
"orphanComments", // comment that was near the node
"allContainedComments", // all comments in the node subtree
"childNodes", // all child nodes without field names
"parentNode" // parent node
).contains(name)) {
continue;
}
Object value = property.getValue(node);
System.out.println(name + " -> " + value);
}Output (fields for WhileStmt):
body -> {
i++;
}
condition -> i < limit
If the JavaParser class is called:
WhileStmt
write this in the DSL pattern:
WhileStmt(...)If the properties are called:
condition
body
write this in the DSL:
condition=<Expression>, body=<Statement>Full example:
<Statement> ::= WhileStmt(condition=<Expression>, body=<Statement>)
=> "while" sp "(" <Expression> ")" sp <Statement>;If a property contains a list, the DSL uses:
statements=[<Statement>*]Example:
<Statement> ::= BlockStmt(statements=[<Statement>*])
=> "{" nl indent join(<Statement>, nl) nl dedent "}";If a property may or may not be present, use ? after the field name:
elseStmt?=<ElseStmt>Example:
<Statement> ::= IfStmt(condition=<Expression>, thenStmt=<ThenStmt>, elseStmt?=<ElseStmt>)
=> "if" sp "(" <Expression> ")" <ThenStmt>
ifpresent(ElseStmt, nl "else" <ElseStmt>);Bad:
<BinaryExpr> ::= BinaryExpr(left=<Expression>, right=<Expression>)
=> <Expression> sp "+" sp <Expression>;This rule may conflict in bindings because the left and right expressions are different.
Better:
<BinaryExpr> ::= BinaryExpr(left=<LeftExpr>, right=<RightExpr>)
=> <LeftExpr> sp "+" sp <RightExpr>;Not all JavaParser properties are nodes. For example:
Modifier.keyword -> FINAL
PrimitiveType.type -> INT
BinaryExpr.operator -> PLUS
AssignExpr.operator -> PLUS
UnaryExpr.operator -> PREFIX_INCREMENT
To understand exactly what is stored in such a field, print not only the value but also the Java class of this value, because the same textual value may mean different things in different JavaParser classes.
For example:
BinaryExpr.operator -> PLUS
AssignExpr.operator -> PLUS
The same name PLUS in different classes:
com.github.javaparser.ast.expr.BinaryExpr.Operator
com.github.javaparser.ast.expr.AssignExpr.Operator
has different meanings:
a + b // BinaryExpr.Operator.PLUS
a += b // AssignExpr.Operator.PLUS
The following set of functions is suitable for determining the names of non-Node constructs:
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.metamodel.PropertyMetaModel;
import java.util.List;
import java.util.Optional;
private static void printNonNodeProperties(Node node) {
for (PropertyMetaModel property : node.getMetaModel().getAllPropertyMetaModels()) {
String name = property.getName();
if (List.of(
"metaModel", // description of the node model itself
"range", // position in the source file
"tokenRange", // token range
"parsed", // whether the node was produced by the parser or created programmatically
"comment", // comment attached to the node
"orphanComments", // comment that was near the node
"allContainedComments", // all comments in the node subtree
"childNodes", // all child nodes without field names
"parentNode" // parent node
).contains(name)) {
continue;
}
printNonNodeValue(name, property.getValue(node));
}
}
private static void printNonNodeValue(String name, Object value) {
switch (value) {
case null -> {
return;
}
case Optional<?> optionalValue -> {
optionalValue.ifPresent(innerValue -> printNonNodeValue(name, innerValue));
return;
}
case NodeList<?> values -> {
for (int i = 1; i <= values.size(); i++) {
printNonNodeValue(name + "[" + i + "]", values.get(i - 1));
}
return;
}
case Node node -> {
return;
}
default -> {
}
}
Class<?> valueClass = value instanceof Enum<?> enumValue
? enumValue.getDeclaringClass()
: value.getClass();
String className = valueClass.getCanonicalName();
// className may be unavailable for anonymous, lambda, or local classes
if (className == null) {
className = valueClass.getName().replace('$', '.');
}
String enumConstant = value instanceof Enum<?> enumValue
? "." + enumValue.name()
: "";
System.out.println(name + " -> " + className + enumConstant + " = " + value);
}Usage example:
String code = "public class Sample{public int one(){if (a == b) return 1;}}";
CompilationUnit compilationUnit = StaticJavaParser.parse(code);
BinaryExpr binaryExpr = compilationUnit.findFirst(BinaryExpr.class).orElseThrow();
printNonNodeProperties(binaryExpr);The output will be:
operator -> com.github.javaparser.ast.expr.BinaryExpr.Operator.EQUALS = EQUALS
Determine the name:
import com.github.javaparser.ast.Modifier;
String code = "final class Empty{}";
CompilationUnit compilationUnit = StaticJavaParser.parse(code);
Modifier modifier = compilationUnit.findFirst(Modifier.class).orElseThrow();
printNonNodeProperties(modifier); // keyword -> com.github.javaparser.ast.Modifier.Keyword.FINAL = FINALIn the DSL, this can still be moved into a placeholder:
<Modifier> ::= Modifier(keyword=<Keyword>) // writes keyword on a separate line
=> nl indent <Keyword> dedent nl; // with one-tab indentation- Find the node in the AST:
SomeNode
- Inspect the properties of this node:
left -> a
operator -> PLUS
right -> b
- For each property, understand what it contains:
public class Example {
void example(Node node) {
for (PropertyMetaModel property : node.getMetaModel().getAllPropertyMetaModels()) {
String name = property.getName();
if (List.of(
"metaModel", "range", "tokenRange", "parsed",
"comment", "orphanComments", "allContainedComments",
"childNodes", "parentNode"
).contains(name)) {
continue;
}
Object value = property.getValue(node);
if (value == null) {
System.out.println(name + " -> null");
} else if (value instanceof Node childNode) {
System.out.println(name
+ " -> Node "
+ childNode.getClass().getSimpleName()
+ " = "
+ childNode);
} else {
Class<?> valueClass = value instanceof Enum<?> enumValue
? enumValue.getDeclaringClass()
: value.getClass();
String className = valueClass.getCanonicalName();
if (className == null) {
className = valueClass.getName().replace('$', '.');
}
String enumConstant = value instanceof Enum<?> enumValue
? "." + enumValue.name()
: "";
System.out.println(name
+ " -> "
+ className
+ enumConstant
+ " = "
+ value);
}
}
}
}- For example, for
BinaryExpr(a + b)the output will be:
left -> Node NameExpr = a
operator -> com.github.javaparser.ast.expr.BinaryExpr.Operator.PLUS = PLUS
right -> Node NameExpr = b
- If a property contains a
Node, move it into a placeholder. If a property contains a non-Node value, for example an enum foroperator,keyword, ortype, it can also be moved into a placeholder. - Example rule:
<SomeRule> ::= SomeNode(left=<LeftExpr>, operator=<Operator>, right=<RightExpr>)
=> <LeftExpr> sp <Operator> sp <RightExpr>;