@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
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# XPath Rule Examples
|
|
2
|
+
|
|
3
|
+
Real-world custom PMD/XPath rules for Apex, derived from community requests and common enforcement needs.
|
|
4
|
+
|
|
5
|
+
## Example 1: Ban System.debug in Production Code
|
|
6
|
+
|
|
7
|
+
**Problem:** Debug statements clutter logs and expose sensitive data.
|
|
8
|
+
|
|
9
|
+
**Sample violating code:**
|
|
10
|
+
```apex
|
|
11
|
+
public class MyService {
|
|
12
|
+
public void doWork() {
|
|
13
|
+
System.debug('Sensitive: ' + record.SSN__c);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**AST nodes (from ast-dump):**
|
|
19
|
+
```xml
|
|
20
|
+
<MethodCallExpression FullMethodName='System.debug' InputParametersSize='1' ...>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**XPath:**
|
|
24
|
+
```xpath
|
|
25
|
+
//MethodCallExpression[@FullMethodName='System.debug']
|
|
26
|
+
[not(ancestor::UserClass[ModifierNode[@Test = true()]])]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**PMD ruleset:**
|
|
30
|
+
```xml
|
|
31
|
+
<rule name="NoSystemDebugInProduction" language="apex"
|
|
32
|
+
message="System.debug statements are not allowed in production code"
|
|
33
|
+
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
|
34
|
+
<priority>3</priority>
|
|
35
|
+
<properties>
|
|
36
|
+
<property name="xpath">
|
|
37
|
+
<value>//MethodCallExpression[@FullMethodName='System.debug'][not(ancestor::UserClass[ModifierNode[@Test = true()]])]</value>
|
|
38
|
+
</property>
|
|
39
|
+
</properties>
|
|
40
|
+
</rule>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Example 2: SOQL Query Inside a Loop
|
|
46
|
+
|
|
47
|
+
**Problem:** Governor limit risk — N+1 queries.
|
|
48
|
+
|
|
49
|
+
**Sample violating code:**
|
|
50
|
+
```apex
|
|
51
|
+
public class AccountProcessor {
|
|
52
|
+
public void process(List<Account> accounts) {
|
|
53
|
+
for (Account acc : accounts) {
|
|
54
|
+
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**AST nodes:**
|
|
61
|
+
```xml
|
|
62
|
+
<ForEachStatement>
|
|
63
|
+
...
|
|
64
|
+
<BlockStatement>
|
|
65
|
+
<VariableDeclarationStatements>
|
|
66
|
+
<VariableDeclaration>
|
|
67
|
+
<SoqlExpression Query='SELECT Id FROM Contact...' />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**XPath:**
|
|
71
|
+
```xpath
|
|
72
|
+
//ForEachStatement/BlockStatement//SoqlExpression
|
|
73
|
+
|
|
|
74
|
+
//ForLoopStatement/BlockStatement//SoqlExpression
|
|
75
|
+
|
|
|
76
|
+
//WhileLoopStatement/BlockStatement//SoqlExpression
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> ⚠️ **Why `/BlockStatement//`?** ForEachStatement has the iterable SOQL as a direct child alongside BlockStatement. Without scoping to `/BlockStatement//`, the XPath also matches `for (Contact c : [SELECT...])` — a valid Apex idiom that is NOT a governor limit risk.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Example 3: Enforce @IsTest(testFor) Annotation
|
|
84
|
+
|
|
85
|
+
**Problem:** Test classes should specify which class they test for traceability.
|
|
86
|
+
(GitHub issue #2008 — real community request)
|
|
87
|
+
|
|
88
|
+
**Sample violating code:**
|
|
89
|
+
```apex
|
|
90
|
+
@IsTest
|
|
91
|
+
public class MyServiceTest {
|
|
92
|
+
// Missing testFor parameter
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**AST nodes:**
|
|
97
|
+
```xml
|
|
98
|
+
<UserClass Image='MyServiceTest'>
|
|
99
|
+
<ModifierNode Test='true'>
|
|
100
|
+
<Annotation Image='IsTest'>
|
|
101
|
+
<!-- No AnnotationParameter with Name='testFor' -->
|
|
102
|
+
</Annotation>
|
|
103
|
+
</ModifierNode>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**XPath:**
|
|
107
|
+
```xpath
|
|
108
|
+
/ApexFile/UserClass/ModifierNode/Annotation[
|
|
109
|
+
@Image='IsTest'
|
|
110
|
+
and count(AnnotationParameter[@Name='testFor']) = 0
|
|
111
|
+
]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Example 4: DML Operations Without Try-Catch
|
|
117
|
+
|
|
118
|
+
**Problem:** Unhandled DML exceptions cause runtime failures.
|
|
119
|
+
(GitHub issue #738 — user requested "detect missing try-catch")
|
|
120
|
+
|
|
121
|
+
**Sample violating code:**
|
|
122
|
+
```apex
|
|
123
|
+
public class AccountService {
|
|
124
|
+
public void createAccount(String name) {
|
|
125
|
+
Account acc = new Account(Name = name);
|
|
126
|
+
insert acc; // No try-catch!
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**AST nodes:**
|
|
132
|
+
```xml
|
|
133
|
+
<DmlInsertStatement>
|
|
134
|
+
<VariableExpression Image='acc' />
|
|
135
|
+
</DmlInsertStatement>
|
|
136
|
+
<!-- No TryCatchFinallyBlockStatement ancestor -->
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**XPath:**
|
|
140
|
+
```xpath
|
|
141
|
+
//DmlInsertStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
|
142
|
+
|
|
|
143
|
+
//DmlUpdateStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
|
144
|
+
|
|
|
145
|
+
//DmlDeleteStatement[not(ancestor::TryCatchFinallyBlockStatement)]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Example 5: Nested If Statements (Max Depth 3)
|
|
151
|
+
|
|
152
|
+
**Problem:** Deep nesting reduces readability and maintainability.
|
|
153
|
+
|
|
154
|
+
**Sample violating code:**
|
|
155
|
+
```apex
|
|
156
|
+
public class ComplexLogic {
|
|
157
|
+
public void process(Integer a, Integer b, Integer c, Integer d) {
|
|
158
|
+
if (a > 0) {
|
|
159
|
+
if (b > 0) {
|
|
160
|
+
if (c > 0) {
|
|
161
|
+
if (d > 0) { // 4th level — violation!
|
|
162
|
+
doWork();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**XPath (flags depth 4+):**
|
|
172
|
+
```xpath
|
|
173
|
+
//IfBlockStatement[
|
|
174
|
+
ancestor::IfBlockStatement[
|
|
175
|
+
ancestor::IfBlockStatement[
|
|
176
|
+
ancestor::IfBlockStatement
|
|
177
|
+
]
|
|
178
|
+
]
|
|
179
|
+
]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Example 6: Class Without Explicit Sharing Declaration
|
|
185
|
+
|
|
186
|
+
**Problem:** Classes without sharing default to "without sharing" — security risk.
|
|
187
|
+
|
|
188
|
+
**Sample violating code:**
|
|
189
|
+
```apex
|
|
190
|
+
public class UnsafeService { // No "with sharing" or "without sharing"
|
|
191
|
+
public List<Account> getAccounts() {
|
|
192
|
+
return [SELECT Id FROM Account];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**AST nodes:**
|
|
198
|
+
```xml
|
|
199
|
+
<UserClass Image='UnsafeService'>
|
|
200
|
+
<ModifierNode WithSharing='false' WithoutSharing='false' InheritedSharing='false' ...>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**XPath:**
|
|
204
|
+
```xpath
|
|
205
|
+
//UserClass[
|
|
206
|
+
ModifierNode[
|
|
207
|
+
@WithSharing = false()
|
|
208
|
+
and @WithoutSharing = false()
|
|
209
|
+
and @InheritedSharing = false()
|
|
210
|
+
]
|
|
211
|
+
and not(ModifierNode[@Test = true()])
|
|
212
|
+
]
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## How to Create These
|
|
218
|
+
|
|
219
|
+
For each example above, the creation process was:
|
|
220
|
+
|
|
221
|
+
1. Write the sample violating code (5-10 lines)
|
|
222
|
+
2. Run: `sf code-analyzer ast-dump --file sample.cls`
|
|
223
|
+
3. Find the relevant node in the XML output
|
|
224
|
+
4. Write XPath using the exact node names and attributes
|
|
225
|
+
5. Create the ruleset XML using the template
|
|
226
|
+
6. Validate: `sf code-analyzer rules --rule-selector pmd:RuleName`
|
|
227
|
+
7. Test: `sf code-analyzer run --rule-selector pmd:RuleName --target sample.cls`
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Advanced PMD Patterns
|
|
2
|
+
|
|
3
|
+
Multi-rule rulesets, overriding built-in rules, exclusion patterns, Java-based rules, and sharing rules across projects.
|
|
4
|
+
|
|
5
|
+
## Multiple Rules in One Ruleset File
|
|
6
|
+
|
|
7
|
+
A single ruleset XML can contain any number of rules. This is the recommended approach for teams maintaining a shared rule set:
|
|
8
|
+
|
|
9
|
+
```xml
|
|
10
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
11
|
+
<ruleset name="TeamStandards"
|
|
12
|
+
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
|
13
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
14
|
+
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
|
15
|
+
|
|
16
|
+
<description>Team coding standards</description>
|
|
17
|
+
|
|
18
|
+
<!-- Rule 1: Custom XPath rule -->
|
|
19
|
+
<rule name="NoSystemDebug" language="apex"
|
|
20
|
+
message="Remove System.debug before merging"
|
|
21
|
+
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
|
22
|
+
<priority>3</priority>
|
|
23
|
+
<properties>
|
|
24
|
+
<property name="xpath">
|
|
25
|
+
<value><![CDATA[
|
|
26
|
+
//MethodCallExpression[@FullMethodName='System.debug']
|
|
27
|
+
[not(ancestor::UserClass[ModifierNode[@Test = true()]])]
|
|
28
|
+
]]></value>
|
|
29
|
+
</property>
|
|
30
|
+
</properties>
|
|
31
|
+
</rule>
|
|
32
|
+
|
|
33
|
+
<!-- Rule 2: Another custom XPath rule -->
|
|
34
|
+
<rule name="SoqlInLoop" language="apex"
|
|
35
|
+
message="SOQL query inside a loop — move query before the loop"
|
|
36
|
+
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
|
37
|
+
<priority>2</priority>
|
|
38
|
+
<properties>
|
|
39
|
+
<property name="xpath">
|
|
40
|
+
<value><![CDATA[
|
|
41
|
+
//ForEachStatement//SoqlExpression | //ForLoopStatement//SoqlExpression | //WhileLoopStatement//SoqlExpression
|
|
42
|
+
]]></value>
|
|
43
|
+
</property>
|
|
44
|
+
</properties>
|
|
45
|
+
</rule>
|
|
46
|
+
|
|
47
|
+
<!-- Rule 3: XML metadata rule in the same file -->
|
|
48
|
+
<rule name="MinApiVersion" language="xml"
|
|
49
|
+
message="Update API version to 60.0+"
|
|
50
|
+
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
|
51
|
+
<priority>3</priority>
|
|
52
|
+
<properties>
|
|
53
|
+
<property name="xpath">
|
|
54
|
+
<value><![CDATA[
|
|
55
|
+
//*[local-name()='apiVersion'][number(substring-before(text(), '.')) < 60]
|
|
56
|
+
]]></value>
|
|
57
|
+
</property>
|
|
58
|
+
</properties>
|
|
59
|
+
</rule>
|
|
60
|
+
</ruleset>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Rules with different `language` values (apex, xml, visualforce, etc.) can coexist in one ruleset. PMD applies each rule only to files matching its language.
|
|
64
|
+
|
|
65
|
+
## Overriding Built-in PMD Rules
|
|
66
|
+
|
|
67
|
+
Override severity, priority, or configurable properties on PMD's built-in rules without writing new ones:
|
|
68
|
+
|
|
69
|
+
```xml
|
|
70
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
71
|
+
<ruleset name="CustomizedBuiltins"
|
|
72
|
+
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
|
73
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
74
|
+
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
|
75
|
+
|
|
76
|
+
<description>Customized built-in rule thresholds</description>
|
|
77
|
+
|
|
78
|
+
<!-- Lower the complexity threshold from default 25 to 10 -->
|
|
79
|
+
<rule ref="category/apex/design.xml/CyclomaticComplexity">
|
|
80
|
+
<priority>2</priority>
|
|
81
|
+
<properties>
|
|
82
|
+
<property name="classReportLevel" value="40" />
|
|
83
|
+
<property name="methodReportLevel" value="10" />
|
|
84
|
+
</properties>
|
|
85
|
+
</rule>
|
|
86
|
+
|
|
87
|
+
<!-- Change excessive parameter count from default 4 to 3 -->
|
|
88
|
+
<rule ref="category/apex/design.xml/ExcessiveParameterList">
|
|
89
|
+
<properties>
|
|
90
|
+
<property name="minimum" value="3" />
|
|
91
|
+
</properties>
|
|
92
|
+
</rule>
|
|
93
|
+
|
|
94
|
+
<!-- Make debug statement rule high-severity instead of moderate -->
|
|
95
|
+
<rule ref="category/apex/bestpractices.xml/DebugsShouldUseLoggingLevel">
|
|
96
|
+
<priority>2</priority>
|
|
97
|
+
<properties>
|
|
98
|
+
<property name="strictMode" value="true" />
|
|
99
|
+
</properties>
|
|
100
|
+
</rule>
|
|
101
|
+
|
|
102
|
+
<!-- Include an entire category with one exclusion -->
|
|
103
|
+
<rule ref="category/apex/security.xml">
|
|
104
|
+
<exclude name="ApexSuggestUsingNamedCred" />
|
|
105
|
+
</rule>
|
|
106
|
+
</ruleset>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Configuration:**
|
|
110
|
+
```yaml
|
|
111
|
+
# code-analyzer.yml
|
|
112
|
+
engines:
|
|
113
|
+
pmd:
|
|
114
|
+
custom_rulesets:
|
|
115
|
+
- "custom-rules/customized-builtins.xml"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### How rule ref works
|
|
119
|
+
|
|
120
|
+
| Pattern | Effect |
|
|
121
|
+
|---------|--------|
|
|
122
|
+
| `<rule ref="category/apex/design.xml/CyclomaticComplexity">` | Override one specific rule |
|
|
123
|
+
| `<rule ref="category/apex/security.xml">` | Include entire category |
|
|
124
|
+
| `<rule ref="..."><exclude name="RuleName"/></rule>` | Include category minus specific rules |
|
|
125
|
+
| Nested `<priority>` | Override severity (1=Critical, 2=High, 3=Moderate, 4=Low, 5=Info) |
|
|
126
|
+
| Nested `<properties>` | Override configurable thresholds/options |
|
|
127
|
+
|
|
128
|
+
### Common built-in rules to customize
|
|
129
|
+
|
|
130
|
+
| Rule | Useful Properties |
|
|
131
|
+
|------|-------------------|
|
|
132
|
+
| `CyclomaticComplexity` | `classReportLevel` (default: 40), `methodReportLevel` (default: 25) |
|
|
133
|
+
| `ExcessiveParameterList` | `minimum` (default: 4) |
|
|
134
|
+
| `ExcessiveClassLength` | `minimum` (default: 1000) |
|
|
135
|
+
| `TooManyFields` | `maxfields` (default: 15) |
|
|
136
|
+
| `NcssMethodCount` | `minimum` (default: 60) |
|
|
137
|
+
| `DebugsShouldUseLoggingLevel` | `strictMode` (default: false) |
|
|
138
|
+
| `ApexUnitTestClassShouldHaveAsserts` | (no properties — exclude if unwanted) |
|
|
139
|
+
|
|
140
|
+
## Exclusion Patterns
|
|
141
|
+
|
|
142
|
+
Three mechanisms for excluding files and rules:
|
|
143
|
+
|
|
144
|
+
### 1. File exclusions in `code-analyzer.yml`
|
|
145
|
+
|
|
146
|
+
Exclude entire directories or file patterns from all engines:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
# code-analyzer.yml
|
|
150
|
+
ignores:
|
|
151
|
+
files:
|
|
152
|
+
- "**/node_modules/**"
|
|
153
|
+
- "**/fflib_*"
|
|
154
|
+
- "**/*Test*.cls"
|
|
155
|
+
- "**/generated/**"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Supports glob patterns: `*`, `**`, `?`, `[...]`, `{...}`
|
|
159
|
+
|
|
160
|
+
### 2. Rule exclusions in PMD rulesets
|
|
161
|
+
|
|
162
|
+
Exclude specific rules when including an entire category:
|
|
163
|
+
|
|
164
|
+
```xml
|
|
165
|
+
<rule ref="category/apex/bestpractices.xml">
|
|
166
|
+
<exclude name="ApexUnitTestClassShouldHaveAsserts" />
|
|
167
|
+
<exclude name="ApexAssertionsShouldIncludeMessage" />
|
|
168
|
+
</rule>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 3. Exclude patterns in PMD rulesets
|
|
172
|
+
|
|
173
|
+
Exclude files at the PMD level (applies only to rules in that ruleset):
|
|
174
|
+
|
|
175
|
+
```xml
|
|
176
|
+
<ruleset name="MyRules" ...>
|
|
177
|
+
<exclude-pattern>.*/fflib_.*</exclude-pattern>
|
|
178
|
+
<exclude-pattern>.*/test/.*</exclude-pattern>
|
|
179
|
+
|
|
180
|
+
<rule name="..." ...>
|
|
181
|
+
...
|
|
182
|
+
</rule>
|
|
183
|
+
</ruleset>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 4. Disabling individual rules via config
|
|
187
|
+
|
|
188
|
+
Disable any rule without editing rulesets:
|
|
189
|
+
|
|
190
|
+
```yaml
|
|
191
|
+
# code-analyzer.yml
|
|
192
|
+
rules:
|
|
193
|
+
pmd:
|
|
194
|
+
ApexUnitTestClassShouldHaveAsserts:
|
|
195
|
+
disabled: true
|
|
196
|
+
CyclomaticComplexity:
|
|
197
|
+
severity: "Low" # Downgrade instead of disable
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Java-Based Custom Rules (Advanced)
|
|
201
|
+
|
|
202
|
+
For rules that need logic beyond XPath (complex data flow, cross-file analysis), write Java PMD rules:
|
|
203
|
+
|
|
204
|
+
1. **Create a Java class** extending `net.sourceforge.pmd.lang.apex.rule.AbstractApexRule`
|
|
205
|
+
2. **Compile into a JAR** file
|
|
206
|
+
3. **Reference in config:**
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
# code-analyzer.yml
|
|
210
|
+
engines:
|
|
211
|
+
pmd:
|
|
212
|
+
java_classpath_entries:
|
|
213
|
+
- "custom-rules/my-rules.jar" # JAR with compiled rule classes
|
|
214
|
+
- "custom-rules/lib/" # Folder of JARs (all JARs inside loaded)
|
|
215
|
+
custom_rulesets:
|
|
216
|
+
- "category/myteam/rules.xml" # Ruleset inside the JAR (classpath resource)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The JAR's ruleset XML references the Java class:
|
|
220
|
+
```xml
|
|
221
|
+
<rule name="AvoidFutureAnnotation" language="apex"
|
|
222
|
+
message="Use Queueable instead of @Future"
|
|
223
|
+
class="com.myteam.pmd.rules.AvoidFutureRule">
|
|
224
|
+
<priority>3</priority>
|
|
225
|
+
</rule>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Requirements:**
|
|
229
|
+
- Java 11+ for compilation
|
|
230
|
+
- PMD 7.x API (use `net.sourceforge.pmd.lang.rule.xpath.XPathRule` for XPath, or extend `AbstractApexRule` for visitor-pattern Java rules)
|
|
231
|
+
|
|
232
|
+
## Sharing Rules Across Projects
|
|
233
|
+
|
|
234
|
+
### Approach 1: Relative paths with shared config
|
|
235
|
+
|
|
236
|
+
If projects share a parent directory or monorepo:
|
|
237
|
+
```yaml
|
|
238
|
+
# code-analyzer.yml
|
|
239
|
+
engines:
|
|
240
|
+
pmd:
|
|
241
|
+
custom_rulesets:
|
|
242
|
+
- "../shared-rules/team-standards.xml" # Relative to config_root
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`config_root` is the directory containing `code-analyzer.yml`. All relative paths resolve from there.
|
|
246
|
+
|
|
247
|
+
### Approach 2: JAR-based distribution
|
|
248
|
+
|
|
249
|
+
Package rules into a JAR and distribute via artifact repository:
|
|
250
|
+
|
|
251
|
+
1. Build a JAR containing your ruleset XML at a classpath resource path (e.g., `category/myteam/standards.xml`)
|
|
252
|
+
2. Place the JAR in each project's `custom-rules/` directory (or download in CI)
|
|
253
|
+
3. Reference in config:
|
|
254
|
+
|
|
255
|
+
```yaml
|
|
256
|
+
engines:
|
|
257
|
+
pmd:
|
|
258
|
+
java_classpath_entries:
|
|
259
|
+
- "custom-rules/myteam-rules-1.0.jar"
|
|
260
|
+
custom_rulesets:
|
|
261
|
+
- "category/myteam/standards.xml" # Resource inside the JAR
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Approach 3: Git submodule or symlink
|
|
265
|
+
|
|
266
|
+
For simpler setups, share the ruleset XML via git submodule:
|
|
267
|
+
```bash
|
|
268
|
+
git submodule add https://github.com/myteam/pmd-rules.git custom-rules/shared
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```yaml
|
|
272
|
+
engines:
|
|
273
|
+
pmd:
|
|
274
|
+
custom_rulesets:
|
|
275
|
+
- "custom-rules/shared/team-standards.xml"
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### CI/CD Integration
|
|
279
|
+
|
|
280
|
+
All approaches work in CI because paths are relative to `config_root`. Example GitHub Actions step:
|
|
281
|
+
|
|
282
|
+
```yaml
|
|
283
|
+
- name: Run Code Analyzer
|
|
284
|
+
run: |
|
|
285
|
+
sf code-analyzer run --rule-selector "pmd:2,regex:2" --target force-app/
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
No absolute paths needed — the config file handles resolution.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Apex AST Node Reference
|
|
2
|
+
|
|
3
|
+
PMD 7.x Apex AST node types and their attributes. Use `sf code-analyzer ast-dump --file <file.cls>` to see the actual tree for your code.
|
|
4
|
+
|
|
5
|
+
## Node Hierarchy (Common Structure)
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
ApexFile
|
|
9
|
+
├── UserClass / UserInterface / UserTrigger / UserEnum
|
|
10
|
+
│ ├── FormalComment (Javadoc)
|
|
11
|
+
│ ├── ModifierNode (public, private, static, etc.)
|
|
12
|
+
│ ├── Field (class-level variables)
|
|
13
|
+
│ │ └── VariableDeclaration
|
|
14
|
+
│ └── Method
|
|
15
|
+
│ ├── ModifierNode
|
|
16
|
+
│ ├── Parameter
|
|
17
|
+
│ └── BlockStatement
|
|
18
|
+
│ ├── VariableDeclarationStatements
|
|
19
|
+
│ ├── ExpressionStatement
|
|
20
|
+
│ │ └── MethodCallExpression
|
|
21
|
+
│ ├── IfElseBlockStatement
|
|
22
|
+
│ │ └── IfBlockStatement
|
|
23
|
+
│ ├── ForEachStatement / ForLoopStatement / WhileLoopStatement
|
|
24
|
+
│ ├── TryCatchFinallyBlockStatement
|
|
25
|
+
│ │ ├── BlockStatement (try body)
|
|
26
|
+
│ │ ├── CatchBlockStatement
|
|
27
|
+
│ │ └── BlockStatement (finally body)
|
|
28
|
+
│ ├── DmlInsertStatement / DmlUpdateStatement / DmlDeleteStatement
|
|
29
|
+
│ ├── ReturnStatement
|
|
30
|
+
│ └── ThrowStatement
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## ModifierNode Attributes
|
|
34
|
+
|
|
35
|
+
Every class, method, field, and parameter has a `ModifierNode` with these boolean attributes:
|
|
36
|
+
|
|
37
|
+
| Attribute | Meaning |
|
|
38
|
+
|-----------|---------|
|
|
39
|
+
| `Public` | public access |
|
|
40
|
+
| `Private` | private access |
|
|
41
|
+
| `Protected` | protected access |
|
|
42
|
+
| `Global` | global access |
|
|
43
|
+
| `Static` | static member |
|
|
44
|
+
| `Final` | final/const |
|
|
45
|
+
| `Abstract` | abstract method/class |
|
|
46
|
+
| `Virtual` | virtual method/class |
|
|
47
|
+
| `Override` | overrides parent method |
|
|
48
|
+
| `Test` | @IsTest annotated |
|
|
49
|
+
| `TestOrTestSetup` | @IsTest or @TestSetup |
|
|
50
|
+
| `WebService` | webservice method |
|
|
51
|
+
| `WithSharing` | with sharing class |
|
|
52
|
+
| `WithoutSharing` | without sharing class |
|
|
53
|
+
| `InheritedSharing` | inherited sharing class |
|
|
54
|
+
| `Transient` | transient field |
|
|
55
|
+
| `Modifiers` | Bitmask integer of all modifiers |
|
|
56
|
+
|
|
57
|
+
⚠️ **PMD 7 Boolean Attribute Comparison:**
|
|
58
|
+
|
|
59
|
+
In PMD 7, these boolean attributes are **always present** on the node — they are never absent. This means:
|
|
60
|
+
- ✅ `@WithSharing = false()` — correct (XPath boolean function)
|
|
61
|
+
- ✅ `@WithSharing = true()` — correct
|
|
62
|
+
- ❌ `@WithSharing = 'false'` — WRONG (string comparison, won't match)
|
|
63
|
+
- ❌ `not(@WithSharing)` — WRONG (attribute always exists, so this is always false)
|
|
64
|
+
- ❌ `@WithSharing` (as existence check) — WRONG (always true, attribute always present)
|
|
65
|
+
|
|
66
|
+
Use the XPath `true()`/`false()` functions for boolean comparisons:
|
|
67
|
+
```xpath
|
|
68
|
+
<!-- Classes without sharing declaration -->
|
|
69
|
+
//UserClass[ModifierNode[@WithSharing = false() and @WithoutSharing = false() and @InheritedSharing = false()]]
|
|
70
|
+
|
|
71
|
+
<!-- Abstract classes -->
|
|
72
|
+
//UserClass[ModifierNode[@Abstract = true()]]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## MethodCallExpression Attributes
|
|
76
|
+
|
|
77
|
+
| Attribute | Example | Notes |
|
|
78
|
+
|-----------|---------|-------|
|
|
79
|
+
| `FullMethodName` | `'System.debug'`, `'Database.query'` | Most reliable for matching |
|
|
80
|
+
| `MethodName` | `'debug'`, `'query'` | Just the method part |
|
|
81
|
+
| `InputParametersSize` | `'1'`, `'0'` | Number of arguments |
|
|
82
|
+
|
|
83
|
+
## LiteralExpression Attributes
|
|
84
|
+
|
|
85
|
+
| Attribute | Values | Notes |
|
|
86
|
+
|-----------|--------|-------|
|
|
87
|
+
| `LiteralType` | `STRING`, `INTEGER`, `DECIMAL`, `DOUBLE`, `LONG`, `BOOLEAN` | Type of literal |
|
|
88
|
+
| `Image` | The literal value | For strings, excludes quotes |
|
|
89
|
+
| `String` | `'true'` / `'false'` | Boolean shorthand |
|
|
90
|
+
| `Null` | `'true'` / `'false'` | Is it a null literal |
|
|
91
|
+
|
|
92
|
+
## SoqlExpression Attributes
|
|
93
|
+
|
|
94
|
+
| Attribute | Example | Notes |
|
|
95
|
+
|-----------|---------|-------|
|
|
96
|
+
| `Query` | `'SELECT Id FROM Account WHERE Name = :name'` | Original SOQL as written |
|
|
97
|
+
| `CanonicalQuery` | `'SELECT Id FROM Account WHERE Name = :tmpVar1'` | Normalized (bind vars replaced) |
|
|
98
|
+
|
|
99
|
+
## Annotation Node
|
|
100
|
+
|
|
101
|
+
```xml
|
|
102
|
+
<Annotation Image="IsTest">
|
|
103
|
+
<AnnotationParameter Name="testFor" Value="'ApexClass:MyService'" />
|
|
104
|
+
</Annotation>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use: `//Annotation[@Image='IsTest']` to find test annotations.
|
|
108
|
+
|
|
109
|
+
## DML Nodes
|
|
110
|
+
|
|
111
|
+
| Statement | Node | Notes |
|
|
112
|
+
|-----------|------|-------|
|
|
113
|
+
| `insert x;` | `DmlInsertStatement` | |
|
|
114
|
+
| `update x;` | `DmlUpdateStatement` | |
|
|
115
|
+
| `delete x;` | `DmlDeleteStatement` | |
|
|
116
|
+
| `upsert x;` | `DmlUpsertStatement` | |
|
|
117
|
+
| `undelete x;` | `DmlUndeleteStatement` | |
|
|
118
|
+
| `Database.insert(x)` | `MethodCallExpression[@FullMethodName='Database.insert']` | Method-form DML |
|
|
119
|
+
|
|
120
|
+
## Tips for Reading AST Dumps
|
|
121
|
+
|
|
122
|
+
1. **`Image` attribute** = the actual source name (variable name, class name, method name)
|
|
123
|
+
2. **`DefiningType` attribute** = which class this node belongs to (present on every node)
|
|
124
|
+
3. **`RealLoc='true'`** = this node has a real source position (not compiler-generated)
|
|
125
|
+
4. **`RealLoc='false'`** = compiler-generated node (e.g., implicit modifiers, empty references)
|
|
126
|
+
5. **`EmptyReferenceExpression`** = placeholder for unqualified variable access (ignore these)
|
|
127
|
+
6. **`Type` attribute on VariableDeclaration** = the declared type (e.g., `'Account'`, `'List<Contact>'`)
|