@salesforce/afv-skills 1.28.0 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/dx-code-analyzer-configure/SKILL.md +31 -13
- package/skills/dx-code-analyzer-custom-rule-create/SKILL.md +484 -0
- package/skills/dx-code-analyzer-custom-rule-create/assets/pmd-ruleset-template.xml +31 -0
- package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-fields-api.md +87 -0
- package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-flows.md +105 -0
- package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-permissions.md +95 -0
- package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-examples.md +84 -0
- package/skills/dx-code-analyzer-custom-rule-create/examples/regex-examples.md +127 -0
- package/skills/dx-code-analyzer-custom-rule-create/examples/xpath-examples.md +227 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/advanced-pmd-patterns.md +288 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/apex-ast-reference.md +127 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/eslint-custom-plugins.md +247 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/eslint-rules-discovery.md +188 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier2-configurable.md +114 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier3-custom-plugins.md +113 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/metadata-xml-rules.md +285 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/regex-rule-schema.md +174 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/troubleshooting.md +141 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-governor-limits.md +83 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-method-calls.md +108 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-security.md +45 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-structure.md +127 -0
- package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns.md +131 -0
- package/skills/dx-code-analyzer-custom-rule-create/scripts/create-pmd-rule.js +209 -0
- package/skills/dx-code-analyzer-custom-rule-create/scripts/create-regex-rule.js +220 -0
- package/skills/dx-code-analyzer-run/SKILL.md +41 -8
- package/skills/mobile-platform-native-capabilities-integrate/SKILL.md +3 -3
- package/skills/platform-custom-field-generate/SKILL.md +86 -126
- package/skills/platform-custom-field-generate/references/advanced-picklists.md +590 -0
- package/skills/platform-value-set-generate/SKILL.md +305 -0
package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-governor-limits.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# XPath Patterns for Governor Limits
|
|
2
|
+
|
|
3
|
+
[← Back to XPath Patterns Index](xpath-patterns.md)
|
|
4
|
+
|
|
5
|
+
## Governor Limit Patterns
|
|
6
|
+
|
|
7
|
+
### SOQL inside loops
|
|
8
|
+
|
|
9
|
+
⚠️ **Must scope to `/BlockStatement//`** — ForEachStatement has the iterable SOQL as a direct child alongside BlockStatement. Without this, `for (Contact c : [SELECT...])` is a false positive.
|
|
10
|
+
|
|
11
|
+
**ForEachStatement child structure (from ast-dump):**
|
|
12
|
+
```text
|
|
13
|
+
ForEachStatement
|
|
14
|
+
├── VariableDeclarationStatements ← loop variable declaration
|
|
15
|
+
├── VariableExpression ← loop variable reference
|
|
16
|
+
├── BlockStatement ← LOOP BODY — only match here
|
|
17
|
+
└── SoqlExpression (or VariableExpression) ← ITERABLE — do NOT flag
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```xpath
|
|
21
|
+
//ForEachStatement/BlockStatement//SoqlExpression
|
|
22
|
+
|
|
|
23
|
+
//ForLoopStatement/BlockStatement//SoqlExpression
|
|
24
|
+
|
|
|
25
|
+
//WhileLoopStatement/BlockStatement//SoqlExpression
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Catches:** `for (Id accId : ids) { Account acc = [SELECT ...]; }` (SOQL in body)
|
|
29
|
+
**Does NOT catch (correct):** `for (Contact c : [SELECT Id FROM Contact]) { ... }` (SOQL as iterable)
|
|
30
|
+
|
|
31
|
+
### DML inside loops
|
|
32
|
+
|
|
33
|
+
Same principle — scope to BlockStatement:
|
|
34
|
+
|
|
35
|
+
```xpath
|
|
36
|
+
//ForEachStatement/BlockStatement//DmlInsertStatement
|
|
37
|
+
|
|
|
38
|
+
//ForEachStatement/BlockStatement//DmlUpdateStatement
|
|
39
|
+
|
|
|
40
|
+
//ForEachStatement/BlockStatement//DmlDeleteStatement
|
|
41
|
+
|
|
|
42
|
+
//ForEachStatement/BlockStatement//DmlUpsertStatement
|
|
43
|
+
|
|
|
44
|
+
//ForLoopStatement/BlockStatement//DmlInsertStatement
|
|
45
|
+
|
|
|
46
|
+
//ForLoopStatement/BlockStatement//DmlUpdateStatement
|
|
47
|
+
|
|
|
48
|
+
//ForLoopStatement/BlockStatement//DmlDeleteStatement
|
|
49
|
+
|
|
|
50
|
+
//ForLoopStatement/BlockStatement//DmlUpsertStatement
|
|
51
|
+
|
|
|
52
|
+
//WhileLoopStatement/BlockStatement//DmlInsertStatement
|
|
53
|
+
|
|
|
54
|
+
//WhileLoopStatement/BlockStatement//DmlUpdateStatement
|
|
55
|
+
|
|
|
56
|
+
//WhileLoopStatement/BlockStatement//DmlDeleteStatement
|
|
57
|
+
|
|
|
58
|
+
//WhileLoopStatement/BlockStatement//DmlUpsertStatement
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Shorter variant** (ForEach only, most common):
|
|
62
|
+
```xpath
|
|
63
|
+
//ForEachStatement/BlockStatement//DmlInsertStatement
|
|
64
|
+
| //ForEachStatement/BlockStatement//DmlUpdateStatement
|
|
65
|
+
| //ForEachStatement/BlockStatement//DmlDeleteStatement
|
|
66
|
+
| //ForEachStatement/BlockStatement//DmlUpsertStatement
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Database methods inside loops
|
|
70
|
+
|
|
71
|
+
```xpath
|
|
72
|
+
//ForEachStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.query']
|
|
73
|
+
|
|
|
74
|
+
//ForEachStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.insert']
|
|
75
|
+
|
|
|
76
|
+
//ForEachStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.update']
|
|
77
|
+
|
|
|
78
|
+
//ForEachStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.delete']
|
|
79
|
+
|
|
|
80
|
+
//ForLoopStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.query']
|
|
81
|
+
|
|
|
82
|
+
//WhileLoopStatement/BlockStatement//MethodCallExpression[@FullMethodName='Database.query']
|
|
83
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# XPath Patterns for Method Calls and Annotations
|
|
2
|
+
|
|
3
|
+
[← Back to XPath Patterns Index](xpath-patterns.md)
|
|
4
|
+
|
|
5
|
+
## Method Call Patterns
|
|
6
|
+
|
|
7
|
+
### Ban System.debug (non-test classes)
|
|
8
|
+
|
|
9
|
+
```xpath
|
|
10
|
+
//MethodCallExpression[@FullMethodName='System.debug']
|
|
11
|
+
[not(ancestor::UserClass[ModifierNode[@Test = true()]])]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**AST evidence:** `MethodCallExpression` has `@FullMethodName='System.debug'`. Test classes have `ModifierNode[@Test = true()]`.
|
|
15
|
+
|
|
16
|
+
**Catches:** `System.debug('anything');` in production classes
|
|
17
|
+
**Does NOT catch (correct):** Same call inside `@IsTest` classes
|
|
18
|
+
|
|
19
|
+
### Ban Database.query (dynamic SOQL — SOQL injection risk)
|
|
20
|
+
|
|
21
|
+
```xpath
|
|
22
|
+
//MethodCallExpression[@FullMethodName='Database.query']
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**AST evidence:** `MethodCallExpression[@FullMethodName='Database.query']` (line 118 of verified AST)
|
|
26
|
+
|
|
27
|
+
To exclude test classes:
|
|
28
|
+
```xpath
|
|
29
|
+
//MethodCallExpression[@FullMethodName='Database.query']
|
|
30
|
+
[not(ancestor::UserClass[ModifierNode[@Test = true()]])]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Ban Test.isRunningTest() in production code
|
|
34
|
+
|
|
35
|
+
```xpath
|
|
36
|
+
//MethodCallExpression[@FullMethodName='Test.isRunningTest']
|
|
37
|
+
[not(ancestor::UserClass[ModifierNode[@Test = true()]])]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Ban specific method calls (generic pattern)
|
|
41
|
+
|
|
42
|
+
```xpath
|
|
43
|
+
//MethodCallExpression[@FullMethodName='ClassName.methodName']
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Common examples:
|
|
47
|
+
- `@FullMethodName='System.debug'`
|
|
48
|
+
- `@FullMethodName='Database.query'`
|
|
49
|
+
- `@FullMethodName='Test.isRunningTest'`
|
|
50
|
+
- `@FullMethodName='UserInfo.getUserId'`
|
|
51
|
+
- `@FullMethodName='Limits.getQueries'`
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Annotation Patterns
|
|
56
|
+
|
|
57
|
+
### @AuraEnabled without cacheable=true
|
|
58
|
+
|
|
59
|
+
```xpath
|
|
60
|
+
//Method/ModifierNode/Annotation[@Name='AuraEnabled'
|
|
61
|
+
and not(AnnotationParameter[@Name='cacheable' and @Value='true'])]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**AST evidence:** `Annotation[@Name='AuraEnabled']` with child `AnnotationParameter[@Name='cacheable'][@Value='true']`
|
|
65
|
+
|
|
66
|
+
**Catches:** `@AuraEnabled public static ...` (no cacheable)
|
|
67
|
+
**Does NOT catch (correct):** `@AuraEnabled(cacheable=true) public static ...`
|
|
68
|
+
|
|
69
|
+
### @future methods (recommend Queueable instead)
|
|
70
|
+
|
|
71
|
+
```xpath
|
|
72
|
+
//Method/ModifierNode/Annotation[@Name='Future']
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**AST evidence:** Annotation `@Name='Future'` (note: PMD normalizes `@future` → `Future` in the Name attribute, but `@RawName='future'`)
|
|
76
|
+
|
|
77
|
+
### @SuppressWarnings usage (audit/ban)
|
|
78
|
+
|
|
79
|
+
```xpath
|
|
80
|
+
//Annotation[@Name='SuppressWarnings']
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**AST evidence:** `Annotation[@Name='SuppressWarnings']` with `AnnotationParameter[@Name='value'][@Value='PMD.RuleName']`
|
|
84
|
+
|
|
85
|
+
To flag specific suppressions:
|
|
86
|
+
```xpath
|
|
87
|
+
//Annotation[@Name='SuppressWarnings']/AnnotationParameter[contains(@Value, 'ApexCRUDViolation')]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### @IsTest without testFor parameter
|
|
91
|
+
|
|
92
|
+
From GitHub issue #2008:
|
|
93
|
+
```xpath
|
|
94
|
+
//UserClass/ModifierNode/Annotation[
|
|
95
|
+
@Name='IsTest'
|
|
96
|
+
and not(AnnotationParameter[@Name='testFor'])
|
|
97
|
+
]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### @IsTest class without System.runAs
|
|
101
|
+
|
|
102
|
+
```xpath
|
|
103
|
+
//UserClass[ModifierNode[@Test = true()]]
|
|
104
|
+
/Method[ModifierNode[@Test = true()]]
|
|
105
|
+
/BlockStatement[not(.//RunAsBlockStatement)]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**AST evidence:** `RunAsBlockStatement` is the node for `System.runAs(...) { }` blocks.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# XPath Patterns for Security
|
|
2
|
+
|
|
3
|
+
[← Back to XPath Patterns Index](xpath-patterns.md)
|
|
4
|
+
|
|
5
|
+
## Security Patterns
|
|
6
|
+
|
|
7
|
+
### Class without sharing declaration
|
|
8
|
+
|
|
9
|
+
```xpath
|
|
10
|
+
//UserClass[
|
|
11
|
+
@Nested = false()
|
|
12
|
+
and ModifierNode[@WithSharing = false() and @WithoutSharing = false() and @InheritedSharing = false()]
|
|
13
|
+
]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**AST evidence:** `ModifierNode` has `@WithSharing`, `@WithoutSharing`, `@InheritedSharing` — all `false()` means no sharing keyword declared (implicit without sharing).
|
|
17
|
+
|
|
18
|
+
Added `@Nested = false()` to exclude inner classes (which inherit from parent).
|
|
19
|
+
|
|
20
|
+
> ⚠️ **PMD 7 boolean attributes:** `@WithSharing`, `@WithoutSharing`, `@InheritedSharing`, and `@Nested` are boolean-typed in PMD 7 — string comparison (`='false'`) errors with "Cannot compare xs:boolean to xs:string". Always use `= false()` / `= true()`. See [xpath-patterns.md](xpath-patterns.md) for the full list.
|
|
21
|
+
|
|
22
|
+
### SOQL without WITH USER_MODE or SECURITY_ENFORCED
|
|
23
|
+
|
|
24
|
+
```xpath
|
|
25
|
+
//SoqlExpression[
|
|
26
|
+
not(contains(@CanonicalQuery, 'WITH USER_MODE'))
|
|
27
|
+
and not(contains(@CanonicalQuery, 'WITH SECURITY_ENFORCED'))
|
|
28
|
+
]
|
|
29
|
+
[not(ancestor::UserClass[ModifierNode[@Test = true()]])]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**AST evidence:** `SoqlExpression[@CanonicalQuery]` contains the full normalized query text.
|
|
33
|
+
|
|
34
|
+
### Hardcoded Salesforce IDs (structural — string literals 15-18 chars)
|
|
35
|
+
|
|
36
|
+
```xpath
|
|
37
|
+
//LiteralExpression[
|
|
38
|
+
@LiteralType='STRING'
|
|
39
|
+
and string-length(@Image) >= 15
|
|
40
|
+
and string-length(@Image) <= 18
|
|
41
|
+
]
|
|
42
|
+
[not(ancestor::UserClass[ModifierNode[@Test = true()]])]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Note:** `matches()` function may not be available in all PMD XPath versions. The string-length check catches most cases. For precision, use Regex engine instead.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# XPath Patterns for Code Structure, Tests, and Naming
|
|
2
|
+
|
|
3
|
+
[← Back to XPath Patterns Index](xpath-patterns.md)
|
|
4
|
+
|
|
5
|
+
## Code Structure Patterns
|
|
6
|
+
|
|
7
|
+
### DML without try-catch
|
|
8
|
+
|
|
9
|
+
```xpath
|
|
10
|
+
//DmlInsertStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
|
11
|
+
|
|
|
12
|
+
//DmlUpdateStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
|
13
|
+
|
|
|
14
|
+
//DmlDeleteStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
|
15
|
+
|
|
|
16
|
+
//DmlUpsertStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**AST evidence:** DML nodes sit inside `TryCatchFinallyBlockStatement` when wrapped in try-catch.
|
|
20
|
+
|
|
21
|
+
To exclude test classes:
|
|
22
|
+
```xpath
|
|
23
|
+
//DmlInsertStatement[
|
|
24
|
+
not(ancestor::TryCatchFinallyBlockStatement)
|
|
25
|
+
and not(ancestor::UserClass[ModifierNode[@Test = true()]])
|
|
26
|
+
]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Empty catch blocks (swallowed exceptions)
|
|
30
|
+
|
|
31
|
+
```xpath
|
|
32
|
+
//CatchBlockStatement[BlockStatement[not(*)]]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**AST evidence:** `CatchBlockStatement` contains a `BlockStatement`. Empty body = `BlockStatement` with no children.
|
|
36
|
+
|
|
37
|
+
### Nested if statements (max depth 3)
|
|
38
|
+
|
|
39
|
+
```xpath
|
|
40
|
+
//IfBlockStatement[
|
|
41
|
+
ancestor::IfBlockStatement[
|
|
42
|
+
ancestor::IfBlockStatement[
|
|
43
|
+
ancestor::IfBlockStatement
|
|
44
|
+
]
|
|
45
|
+
]
|
|
46
|
+
]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Methods with too many parameters
|
|
50
|
+
|
|
51
|
+
```xpath
|
|
52
|
+
//Method[@Arity >= 5 and @Constructor = false()]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**AST evidence:** `Method[@Arity]` gives parameter count directly.
|
|
56
|
+
|
|
57
|
+
### Logic in trigger (should delegate to handler)
|
|
58
|
+
|
|
59
|
+
For trigger files (UserTrigger node):
|
|
60
|
+
```xpath
|
|
61
|
+
//UserTrigger/BlockStatement//DmlInsertStatement
|
|
62
|
+
|
|
|
63
|
+
//UserTrigger/BlockStatement//DmlUpdateStatement
|
|
64
|
+
|
|
|
65
|
+
//UserTrigger/BlockStatement//SoqlExpression
|
|
66
|
+
|
|
|
67
|
+
//UserTrigger/BlockStatement//MethodCallExpression[@FullMethodName='Database.query']
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Test Quality Patterns
|
|
73
|
+
|
|
74
|
+
### Test method without assertions
|
|
75
|
+
|
|
76
|
+
```xpath
|
|
77
|
+
//Method[ModifierNode[@Test = true()]]
|
|
78
|
+
/BlockStatement[
|
|
79
|
+
not(.//MethodCallExpression[
|
|
80
|
+
contains(@FullMethodName, 'System.assert')
|
|
81
|
+
or contains(@FullMethodName, 'Assert.')
|
|
82
|
+
])
|
|
83
|
+
]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Catches:** Test methods with no `System.assert*` or `Assert.*` calls anywhere in their body.
|
|
87
|
+
|
|
88
|
+
### Test method without System.runAs
|
|
89
|
+
|
|
90
|
+
```xpath
|
|
91
|
+
//Method[ModifierNode[@Test = true()]]
|
|
92
|
+
/BlockStatement[not(.//RunAsBlockStatement)]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**AST evidence:** `System.runAs(user) { ... }` becomes `RunAsBlockStatement` in the AST.
|
|
96
|
+
|
|
97
|
+
### Test using seeAllData=true
|
|
98
|
+
|
|
99
|
+
```xpath
|
|
100
|
+
//Annotation[@Name='IsTest']/AnnotationParameter[@Name='seeAllData' and @Value='true']
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Naming Convention Patterns
|
|
106
|
+
|
|
107
|
+
### Class name doesn't match file (non-test)
|
|
108
|
+
|
|
109
|
+
This is better enforced by the built-in rule, but the XPath pattern for a specific convention:
|
|
110
|
+
```xpath
|
|
111
|
+
//UserClass[@Nested = false() and not(starts-with(@Image, 'Test')) and not(ends-with(@Image, 'Test'))]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Method naming (non-standard)
|
|
115
|
+
|
|
116
|
+
```xpath
|
|
117
|
+
//Method[
|
|
118
|
+
@Constructor = false()
|
|
119
|
+
and not(ModifierNode[@Test = true()])
|
|
120
|
+
and not(starts-with(@Image, 'get'))
|
|
121
|
+
and not(starts-with(@Image, 'set'))
|
|
122
|
+
and not(starts-with(@Image, 'is'))
|
|
123
|
+
and matches(@Image, '^[A-Z]')
|
|
124
|
+
]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Catches:** Methods starting with uppercase in non-test code (Apex convention is camelCase).
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# XPath Patterns for Apex Custom Rules
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## XPath Syntax Quick Reference
|
|
6
|
+
|
|
7
|
+
| Syntax | Meaning |
|
|
8
|
+
|--------|---------|
|
|
9
|
+
| `//Node` | Find Node anywhere in tree |
|
|
10
|
+
| `//Node[@attr='value']` | Node with specific string attribute value |
|
|
11
|
+
| `//Node[@attr = true()]` | Node with boolean attribute = true (**PMD 7 requirement**) |
|
|
12
|
+
| `//Parent//Child` | Child anywhere inside Parent (any descendant) |
|
|
13
|
+
| `//Parent/Child` | **Direct child only** — use this to avoid false positives |
|
|
14
|
+
| `//Node[not(...)]` | Node where condition is NOT true |
|
|
15
|
+
| `//Node[ancestor::Other]` | Node that has Other as an ancestor |
|
|
16
|
+
| `//Node[.//Other]` | Node that contains Other somewhere inside |
|
|
17
|
+
| `@Image` | The name/text of the node |
|
|
18
|
+
| `@FullMethodName` | Full qualified method name (e.g., 'System.debug') |
|
|
19
|
+
| `@LiteralType` | Type of literal: STRING, INTEGER, BOOLEAN, TRUE, FALSE, NULL |
|
|
20
|
+
|
|
21
|
+
### PMD 7 Boolean Attributes — MUST use `true()` / `false()`
|
|
22
|
+
|
|
23
|
+
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".
|
|
24
|
+
|
|
25
|
+
Known boolean attributes by node:
|
|
26
|
+
|
|
27
|
+
| Node | Boolean Attributes |
|
|
28
|
+
|------|--------------------|
|
|
29
|
+
| `ModifierNode` | `@Test`, `@Public`, `@Private`, `@Protected`, `@Static`, `@Abstract`, `@Final`, `@Global`, `@WithSharing`, `@WithoutSharing`, `@InheritedSharing` |
|
|
30
|
+
| `UserClass` | `@Nested` |
|
|
31
|
+
| `Method` | `@Constructor` |
|
|
32
|
+
|
|
33
|
+
| WRONG | CORRECT |
|
|
34
|
+
|-------|---------|
|
|
35
|
+
| `UserClass[@Nested='false']` | `UserClass[@Nested = false()]` |
|
|
36
|
+
| `UserClass[@Nested='true']` | `UserClass[@Nested = true()]` |
|
|
37
|
+
| `ModifierNode[@Test='true']` | `ModifierNode[@Test = true()]` |
|
|
38
|
+
| `ModifierNode[@Static='false']` | `ModifierNode[@Static = false()]` |
|
|
39
|
+
| `Method[@Constructor = false()]` | `Method[@Constructor = false()]` |
|
|
40
|
+
|
|
41
|
+
`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()`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### `/` vs `//` — The Most Common Source of False Positives
|
|
46
|
+
|
|
47
|
+
| XPath | Meaning | Risk |
|
|
48
|
+
|-------|---------|------|
|
|
49
|
+
| `//ForEachStatement//SoqlExpression` | SOQL anywhere inside ForEachStatement | ❌ Matches iterable position too |
|
|
50
|
+
| `//ForEachStatement/BlockStatement//SoqlExpression` | SOQL inside loop **body** only | ✅ Correct |
|
|
51
|
+
|
|
52
|
+
**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.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Pattern Categories (By Topic)
|
|
57
|
+
|
|
58
|
+
For detailed patterns in each category, see the dedicated files below:
|
|
59
|
+
|
|
60
|
+
| Category | File | Contents |
|
|
61
|
+
|----------|------|----------|
|
|
62
|
+
| Governor Limits | [xpath-patterns-governor-limits.md](xpath-patterns-governor-limits.md) | SOQL/DML in loops, Database methods in loops |
|
|
63
|
+
| Method Calls & Annotations | [xpath-patterns-method-calls.md](xpath-patterns-method-calls.md) | Ban specific methods, @AuraEnabled, @future, @IsTest, @SuppressWarnings patterns |
|
|
64
|
+
| Security | [xpath-patterns-security.md](xpath-patterns-security.md) | Sharing declarations, SOQL security modes, hardcoded IDs |
|
|
65
|
+
| Code Structure, Tests & Naming | [xpath-patterns-structure.md](xpath-patterns-structure.md) | DML error handling, empty catches, test assertions, naming conventions |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Key Apex AST Node Names
|
|
70
|
+
|
|
71
|
+
| Apex Construct | AST Node | Key Attributes |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Class | `UserClass` | `Image`, `SuperClassName`, `InterfaceNames`, `Nested` |
|
|
74
|
+
| Interface | `UserInterface` | `Image`, `SuperInterfaceName` |
|
|
75
|
+
| Method | `Method` | `Image`, `Arity`, `ReturnType`, `Constructor` |
|
|
76
|
+
| Trigger | `UserTrigger` | `Image`, `TargetName` |
|
|
77
|
+
| SOQL query | `SoqlExpression` | `Query`, `CanonicalQuery` |
|
|
78
|
+
| DML insert | `DmlInsertStatement` | |
|
|
79
|
+
| DML update | `DmlUpdateStatement` | |
|
|
80
|
+
| DML delete | `DmlDeleteStatement` | |
|
|
81
|
+
| DML upsert | `DmlUpsertStatement` | |
|
|
82
|
+
| Method call | `MethodCallExpression` | `FullMethodName`, `MethodName`, `InputParametersSize` |
|
|
83
|
+
| For-each loop | `ForEachStatement` | children: VariableDeclarationStatements, VariableExpression, **BlockStatement** (body), iterable |
|
|
84
|
+
| For loop | `ForLoopStatement` | children: init, condition, update, **BlockStatement** (body) |
|
|
85
|
+
| While loop | `WhileLoopStatement` | children: condition, **BlockStatement** (body) |
|
|
86
|
+
| If/else | `IfElseBlockStatement` > `IfBlockStatement` | `ElseStatement` |
|
|
87
|
+
| Try-catch | `TryCatchFinallyBlockStatement` | |
|
|
88
|
+
| Catch block | `CatchBlockStatement` | `ExceptionType`, `VariableName` |
|
|
89
|
+
| RunAs | `RunAsBlockStatement` | |
|
|
90
|
+
| String literal | `LiteralExpression` | `@LiteralType='STRING'`, `@Image` (value without quotes) |
|
|
91
|
+
| Integer literal | `LiteralExpression` | `@LiteralType='INTEGER'`, `@Image` |
|
|
92
|
+
| Boolean true | `LiteralExpression` | `@LiteralType='TRUE'` |
|
|
93
|
+
| Boolean false | `LiteralExpression` | `@LiteralType='FALSE'` |
|
|
94
|
+
| Null | `LiteralExpression` | `@LiteralType='NULL'` |
|
|
95
|
+
| Variable | `VariableExpression` | `@Image` (name) |
|
|
96
|
+
| Assignment | `AssignmentExpression` | `@Op` (=, +=, etc.) |
|
|
97
|
+
| Binary expression | `BinaryExpression` | `@Op` (+, -, *, /) |
|
|
98
|
+
| Boolean expression | `BooleanExpression` | `@Op` (>, <, ==, !=, >=, <=) |
|
|
99
|
+
| New object | `NewKeyValueObjectExpression` | `@Type` |
|
|
100
|
+
| New object (no-arg) | `NewObjectExpression` | `@Type` |
|
|
101
|
+
| Return | `ReturnStatement` | |
|
|
102
|
+
| Annotation | `Annotation` | `@Name` (IsTest, AuraEnabled, Future, SuppressWarnings) |
|
|
103
|
+
| Annotation param | `AnnotationParameter` | `@Name`, `@Value` |
|
|
104
|
+
| Modifier | `ModifierNode` | `Public`, `Private`, `Static`, `Test`, `WithSharing`, `Global`, etc. |
|
|
105
|
+
| Parameter | `Parameter` | `@Image` (name), `@Type` |
|
|
106
|
+
| New list | `NewListInitExpression` | |
|
|
107
|
+
| New map | `NewMapInitExpression` | |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## XPath Best Practices
|
|
112
|
+
|
|
113
|
+
1. **ALWAYS run `ast-dump` first** — never guess node names, even for patterns listed here
|
|
114
|
+
2. **Use `/BlockStatement//` for loop/if body** — avoids matching iterables, conditions, etc.
|
|
115
|
+
3. **Use `@FullMethodName`** for method calls — not `@Image` or `@MethodName` alone
|
|
116
|
+
4. **Exclude test classes** with `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]`
|
|
117
|
+
5. **Test with BOTH positive AND negative cases** — ensure no false positives
|
|
118
|
+
6. **Prefer `//Node` over absolute paths** — code structure varies
|
|
119
|
+
7. **Use `ancestor::` / `not(ancestor::)`** for structural exclusions (try-catch, test class)
|
|
120
|
+
8. **Keep XPath simple** — complex expressions are fragile and hard to maintain
|
|
121
|
+
|
|
122
|
+
## Common False Positive Traps
|
|
123
|
+
|
|
124
|
+
| Pattern | Trap | Fix |
|
|
125
|
+
|---------|------|-----|
|
|
126
|
+
| SOQL in loop | `//ForEachStatement//SoqlExpression` matches iterable | Use `/BlockStatement//` |
|
|
127
|
+
| DML in loop | Same as above | Use `/BlockStatement//` |
|
|
128
|
+
| Ban method in all code | Flags test code too | Add `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]` |
|
|
129
|
+
| Empty block detection | Matches intentional empty constructors | Add `[@Constructor = false()]` or exclude specific patterns |
|
|
130
|
+
| No sharing declaration | Flags inner classes (which inherit) | Add `[@Nested = false()]` |
|
|
131
|
+
| String literal length check | Matches test data strings | Exclude test classes |
|