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
- The element has for local name the value of
- 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 nodeEnumDeclaration
is backed by the Java gettergetSimpleName
.
- This means, that documentation for attributes can be found in our Javadocs. For
example, the attribute
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:
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, 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
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
|
|||
startLine | Returns the start line of the given node | ||
pmd:startLine(xs:element) as xs:int
|
|||
endLine | Returns the end line of the given node | ||
pmd:endLine(xs:element) as xs:int
|
|||
startColumn | Returns the start column of the given node (inclusive) | ||
pmd:startColumn(xs:element) as xs:int
|
|||
endColumn | Returns the end column of the given node (exclusive) | ||
pmd:endColumn(xs:element) as xs:int
|
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
|
|||
typeIs | Tests a node's static type | ||
pmd-java:typeIs(xs:string) as xs:boolean
|
|||
typeIsExactly | Tests a node's static type, ignoring subtypes | ||
pmd-java:typeIsExactly(xs:string) as xs:boolean
|
|||
metric | Computes and returns the value of a metric | ||
pmd-java:metric(xs:string) as xs:decimal?
|
|||
hasAnnotation | Tests whether an annotation is present on the node | ||
pmd-java:hasAnnotation(xs:string) as xs:boolean
|
|||
modifiers | Produce the effective modifiers of a node | ||
pmd-java:modifiers() as xs:string*
|
|||
explicitModifiers | Produce the explicit modifiers of a node | ||
pmd-java:explicitModifiers() as xs:string*
|
|||
matchesSig | Matches the signature called by a method or constructor call | ||
pmd-java:matchesSig(xs:string) as xs:boolean
|
typeOf
function which is
deprecated and whose usages should be replaced with uses of typeIs
or
typeIsExactly
. That one will be removed with PMD 7.0.0.