This page describes XPath rule support in more details

This page describes some points of XPath rule support in more details. See also the tutorial about how to write an XPath rule.

XPath version

PMD uses XPath 3.1 for its XPath rules since PMD 7. Before then, the default version was XPath 1.0, with opt-in support for XPath 2.0.

See the Saxonica documentation for an introduction to new features in XPath 3.1.

The property version of XPathRule is deprecated and will be removed.

DOM representation of ASTs

XPath rules view the AST as an XML-like DOM, which is what the XPath language is defined on. Concretely, this means:

  • Every AST node is viewed as an XML element
    • The element has for local name the value of getXPathNodeName for the given node
  • Some Java getters are exposed as XML attributes on those elements
    • This means, that documentation for attributes can be found in our Javadocs. For example, the attribute @SimpleName of the Java node EnumDeclaration is backed by the Java getter getSimpleName.

Value conversion

To represent attributes, we must map Java values to XPath Data Model (XDM) values. In the following table we refer to the type conversion function as conv, a function from Java types to XDM types.

Java type T XSD type conv(T)
int xs:integer
long xs:integer
double xs:decimal
float xs:decimal
boolean xs:boolean
String xs:string
Character xs:string
Enum<E> xs:string (uses Object::toString)
List<E> conv(E)* (a sequence type)

The same conv function is used to translate rule property values to XDM values.

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: and string: should not be mentioned explicitly. In XPath 2.0 mode, the engine will complain about an undeclared namespace, but the functions are in the default namespace. Removing the namespace prefixes fixes it.
    • fn:substring("Foo", 1)substring("Foo", 1)
  • 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 functions true() and false(). If your XPath 1.0 rule tests an attribute like @Private="true", then it just needs to be changed to @Private=true() when migrating. A type error will warn you that you must update the comparison. More is explained on issue #1244.
    • "true", 'true'true()
    • "false", 'false'false()
  • 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 named Foo. In XPath 2.0, that expression matches the root, if it is named Foo. Consider the following tree:
    Foo
    └─ Foo
    └─ Foo
    

    Then /Foo will match the root in XPath 2, and the other nodes (but not the root) in XPath 1. See eg an issue caused by this in Apex, with nested classes.

Rule properties

See Defining rule properties

PMD extension functions

PMD provides some language-specific XPath functions to access semantic information from the AST.

On XPath 2.0, the namespace of custom PMD function must be explicitly mentioned.

All languages

Functions available to all languages are in the namespace pmd.

Function name Description (click for details)
fileName Returns the simple name of the current file

pmd:fileName() as xs:string

Returns the current simple file name, without path but including the extension. This can be used to write rules that check file naming conventions.
Since
PMD 6.38.0
Remarks
The requires the context node to be an element
Examples
//b[pmd:fileName() = 'Foo.xml']
Matches any <b> tags in files called Foo.xml.
startLine Returns the start line of the given node

pmd:startLine(xs:element) as xs:int

Returns the line where the node starts in the source file. Line numbers are 1-based.
Since
PMD 6.44.0
Remarks
The function is not context-dependent, but takes a node as its first parameter.
Parameters
element as xs:element
Any element node
Examples
//b[pmd:startLine(.) > 5]
Matches any <b> node which starts after the fifth line.
endLine Returns the end line of the given node

pmd:endLine(xs:element) as xs:int

Returns the line where the node ends in the source file. Line numbers are 1-based.
Since
PMD 6.44.0
Remarks
The function is not context-dependent, but takes a node as its first parameter.
Parameters
element as xs:element
Any element node
Examples
//b[pmd:endLine(.) == pmd:startLine(.)]
Matches any <b> node which doesn't span more than one line.
startColumn Returns the start column of the given node (inclusive)

pmd:startColumn(xs:element) as xs:int

Returns the column number where the node starts in the source file. Column numbers are 1-based. The start column is inclusive.
Since
PMD 6.44.0
Remarks
The function is not context-dependent, but takes a node as its first parameter.
Parameters
element as xs:element
Any element node
Examples
//b[pmd:startColumn(.) = 1]
Matches any <b> node which starts on the first column of a line
endColumn Returns the end column of the given node (exclusive)

pmd:endColumn(xs:element) as xs:int

Returns the column number where the node ends in the source file. Column numbers are 1-based. The end column is exclusive.
Since
PMD 6.44.0
Remarks
The function is not context-dependent, but takes a node as its first parameter.
Parameters
element as xs:element
Any element node
Examples
//b[pmd:startLine(.) = pmd:endLine(.) and pmd:endColumn(.) - pmd:startColumn(.) = 1]
Matches any <b> node which spans exactly one character

Java

Java functions are in the namespace pmd-java.

Function name Description (click for details)
nodeIs Tests the runtime type of the node instance

pmd-java:nodeIs(xs:string) as xs:boolean

Returns true if the runtime type of the AST node is a subtype of the given class. Contrary to typeIs, this tests the type of the AST node. For example, the AST node for a literal (e.g. 5d) has type ASTNumericLiteral, and this function will ignore the static type of the expression (double)
Remarks
Parameters
nodeClassName as xs:string
Simple name of a class or interface in package net.sourceforge.pmd.lang.java.ast, without the 'AST' prefix
Examples
//*[pmd-java:nodeIs("Expression")]
Matches all nodes that implement ASTExpression
//*[pmd-java:nodeIs("AnyTypeDeclaration")]
Matches all nodes that implement ASTAnyTypeDeclaration
//*[pmd-java:nodeIs("Foo")]
Runtime error, there's no class ASTFoo in the package
typeIs Tests a node's static type

pmd-java:typeIs(xs:string) as xs:boolean

Returns true if the context node's static Java type is a subtype of the given type. This tests for the resolved type of the Java construct, not the type of the AST node. For example, the AST node for a literal (e.g. 5d) has type ASTNumericLiteral, however this function will compare the type of the literal (eg here, double) against the argument.
Remarks
The context node must be a TypeNode
Parameters
javaQualifiedName as xs:string
The qualified name of a Java class, possibly with pairs of brackets to indicate an array type. Can also be a primitive type name.
Examples
//FormalParameter[pmd-java:typeIs("java.lang.String[]")]
Matches formal parameters of type String[] (including vararg parameters)
//VariableDeclaratorId[pmd-java:typeIs("java.lang.List")]
Matches variable declarators of type List or any of its subtypes (including e.g. ArrayList)
typeIsExactly Tests a node's static type, ignoring subtypes

pmd-java:typeIsExactly(xs:string) as xs:boolean

Returns true if the context node's static type is exactly the given type. In particular, returns false if the context node's type is a subtype of the given type.
Remarks
The context node must be a TypeNode
Parameters
javaQualifiedName as xs:string
The qualified name of a Java class, possibly with pairs of brackets to indicate an array type. Can also be a primitive type name.
Examples
//VariableDeclaratorId[pmd-java:typeIsExactly("java.lang.List")]
Matches variable declarators of type List (but not e.g. ArrayList)
metric Computes and returns the value of a metric

pmd-java:metric(xs:string) as xs:decimal?

Returns the value of the metric as evaluated on the context node. If the metric cannot be computed on that node, returns an empty sequence (which is falsy).
Remarks
Parameters
metricKey as xs:string
The name of a metric in JavaMetrics (or an alias thereof).
Examples
//ClassOrInterfaceDeclaration[metric('NCSS') > 200]
//MethodDeclaration[metric('CYCLO') > 10 and metric('NCSS') > 20]
//TypeParameter[metric('idontexist') > 50]
Error: no such metric
hasAnnotation Tests whether an annotation is present on the node

pmd-java:hasAnnotation(xs:string) as xs:boolean

Returns true if the node has an annotation with the given qualified name
Remarks
The context node must be an Annotatable, otherwise this returns false
Parameters
annotationClassName as xs:string
Canonical name of an annotation type
Examples
//MethodDeclaration[pmd-java:hasAnnotation("java.lang.Override")]
Matches all method declarations that are annotated with @Override
modifiers Produce the effective modifiers of a node

pmd-java:modifiers() as xs:string*

Returns a sequence of the effective modifiers of a node as strings. This is documented on getEffectiveModifiers.
Remarks
The context node must be an AccessNode, otherwise this returns an empty sequence
Examples
//MethodDeclaration[pmd-java:modifiers() = "native"]
Matches native method declarations
//MethodDeclaration[pmd-java:modifiers() = ("native", "static")]
Matches method declarations that have a 'native' OR a 'static' modifier. This may be counter-intuitive.
//MethodDeclaration[pmd-java:modifiers() = "public"]
Matches method declarations that have a 'public' modifier, explicit or implicit. For example, this would match methods in interfaces, which implicitly have the modifier. Use the explicitModifiers function if you don't want the implicit part. Also note that @Visibility = 'public' is a better use of the API, in this particular instance.
explicitModifiers Produce the explicit modifiers of a node

pmd-java:explicitModifiers() as xs:string*

Returns a sequence of the explicit modifiers of a node as strings. This is documented on getExplicitModifiers.
Remarks
The context node must be an AccessNode, otherwise this returns an empty sequence
Examples
//MethodDeclaration[pmd-java:explicitModifiers() = "public"]
Matches method declarations that have an explicit 'public' modifier.
matchesSig Matches the signature called by a method or constructor call

pmd-java:matchesSig(xs:string) as xs:boolean

Uses an TypeTestUtil.InvocationMatcher to test the method signature called by the context node. The format of the parameter is described on that class.
Remarks
The context node must be an InvocationNode, otherwise this returns false
Parameters
sig as xs:string
A signature, in the format described on TypeTestUtil.InvocationMatcher
Examples
//MethodCall[pmd-java:matchesSig("_#equals(java.lang.Object)")]
Matches calls to the method equals on any receiver type
//MethodCall[pmd-java:matchesSig("java.lang.Enum#equals(java.lang.Object)")]
Matches calls to the method equals on receiver that is a subtype of Enum
//MethodCall[pmd-java:matchesSig("java.lang.String#toString()")]
Matches calls to the method toString on String receivers
//MethodCall[pmd-java:matchesSig("_#_(int,int)")]
Matches calls to any method with 2 int parameters (!= argument)
//ConstructorCall[pmd-java:matchesSig("java.util.ArrayList#new(int)")]
Matches constructors calls of ArrayList with a single int parameter