Migrating to PMD 7 from PMD 6.x
Table of Contents Important:This document might be incomplete and doesnât answer all questions. In that case please reach out to us by opening a
discussionso that we can improve this guide.
Before you updateBefore updating to PMD 7, you should first update to the latest PMD 6 version 6.55.0 and try to fix all deprecation warnings.
There are a couple of deprecated things in PMD 6, you might encounter:
Properties: In order to define property descriptors, you should use PropertyFactory
now. This factory can create properties of any type. E.g. instead of StringProperty.named(...)
use PropertyFactory.stringProperty(...)
.
Also note, that uiOrder
is gone. You can just remove it.
See also Defining rule properties
When reporting a violation, you might see a deprecation of the addViolation
methods. These methods have been moved to RuleContext
. E.g. instead of addViolation(data, node, ...)
use asCtx(data).addViolation(node, ...)
.
-no-cache
â¡ï¸ --no-cache
-failOnViolation
â¡ï¸ --fail-on-violation
-reportfile
â¡ï¸ --report-file
-language
â¡ï¸ --use-version
WARNING: Use of deprecated attribute 'VariableDeclaratorId/@Image' by XPath rule 'VariableNaming' (in ruleset 'VariableNamingRule'), please use @Name instead
and often already suggest an alternative.
When you are using only built-in rules, then you should check, whether you use any deprecated rule. With PMD 7 many deprecated rules are finally removed. You can see a complete list of the removed rules in the release notes for PMD 7. The release notes also mention the replacement rule, that should be used instead. For some rules, there is no replacement.
Then many rules have been changed or improved. New properties have been added to make them more versatile or properties have been removed, if they are not necessary anymore. See changed rules in the release notes for PMD 7.
All properties which accept multiple values now use a comma (,
) as a delimiter. The previous default was a pipe character (|
). The delimiter is not configurable anymore. If needed, the comma can be escaped with a backslash. This affects the following rules: AvoidUsingHardCodedIP
, LooseCoupling
, UnusedPrivateField
, UnusedPrivateMethod
, AtLeastOneConstructor
, CommentDefaultAccessModifier
, FieldNamingConventions
, LinguisticNaming
, UnnecessaryConstructor
, CyclomaticComplexity
, NcssCount
, SingularField
, AvoidBranchingStatementAsLastInLoop
, CloseResource
.
A handful of rules are new to PMD 7. You might want to check these out: new rules.
Once you have reviewed your ruleset(s), you can switch to PMD 7.
Iâm using custom rulesIdeally, you have written good tests already for your custom rules - see Testing your rules. This helps to identify problems early on.
XPath rulesIf you have XPath based rules, the first step will be to migrate to XPath 2.0 and then to XPath 3.1. XPath 2.0 is available in PMD 6 already and can be used right away. PMD 7 will use by default XPath 3.1 and wonât support XPath 1.0 anymore. The difference between XPath 2.0 and XPath 3.1 is not big, so your XPath 2.0 can be expected to work in PMD 7 without any further changes. So the migration path is to simply migrate to XPath 2.0.
After you have migrated your XPath rules to XPath 2.0, remove the âversionâ property, since that will be removed with PMD 7. PMD 7 by default uses XPath 3.1. See below XPath for details.
Java rulesIf you have Java based rules, and you are using rulechain, this works a bit different now. The RuleChain API has changed, see [core] Simplify the rulechain (#2490) for the full details. But in short, you donât call addRuleChainVisit(...)
in the ruleâs constructor anymore. Instead, you override the method buildTargetSelector
:
protected RuleTargetSelector buildTargetSelector() {
return RuleTargetSelector.forTypes(ASTVariableDeclaratorId.class);
}
Java AST changes
The API to navigate the AST also changed significantly:
Additionally, if you have created rules for Java - regardless whether it is a XPath based rule or a Java based rule - you might need to adjust your queries or visitor methods. The Java AST has been refactored substantially. The easiest way is to use the PMD Rule Designer to see the structure of the AST. See the section Java AST below for details.
Iâve extended PMD with a custom languageâ¦The guides for Adding a new language with JavaCC and Adding a new CPD language have been updated.
Most notable changes are:
javacc-wrapper.xml
. This should be used now.JjtreeParserAdapter
. This is the class that needs to be implemented now.TokenManager
- we have now a common base class for JavaCC generated token managers. This base class is AbstractTokenManager
.ViolationDecorator
that a language can implement. These ViolationDecorators are called when a violation is reported and they can provide the additional information. This information can be used by renderers via RuleViolation#getAdditionalInfo
.AstVisitorBase
.In that case we canât provide a general guide unless we know the specific custom feature. If you are having difficulties finding your way around the PMD source code and javadocs and you donât see the aspect of PMD documented you are using, we are probably missing documentation. Please reach out to us by opening a discussion. We then can enhance the documentation and/or the PMD API.
Special topics Release downloadspmd-dist-<version>-bin.zip
, pmd-dist-<version>-src.zip
and pmd-dist-<version>-doc.zip
. Keep that in mind, if you have an automated download script.pmd-bin-<version>
.The CLI has been revamped completely (see Release Notes: Revamped Command Line Interface).
Most notable changes:
run.sh
and pmd.bat
, we now have pmd
only (technically on Windows, there is still a pmd.bat
, but it behaves the same).
run.sh pmd
/ pmd.bat
â¡ï¸ pmd check
run.sh cpd
/ cpd.bat
â¡ï¸ pmd cpd
run.sh designer
/ designer.bat
â¡ï¸ pmd designer
run.sh cpd-gui
/ cpdgui.bat
â¡ï¸ pmd cpd-gui
--fail-on-violation false
â¡ï¸ --no-fail-on-violation
If you donât replace this argument, then âfalseâ will be interpreted as a file to analyze. You might see then an error message such as [main] ERROR net.sourceforge.pmd.cli.commands.internal.PmdCommand - No such file false
.
PMD tries to display a progress bar. If you donât want this (e.g. on a CI build server), you can disable this with --no-progress
.
When creating a custom distribution which only integrates the languages you need, there are some changes to apply:
net.sourceforge.pmd:pmd-cli
in order to get the CLI classes.<includes>scripts/**,LICENSE</includes>
needs to be changed to <includes>scripts/**,LICENSE,conf/**</includes>
.pmd-bin
includes now also a BOM (bill of material), you need to create one for your custom distribution as well. Simply add the following plugin configuration:
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>2.7.6</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>makeAggregateBom</goal>
</goals>
</execution>
</executions>
<!-- https://github.com/CycloneDX/cyclonedx-maven-plugin/issues/326 -->
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.5</version>
</dependency>
</dependencies>
</plugin>
When you have custom rules, and you have written rule tests according to the guide Testing your rules, you might want to consider upgrading your other tests to JUnit 5. The tests in PMD 7 have been migrated to JUnit5 - including the rule tests for the built-in rules.
When executing the rule tests, you need to make sure to have JUnit5 on the classpath - which you automatically get when you depend on net.sourceforge.pmd:pmd-test
. If you also have JUnit4 tests, you need to make sure to have a junit-vintage-engine as well on the test classpath, so that all tests are executed. That means, you might need to add now a dependency to JUnit4 explicitly if needed.
In PMD 6, the reported position of the duplicated tokens in CPD where always including, e.g. the following described a duplication of length 4 in PMD 6: beginLine=1, endLine=1, beginColumn=1, endColumn=4 - these are the first 4 character in the first line. With PMD 7, the endColumn is now excluding. The same duplication will be reported in PMD 7 as: beginLine=1, endLine=1, beginColumn=1, endColumn=5.
The reported positions in a file follow now the usual meaning: line numbering starts from 1, begin line and end line are inclusive, begin column is inclusive and end column is exclusive. This is the usual behavior of the most common text editors and the PMD part already used that meaning in RuleViolations for a long time in PMD 6 already.
This only affects the XML report format as the others donât provide column information.
Node APIStarting from one node in the AST, you can navigate to children or parents with the following methods. This is the âtraditionalâ way for simple cases. For more complex cases, consider to use the new NodeStream API.
Many methods available in PMD 6 have been deprecated and removed for a slicker API with consistent naming, that also integrates tightly with the NodeStream API.
getNthParent(n)
â¡ï¸ ancestors().get(n - 1)
getFirstParentOfType(parentType)
â¡ï¸ ancestors(parentType).first()
getParentsOfType(parentType)
â¡ï¸ ancestors(parentType).toList()
findChildrenOfType(childType)
â¡ï¸ children(childType).toList()
findDescendantsOfType(targetType)
â¡ï¸ descendants(targetType).toList()
getFirstChildOfType(childType)
â¡ï¸ firstChild(childType)
getFirstDescendantOfType(descendantType)
â¡ï¸ descendants(descendantType).first()
hasDescendantOfType(type)
â¡ï¸ descendants(type).nonEmpty()
Tip: First use PMD 7.0.0-rc3, which still has these methods. These methods are marked as deprecated, so you can then start to change them. The replacement method is usually provided in the javadocs. That way you avoid being confronted with just compile errors.
Unchanged methods that work as before:
New methods:
New methods that integrate with NodeStream:
children
- returns a NodeStream containing all the children of this node. Note: in PMD 6, this method returned an Iterable
descendants
descendantsOrSelf
ancestors
ancestorsOrSelf
children
firstChild
descendants
ancestors
Methods removed completely:
getFirstParentOfAnyType(parentTypes)
:ï¸ There is no direct replacement, but something along the lines:
ancestors()
.filter(n -> Arrays.stream(classes)
.anyMatch(c -> c.isInstance(n)))
.first();
findChildNodesWithXPath
: Has been removed, because it is very inefficient. Use NodeStream instead.hasDescendantMatchingXPath
: Has been removed, because it is very inefficient. Use NodeStream instead.jjt*
like jjtGetParent
. These methods were implementation specific. Use the equivalent methods like getParent()
.See Node
for the details.
In java rule implementations, you often need to navigate the AST to find the interesting nodes. In PMD 6, this was often done by calling jjtGetChild(int)
or jjtGetParent(int)
and then checking the node type with instanceof
. There are also helper methods available, like getFirstChildOfType(Class)
or findDescendantsOfType(Class)
. These methods might return null
and you need to check this for every level.
The new NodeStream API provides easy to use methods that follow the Java Stream API (java.util.stream
).
Many complex predicates about nodes can be expressed by testing the emptiness of a node stream. E.g. the following tests if the node is a variable declarator id initialized to the value 0
:
Example:
NodeStream.of(someNode) // the stream here is empty if the node is null
.filterIs(ASTVariableDeclaratorId.class)// the stream here is empty if the node was not a variable declarator id
.followingSiblings() // the stream here contains only the siblings, not the original node
.children(ASTNumericLiteral.class)
.filter(ASTNumericLiteral::isIntLiteral)
.filterMatching(ASTNumericLiteral::getValueAsInt, 0)
.nonEmpty(); // If the stream is non empty here, then all the pipeline matched
See NodeStream
for the details. Note: This was implemented via PR #1622 [core] NodeStream API
XPath 1.0 and 2.0 have some incompatibilities. The XPath 2.0 specification describes them precisely. Those are however mostly corner cases and XPath rules usually donât feature any of them.
The incompatibilities that are most relevant to migrating your rules are not caused by the specification, but by the different engines we use to run XPath 1.0 and 2.0 queries. Hereâs a list of known incompatibilities:
fn:
and string:
should not be mentioned explicitly. In XPath 2.0 mode, the engine will complain about an undeclared namespace, but the functions are in the default namespace. Removing the namespace prefixes fixes it.
fn:substring("Foo", 1)
â substring("Foo", 1)
typeIs
must be prefixed with the namespace of the declaring module (pmd-java
).
typeIs("Foo")
â pmd-java:typeIs("Foo")
"true"
and "false"
. In 2.0 mode though, boolean values are truly represented as boolean values, which in XPath may only be obtained through the functions true()
and false()
. If your XPath 1.0 rule tests an attribute like @Private="true"
, then it just needs to be changed to @Private=true()
when migrating. A type error will warn you that you must update the comparison. More is explained on issue #1244.
"true"
, 'true'
â true()
"false"
, 'false'
â false()
@BeginLine > "1"
worked âthatâs not the case in 2.0 mode.
@ArgumentCount > '1'
â @ArgumentCount > 1
/Foo
matches the children of the root named Foo
. In XPath 2.0, that expression matches the root, if it is named Foo
. Consider the following tree:
Foo
ââ Foo
ââ Foo
Then /Foo
will match the root in XPath 2.0, and the other nodes (but not the root) in XPath 1.0. See e.g. an issue caused by this in Apex, with nested classes.
The Java grammar has been refactored substantially in order to make it easier to maintain and more correct regarding the Java Language Specification.
Here you can see the most important changes as a comparison between the PMD 6 AST (âOld ASTâ) and PMD 7 AST (âNew ASTâ) and with some background info about the changes.
When in doubt, it is recommended to use the PMD Designer which can also display the AST.
AnnotationsSingleMemberAnnotation
, NormalAnnotation
and MarkerAnnotation
are removed in favour of ASTAnnotation
. The Name node is removed, replaced by a ASTClassOrInterfaceType
.@A
and @A()
are semantically equivalent, yet they were parsed as MarkerAnnotation resp. NormalAnnotation. Similarly, @A("")
and @A(value="")
were parsed as SingleMemberAnnotation resp. NormalAnnotation. This also makes parsing much simpler. The nested ClassOrInterface type is used to share the disambiguation logic.@A
ââ Annotation "A"
ââ MarkerAnnotation "A"
ââ Name "A"
ââ Annotation "A"
ââ ClassOrInterfaceType "A"
@A()
ââ Annotation "A"
ââ NormalAnnotation "A"
ââ Name "A"
ââ Annotation "A"
ââ ClassOrInterfaceType "A"
ââ AnnotationMemberList
@A(value="v")
ââ Annotation "A"
ââ NormalAnnotation "A"
ââ Name "A"
ââ MemberValuePairs
ââ MemberValuePair "value"
ââ MemberValue
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal '"v"'
ââ Annotation "A"
ââ ClassOrInterfaceType "A"
ââ AnnotationMemberList
ââ MemberValuePair "value" [ @Shorthand = false() ]
ââ StringLiteral '"v"'
@A("v")
ââ Annotation "A"
ââ SingleMemberAnnotation "A"
ââ Name "A"
ââ MemberValue
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal '"v"'
ââ Annotation "A"
ââ ClassOrInterfaceType "A"
ââ AnnotationMemberList
ââ MemberValuePair "value" [ @Shorthand = true() ]
ââ StringLiteral '"v"'
@A(value="v", on=true)
ââ Annotation "A"
ââ NormalAnnotation "A"
ââ Name "A"
ââ MemberValuePairs
ââ MemberValuePair "value"
â ââ MemberValue
â ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal '"v"'
ââ MemberValuePair "on"
ââ MemberValue
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal
ââ BooleanLiteral [ @True = true() ]
ââ Annotation "A"
ââ ClassOrInterfaceType "A"
ââ AnnotationMemberList
ââ MemberValuePair "value" [ @Shorthand = false() ]
â ââ StringLiteral '"v"'
ââ MemberValuePair "on"
ââ BooleanLiteral [ @True = true() ]
Annotation nesting
ASTAnnotation
s are now nested within the node, to which they are applied to. E.g. if a method is annotated, the Annotation node is now a child of a ASTModifierList
, inside the ASTMethodDeclaration
.@A
public void set(int x) { }
ââ ClassOrInterfaceBodyDeclaration
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ MethodDeclaration
ââ ResultType[ @Void = true ]
ââ ...
ââ MethodDeclaration
ââ ModifierList
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ VoidType
ââ ...
Top-level type declaration
@A class C {}
ââ TypeDeclaration
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ ClassOrInterfaceDeclaration "C"
ââ ClassOrInterfaceBody
ââ ClassOrInterfaceDeclaration
ââ ModifierList
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ ClassOrInterfaceBody
Cast expression
var x = (@A T.@B S) expr;
ââ CastExpression
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ Type
â ââ ReferenceType
â ââ ClassOrInterfaceType "T.S"
â ââ Annotation "B"
â ââ MarkerAnnotation "B"
â ââ Name "B"
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "expr"
ââ CastExpression
ââ ClassOrInterfaceType "S"
â ââ ClassOrInterfaceType "T"
â â ââ Annotation "A"
â â ââ ClassOrInterfaceType "A"
â ââ Annotation "B"
â ââ ClassOrInterfaceType "B"
ââ VariableAccess "expr"
Cast expression with intersection
var x = (@A T & S) expr;
ââ CastExpression
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ Type
â ââ ReferenceType
â ââ ClassOrInterfaceType "T"
ââ ReferenceType
â ââ ClassOrInterfaceType "S"
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "expr"
ââ CastExpression
ââ IntersectionType
â ââ ClassOrInterfaceType "T"
â â ââ Annotation "A"
â â ââ ClassOrInterfaceType "A"
â ââ ClassOrInterfaceType "S"
ââ VariableAccess "expr"
Notice @A
binds to T
, not T & S
Constructor call
new @A T()
ââ AllocationExpression
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ ClassOrInterfaceType "T"
ââ Arguments
ââ ConstructorCall
ââ ClassOrInterfaceType "T"
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ ArgumentList
Array allocation
new @A int[0]
ââ AllocationExpression
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ PrimitiveType "int"
ââ ArrayDimsAndInits
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "0"
ââ ArrayAllocation
ââ ArrayType
ââ PrimitiveType "int"
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ ArrayDimensions
ââ ArrayDimExpr
ââ NumericLiteral "0"
Array type
@A int @B[] x;
ââ LocalVariableDeclaration
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ Type[ @ArrayType = true() ]
â ââ ReferenceType
â ââ PrimitiveType "int"
â ââ Annotation "B"
â ââ MarkerAnnotation "B"
â ââ Name "B"
ââ VariableDeclarator
ââ VariableDeclaratorId "x"
ââ LocalVariableDeclaration
ââ ModifierList
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ ArrayType
â ââ PrimitiveType "int"
â ââ ArrayDimensions
â ââ ArrayTypeDim
â ââ Annotation "B"
â ââ ClassOrInterfaceType "B"
ââ VariableDeclarator
ââ VariableDeclaratorId "x"
Type parameters
<@A T, @B S extends @C Object>
ââ TypeParameters
ââ TypeParameter "T"
â ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ TypeParameter "S"
ââ Annotation "B"
â ââ MarkerAnnotation "B"
â ââ Name "B"
ââ TypeBound
ââ Annotation "C"
â ââ MarkerAnnotation "C"
â ââ Name "C"
ââ ClassOrInterfaceType "Object"
ââ TypeParameters
ââ TypeParameter "T"
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ TypeParameter "S" [ @TypeBound = true() ]
ââ Annotation "B"
â ââ ClassOrInterfaceType "B"
ââ ClassOrInterfaceType "Object"
ââ Annotation "C"
ââ ClassOrInterfaceType "C"
enum E {
@A E1, @B E2;
}
ââ EnumBody
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ EnumConstant "E1"
ââ Annotation "B"
â ââ MarkerAnnotation "B"
â ââ Name "B"
ââ EnumConstant "E2"
ââ EnumBody
ââ EnumConstant "E1"
â ââ ModifierList
â â ââ Annotation "A"
â â ââ ClassOrInterfaceType "A"
â ââ VariableDeclaratorId "E1"
ââ EnumConstant "E2"
ââ ModifierList
â ââ Annotation "B"
â ââ ClassOrInterfaceType "B"
ââ VariableDeclaratorId "E2"
ASTType
and ASTReferenceType
have been turned into interfaces, implemented by ASTPrimitiveType
, ASTClassOrInterfaceType
, and the new node ASTArrayType
. This reduces the depth of the relevant subtrees, and allows to explore them more easily and consistently.Type
and ReferenceType
name tests wonât match anything anymore.Type/ReferenceType/ClassOrInterfaceType
â¡ï¸ ClassOrInterfaceType
Type/PrimitiveType
â¡ï¸ PrimitiveType
.Type/ReferenceType[@ArrayDepth > 1]/ClassOrInterfaceType
â¡ï¸ ArrayType/ClassOrInterfaceType
.Type/ReferenceType/PrimitiveType
â¡ï¸ ArrayType/PrimitiveType
.VariableDeclaratorId[pmd-java:typeIs("java.lang.String[]")]
because it considers the additional dimensions on declarations like String foo[];
. The Java equivalent is TypeHelper.isA(id, String[].class);
// in the context of a variable declaration
List<String> strs;
ââ Type (1)
ââ ReferenceType
ââ ClassOrInterfaceType "List"
ââ TypeArguments
ââ TypeArgument
ââ ReferenceType (2)
ââ ClassOrInterfaceType "String"
ââ ClassOrInterfaceType "List"
ââ TypeArguments
ââ ClassOrInterfaceType "String"
ASTArrayType
, ASTArrayTypeDim
, ASTArrayTypeDims
, ASTArrayAllocation
.String[][] myArray;
ââ Type[ @ArrayType = true() ]
ââ ReferenceType
ââ ClassOrInterfaceType[ @Array = true() ][ @ArrayDepth = 2 ] "String"
ââ ArrayType[ @ArrayDepth = 2 ]
ââ ClassOrInterfaceType "String"
ââ ArrayDimensions[ @Size = 2 ]
ââ ArrayTypeDim
ââ ArrayTypeDim
String @Annotation1[] @Annotation2[] myArray;
ââ Type[ @ArrayType = true() ]
ââ ReferenceType
ââ ClassOrInterfaceType[ @Array = true() ][ @ArrayDepth = 2 ] "String"
ââ Annotation "Annotation1"
â ââ MarkerAnnotation "Annotation1"
â ââ Name "Annotation1"
ââ Annotation "Annotation2"
ââ MarkerAnnotation "Annotation2"
ââ Name "Annotation2"
ââ ArrayType[ @ArrayDepth = 2 ]
ââ ClassOrInterfaceType "String"
ââ ArrayDimensions[ @Size = 2 ]
ââ ArrayTypeDim
â ââ Annotation "Annotation1"
â ââ ClassOrInterfaceType "Annotation1"
ââ ArrayTypeDim
ââ Annotation "Annotation2"
ââ ClassOrInterfaceType "Annotation2"
new int[2][];
new @Bar int[3][2];
new Foo[] { f, g };
ââ AllocationExpression
ââ PrimitiveType "int"
ââ ArrayDimsAndInits[ @ArrayDepth = 2 ]
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "2"
ââ AllocationExpression
ââ Annotation "Bar"
â ââ MarkerAnnotation "Bar"
â ââ Name "Bar"
ââ PrimitiveType "int"
ââ ArrayDimsAndInits[ @ArrayDepth = 2 ]
ââ Expression
â ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal "3"
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "2"
ââ AllocationExpression
ââ ClassOrInterfaceType "Foo"
ââ ArrayDimsAndInits[ @ArrayDepth = 1 ]
ââ ArrayInitializer
ââ VariableInitializer
â ââ Expression
â ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Name "f"
ââ VariableInitializer
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "g"
ââ ArrayAllocation[ @ArrayDepth = 2 ]
ââ ArrayType[ @ArrayDepth = 2 ]
ââ PrimitiveType "int"
ââ ArrayDimensions[ @Size = 2]
ââ ArrayDimExpr
â ââ NumericLiteral "2"
ââ ArrayTypeDim
ââ ArrayAllocation[ @ArrayDepth = 2 ]
ââ ArrayType[ @Array Depth = 2 ]
ââ PrimitiveType "int"
â ââ Annotation "Bar"
â ââ ClassOrInterfaceType "Bar"
ââ ArrayDimensions[ @Size = 2 ]
ââ ArrayDimExpr
â ââ NumericLiteral "3"
ââ ArrayDimExpr
ââ NumericLiteral "2"
ââ ArrayAllocation[ @ArrayDepth = 1 ]
ââ ArrayType[ @ArrayDepth = 1 ]
â ââ ClassOrInterfaceType "Foo"
â ââ ArrayDimensions[ @Size = 1 ]
â ââ ArrayTypeDim
ââ ArrayInitializer[ @Length = 2 ]
ââ VariableAccess "f"
ââ VariableAccess "g"
ClassOrInterfaceType nesting
ASTClassOrInterfaceType
appears to be left recursive now, and encloses its qualifying type.Map.Entry<K,V>
ââ ClassOrInterfaceType "Map.Entry"
ââ TypeArguments
ââ TypeArgument
â ââ ReferenceType
â ââ ClassOrInterfaceType "K"
ââ TypeArgument
ââ ReferenceType
ââ ClassOrInterfaceType "V"
ââ ClassOrInterfaceType "Entry"
ââ ClassOrInterfaceType "Map"
ââ TypeArguments[ @Size = 2 ]
ââ ClassOrInterfaceType "K"
ââ ClassOrInterfaceType "V"
First<K>.Second.Third<V>
ââ ClassOrInterfaceType "First.Second.Third"
ââ TypeArguments
â ââ TypeArgument
â ââ ReferenceType
â ââ ClassOrInterfaceType "K"
ââ TypeArguments
ââ TypeArgument
ââ ReferenceType
ââ ClassOrInterfaceType "V"
ââ ClassOrInterfaceType "Third"
ââ ClassOrInterfaceType "Second"
â ââ ClassOrInterfaceType "First"
â ââ TypeArguments[ @Size = 1]
â ââ ClassOrInterfaceType "K"
ââ TypeArguments[ @Size = 1 ]
ââ ClassOrInterfaceType "V"
TypeArgument and WildcardType
ASTTypeArgument
is removed. Instead, the ASTTypeArguments
node contains directly a sequence of ASTType
nodes. To support this, the new node type ASTWildcardType
captures the syntax previously parsed as a TypeArgument.ASTWildcardBounds
node is removed. Instead, the bound is a direct child of the WildcardType.Entry<String, ? extends Node>
ââ ClassOrInterfaceType "Entry"
ââ TypeArguments
ââ TypeArgument
â ââ ReferenceType
â ââ ClassOrInterfaceType "String"
ââ TypeArgument[ @Wildcard = true() ]
ââ WildcardBounds[ @UpperBound = true() ]
ââ ReferenceType
ââ ClassOrInterfaceType "Node"
ââ ClassOrInterfaceType "Entry"
ââ TypeArguments[ @Size = 2 ]
ââ ClassOrInterfaceType "String"
ââ WildcardType[ @UpperBound = true() ]
ââ ClassOrInterfaceType "Node"
List<?>
ââ ClassOrInterfaceType "List"
ââ TypeArguments
ââ TypeArgument[ @Wildcard = true() ]
ââ ClassOrInterfaceType "List"
ââ TypeArguments[ @Size = 1 ]
ââ WildcardType[ @UpperBound = true() ]
Declarations Import and Package declarations
ASTAmbiguousName
in that it describes nothing about what it represents. The name in an import may represent a method name, a type name, a field name⦠Itâs too ambiguous to treat in the parser and could just be the image of the import, or package, or module.import java.util.ArrayList;
import static java.util.Comparator.reverseOrder;
import java.util.*;
ââ ImportDeclaration
â ââ Name "java.util.ArrayList"
ââ ImportDeclaration[ @Static=true() ]
â ââ Name "java.util.Comparator.reverseOrder"
ââ ImportDeclaration[ @ImportOnDemand = true() ]
ââ Name "java.util"
ââ ImportDeclaration "java.util.ArrayList"
ââ ImportDeclaration[ @Static = true() ] "java.util.Comparator.reverseOrder"
ââ ImportDeclaration[ @ImportOnDemand = true() ] "java.util"
package com.example.tool;
ââ PackageDeclaration
ââ Name "com.example.tool"
ââ PackageDeclaration "com.example.tool"
ââ ModifierList
Modifier lists
AccessNode
is now based on a node: ASTModifierList
. That node represents modifiers occurring before a declaration. It provides a flexible API to query modifiers, both explicit and implicit. All declaration nodes now have such a modifier list, even if itâs implicit (no explicit modifiers).ASTFieldDeclaration::isSynchronized
makes no sense. Now, these irrelevant methods donât clutter the API. The API of ModifierList is both more general and flexible@A
public void set(final int x, int y) { }
ââ ClassOrInterfaceBodyDeclaration
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ MethodDeclaration[ @Public = true() ] "set"
ââ ResultType[ @Void = true() ]
ââ MethodDeclarator
ââ FormalParameters[ @Size = 2 ]
ââ FormalParameter[ @Final = true() ]
â ââ Type
â â ââ PrimitiveType "int"
â ââ VariableDeclaratorId "x"
ââ FormalParameter[ @Final = false() ]
ââ Type
â ââ PrimitiveType "int"
ââ VariableDeclaratorId "y"
ââ MethodDeclaration[ pmd-java:modifiers() = 'public' ] "set"
ââ ModifierList
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ VoidType
ââ FormalParameters
ââ FormalParameter[ pmd-java:modifiers() = 'final' ]
â ââ ModifierList
â ââ VariableDeclaratorId "x"
ââ FormalParameter[ pmd-java:modifiers() = () ]
ââ ModifierList
ââ VariableDeclaratorId "y"
Top-level type declaration
public @A class C {}
ââ TypeDeclaration
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ ClassOrInterfaceDeclaration[ @Public = true() ] "C"
ââ ClassOrInterfaceBody
ââ ClassOrInterfaceDeclaration[ pmd-java:modifiers() = 'public' ] "C"
ââ ModifierList
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ ClassOrInterfaceBody
Flattened body declarations
ASTClassOrInterfaceBodyDeclaration
, ASTTypeDeclaration
, and ASTAnnotationTypeMemberDeclaration
. These were unnecessary since annotations are nested (see above Annotation nesting).public class Flat {
private int f;
}
ââ CompilationUnit
ââ TypeDeclaration
ââ ClassOrInterfaceDeclaration "Flat"
ââ ClassOrInterfaceBody
ââ ClassOrInterfaceBodyDeclaration
ââ FieldDeclaration
ââ Type
â ââ PrimitiveType "int"
ââ VariableDeclarator
ââ VariableDeclaratorId "f"
ââ CompilationUnit
ââ ClassOrInterfaceDeclaration "Flat"
ââ ModifierList
ââ ClassOrInterfaceBody
ââ FieldDeclaration
ââ ModifierList
ââ PrimitiveType "int"
ââ VariableDeclarator
ââ VariableDeclaratorId "f"
public @interface FlatAnnotation {
String value() default "";
}
ââ CompilationUnit
ââ TypeDeclaration
ââ AnnotationTypeDeclaration "FlatAnnotation"
ââ AnnotationTypeBody
ââ AnnotationTypeMemberDeclaration
ââ AnnotationMethodDeclaration "value"
ââ Type
â ââ ReferenceType
â ââ ClassOrInterfaceType "String"
ââ DefaultValue
ââ MemberValue
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "\"\""
ââ CompilationUnit
ââ AnnotationTypeDeclaration "FlatAnnotation"
ââ ModifierList
ââ AnnotationTypeBody
ââ MethodDeclaration "value"
ââ ModifierList
ââ ClassOrInterfaceType "String"
ââ FormalParameters
ââ DefaultValue
ââ StringLiteral "\"\""
Module declarations
ASTClassOrInterfaceType
where appropriate. Also uses specific node types for different directives (requires, exports, uses, provides).open module com.example.foo {
requires com.example.foo.http;
requires java.logging;
requires transitive com.example.foo.network;
exports com.example.foo.bar;
exports com.example.foo.internal to com.example.foo.probe;
uses com.example.foo.spi.Intf;
provides com.example.foo.spi.Intf with com.example.foo.Impl;
}
ââ CompilationUnit
ââ ModuleDeclaration[ @Image = 'com.example.foo' ][ @Open = true() ]
ââ ModuleDirective[ @Type = 'REQUIRES' ]
â ââ ModuleName[ @Image = 'com.example.foo.http' ]
ââ ModuleDirective[ @Type = 'REQUIRES' ]
â ââ ModuleName[ @Image = 'java.logging' ]
ââ ModuleDirective[ @Type = 'REQUIRES' ][ @RequiresModifier = 'TRANSITIVE' ]
â ââ ModuleName[ @Image = 'com.example.foo.network' ]
ââ ModuleDirective[ @Type = 'EXPORTS' ]
â ââ Name[ @Image = 'com.example.foo.bar' ]
ââ ModuleDirective[ @Type = 'EXPORTS' ]
â ââ Name[ @Image = 'com.example.foo.internal' ]
â ââ ModuleName[ @Image = 'com.example.foo.probe' ]
ââ ModuleDirective[ @Type = 'USES' ]
â ââ Name[ @Image = 'com.example.foo.spi.Intf' ]
ââ ModuleDirective[ @Type = 'PROVIDES' ]
ââ Name[ @Image = 'com.example.foo.spi.Intf' ]
ââ Name[ @Image = 'com.example.foo.Impl' ]
ââ CompilationUnit
ââ ModuleDeclaration[ @Name = 'com.example.foo' ][ @Open = true() ]
ââ ModuleName[ @Name = 'com.example.foo' ]
ââ ModuleRequiresDirective
â ââ ModuleName[ @Name = 'com.example.foo.http' ]
ââ ModuleRequiresDirective
â ââ ModuleName[ @Name = 'java.logging' ]
ââ ModuleRequiresDirective[ @Transitive = true ]
â ââ ModuleName[ @Name = 'com.example.foo.network' ]
ââ ModuleExportsDirective[ @PackageName = 'com.example.foo.bar' ]
ââ ModuleExportsDirective[ @PackageName = 'com.example.foo.internal' ]
â ââ ModuleName [ @Name = 'com.example.foo.probe' ]
ââ ModuleUsesDirective
â ââ ClassOrInterfaceType[ pmd-java:typeIs("com.example.foo.spi.Intf") ]
ââ ModuleProvidesDirective
ââ ClassOrInterfaceType[ pmd-java:typeIs("com.example.foo.spi.Intf") ]
ââ ClassOrInterfaceType[ pmd-java:typeIs("com.example.foo.Impl") ]
Anonymous class declarations
ASTAnonymousClassDeclaration
is introduced for anonymous classes.Object anonymous = new Object() { };
ââ LocalVariableDeclaration
ââ Type
â ââ ReferenceType
â ââ ClassOrInterfaceType[ @Image = 'Object' ]
ââ VariableDeclarator
ââ VariableDeclaratorId "anonymous"
ââ VariableInitializer
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ AllocationExpression
ââ ClassOrInterfaceType[ @AnonymousClass = true() ][ @Image = 'Object' ]
ââ Arguments
ââ ClassOrInterfaceBody
ââ LocalVariableDeclaration
ââ ModifierList
ââ ClassOrInterfaceType[ @SimpleName = 'Object' ]
ââ VariableDeclarator
ââ VariableDeclaratorId[ @Name = 'anonymous' ]
ââ ConstructorCall
ââ ClassOrInterfaceType[ @SimpleName = 'Object' ]
ââ ArgumentList
ââ AnonymousClassDeclaration
ââ ModifierList
ââ ClassOrInterfaceBody
Method and Constructor declarations Method grammar simplification
public class Sample {
public Sample(int arg) throws Exception {
super();
greet(arg);
}
public void greet(int arg) throws Exception {
System.out.println("Hello");
}
}
ââ ClassOrInterfaceBody
ââ ClassOrInterfaceBodyDeclaration
â ââ ConstructorDeclaration[ @Image = 'Sample' ]
â ââ FormalParameters
â â ââ FormalParameter
â â ââ ...
â ââ NameList
â â ââ Name[ @Image = 'Exception' ]
â ââ ExplicitConstructorInvocation
â â ââ Arguments
â ââ BlockStatement
â ââ Statement
â ââ ...
ââ ClassOrInterfaceBodyDeclaration
ââ MethodDeclaration[ @Name = 'greet' ]
ââ ResultType
ââ MethodDeclarator[ @Image = 'greet' ]
â ââ FormalParameters
â ââ FormalParameter
â ââ ...
ââ NameList
â ââ Name[ @Image = 'Exception' ]
ââ Block
ââ BlockStatement
ââ Statement
ââ ...
ââ ClassOrInterfaceBody
ââ ConstructorDeclaration[ @Name = 'Sample' ]
â ââ ModifierList
â ââ FormalParameters
â â ââ FormalParameter
â â ââ ...
â ââ ThrowsList
â â ââ ClassOrInterfaceType[ @SimpleName = 'Exception' ]
â ââ Block
â ââ ExplicitConstructorInvocation
â â ââ ArgumentList
â ââ ExpressionStatement
â ââ ...
ââ MethodDeclaration[ @Name = 'greet' ]
ââ ModifierList
ââ VoidType
ââ FormalParameters
â ââ FormalParameter
â ââ ...
ââ ThrowsList
â ââ ClassOrInterfaceType[ @SimpleName = 'Exception' ]
ââ Block
ââ ExpressionStatement
ââ ...
public @interface MyAnnotation {
int value() default 1;
}
ââ AnnotationTypeDeclaration[ @SimpleName = 'MyAnnotation' ]
ââ AnnotationTypeBody
ââ AnnotationTypeMemberDeclaration
ââ AnnotationMethodDeclaration[ @Image = 'value' ]
ââ Type ...
ââ DefaultValue ...
ââ AnnotationTypeDeclaration[ @SimpleName = 'MyAnnotation' ]
ââ ModifierList
ââ AnnotationTypeBody
ââ MethodDeclaration[ @Name = 'value' ]
ââ ModifierList
ââ PrimitiveType
ââ FormalParameters
ââ DefaultValue ...
Formal parameters
ASTFormalParameter
only for method and constructor declaration. Lambdas use ASTLambdaParameter
, catch clauses use ASTCatchParameter
.ASTUnionType
now)try {
} catch (@A IOException | IllegalArgumentException e) {
}
ââ TryStatement
ââ Block
ââ CatchStatement
ââ FormalParameter
â ââ Annotation[ @AnnotationName = 'A' ]
â â ââ MarkerAnnotation[ @AnnotationName = 'A' ]
â â ââ Name[ @Image = 'A' ]
â ââ Type
â â ââ ReferenceType
â â ââ ClassOrInterfaceType[ @Image = 'IOException' ]
â ââ Type
â â ââ ReferenceType
â â ââ ClassOrInterfaceType[ @Image = 'IllegalArgumentException' ]
â ââ VariableDeclaratorId[ @Name = 'e' ]
ââ Block
ââ TryStatement
ââ Block
ââ CatchClause
ââ CatchParameter
â ââ ModifierList
â â ââ Annotation[ @SimpleName = 'A' ]
â â ââ ClassOrInterfaceType[ @SimpleName = 'A' ]
â ââ UnionType
â â ââ ClassOrInterfaceType[ @SimpleName = 'IOException' ]
â â ââ ClassOrInterfaceType[ @SimpleName = 'IllegalArgumentException' ]
â ââ VariableDeclaratorId[ @Name = 'e' ]
ââ Block
(a, b) -> {};
c -> {};
(@A var d) -> {};
(@A int e) -> {};
ââ StatementExpression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ LambdaExpression
ââ VariableDeclaratorId[ @Name = 'a' ]
ââ VariableDeclaratorId[ @Name = 'b' ]
ââ Block
ââ StatementExpression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ LambdaExpression
ââ VariableDeclaratorId[ @Name = 'c' ]
ââ Block
ââ StatementExpression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ LambdaExpression
ââ FormalParameters
â ââ FormalParameter
â ââ Annotation[ @AnnotationName = 'A' ]
â â ââ MarkerAnnotation[ @AnnotationName = 'A' ]
â â ââ Name[ @Image = 'A' ]
â ââ VariableDeclaratorId[ @Name = 'd' ]
ââ Block
ââ StatementExpression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ LambdaExpression
ââ FormalParameters
â ââ FormalParameter
â ââ Annotation[ @AnnotationName = 'A' ]
â â ââ MarkerAnnotation[ @AnnotationName = 'A' ]
â â ââ Name[ @Image = 'A' ]
â ââ Type
â â ââ PrimitiveType[ @Image = 'int' ]
â ââ VariableDeclaratorId[ @Name = 'e' ]
ââ Block
ââ ExpressionStatement
ââ LambdaExpression
ââ LambdaParameterList
â ââ LambdaParameter
â â ââ ModifierList
â â ââ VariableDeclaratorId[ @Name = 'a' ]
â ââ LambdaParameter
â ââ ModifierList
â ââ VariableDeclaratorId[ @Name = 'b' ]
ââ Block
ââ ExpressionStatement
ââ LambdaExpression
ââ LambdaParameterList
â ââ LambdaParameter
â ââ ModifierList
â ââ VariableDeclaratorId[ @Name = 'c' ]
ââ Block
ââ ExpressionStatement
ââ LambdaExpression
ââ LambdaParameterList
â ââ LambdaParameter
â ââ ModifierList
â â ââ Annotation[ @SimpleName = 'A' ]
â â ââ ClassOrInterfaceType[ @SimpleName = 'A' ]
â ââ VariableDeclaratorId[ @Name = 'd' ]
ââ Block
ââ ExpressionStatement
ââ LambdaExpression
ââ LambdaParameterList
â ââ LambdaParameter
â ââ ModifierList
â â ââ Annotation[ @SimpleName = 'A' ]
â â ââ ClassOrInterfaceType[ @SimpleName = 'A' ]
â ââ PrimitiveType[ @Kind = 'int' ]
â ââ VariableDeclaratorId[ @Name = 'e' ]
ââ Block
New node for explicit receiver parameter
ASTReceiverParameter
is introduced to differentiate it from formal parameters.ASTFormalParameter
and ASTVariableDeclaratorId
.void myMethod(@A Foo this, Foo other) {}
ââ FormalParameters (1)
ââ FormalParameter[ @ExplicitReceiverParameter = true() ]
â ââ Annotation "A"
â â ââ MarkerAnnotation "A"
â â ââ Name "A"
â ââ Type
â â ââ ReferenceType
â â ââ ClassOrInterfaceType "Foo"
â ââ VariableDeclaratorId[ @ExplicitReceiverParameter = true() ] "this"
ââ FormalParameter
ââ Type
â ââ ReferenceType
â ââ ClassOrInterfaceType "Foo"
ââ VariableDeclaratorId "other"
ââ FormalParameters (1)
ââ ReceiverParameter
â ââ ClassOrInterfaceType "Foo"
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ FormalParameter
ââ ModifierList
ââ ClassOrInterfaceType "Foo"
ââ VariableDeclaratorId "other"
Varargs
ASTArrayType
.void myMethod(int... is) {}
ââ FormalParameter[ @Varargs = true() ]
ââ Type
â ââ PrimitiveType "int"
ââ VariableDeclaratorId "is"
ââ FormalParameter[ @Varargs = true() ]
ââ ModifierList
ââ ArrayType
â ââ PrimitiveType "int"
â ââ ArrayDimensions
â ââ ArrayTypeDim[ @Varargs = true() ]
ââ VariableDeclaratorId "is"
void myMethod(int @A ... is) {}
ââ FormalParameter[ @Varargs = true() ]
ââ Type
â ââ PrimitiveType "int"
ââ Annotation "A"
â ââ MarkerAnnotation "A"
â ââ Name "A"
ââ VariableDeclaratorId "is"
ââ FormalParameter[ @Varargs = true() ]
ââ ModifierList
ââ ArrayType
â ââ PrimitiveType "int"
â ââ ArrayDimensions
â ââ ArrayTypeDim[ @Varargs = true() ]
â ââ Annotation "A"
â ââ ClassOrInterfaceType "A"
ââ VariableDeclaratorId "is"
void myMethod(int[]... is) {}
ââ FormalParameter[ @Varargs = true() ]
ââ Type[ @ArrayType = true() ]
â ââ ReferenceType
â ââ PrimitiveType "int"
ââ VariableDeclaratorId "is"
ââ FormalParameter[ @Varargs = true() ]
ââ ModifierList
ââ ArrayType (2)
â ââ PrimitiveType "int"
â ââ ArrayDimensions (2)
â ââ ArrayTypeDim
â ââ ArrayTypeDim[ @Varargs = true() ]
ââ VariableDeclaratorId "is"
Add void type node to replace ResultType
ASTVoidType
node to replace ASTResultType
.void foo();
ââ MethodDeclaration "foo"
ââ ResultType[ @Void = true() ]
ââ MethodDeclarator
ââ FormalParameters
ââ MethodDeclaration "foo"
ââ ModifierList
ââ VoidType
ââ FormalParameters
int foo();
ââ MethodDeclaration "foo"
ââ ResultType[ @Void = false() ]
â ââ Type
â ââ PrimitiveType "int"
ââ MethodDeclarator
ââ FormalParameters
ââ MethodDeclaration "foo"
ââ ModifierList
ââ PrimitiveType "int"
ââ FormalParameters
Statements Statements are flattened
ASTBlock
are by definition ASTStatement
s, which is now an interface implemented by all statements.int i;
i = 1;
ââ Block
ââ BlockStatement
â ââ LocalVariableDeclaration
â ââ Type
â â ââ PrimitiveType "int"
â ââ VariableDeclarator
â ââ VariableDeclaratorId "i"
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Name "i"
ââ AssignmentOperator "="
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "1"
ââ Block
ââ LocalVariableDeclaration
â ââ ModifierList
â ââ PrimitiveType "int"
â ââ VariableDeclarator
â ââ VariableDeclaratorId "i"
ââ ExpressionStatement
ââ AssignmentExpression "="
ââ VariableAccess "i"
ââ NumericLiteral "1"
New node for For-each statements
ASTForeachStatement
instead of ForStatement.for (String s : List.of("a", "b")) { }
ââ BlockStatement
ââ Statement
ââ ForStatement[ @Foreach = true() ]
ââ LocalVariableDeclaration
â ââ Type
â â ââ ReferenceType
â â ââ ClassOrInterfaceType "String"
â ââ VariableDeclarator
â ââ VariableDeclaratorId "s"
ââ Expression
â ââ PrimaryExpression
â ââ PrimaryPrefix
â â ââ Name "List.of"
â ââ PrimarySuffix
â ââ Arguments (2)
â ââ ArgumentList (2)
â ââ Expression
â â ââ PrimaryExpression
â â ââ PrimaryPrefix
â â ââ Literal[ @StringLiteral = true() ][ @Image = '"a"' ]
â ââ Expression
â ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal[ @StringLiteral = true() ][ @Image = '"b"' ]
ââ Statement
ââ Block
ââ Block
ââ ForeachStatement
ââ LocalVariableDeclaration
â ââ ModifierList
â ââ ClassOrInterfaceType "String"
â ââ VariableDeclarator "s"
â ââ VariableDeclaratorId "s"
ââ MethodCall "of"
â ââ TypeExpression
â â ââ ClassOrInterfaceType "List"
â ââ ArgumentList (2)
â ââ StringLiteral[ @Image = '"a"' ]
â ââ StringLiteral[ @Image = '"b"' ]
ââ Block
New nodes for ExpressionStatement, LocalClassStatement
ASTExpressionStatement
. Added new node ASTLocalClassStatement
.ASTStatement
, that can be used as a child in a block. It itself has only one child, which is some kind of ASTExpression
, which can be really any kind of expression (like assignment). In order to allow local class declarations as part of a block, we introduced ASTLocalClassStatement
which is a statement that carries a type declaration. Now blocks are just a list of statements. This allows us to have two distinct hierarchies for expressions and statements.i++;
class LocalClass {}
ââ Block
ââ BlockStatement
â ââ Statement
â ââ StatementExpression
â ââ PostfixExpression "++"
â ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Name "i"
ââ BlockStatement
ââ ClassOrInterfaceDeclaration[ @Local = true() ] "LocalClass"
ââ ClassOrInterfaceBody
ââ Block
ââ ExpressionStatement
â ââ UnaryExpression "++"
â ââ VariableAccess "i"
ââ LocalClassStatement
ââ ClassOrInterfaceDeclaration "LocalClass"
ââ ModifierList
ââ ClassOrInterfaceBody
Improve try-with-resources grammar
ASTLocalVariableDeclaration
unless it is a concise try-with-resources.try (InputStream in = new FileInputStream(); OutputStream out = new FileOutputStream();) { }
ââ TryStatement
ââ ResourceSpecification
ââ Resources
ââ Resource
â ââ Type
â â ââ ReferenceType
â â ââ ClassOrInterfaceType "InputStream"
â ââ VariableDeclaratorId "in"
â ââ Expression
â ââ ...
ââ Resource
ââ Type
â ââ ReferenceType
â ââ ClassOrInterfaceType "OutputStream"
ââ VariableDeclaratorId "out"
ââ Expression
ââ ...
ââ TryStatement
ââ ResourceList[ @TrailingSemiColon = true() ] (2)
ââ Resource[ @ConciseResource = false() ] "in"
â ââ LocalVariableDeclaration
â ââ ModifierList
â ââ ClassOrInterfaceType "InputStream"
â ââ VariableDeclarator
â ââ VariableDeclaratorId "in"
â ââ ConstructorCall
â ââ ClassOrInterfaceType "FileInputStream"
â ââ ArgumentList (0)
ââ Resource[ @ConciseResource = false() ] "out"
ââ LocalVariableDeclaration
ââ ModifierList
ââ ClassOrInterfaceType "OutputStream"
ââ VariableDeclarator
ââ VariableDeclaratorId "out"
ââ ConstructorCall
ââ ClassOrInterfaceType "FileOutputStream"
ââ ArgumentList (0)
InputStream in = new FileInputStream();
try (in) {}
ââ TryStatement
ââ ResourceSpecification
ââ Resources
ââ Resource "in"
ââ Name "in"
ââ TryStatement
ââ ResourceList[ @TrailingSemiColon = false() ] (1)
ââ Resource[ @ConciseResource = true() ] "in"
ââ VariableAccess "in"
Expressions
ASTExpression
and ASTPrimaryExpression
have been turned into interfaces. These added no information to the AST and increased its depth unnecessarily. All expressions implement the first interface. Both of those nodes can no more be found in ASTs.
Migrating:
Expression/X
or Expression/PrimaryExpression/X
, just becomes X
Expression
or PrimaryExpression
name tests wonât match anything anymore. However, the axis step *[@Expression=true()] matches any expression.ASTLiteral
has been turned into an interface.ASTNumericLiteral
, ASTCharLiteral
, ASTStringLiteral
, and ASTClassLiteral
are new nodes that implement that interface.ASTPrimaryExpression
ASTNullLiteral
and ASTBooleanLiteral
were nested within it but other literals types were all directly represented by it was inconsistent, and ultimately that level of nesting was unnecessary./Literal/
segments from your XPath expressions/*[self::StringLiteral or self::CharLiteral]/
char c = 'c';
boolean b = true;
int i = 1;
double d = 1.0;
String s = "s";
Object n = null;
ââ Literal[ @CharLiteral = true() ] "'c'"
ââ Literal
ââ BooleanLiteral[ @True = true() ]
ââ Literal[ @IntLiteral = true() ] "1"
ââ Literal[ @DoubleLiteral = true() ] "1.0"
ââ Literal[ @StringLiteral = true() ] "\"s\""
ââ Literal
ââ NullLiteral
ââ CharLiteral "'c'"
ââ BooleanLiteral[ @True = true() ]
ââ NumericLiteral[ @IntLiteral = true() ] "1"
ââ NumericLiteral[ @DoubleLiteral = true() ] "1.0"
ââ StringLiteral "\"s\""
ââ NullLiteral
Method calls, constructor calls, array allocations
o.myMethod("a");
new Object("b");
new int[10];
new int[] { 1, 2, 3 };
ââ PrimaryExpression
ââ PrimaryPrefix
â ââ Name "o.myMethod"
ââ PrimarySuffix
ââ Arguments
ââ ArgumentList (1)
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "\"a\""
ââ PrimaryExpression
ââ PrimaryPrefix
ââ AllocationExpression
ââ ClassOrInterfaceType "Object"
ââ Arguments
ââ ArgumentList
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "\"b\""
ââ PrimaryExpression
ââ PrimaryPrefix
ââ AllocationExpression
ââ PrimitiveType "int"
ââ ArrayDimsAndInits
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "10"
ââ PrimaryPrefix
ââ AllocationExpression
ââ PrimitiveType "int"
ââ ArrayDimsAndInits
ââ ArrayInitializer
ââ VariableInitializer
â ââ Expression
â ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal "1"
ââ VariableInitializer
â ââ Expression
â ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal "2"
ââ VariableInitializer
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "3"
ââ MethodCall "myMethod"
ââ VariableAccess "o"
ââ ArgumentList (1)
ââ StringLiteral "\"a\""
ââ ConstructorCall
ââ ClassOrInterfaceType "Object"
ââ ArgumentList (1)
ââ StringLiteral "\"b\""
ââ ArrayAllocation[ @ArrayDepth = 1 ]
ââ ArrayType
ââ PrimitiveType "int"
ââ ArrayDimensions (1)
ââ ArrayDimExpr
ââ NumericLiteral "10"
ââ ArrayAllocation[ @ArrayDepth = 1 ]
ââ ArrayType
â ââ PrimitiveType "int"
â ââ ArrayDimensions (1)
â ââ ArrayTypeDim
ââ ArrayInitializer[ @Length = 3 ]
ââ NumericLiteral "1"
ââ NumericLiteral "2"
ââ NumericLiteral "3"
Method call chains are left-recursive
ASTPrimaryPrefix
and ASTPrimarySuffix
are removed from the grammar. Subtrees for primary expressions appear to be left-recursive now.new Foo().bar.foo(1);
ââ StatementExpression
ââ PrimaryExpression
ââ PrimaryPrefix
â ââ AllocationExpression
â ââ ClassOrInterfaceType "Foo"
â ââ Arguments (0)
ââ PrimarySuffix "bar"
ââ PrimarySuffix "foo"
ââ PrimarySuffix[ @Arguments = true() ]
ââ Arguments (1)
ââ ArgumentList
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "1"
ââ ExpressionStatement
ââ MethodCall "foo"
ââ FieldAccess "bar"
â ââ ConstructorCall
â ââ ClassOrInterfaceType "Foo"
â ââ ArgumentList (0)
ââ ArgumentList (1)
ââ NumericLiteral "1"
Instead of being flat, the subexpressions are now nested within one another. The nesting follows the naturally recursive structure of expressions:
new Foo().bar.foo(1)
âââââââââ â â ConstructorCall
âââââââââââââ â FieldAccess
ââââââââââââââââââââ MethodCall
This makes the AST more regular and easier to navigate. Each node contains the other nodes that are relevant to it (e.g. arguments) instead of them being spread out over several siblings. The API of all nodes has been enriched with high-level accessors to query the AST in a semantic way, without bothering with the placement details.
The amount of changes in the grammar that this change entails is enormous, but hopefully firing up the designer to inspect the new structure should give you the information you need quickly.
Note: this doesnât affect binary expressions like ASTAdditiveExpression
. E.g. a+b+c
is not parsed as
AdditiveExpression
+ AdditiveExpression
+ (a)
+ (b)
+ (c)
Itâs still
AdditiveExpression
+ (a)
+ (b)
+ (c)
which is easier to navigate, especially from XPath.
Field access, array access, variable accessfield = 1;
localVar = 1;
array[0] = 1;
Foo.staticField = localVar;
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Name "field"
ââ AssignmentOperator "="
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "1"
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Name "localVar"
ââ AssignmentOperator "="
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "1"
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
â ââ PrimaryPrefix
â â ââ Name "array"
â ââ PrimarySuffix[ @ArrayDereference = true() ]
â ââ Expression
â ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal "0"
ââ AssignmentOperator "="
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "1"
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Name "Foo.staticField"
ââ AssignmentOperator "="
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "localVar"
ââ ExpressionStatement
ââ AssignmentExpression "="
ââ VariableAccess "field"
ââ NumericLiteral "1"
ââ ExpressionStatement
ââ AssignmentExpression "="
ââ VariableAccess "localVar"
ââ NumericLiteral "1"
ââ ExpressionStatement
ââ AssignmentExpression "="
ââ ArrayAccess[ @AccessType = "WRITE" ]
â ââ VariableAccess "array"
â ââ NumericLiteral "0"
ââ NumericLiteral "1"
ââ ExpressionStatement
ââ AssignmentExpression "="
ââ FieldAccess[ @AccessType = "WRITE" ] "staticField"
â ââ TypeExpression
â ââ ClassOrInterfaceType "Foo"
ââ VariableAccess[ @AccessType = "READ" ] "localVar"
this
and super
are now explicit nodes instead of PrimaryPrefix.
this.field = 1;
super.field = 1;
this.method();
super.method();
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
â ââ PrimaryPrefix[ @ThisModifier = true() ]
â ââ PrimarySuffix "field"
ââ AssignmentOperator "="
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "1"
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
â ââ PrimaryPrefix[ @SuperModifier = true() ]
â ââ PrimarySuffix "field"
ââ AssignmentOperator "="
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "1"
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
ââ PrimaryPrefix[ @ThisModifier = true() ]
ââ PrimarySuffix "method"
ââ PrimarySuffix[ @Arguments = true() ]
ââ Arguments (0)
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
ââ PrimaryPrefix[ @SuperModifier = true() ]
ââ PrimarySuffix "method"
ââ PrimarySuffix[ @Arguments = true() ]
ââ Arguments (0)
ââ ExpressionStatement
ââ AssignmentExpression "="
ââ FieldAccess[ @AccessType = "WRITE" ] "field"
â ââ ThisExpression
ââ NumericLiteral "1"
ââ ExpressionStatement
ââ AssignmentExpression "="
ââ FieldAccess[ @AcessType = "WRITE" ] "field"
â ââ SuperExpression
ââ NumericLiteral "1"
ââ ExpressionStatement
ââ MethodCall "method"
ââ ThisExpression
ââ ArgumentList (0)
ââ ExpressionStatement
ââ MethodCall "method"
ââ SuperExpression
ââ ArgumentList (0)
Type expressions
ASTTypeExpression
wraps a ASTType
node (such as ASTClassOrInterfaceType
) and is used to qualify a method call or field access or method reference.Foo.staticMethod();
if (x instanceof Foo) {}
var x = Foo::method;
ââ BlockStatement
ââ Statement
ââ StatementExpression
ââ PrimaryExpression
ââ PrimaryPrefix
â ââ Name "Foo.staticMethod"
ââ PrimarySuffix[ @Arguments = true() ]
ââ Arguments (0)
ââ BlockStatement
ââ Statement
ââ IfStatement
ââ Expression
â ââ InstanceOfExpression
â ââ PrimaryExpression
â â ââ PrimaryPrefix
â â ââ Name "x"
â ââ Type
â ââ ReferenceType
â ââ ClassOrInterfaceType "Foo"
ââ Statement
ââ Block
ââ BlockStatement
ââ LocalVariableDeclaration
ââ VariableDeclarator
ââ VariableDeclaratorId "x"
ââ VariableInitializer
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
â ââ Name "Foo"
ââ PrimarySuffix
ââ MemberSelector
ââ MethodReference "method"
ââ ExpressionStatement
ââ MethodCall "staticMethod"
ââ TypeExpression
â ââ ClassOrInterfaceType "Foo"
ââ ArgumentList (0)
ââ IfStatement
ââ InfixExpression "instanceof"
â ââ VariableAccess[ @AccessType = "READ" ] "x"
â ââ TypeExpression
â ââ ClassOrInterfaceType "Foo"
ââ Block
ââ LocalVariableDeclaration
ââ ModifierList
ââ VariableDeclarator
ââ VariableDeclaratorId "x"
ââ MethodReference "method"
ââ TypeExpression
ââ ClassOrInterfaceType "Foo"
Merge unary expressions
ASTUnaryExpression
node. The merged nodes are:
++a;
--b;
c++;
d--;
ââ StatementExpression
ââ PreIncrementExpression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "a"
ââ StatementExpression
ââ PreDecrementExpression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "b"
ââ StatementExpression
ââ PostfixExpression "++"
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "c"
ââ StatementExpression
ââ PostfixExpression "--"
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "d"
ââ ExpressionStatement
ââ UnaryExpression[ @Prefix = true() ][ @Operator = '++' ]
ââ VariableAccess[ @AccessType = "WRITE" ] "a"
ââ ExpressionStatement
ââ UnaryExpression[ @Prefix = true() ][ @Operator = '--' ]
ââ VariableAccess[ @AccessType = "WRITE" ] "b"
ââ ExpressionStatement
ââ UnaryExpression[ @Prefix = false() ][ @Operator = '++' ]
ââ VariableAccess[ @AccessType = "WRITE" ] "c"
ââ ExpressionStatement
ââ UnaryExpression[ @Prefix = false() ][ @Operator = '--' ]
ââ VariableAccess[ @AccessType = "WRITE" ] "d"
x = ~a;
x = +a;
ââ UnaryExpressionNotPlusMinus "~"
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "a"
ââ UnaryExpression "+"
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Name "a"
ââ UnaryExpression[ @Prefix = true() ] "~"
ââ VariableAccess "a"
ââ UnaryExpression[ @Prefix = true() ] "+"
ââ VariableAccess "a"
Binary operators are left-recursive
InfixExpression
, which gives access to the operator via getOperator()
and to the operands (getLhs()
, getRhs()
). Additionally, the resulting AST is not flat anymore, but a more structured tree.int i = 1 * 2 * 3 % 4;
ââ Expression
ââ MultiplicativeExpression "%"
ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal "1"
ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal "2"
ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Literal "3"
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "4"
ââ InfixExpression[ @Operator = '%' ]
ââ InfixExpression[@Operator='*']
â ââ InfixExpression[@Operator='*']
â â ââ NumericLiteral[@ValueAsInt=1]
â â ââ NumericLiteral[@ValueAsInt=2]
â ââ NumericLiteral[@ValueAsInt=3]
ââ NumericLiteral[@ValueAsInt=4]
Parenthesized expressions
@Parenthesized
and @ParenthesisDepth
a = (((1)));
ââ StatementExpression
ââ PrimaryExpression
â ââ PrimaryPrefix
â ââ Name "a"
ââ AssignmentOperator "="
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Expression
ââ PrimaryExpression
ââ PrimaryPrefix
ââ Literal "1"
ââ ExpressionStatement
ââ AssignmentExpression
ââ VariableAccess "a"
ââ NumericLiteral[ @Parenthesized = true() ][ @ParenthesisDepth = 3 ] "1"
Language versions
minimumLanguageVersion
and maximumLanguageVersion
).--use-version
, then this default version will be used. Usually the latest version is the default version.pmd check --help
.This is only relevant, if you are maintaining a CPD language module for a custom language.
AbstractLanguage
extend now CpdOnlyLanguageModuleBase
.AntlrTokenManager
use now TokenManager
AntlrTokenFilter
also use now TokenManager
AntlrTokenFilter
extend now BaseTokenFilter
src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language
but instead src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language
. This is the unified language interface for both PMD and CPD capable languages. See also the subinterfaces CpdCapableLanguage
and PmdCapableLanguage
.When you switch from PMD 6.x to PMD 7 in your build tools, you most likely need to review your ruleset(s) as well and check for removed rules. See the use case
Iâm using only built-in rulesabove.
AntPMDTask
and CPDTask
have been moved from the module pmd-core
into the new module pmd-ant
.net.sourceforge.pmd:pmd-ant
) in order to import the tasks into your build file. <pluginRepository>
<id>apache.snapshots</id>
<name>Apache Snapshot Repository</name>
<url>https://repository.apache.org/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<project>
<properties>
<pmdVersion>7.0.0-rc4</pmdVersion>
<mavenPmdPluginVersion>3.21.1-pmd-7.0.0-SNAPSHOT</mavenPmdPluginVersion>
</properties>
...
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>${mavenPmdPluginVersion}</version>
<dependencies>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-core</artifactId>
<version>${pmdVersion}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-java</artifactId>
<version>${pmdVersion}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-javascript</artifactId>
<version>${pmdVersion}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-jsp</artifactId>
<version>${pmdVersion}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
</build>
...
</project>
toolVersion = "7.0.0-rc4"
, but you also need configure the dependencies manually for now, since the ant task is in an own dependency with PMD 7:
pmd 'net.sourceforge.pmd:pmd-ant:7.0.0-rc4'
pmd 'net.sourceforge.pmd:pmd-java:7.0.0-rc4'
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4