Table of Contents
Before you update
Before 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 ofStringProperty.named(...)
usePropertyFactory.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 toRuleContext
. E.g. instead ofaddViolation(data, node, ...)
useasCtx(data).addViolation(node, ...)
. - When you are calling PMD from CLI, you need to stop using deprecated CLI params, e.g.
-no-cache
➡️--no-cache
-failOnViolation
➡️--fail-on-violation
-reportfile
➡️--report-file
-language
➡️--use-version
- If you have written custom XPath rule, look out for warnings about deprecated XPath attributes. These warnings
might look like
WARNING: Use of deprecated attribute 'VariableDeclaratorId/@Image' by XPath rule 'VariableNaming' (in ruleset 'VariableNamingRule'), please use @Name instead
and often already suggest an alternative.
Use cases
I’m using only built-in rules
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 rules
Ideally, you have written good tests already for your custom rules - see Testing your rules. This helps to identify problems early on.
XPath rules
If 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 rules
If 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:
- Tree traversal using Node API
- Consider using the new NodeStream API to navigate with null-safety. This is optional.
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:
- As an alternative, PMD 7 now supports ANTLR in addition to JavaCC: Adding a new language with ANTLR.
- There is a shared ant script that wraps the calls to javacc:
javacc-wrapper.xml
. This should be used now. - PMD’s parser adapter for JavaCC generated parsers is called now
JjtreeParserAdapter
. This is the class that needs to be implemented now. - There is no need anymore to write a custom
TokenManager
- we have now a common base class for JavaCC generated token managers. This base class isAbstractTokenManager
. - A rule violation factory is not needed anymore. For language specific information on rule violations, there is
now a
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 viaRuleViolation#getAdditionalInfo
. - A parser visitor adapter is not needed anymore. The visitor interface now provides a default implementation.
Instead, a base visitor for the language should be created, which extends
AstVisitorBase
. - A rule chain visitor is not needed anymore. PMD provides a common implementation that fits all languages.
I’ve extended PMD with a custom feature…
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 downloads
- The asset filenames of PMD on GitHub Releases are
now
pmd-dist-<version>-bin.zip
,pmd-dist-<version>-src.zip
andpmd-dist-<version>-doc.zip
. Keep that in mind, if you have an automated download script. - The structure inside the ZIP files stay the same, e.g. we still provide inside the binary distribution
ZIP file the base directory
pmd-bin-<version>
.
CLI Changes
The CLI has been revamped completely (see Release Notes: Revamped Command Line Interface).
Most notable changes:
- Unified start script on all platforms for all commands (PMD, CPD, Designer). Instead of
run.sh
andpmd.bat
, we now havepmd
only (technically on Windows, there is still apmd.bat
, but it behaves the same).- Executing PMD from CLI now means:
run.sh pmd
/pmd.bat
➡️pmd check
- Executing CPD:
run.sh cpd
/cpd.bat
➡️pmd cpd
- Executing Designer:
run.sh designer
/designer.bat
➡️pmd designer
- Executing CPD GUI:
run.sh cpd-gui
/cpdgui.bat
➡️pmd cpd-gui
- Executing PMD from CLI now means:
- There are some changes to the CLI arguments:
-
--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
.
-
Custom distribution packages
When creating a custom distribution which only integrates the languages you need, there are some changes to apply:
- In addition to the language dependencies you want, you also need add a dependency to
net.sourceforge.pmd:pmd-cli
in order to get the CLI classes. - When fetching the scripts for the CLI with “maven-dependency-plugin”, you need to additionally fetch the
logging configuration. That means, the line
<includes>scripts/**,LICENSE</includes>
needs to be changed to<includes>scripts/**,LICENSE,conf/**</includes>
. - Since the assembly descriptor
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>
Rule tests are now using JUnit5
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.
CPD: Reported endcolumn is now exclusive
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 API
Starting 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()
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 anIterable
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*
likejjtGetParent
. These methods were implementation specific. Use the equivalent methods likegetParent()
.
See Node
for the details.
NodeStream API
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: Migrating from 1.0 to 2.0
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:
- The namespace prefixes
fn:
andstring:
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)
- Conversely, calls to custom PMD functions like
typeIs
must be prefixed with the namespace of the declaring module (pmd-java
).typeIs("Foo")
→pmd-java:typeIs("Foo")
- Boolean attribute values on our 1.0 engine are represented as the string values
"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 functionstrue()
andfalse()
. 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()
- In XPath 1.0, comparing a number to a string coerces the string to a number.
In XPath 2.0, a type error occurs. Like for boolean values, numeric values are
represented by our 1.0 implementation as strings, meaning that
@BeginLine > "1"
worked —that’s not the case in 2.0 mode.@ArgumentCount > '1'
→@ArgumentCount > 1
- In XPath 1.0, the expression
/Foo
matches the children of the root namedFoo
. In XPath 2.0, that expression matches the root, if it is namedFoo
. 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 custom function “pmd:matches” which checks a regular expression against a string has been removed, since there is a built-in function available since XPath 2.0 which can be used instead. If you use “pmd:matches” simply remove the “pmd:” prefix.
Java AST
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.
Annotations
- What: Annotations are consolidated into a single node.
SingleMemberAnnotation
,NormalAnnotation
andMarkerAnnotation
are removed in favour ofASTAnnotation
. The Name node is removed, replaced by aASTClassOrInterfaceType
. - Why: Those different node types implement a syntax-only distinction, that only makes semantically equivalent annotations
have different possible representations. For example,
@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. - Related issue: [java] Use single node for annotations (#2282)
Annotation AST Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Annotation nesting
- What:
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 aASTModifierList
, inside theASTMethodDeclaration
. - Why: Fixes a lot of inconsistencies, where sometimes the annotations were inside the node, and sometimes just somewhere in the parent, with no real structure.
- Related issue: [java] Move annotations inside the node they apply to (#1875)
Annotation nesting Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Method | ||
Top-level type declaration | ||
Cast expression | ||
Cast expression with intersection |
Notice @A binds to T , not T & S
| |
Constructor call | ||
Array allocation | ||
Array type | ||
Type parameters |
| |
Enum constants |
|
Types
Type and ReferenceType
- What:
ASTType
andASTReferenceType
have been turned into interfaces, implemented byASTPrimitiveType
,ASTClassOrInterfaceType
, and the new nodeASTArrayType
. This reduces the depth of the relevant subtrees, and allows to explore them more easily and consistently.
- Why:
- some syntactic contexts only allow reference types, other allow any kind of type. If you want to match all types of a program, then matching Type would be the intuitive solution. But in 6.0.x, it wouldn’t have sufficed, since in some contexts, no Type node was pushed, only a ReferenceType
- Regardless of the original syntactic context, any reference type is a type, and searching for ASTType should yield all the types in the tree.
- Using interfaces allows to abstract behaviour and make a nicer and safer API.
- Migrating
- There is currently no way to match abstract types (or interfaces) with XPath, so
Type
andReferenceType
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
.- Note that in most cases you should check the type of a variable with e.g.
VariableDeclaratorId[pmd-java:typeIs("java.lang.String[]")]
because it considers the additional dimensions on declarations likeString foo[];
. The Java equivalent isTypeHelper.isA(id, String[].class);
- There is currently no way to match abstract types (or interfaces) with XPath, so
Type and ReferenceType Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
|
|
Array changes
- What: Additional nodes
ASTArrayType
,ASTArrayTypeDim
,ASTArrayTypeDims
,ASTArrayAllocation
. - Why: Support annotated array types ([java] Java8 parsing corner case with annotated array types (#997))
- Related issue: [java] Simplify array allocation expressions (#1981)
Array Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
ClassOrInterfaceType nesting
- What:
ASTClassOrInterfaceType
appears to be left recursive now, and encloses its qualifying type. - Why: To preserve the position of annotations and type arguments
- Related issue: [java] ClassOrInterfaceType AST improvements (#1150)
ClassOrInterfaceType Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
TypeArgument and WildcardType
- What:
ASTTypeArgument
is removed. Instead, theASTTypeArguments
node contains directly a sequence ofASTType
nodes. To support this, the new node typeASTWildcardType
captures the syntax previously parsed as a TypeArgument.- The
ASTWildcardBounds
node is removed. Instead, the bound is a direct child of the WildcardType.
- Why: Because wildcard types are types in their own right, and having a node to represent them skims several levels of nesting off.
TypeArgument and WildcardType Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Declarations
Import and Package declarations
- What: Remove the Name node in imports and package declaration nodes.
- Why: Name is a TypeNode, but it’s equivalent to
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. - Related issue: [java] Remove Name nodes in Import- and PackageDeclaration (#1888)
Import and Package declarations Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Modifier lists
- What:
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). - Why: AccessNode gave a lot of irrelevant methods to its subtypes. E.g.
ASTFieldDeclaration::isSynchronized
makes no sense. Now, these irrelevant methods don’t clutter the API. The API of ModifierList is both more general and flexible - Related issue: [java] Rework AccessNode (#2259)
Modifier lists Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Method | ||
Top-level type declaration |
Flattened body declarations
- What: Removes
ASTClassOrInterfaceBodyDeclaration
,ASTTypeDeclaration
, andASTAnnotationTypeMemberDeclaration
. These were unnecessary since annotations are nested (see above Annotation nesting). - Why: This flattens the tree, makes it less verbose and simpler.
- Related issue: [java] Flatten body declarations (#2300)
Flattened body declarations Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Module declarations
- What: Removes the generic Name node and uses instead
ASTClassOrInterfaceType
where appropriate. Also uses specific node types for different directives (requires, exports, uses, provides). - Why: Simplify queries, support type resolution
- Related issue: [java] Improve module grammar (#3890)
Module declarations Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Anonymous class declarations
- What: A separate node type
ASTAnonymousClassDeclaration
is introduced for anonymous classes. - Why: Unify the AST for type declarations including anonymous class declaration in constructor calls and enums.
- Related issues:
Anonymous class declarations Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Method and Constructor declarations
Method grammar simplification
- What: Simplify and align the grammar used for method and constructor declarations. The methods in an annotation type are now also method declarations.
- Why: The method declaration had a nested node “MethodDeclarator”, which was not available for constructor declarations. This made it difficult to write rules, that concern both methods and constructors without explicitly differentiate between these two.
- Related issue: [java] Align method and constructor declaration grammar (#2034)
Method grammar Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Formal parameters
- What: Use
ASTFormalParameter
only for method and constructor declaration. Lambdas useASTLambdaParameter
, catch clauses useASTCatchParameter
. - Why: FormalParameter’s API is different from the other ones.
- FormalParameter must mention a type node.
- LambdaParameter can be inferred
- CatchParameter cannot be varargs
- CatchParameter can have multiple exception types (a
ASTUnionType
now)
Formal parameters Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
New node for explicit receiver parameter
- What: A separate node type
ASTReceiverParameter
is introduced to differentiate it from formal parameters. - Why: A receiver parameter is not a formal parameter, even though it looks like one: it doesn’t declare a variable,
and doesn’t affect the arity of the method or constructor. It’s so rarely used that giving it its own node avoids
matching it by mistake and simplifies the API and grammar of the ubiquitous
ASTFormalParameter
andASTVariableDeclaratorId
. - Related issue: [java] Separate receiver parameter from formal parameter (#1980)
explicit receiver parameter Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Varargs
- What: parse the varargs ellipsis as an
ASTArrayType
. - Why: this improves regularity of the grammar, and allows type annotations to be added to the ellipsis
Varargs Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Add void type node to replace ResultType
- What: Add a
ASTVoidType
node to replaceASTResultType
. - Why: This means we don’t need the ResultType wrapper when the method is not void, and the result type node is never null.
- Related issue: [java] Add void type node to replace ResultType (#2715)
Void Type Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Statements
Statements are flattened
- What: Statements are flattened. There are no superfluous BlockStatement and Statement nodes anymore.
All children of a
ASTBlock
are by definitionASTStatement
s, which is now an interface implemented by all statements. - Why: This simplifies the tree traversal. The removed nodes BlockStatement and Statement didn’t add any additional information. We only need a Statement abstraction. BlockStatement was used to enforce, that no variable or local class declaration is found alone as the child of e.g. an unbraced if, else, for, etc. This is a parser-only distinction that’s not that useful for analysis later on.
- Related issue: [java] Improve statement grammar (#2164)
Statements Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
New node for For-each statements
- What: New node for For-each statements:
ASTForeachStatement
instead of ForStatement. - Why: This makes it a lot easier to distinguish in the AST between For-loops and For-Each-loops. E.g. some rules only apply to one or the other, and it was complicated to write a rule that works with both different subtrees (for loops have additional children ForInit and ForUpdate)
- Related issue: [java] Improve statement grammar (#2164)
For-each statement Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
New nodes for ExpressionStatement, LocalClassStatement
- What: Renamed StatementExpression to
ASTExpressionStatement
. Added new nodeASTLocalClassStatement
. - Why: ExpressionStatement is now a
ASTStatement
, that can be used as a child in a block. It itself has only one child, which is some kind ofASTExpression
, which can be really any kind of expression (like assignment). In order to allow local class declarations as part of a block, we introducedASTLocalClassStatement
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. - Related issue: [java] Improve statement grammar (#2164)
ExpressionStatement, LocalClassStatement Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Improve try-with-resources grammar
- What: The AST representation of a try-with-resources statement has been simplified.
It uses now
ASTLocalVariableDeclaration
unless it is a concise try-with-resources. - Why: Simpler integration try-with-resources into symboltable and type resolution.
- Related issue: [java] Improve try-with-resources grammar (#1897)
Try-With-Resources Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Expressions
-
ASTExpression
andASTPrimaryExpression
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:
- Basically,
Expression/X
orExpression/PrimaryExpression/X
, just becomesX
- There is currently no way to match abstract or interface types with XPath, so
Expression
orPrimaryExpression
name tests won’t match anything anymore. However, the axis step *[@Expression=true()] matches any expression.
- Basically,
New nodes for different literals types
- What:
ASTLiteral
has been turned into an interface.ASTNumericLiteral
,ASTCharLiteral
,ASTStringLiteral
, andASTClassLiteral
are new nodes that implement that interface.- ASTLiteral implements
ASTPrimaryExpression
- Why: The fact that
ASTNullLiteral
andASTBooleanLiteral
were nested within it but other literals types were all directly represented by it was inconsistent, and ultimately that level of nesting was unnecessary. - Related issue: [java] New expression and type grammar (#1759)
- Migrating:
- Remove all
/Literal/
segments from your XPath expressions - If you tested several types of literals, you can e.g. do it like
/*[self::StringLiteral or self::CharLiteral]/
- As usual, use the designer to explore the new AST structure
- Remove all
Literals Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Method calls, constructor calls, array allocations
- What: Extra nodes dedicated for method and constructor calls and array allocations
- Why: It was extremely difficult to identify method calls in PMD 6 - these consisted of multiple nodes with primary prefix, suffix and expressions. This was too low level to be easy to be used.
- Related issue: [java] New expression and type grammar (#1759)
Method calls, constructor calls, array allocations Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Method call chains are left-recursive
- What: The nodes
ASTPrimaryPrefix
andASTPrimarySuffix
are removed from the grammar. Subtrees for primary expressions appear to be left-recursive now. - Why: Allows to reuse abstractions like method calls without introducing a new artificial node (like method chain).
- Related issue: [java] New expression and type grammar (#1759)
Method call chain Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
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 access
- What: New nodes dedicated to accessing field, variables and referencing arrays. Also provide info about the access type, like whether a variable is read or written.
- Why: Like MethodCalls, this was a missing abstraction in the AST that has been added now.
- Related issue: [java] New expression and type grammar (#1759)
Field access, array access, variable access Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
|
Explicit nodes for this/super expressions
- What:
this
andsuper
are now explicit nodes instead of PrimaryPrefix. - Why: That way these nodes can qualify other nodes like FieldAccess.
- Related issue: [java] New expression and type grammar (#1759)
this/super expressions Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Type expressions
- What: The node
ASTTypeExpression
wraps aASTType
node (such asASTClassOrInterfaceType
) and is used to qualify a method call or field access or method reference. - Why: Simplify the qualifier of method calls, treat instanceof as infix expression.
- Related issue: [java] Grammar type expr (#2039)
Type expressions Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Merge unary expressions
- What: Merge AST nodes for postfix and prefix expressions into the single
ASTUnaryExpression
node. The merged nodes are:- PreIncrementExpression
- PreDecrementExpression
- UnaryExpression
- UnaryExpressionNotPlusMinus
- Why: Those nodes were asymmetric, and inconsistently nested within UnaryExpression. By definition, they’re all unary, so that using a single node is appropriate.
- Related issues:
Unary Expressions Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Binary operators are left-recursive
- What: For each operator, there were separate AST nodes (like AdditiveExpression, AndExpression, …).
These are now unified into a
InfixExpression
, which gives access to the operator viagetOperator()
and to the operands (getLhs()
,getRhs()
). Additionally, the resulting AST is not flat anymore, but a more structured tree. - Why: Having different AST node types doesn’t add information, that the operator doesn’t already provide. The new structure as a result, that the expressions are now parsed left recursive, makes the AST more JLS-like. This makes it easier for the type mapping algorithms. It also provides the information, which operands are used with which operator. This information was lost if more than 2 operands where used and the tree was flattened with PMD 6.
- Related issue: [java] Make binary operators left-recursive (#1979)
Binary operators Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Parenthesized expressions
- What: Parentheses are not modelled in the AST anymore, but can be checked with the attributes
@Parenthesized
and@ParenthesisDepth
- Why: This keeps the tree flat while still preserving the information. The tree is the same in case of unnecessary parenthesis, which makes it harder to fool rules that look at the structure of the tree.
- Related issue: [java] Remove ParenthesizedExpr (#1872)
Parenthesized expressions Examples
Code | Old AST (PMD 6) | New AST (PMD 7) |
---|---|---|
Language versions
- Since all languages now have defined language versions, you could now write rules that apply only for specific
versions (using
minimumLanguageVersion
andmaximumLanguageVersion
). - All languages have a default version. If no specific version on the CLI is given using
--use-version
, then this default version will be used. Usually the latest version is the default version. - The available versions for each language can be seen in the help message of the CLI
pmd check --help
. - See also Changed: Language versions
Migrating custom CPD language modules
This is only relevant, if you are maintaining a CPD language module for a custom language.
- Instead of
AbstractLanguage
extend nowCpdOnlyLanguageModuleBase
. - Instead of
AntlrTokenManager
use nowTokenManager
- Instead of
AntlrTokenFilter
also use nowTokenManager
- Instead of
AntlrTokenFilter
extend nowBaseTokenFilter
- CPD Module discovery change. The service loader won’t load anymore
src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language
but insteadsrc/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 subinterfacesCpdCapableLanguage
andPmdCapableLanguage
. - The documentation How to add a new CPD language has been updated to reflect these changes.
Build Tools
Ant
- The Ant tasks
PMDTask
andCPDTask
have been moved from the modulepmd-core
into the new modulepmd-ant
. - You need to add this dependency/jar file onto the class path (
net.sourceforge.pmd:pmd-ant
) in order to import the tasks into your build file. - When using the guide Ant Task Usage then no change is needed, since the pmd-ant jar file is included in the binary distribution of PMD. It is part of PMD’s lib folder.
Maven
- Due to some changes in PMD’s API, you can’t simply pull in the new PMD 7 dependency using the approach documented in Upgrading PMD at Runtime.
- A new maven-pmd-plugin version, that supports PMD 7 is in the works. See MPMD-379.
- As long as no new maven-pmd-plugin version with PMD 7 support is released, you can try it out using a
SNAPSHOT version:
- Add the Apache SNAPSHOT maven repository:
<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>
- Use the version 3.21.1-pmd-7-SNAPSHOT of the maven-pmd-plugin
- Override the dependencies of the plugin to use PMD 7, e.g.
<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>
- Add the Apache SNAPSHOT maven repository:
Gradle
- Gradle uses internally PMD’s Ant task to execute PMD
- You can set
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'
- Gradle 8.3 most likely will support PMD 7 out of the box.
- See Support for PMD 7.0