Movatterモバイル変換


[0]ホーム

URL:


 
» Migration Guide for PMD 7 Edit on GitHub

Migration Guide for PMD 7

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 usby opening adiscussion so that we can improve this guide.

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 alldeprecation warnings.

There are a couple of deprecated things in PMD 6, you might encounter:

  • Properties: In order to define property descriptors, you should usePropertyFactory now.This factory can create properties of any type. E.g. instead ofStringProperty.named(...) usePropertyFactory.stringProperty(...).

    Also note, thatuiOrder is gone. You can just remove it.

    See alsoDefining rule properties

  • When reporting a violation, you might see a deprecation of theaddViolation methods. These methods have been movedtoRuleContext. 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 warningsmight look like
    WARNING: Use of deprecated attribute 'VariableId/@Image' by XPath rule 'VariableNaming' (in ruleset 'VariableNamingRule'), please use @Name instead

    and often already suggest an alternative.

  • If you still reference rulesets or rules the old way which has been deprecated since 6.46.0:
    • <lang-name>-<ruleset-name>, egjava-basic, which resolves torulesets/java/basic.xml
    • the internal release number, eg600, which resolves torulesets/releases/600.xml

    Such usages produce deprecation warnings that should be easy to spot, e.g.

    Ruleset reference 'java-basic' uses a deprecated form, use 'rulesets/java/basic.xml' instead

    Use the explicit forms of these references to be compatible with PMD 7.

    Note: Since PMD 6, all rules are sorted into categories (such as “Best Practices”, “Design”, “Error Prone”)and the old rulesets likebasic.xml have been deprecated and have been removed with PMD 7.It is about time to create acustom ruleset.

Approaching 7.0.0

After that, migrate to the release candidates, and fix any problems you encounter. Start with 7.0.0-rc1 via7.0.0-rc2, 7.0.0-rc3 and 7.0.0-rc4 until you finally use 7.0.0.

You might encounter additionally the following types of problems:

  • If you use any programmatic API of PMD, first avoid any usage of deprecated or internal classes/methods. Theseare marked with one of these annotations:@Deprecated,@DeprecatedUtil700,@InternalApi.
    • Some of these classes are available until 7.0.0-rc4 but are finally removed with 7.0.0.
    • SeeAPI changes for details.
  • Some rules have been removed, because they have been deprecated. SeeRemoved Rules.
  • Some rule properties have been removed or changed. SeeChanged Rules.
  • The filenames of the assets of a release (the “binary distribution zip file”) have changed,seeRelease downloads.
  • Some CLI options have been removed, because they have been deprecated. SeeCLI Changes for details.
  • If you call CPD programmatically, the API has changed, seeNew Programmatic API for CPD.
  • If you use Visualforce, then you need to change “vf” to “visualforce”, e.g.category/vf/security.xml ➡️category/visualforce/security.xml
  • If you use Velocity, then you need to change “vm” to “velocity”, e.g.category/vm/... ➡️category/velocity/...

The following topics describe well known migration challenges in more detail.

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 7many deprecated rules are finally removed. You can see a complete list of theremoved rulesin the release notes for PMD 7.The release notes also mention the replacement rule, that should be used instead. For some rules, there is noreplacement.

Then many rules have been changed or improved. New properties have been added to make them more versatile orproperties have been removed, if they are not necessary anymore. Seechanged rulesin the release notes for PMD 7.

All properties which accept multiple values now use a comma (,) as a delimiter. The previous default was apipe 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

Testing

Ideally, you have written good tests already for your custom rules - seeTesting your rules.This helps to identify problems early on.

The base test classesPmdRuleTst andSimpleAggregatorTst have been moved outof packagenet.sourceforge.pmd.testframework. You’ll need to adjust your imports.

Ruleset XML

The<rule> tag, that defines your custom rule, is required to have alanguage attribute now. This was always thecase for XPath rules, but is now a requirement for Java rules.

XPath rules

If you haveXPath 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 andwon’t support XPath 1.0 anymore. The difference between XPath 2.0 and XPath 3.1 is not big, so your XPath 2.0can 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 has been removedwith PMD 7. PMD 7 by default uses XPath 3.1. See belowXPath for details.

Then change theclass attribute of your rule tonet.sourceforge.pmd.lang.rule.xpath.XPathRule - because theclassXPathRule has been moved into subpackagenet.sourceforge.pmd.lang.rule.xpath.

There are some general changes for AST nodes regarding the@Image attribute.See belowGeneral AST Changes to avoid @Image.

Additional infos:

  • The custom XPath functiontypeOf has been removed (deprecated since 6.4.0).Use the functionpmd-java:typeIs orpmd-java:typeIsExactly instead.SeePMD extension functions for availablefunctions.

Java rules

If you haveJava based rules, and you are using rulechain, this works a bit different now. The RuleChain APIhas changed, see[core] Simplify the rulechain (#2490) for the full details.But in short, you don’t calladdRuleChainVisit(...) in the rule’s constructor anymore. Instead, youoverride the methodbuildTargetSelector:

protectedRuleTargetSelectorbuildTargetSelector(){returnRuleTargetSelector.forTypes(ASTVariableId.class);}

Java AST changes

The API tonavigate the AST also changed significantly:

  • Tree traversal usingNode API
  • Consider using the newNodeStream API to navigate with null-safety. This is optional.

Additionally, if you have created rules forJava - regardless whether it is a XPath based rule or a Java basedrule - you might need to adjust your queries or visitor methods. The Java AST has been refactored substantially.The easiest way is to use thePMD Rule Designer to see the structureof the AST. See the sectionJava AST below for details.

I’ve extended PMD with a custom language…

The guides forAdding a new language with JavaCC andAdding 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 nowJjtreeParserAdapter. This is the class that needs to be implemented now.
  • There is no need anymore to write a customTokenManager - we have now a common base class for JavaCC generatedtoken managers. This base class isAbstractTokenManager.
  • A rule violation factory is not needed anymore. For language specific information on rule violations, there isnow aViolationDecorator that a language can implement. These ViolationDecoratorsare called when a violation is reported and they can provide the additional information. This information can beused 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 extendsAstVisitorBase.
  • 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 difficultiesfinding your way around the PMD source code and javadocs and you don’t see the aspect of PMD documented you areusing, we are probably missing documentation. Please reach out to us by opening adiscussion. We then can enhance the documentation and/or the PMD API.

Special topics

Release downloads

  • The asset filenames of PMD onGitHub Releases arenowpmd-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 distributionZIP file the base directorypmd-bin-<version>.

CLI Changes

The CLI has been revamped completely(seeRelease Notes: Revamped Command Line Interface).

Most notable changes:

  • Unified start script on all platforms for all commands (PMD, CPD, Designer). Instead ofrun.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
  • 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 thenan 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 thiswith--no-progress.
    • --no-ruleset-compatibility has been removed without replacement.
    • --stress (or-stress) has been removed without replacement.

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 tonet.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 thelogging configuration. That means, the line<includes>scripts/**,LICENSE</includes> needs to be changed to<includes>scripts/**,LICENSE,conf/**</includes>.
  • Since the assembly descriptorpmd-bin includes now optionally also a BOM (bill of material). If you want tocreate this for your custom distribution, simply add the following plugin configuration:
    <plugin><groupId>org.cyclonedx</groupId><artifactId>cyclonedx-maven-plugin</artifactId><version>2.7.11</version><configuration><outputName>pmd-${project.version}-cyclonedx</outputName></configuration><executions><execution><phase>package</phase><goals><goal>makeAggregateBom</goal></goals></execution></executions></plugin>
  • The artifact name for PMD Designer has been renamed, you need to use nownet.sourceforge.pmd:pmd-designerinstead of “pmd-ui”.

Note:

The examples onhttps://github.com/pmd/pmd-examples have been updated.

Rule tests are now using JUnit5

When you have custom rules, and you have written rule tests according to the guideTesting your rules, you might want to consider upgrading your other tests toJUnit 5. The tests in PMD 7 have been migrated to JUnit5 - including the rule testsfor the built-in rules.

When executing the rule tests, you need to make sure to have JUnit5 on the classpath - which you automaticallyget when you depend onnet.sourceforge.pmd:pmd-test. If you also have JUnit4 tests, you need to make sureto have ajunit-vintage-engineas well on the test classpath, so that all tests are executed. That means, you mightneed 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 followingdescribed a duplication of length 4 in PMD 6: beginLine=1, endLine=1, beginColumn=1, endColumn=4 - these arethe first 4 character in the first line. With PMD 7, the endColumn is nowexcluding. The same duplicationwill 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 lineare inclusive, begin column is inclusive and end column is exclusive. This is the usual behavior of the mostcommon 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 isthe “traditional” way for simple cases. For more complex cases, consider to use the newNodeStream 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 asdeprecated, 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:

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().

SeeNode for the details.

NodeStream API

In java rule implementations, you often need to navigate the AST to find the interesting nodes. In PMD 6, thiswas often done by callingjjtGetChild(int) orjjtGetParent(int) and then checking the node typewithinstanceof. There are also helper methods available, likegetFirstChildOfType(Class) orfindDescendantsOfType(Class). These methods might returnnull and you need to check this for everylevel.

The newNodeStream 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 value0:

Example:

NodeStream.of(someNode)// the stream here is empty if the node is null.filterIs(ASTVariableId.class)// the stream here is empty if the node was not a variable 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

SeeNodeStream for the details.Note: This was implemented viaPR #1622 [core] NodeStream API

XPath: Migrating from 1.0 to 2.0

XPath 1.0 and 2.0 have some incompatibilities. TheXPath 2.0 specificationdescribes them precisely. Those are however mostly corner cases and XPathrules usually don’t feature any of them.

The incompatibilities that are most relevant to migrating your rules are notcaused by the specification, but by the different engines we use to runXPath 1.0 and 2.0 queries. Here’s a list of known incompatibilities:

  • The namespace prefixesfn: andstring: should not be mentioned explicitly.In XPath 2.0 mode, the engine will complain about an undeclared namespace, butthe 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 liketypeIsmust be prefixedwith 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 representedas 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 justneeds to be changed to@Private=true() when migrating. A type error will warnyou that you must update the comparison. More is explained onissue #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 arerepresented 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 thechildren 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.

General AST Changes to avoid @Image

An abstract syntax tree should be abstract, but in the same time, should not be too abstract. One of thebase interfaces for PMD’s AST for all languages isNode, which providesthe methodsgetImage andhasImageEqualTo.However, these methods don’t necessarily make sense for all nodes in all contexts. That’s whygetImage()often returns justnull. Also, the name is not very describing. AST nodes should try to use more specificnames, such asgetValue() orgetName().

For PMD 7, most languages have been adapted. And when writing XPath rules, you need to replace@Image withwhatever is appropriate now (e.g.@Name). See below for details.

Apex and Visualforce

There are many usages of@Image. These will be refactored after PMD 7 is releasedby deprecating the attribute and providing alternatives.

See also issueDeprecate getImage/@Image #4787.

Html

  • ASTHtmlTextNode:@Image ➡️@Text,@NormalizedText ➡️@Text,@Text ➡️@WholeText.

Java

There are still many usages of@Image which are not refactored yet. This will be done after PMD 7 is releasedby deprecating the attribute and providing alternatives.

See also issueDeprecate getImage/@Image #4787.

Some nodes have already the image attribute (and others) deprecated. These deprecated attributes are removed now:

JavaScript

JSP

Modelica

PLSQL

There are many usages of@Image. These will be refactored after PMD 7 is releasedby deprecating the attribute and providing alternatives.

See also issueDeprecate getImage/@Image #4787.

Scala

XML (and POM)

When usingXPathRule, text of text nodes was exposed as@Image ofnormal element type nodes. Now the attribute is called@Text.

Note: In general, it is recommended to useDomXPathRule instead,which exposes text nodes as real XPath/XML text nodes which conforms to the XPath spec.There is no difference, text of text nodes can be selected usingtext().

Java AST

The Java grammar has been refactored substantially in order to make it easier to maintain and more correctregarding the Java Language Specification.

Here you can see the most important changes as a comparison between the PMD 6 AST (“Old AST”) andPMD 7 AST (“New AST”) and with some background info about the changes.

When in doubt, it is recommended to use thePMD Designerwhich can also display the AST.

Renamed classes / interfaces

Annotations

  • What: Annotations are consolidated into a single node.SingleMemberAnnotation,NormalAnnotation andMarkerAnnotationare removed in favour ofASTAnnotation. The Name node is removed, replaced by aASTClassType.
  • Why: Those different node types implement a syntax-only distinction, that only makes semantically equivalent annotationshave different possible representations. For example,@A and@A() are semantically equivalent, yet they wereparsed as MarkerAnnotation resp. NormalAnnotation. Similarly,@A("") and@A(value="") were parsed asSingleMemberAnnotation resp. NormalAnnotation. This also makes parsing much simpler. The nested ClassOrInterfacetype is used to share the disambiguation logic.
  • Related issue:[java] Use single node for annotations (#2282)
Annotation AST Examples
CodeOld AST (PMD 6)New AST (PMD 7)
@A
└─Annotation"A"└─MarkerAnnotation"A"└─Name"A"
└─Annotation"A"└─ClassOrInterfaceType"A"
@A()
└─Annotation"A"└─NormalAnnotation"A"└─Name"A"
└─Annotation"A"├─ClassType"A"└─AnnotationMemberList
@A(value="v")
└─Annotation"A"└─NormalAnnotation"A"├─Name"A"└─MemberValuePairs└─MemberValuePair"value"└─MemberValue└─PrimaryExpression└─PrimaryPrefix└─Literal'"v"'
└─Annotation"A"├─ClassType"A"└─AnnotationMemberList└─MemberValuePair"value"[@Shorthand=false()]└─StringLiteral'"v"'
@A("v")
└─Annotation"A"└─SingleMemberAnnotation"A"├─Name"A"└─MemberValue└─PrimaryExpression└─PrimaryPrefix└─Literal'"v"'
└─Annotation"A"├─ClassType"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"├─ClassType"A"└─AnnotationMemberList├─MemberValuePair"value"[@Shorthand=false()]└─StringLiteral'"v"'└─MemberValuePair"on"└─BooleanLiteral[@True=true()]
Annotation nesting
Annotation nesting Examples
CodeOld AST (PMD 6)New AST (PMD 7)
Method
@Apublicvoidset(intx){}
└─ClassOrInterfaceBodyDeclaration├─Annotation"A"└─MarkerAnnotation"A"└─Name"A"└─MethodDeclaration├─ResultType[@Void=true]├─...
└─MethodDeclaration├─ModifierList└─Annotation"A"└─ClassType"A"├─VoidType├─...
Top-level type declaration
@AclassC{}
└─TypeDeclaration├─Annotation"A"└─MarkerAnnotation"A"└─Name"A"└─ClassOrInterfaceDeclaration"C"└─ClassOrInterfaceBody
└─ClassDeclaration├─ModifierList└─Annotation"A"└─ClassType"A"└─ClassBody
Cast expression
varx=(@AT.@BS)expr;
└─CastExpression├─Annotation"A"└─MarkerAnnotation"A"└─Name"A"├─Type└─ReferenceType└─ClassOrInterfaceType"T.S"└─Annotation"B"└─MarkerAnnotation"B"└─Name"B"└─PrimaryExpression└─PrimaryPrefix└─Name"expr"
└─CastExpression├─ClassType"S"├─ClassType"T"└─Annotation"A"└─ClassType"A"└─Annotation"B"└─ClassType"B"└─VariableAccess"expr"
Cast expression with intersection
varx=(@AT&S)expr;
└─CastExpression├─Annotation"A"└─MarkerAnnotation"A"└─Name"A"├─Type└─ReferenceType└─ClassOrInterfaceType"T"├─ReferenceType└─ClassOrInterfaceType"S"└─PrimaryExpression└─PrimaryPrefix└─Name"expr"
└─CastExpression├─IntersectionType├─ClassType"T"└─Annotation"A"└─ClassType"A"└─ClassType"S"└─VariableAccess"expr"
Notice@A binds toT, notT & S
Constructor call
new@AT()
└─AllocationExpression├─Annotation"A"└─MarkerAnnotation"A"└─Name"A"├─ClassOrInterfaceType"T"└─Arguments
└─ConstructorCall├─ClassType"T"└─Annotation"A"└─ClassType"A"└─ArgumentList
Array allocation
new@Aint[0]
└─AllocationExpression├─Annotation"A"└─MarkerAnnotation"A"└─Name"A"├─PrimitiveType"int"└─ArrayDimsAndInits└─Expression└─PrimaryExpression└─PrimaryPrefix└─Literal"0"
└─ArrayAllocation└─ArrayType├─PrimitiveType"int"└─Annotation"A"└─ClassType"A"└─ArrayDimensions└─ArrayDimExpr└─NumericLiteral"0"
Array type
@Aint@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"└─ClassType"A"├─ArrayType├─PrimitiveType"int"└─ArrayDimensions└─ArrayTypeDim└─Annotation"B"└─ClassType"B"└─VariableDeclarator└─VariableId"x"
Type parameters
<@AT,@BSextends@CObject>
└─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"└─ClassType"A"└─TypeParameter"S"[@TypeBound=true()]├─Annotation"B"└─ClassType"B"└─ClassType"Object"└─Annotation"C"└─ClassType"C"
  • TypeParameters now only can have TypeParameter as a child
  • Annotations that apply to the param arein the param
  • Annotations that apply to the bound arein the type
  • This removes the need for TypeBound, because annotations are cleanly placed.
Enum constants
enumE{@AE1,@BE2;}
└─EnumBody├─Annotation"A"└─MarkerAnnotation"A"└─Name"A"├─EnumConstant"E1"├─Annotation"B"└─MarkerAnnotation"B"└─Name"B"└─EnumConstant"E2"
└─EnumBody├─EnumConstant"E1"├─ModifierList└─Annotation"A"└─ClassType"A"└─VariableId"E1"└─EnumConstant"E2"├─ModifierList└─Annotation"B"└─ClassType"B"└─VariableId"E2"
  • Annotations are not just randomly in the enum body anymore

Types

Type and ReferenceType
  • What:
  • Why:
    • some syntactic contexts only allow reference types, other allow any kind of type. If you want to match all typesof 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 typeis a type, and searching for ASTType shouldyield 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, soTypeandReferenceType name tests won’t match anything anymore.
    • Type/ReferenceType/ClassOrInterfaceType ➡️ClassType
    • Type/PrimitiveType ➡️PrimitiveType.
    • Type/ReferenceType[@ArrayDepth > 1]/ClassOrInterfaceType ➡️ArrayType/ClassType.
    • Type/ReferenceType/PrimitiveType ➡️ArrayType/PrimitiveType.
    • Note that in most cases you should check the type of a variable with e.g.VariableId[pmd-java:typeIs("java.lang.String[]")] because itconsiders the additional dimensions on declarations likeString foo[];.The Java equivalent isTypeHelper.isA(id, String[].class);
Type and ReferenceType Examples
CodeOld AST (PMD 6)New AST (PMD 7)
// in the context of a variable declarationList<String>strs;
└─Type(1)└─ReferenceType└─ClassOrInterfaceType"List"└─TypeArguments└─TypeArgument└─ReferenceType(2)└─ClassOrInterfaceType"String"
  1. Notice that there is a Type node here, since a local var can have a primitive type.
  2. In contrast, notice that there is no Type here, since only reference types are allowed as type arguments.
└─ClassType"List"└─TypeArguments└─ClassType"String"
  • ClassType implements ASTReferenceType, which implements ASTType.
Array changes
Array Examples
CodeOld AST (PMD 6)New AST (PMD 7)
String[][]myArray;
└─Type[@ArrayType=true()]└─ReferenceType└─ClassOrInterfaceType[@Array=true()][@ArrayDepth=2]"String"
└─ArrayType[@ArrayDepth=2]├─ClassType"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]├─ClassType"String"└─ArrayDimensions[@Size=2]├─ArrayTypeDim└─Annotation"Annotation1"└─ClassType"Annotation1"└─ArrayTypeDim└─Annotation"Annotation2"└─ClassType"Annotation2"
newint[2][];new@Barint[3][2];newFoo[]{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[@ArrayDepth=2]├─PrimitiveType"int"└─Annotation"Bar"└─ClassType"Bar"└─ArrayDimensions[@Size=2]├─ArrayDimExpr└─NumericLiteral"3"└─ArrayDimExpr└─NumericLiteral"2"└─ArrayAllocation[@ArrayDepth=1]└─ArrayType[@ArrayDepth=1]├─ClassType"Foo"└─ArrayDimensions[@Size=1]└─ArrayTypeDim└─ArrayInitializer[@Length=2]├─VariableAccess"f"└─VariableAccess"g"
ClassType nesting
ClassType Examples
CodeOld AST (PMD 6)New AST (PMD 7)
Map.Entry<K,V>
└─ClassOrInterfaceType"Map.Entry"└─TypeArguments├─TypeArgument└─ReferenceType└─ClassOrInterfaceType"K"└─TypeArgument└─ReferenceType└─ClassOrInterfaceType"V"
└─ClassType"Entry"├─ClassType"Map"└─TypeArguments[@Size=2]├─ClassType"K"└─ClassType"V"
First<K>.Second.Third<V>
└─ClassOrInterfaceType"First.Second.Third"├─TypeArguments└─TypeArgument└─ReferenceType└─ClassOrInterfaceType"K"└─TypeArguments└─TypeArgument└─ReferenceType└─ClassOrInterfaceType"V"
└─ClassType"Third"├─ClassType"Second"└─ClassType"First"└─TypeArguments[@Size=1]└─ClassType"K"└─TypeArguments[@Size=1]└─ClassType"V"
TypeArgument and WildcardType
  • What:
  • Why: Because wildcard types are types in their own right, and having a node to represent them skims several levelsof nesting off.
TypeArgument and WildcardType Examples
CodeOld AST (PMD 6)New AST (PMD 7)
Entry<String,?extendsNode>
└─ClassOrInterfaceType"Entry"└─TypeArguments├─TypeArgument└─ReferenceType└─ClassOrInterfaceType"String"└─TypeArgument[@Wildcard=true()]└─WildcardBounds[@UpperBound=true()]└─ReferenceType└─ClassOrInterfaceType"Node"
└─ClassType"Entry"└─TypeArguments[@Size=2]├─ClassType"String"└─WildcardType[@UpperBound=true()]└─ClassType"Node"
List<?>
└─ClassOrInterfaceType"List"└─TypeArguments└─TypeArgument[@Wildcard=true()]
└─ClassType"List"└─TypeArguments[@Size=1]└─WildcardType[@UpperBound=true()]

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 toASTAmbiguousName in that it describes nothingabout 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
CodeOld AST (PMD 6)New AST (PMD 7)
importjava.util.ArrayList;importstaticjava.util.Comparator.reverseOrder;importjava.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"
packagecom.example.tool;
└─PackageDeclaration└─Name"com.example.tool"
└─PackageDeclaration"com.example.tool"└─ModifierList
Modifier lists
  • What:ModifierOwner (formerly AccessNode) is now based on a node:ASTModifierList.That node representsmodifiers occurring before a declaration. It provides a flexible API to query modifiers, both explicit andimplicit. All declaration nodes now have such a modifier list, even if it’s implicit (no explicit modifiers).
  • Why: ModifierOwner (formerly AccessNode) gave a lot of irrelevant methods to its subtypes.E.g.ASTFieldDeclaration::isSynchronizedmakes no sense. Now, these irrelevant methods don’t clutter the API. The API of ModifierList is both moregeneral and flexible.
  • Related issue:[java] Rework AccessNode (#2259)
Modifier lists Examples
CodeOld AST (PMD 6)New AST (PMD 7)
Method
@Apublicvoidset(finalintx,inty){}
└─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"└─ClassType"A"├─VoidType└─FormalParameters├─FormalParameter[pmd-java:modifiers()='final']├─ModifierList└─VariableId"x"└─FormalParameter[pmd-java:modifiers()=()]├─ModifierList└─VariableId"y"
Top-level type declaration
public@AclassC{}
└─TypeDeclaration├─Annotation"A"└─MarkerAnnotation"A"└─Name"A"└─ClassOrInterfaceDeclaration[@Public=true()]"C"└─ClassOrInterfaceBody
└─ClassDeclaration[pmd-java:modifiers()='public']"C"├─ModifierList└─Annotation"A"└─ClassType"A"└─ClassBody
Flattened body declarations
Flattened body declarations Examples
CodeOld AST (PMD 6)New AST (PMD 7)
publicclassFlat{privateintf;}
└─CompilationUnit└─TypeDeclaration└─ClassOrInterfaceDeclaration"Flat"└─ClassOrInterfaceBody└─ClassOrInterfaceBodyDeclaration└─FieldDeclaration├─Type└─PrimitiveType"int"└─VariableDeclarator└─VariableDeclaratorId"f"
└─CompilationUnit└─ClassDeclaration"Flat"├─ModifierList└─ClassBody└─FieldDeclaration├─ModifierList├─PrimitiveType"int"└─VariableDeclarator└─VariableId"f"
public@interfaceFlatAnnotation{Stringvalue()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├─ClassType"String"├─FormalParameters└─DefaultValue└─StringLiteral"\"\""
Module declarations
  • What: Removes the generic Name node and uses insteadASTClassType where appropriate. Alsouses 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
CodeOld AST (PMD 6)New AST (PMD 7)
openmodulecom.example.foo{requirescom.example.foo.http;requiresjava.logging;requirestransitivecom.example.foo.network;exportscom.example.foo.bar;exportscom.example.foo.internaltocom.example.foo.probe;usescom.example.foo.spi.Intf;providescom.example.foo.spi.Intfwithcom.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└─ClassType[pmd-java:typeIs("com.example.foo.spi.Intf")]└─ModuleProvidesDirective├─ClassType[pmd-java:typeIs("com.example.foo.spi.Intf")]└─ClassType[pmd-java:typeIs("com.example.foo.Impl")]
Anonymous class declarations
Anonymous class declarations Examples
CodeOld AST (PMD 6)New AST (PMD 7)
Objectanonymous=newObject(){};
└─LocalVariableDeclaration├─Type└─ReferenceType└─ClassOrInterfaceType[@Image='Object']└─VariableDeclarator├─VariableDeclaratorId"anonymous"└─VariableInitializer└─Expression└─PrimaryExpression└─PrimaryPrefix└─AllocationExpression├─ClassOrInterfaceType[@AnonymousClass=true()][@Image='Object']├─Arguments└─ClassOrInterfaceBody
└─LocalVariableDeclaration├─ModifierList├─ClassType[@SimpleName='Object']└─VariableDeclarator├─VariableId[@Name='anonymous']└─ConstructorCall├─ClassType[@SimpleName='Object']├─ArgumentList└─AnonymousClassDeclaration├─ModifierList└─ClassBody

Method and Constructor declarations

Method grammar simplification
  • What: Simplify and align the grammar used for method and constructor declarations. The methods in an annotationtype are now also method declarations.
  • Why: The method declaration had a nested node “MethodDeclarator”, which was not available for constructordeclarations. This made it difficult to write rules, that concern both methods and constructors withoutexplicitly differentiate between these two.
  • Related issue:[java] Align method and constructor declaration grammar (#2034)
Method grammar Examples
CodeOld AST (PMD 6)New AST (PMD 7)
publicclassSample{publicSample(intarg)throwsException{super();greet(arg);}publicvoidgreet(intarg)throwsException{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└─...
└─ClassBody├─ConstructorDeclaration[@Name='Sample']├─ModifierList├─FormalParameters└─FormalParameter├─...├─ThrowsList└─ClassType[@SimpleName='Exception']└─Block├─ExplicitConstructorInvocation└─ArgumentList└─ExpressionStatement└─...└─MethodDeclaration[@Name='greet']├─ModifierList├─VoidType├─FormalParameters└─FormalParameter├─...├─ThrowsList└─ClassType[@SimpleName='Exception']└─Block└─ExpressionStatement└─...
public@interfaceMyAnnotation{intvalue()default1;}
└─AnnotationTypeDeclaration[@SimpleName='MyAnnotation']└─AnnotationTypeBody└─AnnotationTypeMemberDeclaration└─AnnotationMethodDeclaration[@Image='value']├─Type...└─DefaultValue...
└─AnnotationTypeDeclaration[@SimpleName='MyAnnotation']├─ModifierList└─AnnotationTypeBody└─MethodDeclaration[@Name='value']├─ModifierList├─PrimitiveType├─FormalParameters└─DefaultValue...
Formal parameters
  • What: UseASTFormalParameter 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 (aASTUnionType now)
Formal parameters Examples
CodeOld AST (PMD 6)New AST (PMD 7)
try{}catch(@AIOException|IllegalArgumentExceptione){}
└─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']└─ClassType[@SimpleName='A']├─UnionType├─ClassType[@SimpleName='IOException']└─ClassType[@SimpleName='IllegalArgumentException']└─VariableId[@Name='e']└─Block
(a,b)->{};c->{};(@Avard)->{};(@Ainte)->{};
└─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└─VariableId[@Name='a']└─LambdaParameter├─ModifierList└─VariableId[@Name='b']└─Block└─ExpressionStatement└─LambdaExpression├─LambdaParameterList└─LambdaParameter├─ModifierList└─VariableId[@Name='c']└─Block└─ExpressionStatement└─LambdaExpression├─LambdaParameterList└─LambdaParameter├─ModifierList└─Annotation[@SimpleName='A']└─ClassType[@SimpleName='A']└─VariableId[@Name='d']└─Block└─ExpressionStatement└─LambdaExpression├─LambdaParameterList└─LambdaParameter├─ModifierList└─Annotation[@SimpleName='A']└─ClassType[@SimpleName='A']├─PrimitiveType[@Kind='int']└─VariableId[@Name='e']└─Block
New node for explicit receiver parameter
explicit receiver parameter Examples
CodeOld AST (PMD 6)New AST (PMD 7)
voidmyMethod(@AFoothis,Fooother){}
└─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└─ClassType"Foo"└─Annotation"A"└─ClassType"A"└─FormalParameter├─ModifierList├─ClassType"Foo"└─VariableId"other"
Varargs
  • What: parse the varargs ellipsis as anASTArrayType.
  • Why: this improves regularity of the grammar, and allows type annotations to be added to the ellipsis
Varargs Examples
CodeOld AST (PMD 6)New AST (PMD 7)
voidmyMethod(int...is){}
└─FormalParameter[@Varargs=true()]├─Type└─PrimitiveType"int"└─VariableDeclaratorId"is"
└─FormalParameter[@Varargs=true()]├─ModifierList├─ArrayType├─PrimitiveType"int"└─ArrayDimensions└─ArrayTypeDim[@Varargs=true()]└─VariableId"is"
voidmyMethod(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"└─ClassType"A"└─VariableId"is"
voidmyMethod(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()]└─VariableId"is"
Add void type node to replace ResultType
Void Type Examples
CodeOld AST (PMD 6)New AST (PMD 7)
voidfoo();
└─MethodDeclaration"foo"├─ResultType[@Void=true()]└─MethodDeclarator└─FormalParameters
└─MethodDeclaration"foo"├─ModifierList├─VoidType└─FormalParameters
intfoo();
└─MethodDeclaration"foo"├─ResultType[@Void=false()]└─Type└─PrimitiveType"int"└─MethodDeclarator└─FormalParameters
└─MethodDeclaration"foo"├─ModifierList├─PrimitiveType"int"└─FormalParameters

Statements

Statements are flattened
  • What: Statements are flattened. There are no superfluous BlockStatement and Statement nodes anymore.All children of aASTBlock are by definitionASTStatements, which is now an interface implemented by all statements.
  • Why: This simplifies the tree traversal. The removed nodes BlockStatement and Statement didn’t add anyadditional information. We only need a Statement abstraction. BlockStatement was used to enforce, that novariable 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
CodeOld AST (PMD 6)New AST (PMD 7)
inti;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└─VariableId"i"└─ExpressionStatement└─AssignmentExpression"="├─VariableAccess"i"└─NumericLiteral"1"
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. somerules only apply to one or the other, and it was complicated to write a rule that works with both differentsubtrees (for loops have additional children ForInit and ForUpdate)
  • Related issue:[java] Improve statement grammar (#2164)
For-each statement Examples
CodeOld AST (PMD 6)New AST (PMD 7)
for(Strings: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├─ClassType"String"└─VariableDeclarator"s"└─VariableId"s"├─MethodCall"of"├─TypeExpression└─ClassType"List"└─ArgumentList(2)├─StringLiteral[@Image='"a"']└─StringLiteral[@Image='"b"']└─Block
New nodes for ExpressionStatement, LocalClassStatement
ExpressionStatement, LocalClassStatement Examples
CodeOld AST (PMD 6)New AST (PMD 7)
i++;classLocalClass{}
└─Block├─BlockStatement└─Statement└─StatementExpression└─PostfixExpression"++"└─PrimaryExpression└─PrimaryPrefix└─Name"i"└─BlockStatement└─ClassOrInterfaceDeclaration[@Local=true()]"LocalClass"└─ClassOrInterfaceBody
└─Block├─ExpressionStatement└─UnaryExpression"++"└─VariableAccess"i"└─LocalClassStatement└─ClassDeclaration"LocalClass"├─ModifierList└─ClassBody
Improve try-with-resources grammar
Try-With-Resources Examples
CodeOld AST (PMD 6)New AST (PMD 7)
try(InputStreamin=newFileInputStream();OutputStreamout=newFileOutputStream();){}
└─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├─ClassType"InputStream"└─VariableDeclarator├─VariableId"in"└─ConstructorCall├─ClassType"FileInputStream"└─ArgumentList(0)└─Resource[@ConciseResource=false()]"out"└─LocalVariableDeclaration├─ModifierList├─ClassType"OutputStream"└─VariableDeclarator├─VariableId"out"└─ConstructorCall├─ClassType"FileOutputStream"└─ArgumentList(0)
InputStreamin=newFileInputStream();try(in){}
└─TryStatement└─ResourceSpecification└─Resources└─Resource"in"└─Name"in"
└─TryStatement└─ResourceList[@TrailingSemiColon=false()](1)└─Resource[@ConciseResource=true()]"in"└─VariableAccess"in"

Expressions

  • ASTExpression andASTPrimaryExpression havebeen turned into interfaces. These added no information to the AST and increasedits depth unnecessarily. All expressions implement the first interface. Both ofthose 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, soExpression orPrimaryExpressionname tests won’t match anything anymore. However, the axis step *[@Expression=true()] matches any expression.
New nodes for different literals types
Literals Examples
CodeOld AST (PMD 6)New AST (PMD 7)
charc='c';booleanb=true;inti=1;doubled=1.0;Strings="s";Objectn=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
Method calls, constructor calls, array allocations Examples
CodeOld AST (PMD 6)New AST (PMD 7)
o.myMethod("a");newObject("b");newint[10];newint[]{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├─ClassType"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
Method call chain Examples
CodeOld AST (PMD 6)New AST (PMD 7)
newFoo().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├─ClassType"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:

newFoo().bar.foo(1)└───────┘ConstructorCall└───────────┘FieldAccess└──────────────────┘MethodCall

This makes the AST more regular and easier to navigate. Each node containsthe other nodes that are relevant to it (e.g. arguments) instead of thembeing spread out over several siblings. The API of all nodes has beenenriched 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 shouldgive you the information you need quickly.

Note: this also affect binary expressions likeASTInfixExpression.E.g.a+b+c is not parsed as

AdditiveExpression+ (a)+ (b)+ (c)

But it is now (note: AdditiveExpression is now InfixExpression)

InfixExpression+ InfixExpression  + (a)  + (b)+ (c)
Field access, array access, variable access
Field access, array access, variable access Examples
CodeOld AST (PMD 6)New AST (PMD 7)
field=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└─ClassType"Foo"└─VariableAccess[@AccessType="READ"]"localVar"
  • As seen above, an unqualified field access currently shows up as a VariableAccess. This may be fixed future versions of PMD.
Explicit nodes for this/super expressions
this/super expressions Examples
CodeOld AST (PMD 6)New AST (PMD 7)
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
Type expressions Examples
CodeOld AST (PMD 6)New AST (PMD 7)
Foo.staticMethod();if(xinstanceofFoo){}varx=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└─ClassType"Foo"└─ArgumentList(0)└─IfStatement├─InfixExpression"instanceof"├─VariableAccess[@AccessType="READ"]"x"└─TypeExpression└─ClassType"Foo"└─Block└─LocalVariableDeclaration├─ModifierList└─VariableDeclarator├─VariableId"x"└─MethodReference"method"└─TypeExpression└─ClassType"Foo"
Merge unary expressions
Unary Expressions Examples
CodeOld AST (PMD 6)New AST (PMD 7)
++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
  • What: For each operator, there were separate AST nodes (like AdditiveExpression, AndExpression, …).These are now unified into aInfixExpression, 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 areused with which operator. This information was lost if more than 2 operands where used and the tree wasflattened with PMD 6.
  • Related issue:[java] Make binary operators left-recursive (#1979)
Binary operators Examples
CodeOld AST (PMD 6)New AST (PMD 7)
inti=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
  • What: Parentheses are not modelled in the AST anymore, but can be checked with the attributes@Parenthesizedand@ParenthesisDepth
  • Why: This keeps the tree flat while still preserving the information. The tree is the same in case of unnecessaryparenthesis, which makes it harder to fool rules that look at the structure of the tree.
  • Related issue:[java] Remove ParenthesizedExpr (#1872)
Parenthesized expressions Examples
CodeOld AST (PMD 6)New AST (PMD 7)
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"

Apex AST

PMD 7.0.0 switched the underlying parser for Apex code from Jorje toSummit AST,which is based on an open source grammar for Apex:apex-parser.

The produced AST is mostly compatible, there are some unavoidable changes however:

  • NodeMethod (ASTMethod)
    • No attribute@Synthetic anymore. Unlike Jorje, Summit AST doesn’t generate synthetic methods anymore, sothis attribute would have been always false and is of no use. Therefore it has been removed completely.
    • There will be no methods anymore with the name<clinit>,<init>.
  • There is no nodeBridgeMethodCreator anymore. This was an artificially generated node by Jorje. Since thenew parser doesn’t generate synthetic methods anymore, this node is not needed anymore.
  • There is in general no attribute@Namespace anymore. The attribute has been removed, as it was never fullyimplemented. It always returned an empty string.
  • NodeReferenceExpression (ASTReferenceExpression)
    • No attribute@Context anymore. It was not used and always returnednull.

Language versions

  • Since all languages now have defined language versions, you could now write rules that apply only for specificversions (usingminimumLanguageVersion andmaximumLanguageVersion).
  • All languages have a default version. If no specific version on the CLI is given using--use-version, thenthis 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 CLIpmd check --help.
  • See alsoChanged: Language versions

Migrating custom CPD language modules

This is only relevant, if you are maintaining a CPD language module for a custom language.

  • Instead ofAbstractLanguage extend nowCpdOnlyLanguageModuleBase.
  • Instead ofAntlrTokenManager use nowTokenManager
  • Instead ofAntlrTokenFilter also use nowTokenManager
  • Instead ofAntlrTokenFilter extend nowBaseTokenFilter
  • CPD Module discovery change. The service loader won’t load anymoresrc/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Languagebut insteadsrc/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language. This is the unifiedlanguage interface for both PMD and CPD capable languages. See also the subinterfacesCpdCapableLanguage andPmdCapableLanguage.
  • The documentationHow to add a new CPD language has been updatedto reflect these changes.

Build Tools

Note:

When you switch from PMD 6.x to PMD 7 in your build tools, you most likely need to review yourruleset(s) as well and check for removed rules.See the use caseI’m using only built-in rules above.

Ant

  • The Ant tasksPMDTask 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 toimport the tasks into your build file.
  • When using the guideAnt Task Usage then no change is needed, sincethe pmd-ant jar file is included in the binary distribution of PMD. It is part of PMD’s lib folder.

Maven

Gradle

  • Gradle uses internally PMD’s Ant task to execute PMD
  • Gradle 8.6 supports PMD 7 out of the box, but does not yet use PMD 7 by default.
  • You can settoolVersion = "7.15.0".
  • Only for older gradle versions you need to configure the dependencies manually for now, sincethe ant task is in an own dependency with PMD 7:
    pmd'net.sourceforge.pmd:pmd-ant:7.15.0'pmd'net.sourceforge.pmd:pmd-java:7.15.0'
  • SeeSupport for PMD 7.0

XML Report Format

TheXML Report format supports renderingsuppressed violations.

The content of the attributesuppressiontype is changed in PMD 7.0.0:

  • nopmd ➡️//nopmd
  • annotation ➡️@suppresswarnings
  • xpath - new value. Suppressed via property “violationSuppressXPath”.
  • regex - new value. Suppressed via property “violationSuppressRegex”.

This documentation is written in markdown.
If there is something missing or can be improved, edit this page on github and create a PR: Edit on GitHub

©2025 PMD Open Source Project. All rights reserved.
Page last updated: June 2024 (7.3.0)
Site last generated: Jun 27, 2025

PMD                logo


[8]ページ先頭

©2009-2025 Movatter.jp