afv-library/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns.md

7.5 KiB

XPath Patterns for Apex Custom Rules

Pre-validated PMD XPath patterns for Salesforce Apex code. Every pattern below has been verified against actual sf code-analyzer ast-dump output. Use these directly — but still run ast-dump on YOUR code to confirm node names haven't changed in newer PMD versions.

XPath Syntax Quick Reference

Syntax Meaning
//Node Find Node anywhere in tree
//Node[@attr='value'] Node with specific string attribute value
//Node[@attr = true()] Node with boolean attribute = true (PMD 7 requirement)
//Parent//Child Child anywhere inside Parent (any descendant)
//Parent/Child Direct child only — use this to avoid false positives
//Node[not(...)] Node where condition is NOT true
//Node[ancestor::Other] Node that has Other as an ancestor
//Node[.//Other] Node that contains Other somewhere inside
@Image The name/text of the node
@FullMethodName Full qualified method name (e.g., 'System.debug')
@LiteralType Type of literal: STRING, INTEGER, BOOLEAN, TRUE, FALSE, NULL

PMD 7 Boolean Attributes — MUST use true() / false()

In PMD 7, many node attributes are boolean typed, not strings — even though ast-dump renders them as Nested='false' or Test='true' in the XML output. String attributes (@FullMethodName, @Name, @Image, @LiteralType, etc.) are safe to copy from the AST dump as-is. But attributes whose values are true or false in the dump are boolean-typed — comparing them with string literals (@Nested='false') causes the error: "Cannot compare xs:boolean to xs:string".

Known boolean attributes by node:

Node Boolean Attributes
ModifierNode @Test, @Public, @Private, @Protected, @Static, @Abstract, @Final, @Global, @WithSharing, @WithoutSharing, @InheritedSharing
UserClass @Nested
Method @Constructor
WRONG CORRECT
UserClass[@Nested='false'] UserClass[@Nested = false()]
UserClass[@Nested='true'] UserClass[@Nested = true()]
ModifierNode[@Test='true'] ModifierNode[@Test = true()]
ModifierNode[@Static='false'] ModifierNode[@Static = false()]
Method[@Constructor = false()] Method[@Constructor = false()]

true() and false() are XPath boolean functions. If you see an attribute that looks boolean in the AST dump, assume it is typed as boolean and use true()/false().


/ vs // — The Most Common Source of False Positives

XPath Meaning Risk
//ForEachStatement//SoqlExpression SOQL anywhere inside ForEachStatement Matches iterable position too
//ForEachStatement/BlockStatement//SoqlExpression SOQL inside loop body only Correct

Rule of thumb: When matching inside a structural node (loop, if, try), always scope to /BlockStatement// to target the body, not sibling children like iterables or conditions.


Pattern Categories (By Topic)

For detailed patterns in each category, see the dedicated files below:

Category File Contents
Governor Limits xpath-patterns-governor-limits.md SOQL/DML in loops, Database methods in loops
Method Calls & Annotations xpath-patterns-method-calls.md Ban specific methods, @AuraEnabled, @future, @IsTest, @SuppressWarnings patterns
Security xpath-patterns-security.md Sharing declarations, SOQL security modes, hardcoded IDs
Code Structure, Tests & Naming xpath-patterns-structure.md DML error handling, empty catches, test assertions, naming conventions

Key Apex AST Node Names

Apex Construct AST Node Key Attributes
Class UserClass Image, SuperClassName, InterfaceNames, Nested
Interface UserInterface Image, SuperInterfaceName
Method Method Image, Arity, ReturnType, Constructor
Trigger UserTrigger Image, TargetName
SOQL query SoqlExpression Query, CanonicalQuery
DML insert DmlInsertStatement
DML update DmlUpdateStatement
DML delete DmlDeleteStatement
DML upsert DmlUpsertStatement
Method call MethodCallExpression FullMethodName, MethodName, InputParametersSize
For-each loop ForEachStatement children: VariableDeclarationStatements, VariableExpression, BlockStatement (body), iterable
For loop ForLoopStatement children: init, condition, update, BlockStatement (body)
While loop WhileLoopStatement children: condition, BlockStatement (body)
If/else IfElseBlockStatement > IfBlockStatement ElseStatement
Try-catch TryCatchFinallyBlockStatement
Catch block CatchBlockStatement ExceptionType, VariableName
RunAs RunAsBlockStatement
String literal LiteralExpression @LiteralType='STRING', @Image (value without quotes)
Integer literal LiteralExpression @LiteralType='INTEGER', @Image
Boolean true LiteralExpression @LiteralType='TRUE'
Boolean false LiteralExpression @LiteralType='FALSE'
Null LiteralExpression @LiteralType='NULL'
Variable VariableExpression @Image (name)
Assignment AssignmentExpression @Op (=, +=, etc.)
Binary expression BinaryExpression @Op (+, -, *, /)
Boolean expression BooleanExpression @Op (>, <, ==, !=, >=, <=)
New object NewKeyValueObjectExpression @Type
New object (no-arg) NewObjectExpression @Type
Return ReturnStatement
Annotation Annotation @Name (IsTest, AuraEnabled, Future, SuppressWarnings)
Annotation param AnnotationParameter @Name, @Value
Modifier ModifierNode Public, Private, Static, Test, WithSharing, Global, etc.
Parameter Parameter @Image (name), @Type
New list NewListInitExpression
New map NewMapInitExpression

XPath Best Practices

  1. ALWAYS run ast-dump first — never guess node names, even for patterns listed here
  2. Use /BlockStatement// for loop/if body — avoids matching iterables, conditions, etc.
  3. Use @FullMethodName for method calls — not @Image or @MethodName alone
  4. Exclude test classes with [not(ancestor::UserClass[ModifierNode[@Test = true()]])]
  5. Test with BOTH positive AND negative cases — ensure no false positives
  6. Prefer //Node over absolute paths — code structure varies
  7. Use ancestor:: / not(ancestor::) for structural exclusions (try-catch, test class)
  8. Keep XPath simple — complex expressions are fragile and hard to maintain

Common False Positive Traps

Pattern Trap Fix
SOQL in loop //ForEachStatement//SoqlExpression matches iterable Use /BlockStatement//
DML in loop Same as above Use /BlockStatement//
Ban method in all code Flags test code too Add [not(ancestor::UserClass[ModifierNode[@Test = true()]])]
Empty block detection Matches intentional empty constructors Add [@Constructor = false()] or exclude specific patterns
No sharing declaration Flags inner classes (which inherit) Add [@Nested = false()]
String literal length check Matches test data strings Exclude test classes