5.4 KiB
XPath Rule Examples
Real-world custom PMD/XPath rules for Apex, derived from community requests and common enforcement needs.
Example 1: Ban System.debug in Production Code
Problem: Debug statements clutter logs and expose sensitive data.
Sample violating code:
public class MyService {
public void doWork() {
System.debug('Sensitive: ' + record.SSN__c);
}
}
AST nodes (from ast-dump):
<MethodCallExpression FullMethodName='System.debug' InputParametersSize='1' ...>
XPath:
//MethodCallExpression[@FullMethodName='System.debug']
[not(ancestor::UserClass[ModifierNode[@Test = true()]])]
PMD ruleset:
<rule name="NoSystemDebugInProduction" language="apex"
message="System.debug statements are not allowed in production code"
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
<priority>3</priority>
<properties>
<property name="xpath">
<value>//MethodCallExpression[@FullMethodName='System.debug'][not(ancestor::UserClass[ModifierNode[@Test = true()]])]</value>
</property>
</properties>
</rule>
Example 2: SOQL Query Inside a Loop
Problem: Governor limit risk — N+1 queries.
Sample violating code:
public class AccountProcessor {
public void process(List<Account> accounts) {
for (Account acc : accounts) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}
}
}
AST nodes:
<ForEachStatement>
...
<BlockStatement>
<VariableDeclarationStatements>
<VariableDeclaration>
<SoqlExpression Query='SELECT Id FROM Contact...' />
XPath:
//ForEachStatement/BlockStatement//SoqlExpression
|
//ForLoopStatement/BlockStatement//SoqlExpression
|
//WhileLoopStatement/BlockStatement//SoqlExpression
⚠️ Why
/BlockStatement//? ForEachStatement has the iterable SOQL as a direct child alongside BlockStatement. Without scoping to/BlockStatement//, the XPath also matchesfor (Contact c : [SELECT...])— a valid Apex idiom that is NOT a governor limit risk.
Example 3: Enforce @IsTest(testFor) Annotation
Problem: Test classes should specify which class they test for traceability. (GitHub issue #2008 — real community request)
Sample violating code:
@IsTest
public class MyServiceTest {
// Missing testFor parameter
}
AST nodes:
<UserClass Image='MyServiceTest'>
<ModifierNode Test='true'>
<Annotation Image='IsTest'>
<!-- No AnnotationParameter with Name='testFor' -->
</Annotation>
</ModifierNode>
XPath:
/ApexFile/UserClass/ModifierNode/Annotation[
@Image='IsTest'
and count(AnnotationParameter[@Name='testFor']) = 0
]
Example 4: DML Operations Without Try-Catch
Problem: Unhandled DML exceptions cause runtime failures. (GitHub issue #738 — user requested "detect missing try-catch")
Sample violating code:
public class AccountService {
public void createAccount(String name) {
Account acc = new Account(Name = name);
insert acc; // No try-catch!
}
}
AST nodes:
<DmlInsertStatement>
<VariableExpression Image='acc' />
</DmlInsertStatement>
<!-- No TryCatchFinallyBlockStatement ancestor -->
XPath:
//DmlInsertStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
//DmlUpdateStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
//DmlDeleteStatement[not(ancestor::TryCatchFinallyBlockStatement)]
Example 5: Nested If Statements (Max Depth 3)
Problem: Deep nesting reduces readability and maintainability.
Sample violating code:
public class ComplexLogic {
public void process(Integer a, Integer b, Integer c, Integer d) {
if (a > 0) {
if (b > 0) {
if (c > 0) {
if (d > 0) { // 4th level — violation!
doWork();
}
}
}
}
}
}
XPath (flags depth 4+):
//IfBlockStatement[
ancestor::IfBlockStatement[
ancestor::IfBlockStatement[
ancestor::IfBlockStatement
]
]
]
Example 6: Class Without Explicit Sharing Declaration
Problem: Classes without sharing default to "without sharing" — security risk.
Sample violating code:
public class UnsafeService { // No "with sharing" or "without sharing"
public List<Account> getAccounts() {
return [SELECT Id FROM Account];
}
}
AST nodes:
<UserClass Image='UnsafeService'>
<ModifierNode WithSharing='false' WithoutSharing='false' InheritedSharing='false' ...>
XPath:
//UserClass[
ModifierNode[
@WithSharing = false()
and @WithoutSharing = false()
and @InheritedSharing = false()
]
and not(ModifierNode[@Test = true()])
]
How to Create These
For each example above, the creation process was:
- Write the sample violating code (5-10 lines)
- Run:
sf code-analyzer ast-dump --file sample.cls - Find the relevant node in the XML output
- Write XPath using the exact node names and attributes
- Create the ruleset XML using the template
- Validate:
sf code-analyzer rules --rule-selector pmd:RuleName - Test:
sf code-analyzer run --rule-selector pmd:RuleName --target sample.cls