@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,285 @@
|
|
|
1
|
+
# Metadata XML Rules (PMD with language="xml")
|
|
2
|
+
|
|
3
|
+
Write PMD XPath rules that target Salesforce metadata XML files (`.field-meta.xml`, `.permissionset-meta.xml`, `.profile-meta.xml`, `.flow-meta.xml`, etc.) to enforce org governance.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
PMD's `language="xml"` mode parses any XML file into a DOM-like AST. You write XPath expressions against element/attribute names in the XML structure — same mechanism as Apex rules, but the AST is the XML DOM.
|
|
8
|
+
|
|
9
|
+
## Critical: PMD 7 XML XPath Rules
|
|
10
|
+
|
|
11
|
+
### Rule 1: Namespace — use `local-name()`
|
|
12
|
+
|
|
13
|
+
Salesforce metadata files declare a namespace:
|
|
14
|
+
```xml
|
|
15
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
16
|
+
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
17
|
+
<fullName>My_Field__c</fullName>
|
|
18
|
+
...
|
|
19
|
+
</CustomField>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This namespace **breaks standard XPath** because `//fullName` won't match — PMD sees it as `//metadata:fullName` internally.
|
|
23
|
+
|
|
24
|
+
**Solution:** Use `local-name()` to match element names regardless of namespace:
|
|
25
|
+
|
|
26
|
+
```xpath
|
|
27
|
+
//*[local-name()='CustomField']/*[local-name()='fullName']
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**This applies to ALL Salesforce metadata XPath rules.** Never use bare element names without `local-name()`.
|
|
31
|
+
|
|
32
|
+
### Rule 2: Text content — use `@Text` (NOT `text()`)
|
|
33
|
+
|
|
34
|
+
⚠️ **PMD 7's XML language does NOT support `text()` for value comparison.** The `text()` function returns 0 matches regardless of content. This is a PMD 7 change from PMD 6.
|
|
35
|
+
|
|
36
|
+
**Use the `@Text` attribute instead:**
|
|
37
|
+
|
|
38
|
+
```xpath
|
|
39
|
+
// ❌ WRONG — does not work in PMD 7:
|
|
40
|
+
//*[local-name()='name'][text()='ModifyAllData']
|
|
41
|
+
|
|
42
|
+
// ✅ CORRECT — use @Text attribute:
|
|
43
|
+
//*[@Text='ModifyAllData']
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Rule 3: Navigation — text nodes require `../..`
|
|
47
|
+
|
|
48
|
+
In PMD 7 XML, `@Text` matches TEXT NODES (child nodes of elements), not the elements themselves. To navigate from a matched text node to its parent element hierarchy:
|
|
49
|
+
|
|
50
|
+
```xpath
|
|
51
|
+
// @Text='ModifyAllData' matches the TEXT NODE inside <name>ModifyAllData</name>
|
|
52
|
+
// To get the <name> element: go up one level
|
|
53
|
+
//*[@Text='ModifyAllData']/..
|
|
54
|
+
|
|
55
|
+
// To get the <userPermissions> parent: go up two levels
|
|
56
|
+
//*[@Text='ModifyAllData']/../..
|
|
57
|
+
|
|
58
|
+
// To check a sibling's text value from the parent:
|
|
59
|
+
//*[@Text='ModifyAllData']/../..[.//*[@Text='true']]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Complete Pattern for Checking Parent + Sibling Text Values
|
|
63
|
+
|
|
64
|
+
The canonical PMD 7 pattern for "find element X that contains child with text A AND child with text B":
|
|
65
|
+
|
|
66
|
+
```xpath
|
|
67
|
+
//*[@Text='TargetValue']/../..
|
|
68
|
+
[local-name()='ParentElement']
|
|
69
|
+
[.//*[@Text='ConditionValue']]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Example — find `userPermissions` where name=ModifyAllData AND enabled=true:
|
|
73
|
+
```xpath
|
|
74
|
+
//*[@Text='ModifyAllData']/../..
|
|
75
|
+
[local-name()='userPermissions']
|
|
76
|
+
[.//*[@Text='true']]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## AST Dump for Metadata Files
|
|
80
|
+
|
|
81
|
+
Dump the AST to see the exact XML structure:
|
|
82
|
+
```bash
|
|
83
|
+
sf code-analyzer ast-dump --file force-app/main/default/objects/Account/fields/MyField__c.field-meta.xml --language xml
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The output shows the DOM tree — element nodes, text nodes, and attributes. Use this to confirm element names before writing XPath.
|
|
87
|
+
|
|
88
|
+
## Ruleset XML Template for Metadata Rules
|
|
89
|
+
|
|
90
|
+
```xml
|
|
91
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
92
|
+
<ruleset name="MetadataGovernanceRules"
|
|
93
|
+
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
|
94
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
95
|
+
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
|
96
|
+
|
|
97
|
+
<description>Custom rules for Salesforce metadata governance</description>
|
|
98
|
+
|
|
99
|
+
<rule name="MyMetadataRule"
|
|
100
|
+
language="xml"
|
|
101
|
+
message="Violation message here"
|
|
102
|
+
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
|
103
|
+
|
|
104
|
+
<description>What this rule enforces</description>
|
|
105
|
+
<priority>3</priority>
|
|
106
|
+
|
|
107
|
+
<properties>
|
|
108
|
+
<property name="xpath">
|
|
109
|
+
<value><![CDATA[
|
|
110
|
+
//*[local-name()='YourElement'][your-condition]
|
|
111
|
+
]]></value>
|
|
112
|
+
</property>
|
|
113
|
+
</properties>
|
|
114
|
+
</rule>
|
|
115
|
+
</ruleset>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
# code-analyzer.yml
|
|
122
|
+
engines:
|
|
123
|
+
pmd:
|
|
124
|
+
custom_rulesets:
|
|
125
|
+
- "custom-rules/metadata-governance.xml"
|
|
126
|
+
file_extensions:
|
|
127
|
+
xml: [".xml"]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
⚠️ **IMPORTANT:** PMD's XML language only scans `.xml` files by default. You MUST add `file_extensions: { xml: [".xml"] }` under `engines.pmd` in your config. This single entry covers ALL Salesforce metadata compound extensions (`.permissionset-meta.xml`, `.field-meta.xml`, `.flow-meta.xml`, etc.) because the system reads the final `.xml` portion of the filename.
|
|
131
|
+
|
|
132
|
+
⚠️ **Do NOT add compound extensions** like `.permissionset-meta.xml` to the list — Code Analyzer's validator only accepts single-segment extensions matching `/^[.][a-zA-Z0-9]+$/` and will reject them with a config error.
|
|
133
|
+
|
|
134
|
+
## Common Metadata File Structures
|
|
135
|
+
|
|
136
|
+
### Custom Field (`.field-meta.xml`)
|
|
137
|
+
```xml
|
|
138
|
+
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
139
|
+
<fullName>Revenue__c</fullName>
|
|
140
|
+
<label>Revenue</label>
|
|
141
|
+
<type>Currency</type>
|
|
142
|
+
<required>false</required>
|
|
143
|
+
<description>Annual revenue</description>
|
|
144
|
+
</CustomField>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Permission Set (`.permissionset-meta.xml`)
|
|
148
|
+
```xml
|
|
149
|
+
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
150
|
+
<label>Admin Access</label>
|
|
151
|
+
<userPermissions>
|
|
152
|
+
<enabled>true</enabled>
|
|
153
|
+
<name>ModifyAllData</name>
|
|
154
|
+
</userPermissions>
|
|
155
|
+
<fieldPermissions>
|
|
156
|
+
<editable>true</editable>
|
|
157
|
+
<field>Account.Revenue__c</field>
|
|
158
|
+
<readable>true</readable>
|
|
159
|
+
</fieldPermissions>
|
|
160
|
+
</PermissionSet>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Profile (`.profile-meta.xml`)
|
|
164
|
+
```xml
|
|
165
|
+
<Profile xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
166
|
+
<userPermissions>
|
|
167
|
+
<enabled>true</enabled>
|
|
168
|
+
<name>ViewSetup</name>
|
|
169
|
+
</userPermissions>
|
|
170
|
+
<fieldPermissions>
|
|
171
|
+
<editable>true</editable>
|
|
172
|
+
<field>Account.SSN__c</field>
|
|
173
|
+
</fieldPermissions>
|
|
174
|
+
</Profile>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Flow (`.flow-meta.xml`)
|
|
178
|
+
```xml
|
|
179
|
+
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
180
|
+
<apiVersion>58.0</apiVersion>
|
|
181
|
+
<label>My Flow</label>
|
|
182
|
+
<processType>AutoLaunchedFlow</processType>
|
|
183
|
+
<status>Active</status>
|
|
184
|
+
<processMetadataValues>
|
|
185
|
+
<name>CanvasMode</name>
|
|
186
|
+
<value><stringValue>AUTO_LAYOUT_CANVAS</stringValue></value>
|
|
187
|
+
</processMetadataValues>
|
|
188
|
+
<recordCreates>
|
|
189
|
+
<name>Create_Account</name>
|
|
190
|
+
<object>Account</object>
|
|
191
|
+
</recordCreates>
|
|
192
|
+
<loops>
|
|
193
|
+
<name>Loop_Records</name>
|
|
194
|
+
<collectionReference>Get_Records</collectionReference>
|
|
195
|
+
</loops>
|
|
196
|
+
</Flow>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## XPath Patterns for Metadata (PMD 7)
|
|
200
|
+
|
|
201
|
+
All patterns below use `@Text` for text content matching (NOT `text()` which is broken in PMD 7).
|
|
202
|
+
|
|
203
|
+
### Require description on Custom Fields
|
|
204
|
+
|
|
205
|
+
**Missing description entirely:**
|
|
206
|
+
```xpath
|
|
207
|
+
//*[local-name()='CustomField'][not(*[local-name()='description'])]
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Missing OR empty description (catches `<description></description>` too):**
|
|
211
|
+
```xpath
|
|
212
|
+
//*[local-name()='CustomField'][not(*[local-name()='description']) or *[local-name()='description'][not(.//*[@Text])]]
|
|
213
|
+
```
|
|
214
|
+
Note: `.//*[@Text]` checks for any descendant text node with content. Do NOT put `[not(@Text)]` directly on the `<description>` element — `@Text` only exists on child text nodes, not on elements themselves. Putting it on the element always evaluates to true (false positive).
|
|
215
|
+
|
|
216
|
+
### Flag ModifyAllData / ViewAllData in Permission Sets
|
|
217
|
+
```xpath
|
|
218
|
+
//*[@Text='ModifyAllData' or @Text='ViewAllData']/../..
|
|
219
|
+
[local-name()='userPermissions']
|
|
220
|
+
[.//*[@Text='true']]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Enforce minimum API version (60.0+)
|
|
224
|
+
```xpath
|
|
225
|
+
//*[local-name()='apiVersion']/*[number(@Text) < 60]
|
|
226
|
+
```
|
|
227
|
+
Note: `/*` navigates to the **child text node** where `@Text` lives. Do NOT put `@Text` directly on the element — it won't match because `@Text` only exists on text nodes (children), not on elements themselves.
|
|
228
|
+
|
|
229
|
+
### Flag field permissions in Profiles (should be in Perm Sets)
|
|
230
|
+
```xpath
|
|
231
|
+
//*[local-name()='Profile']/*[local-name()='fieldPermissions']
|
|
232
|
+
```
|
|
233
|
+
(No text matching needed — structural only.)
|
|
234
|
+
|
|
235
|
+
### Flag dangerous permissions (ModifyAllData only, enabled)
|
|
236
|
+
```xpath
|
|
237
|
+
//*[@Text='ModifyAllData']/../..
|
|
238
|
+
[local-name()='userPermissions']
|
|
239
|
+
[.//*[@Text='true']]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Flag DML (recordCreates/recordUpdates/recordDeletes) inside Flow loops
|
|
243
|
+
```xpath
|
|
244
|
+
//*[local-name()='loops'][
|
|
245
|
+
following-sibling::*[local-name()='recordCreates' or local-name()='recordUpdates' or local-name()='recordDeletes']
|
|
246
|
+
]
|
|
247
|
+
```
|
|
248
|
+
(No text matching needed — structural only.)
|
|
249
|
+
|
|
250
|
+
## Targeting Specific File Types
|
|
251
|
+
|
|
252
|
+
PMD only scans `.xml` files by default. You must add `.xml` to the config. This single extension covers ALL Salesforce compound metadata extensions (`.permissionset-meta.xml`, `.field-meta.xml`, etc.) because `path.extname()` returns `.xml` from these filenames.
|
|
253
|
+
|
|
254
|
+
```yaml
|
|
255
|
+
# code-analyzer.yml
|
|
256
|
+
engines:
|
|
257
|
+
pmd:
|
|
258
|
+
file_extensions:
|
|
259
|
+
xml: [".xml"]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
⚠️ Do NOT add compound extensions — the validator rejects anything that doesn't match `/^[.][a-zA-Z0-9]+$/`.
|
|
263
|
+
|
|
264
|
+
Additionally, scope your XPath to the correct root element to avoid false positives across file types:
|
|
265
|
+
|
|
266
|
+
```xpath
|
|
267
|
+
//*[@Text='ModifyAllData']/../..[local-name()='userPermissions']
|
|
268
|
+
[ancestor::*[local-name()='PermissionSet']]
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
This ensures the rule only fires on permission set files, not on profile or other metadata files.
|
|
272
|
+
|
|
273
|
+
## Gotchas
|
|
274
|
+
|
|
275
|
+
| Issue | Resolution |
|
|
276
|
+
|-------|------------|
|
|
277
|
+
| XPath matches nothing | Three causes: (1) Forgot `local-name()`. (2) Used `text()` — broken in PMD 7, use `@Text`. (3) File extension not in `file_extensions.xml` config. |
|
|
278
|
+
| Rule fires on wrong metadata type | Add root element scope: `[ancestor::*[local-name()='PermissionSet']]` |
|
|
279
|
+
| Can't match text content | **Use `@Text` attribute** (NOT `text()`). Example: `//*[@Text='ModifyAllData']` |
|
|
280
|
+
| `@Text` matches but parent navigation fails | `@Text` matches TEXT NODES. Go up with `../..`: text→element→parent. |
|
|
281
|
+
| Number comparison fails | `@Text` lives on the **child text node**, NOT on the element. Use: `//*[local-name()='element']/*[number(@Text) < N]` — the `/*` navigates to the text node. Do NOT put `[@Text]` predicate directly on the element (returns 0 matches). |
|
|
282
|
+
| Too many violations on large orgs | Scope with `file_extensions` in PMD config or add `ignores.files` patterns |
|
|
283
|
+
| Rule doesn't fire on metadata files | Add `file_extensions: { xml: [".xml"] }` under `engines.pmd` — this covers all compound extensions. Do NOT add `.permissionset-meta.xml` etc. (validator rejects them). |
|
|
284
|
+
| ast-dump fails with "XmlEncoding" error | Known issue. Read the raw XML file — the file content IS the DOM structure |
|
|
285
|
+
| ast-dump shows different structure | XML AST is the literal DOM — what you see in the file is what you get |
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Regex Rule Schema Reference
|
|
2
|
+
|
|
3
|
+
Custom regex rules are defined inline in `code-analyzer.yml` under `engines.regex.custom_rules`.
|
|
4
|
+
|
|
5
|
+
## Complete Schema
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
engines:
|
|
9
|
+
regex:
|
|
10
|
+
custom_rules:
|
|
11
|
+
RuleName: # Must match: /^[A-Za-z@][A-Za-z_0-9@\-/]*$/
|
|
12
|
+
regex: "/pattern/flags" # REQUIRED — JavaScript regex literal format
|
|
13
|
+
description: "What this checks" # REQUIRED — Purpose of the rule
|
|
14
|
+
violation_message: "Fix this" # Optional — Message shown on violation
|
|
15
|
+
severity: 3 # Optional — 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Info
|
|
16
|
+
tags: # Optional — Default: ['Recommended', 'Custom']
|
|
17
|
+
- "Custom"
|
|
18
|
+
- "Security"
|
|
19
|
+
file_extensions: # Optional — Default: all text files
|
|
20
|
+
- ".cls"
|
|
21
|
+
- ".trigger"
|
|
22
|
+
regex_ignore: "/exception/i" # Optional — Matches excluded from violations
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Field Details
|
|
26
|
+
|
|
27
|
+
### `regex` (Required)
|
|
28
|
+
|
|
29
|
+
JavaScript regex literal format: `/pattern/flags`
|
|
30
|
+
|
|
31
|
+
| Format | Example | Meaning |
|
|
32
|
+
|--------|---------|---------|
|
|
33
|
+
| `/pattern/g` | `/TODO/g` | Case-sensitive, global (find all matches) — **minimum required** |
|
|
34
|
+
| `/pattern/gi` | `/todo/gi` | Global + case-insensitive |
|
|
35
|
+
| `/pattern/gm` | `/^import/gm` | Global + multiline (^ matches each line start) |
|
|
36
|
+
|
|
37
|
+
**Common pitfalls:**
|
|
38
|
+
- **The `/g` (global) flag is REQUIRED.** Code Analyzer rejects regex patterns without at least one flag after the closing `/`. Always include `/g` (or `/gi`, `/gm`, etc.).
|
|
39
|
+
- Backslashes must be escaped: `\d` → write as `/\\d/`
|
|
40
|
+
- Dots match any character: use `/\\.cls/` not `/.cls/`
|
|
41
|
+
- Regex is tested against file content line by line
|
|
42
|
+
|
|
43
|
+
**Named capture groups** narrow the violation highlight to a specific portion of the match:
|
|
44
|
+
```yaml
|
|
45
|
+
# Without named group — entire match is highlighted:
|
|
46
|
+
regex: "/System\\.debug\\([^)]*\\)/g"
|
|
47
|
+
# Highlights: "System.debug('hello')" (entire expression)
|
|
48
|
+
|
|
49
|
+
# With named group — only captured portion highlighted:
|
|
50
|
+
regex: "/(?<target>System\\.debug)\\([^)]*\\)/g"
|
|
51
|
+
# Highlights: "System.debug" (just the method name)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use `(?<target>...)` when the regex needs surrounding context to match correctly, but the violation should point to a narrower location. Common patterns:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
# Highlight just the hardcoded ID, not the surrounding quotes
|
|
58
|
+
regex: "/['\"](?<target>[0-9a-zA-Z]{15,18})['\"]/g"
|
|
59
|
+
|
|
60
|
+
# Highlight the annotation, not the whole line
|
|
61
|
+
regex: "/(?<target>@SuppressWarnings\\([^)]*\\))/g"
|
|
62
|
+
|
|
63
|
+
# Highlight the method call within a larger expression
|
|
64
|
+
regex: "/(?<target>Database\\.query)\\(/g"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `description` (Required)
|
|
68
|
+
|
|
69
|
+
Brief explanation of what the rule enforces. Used in rule listings and generated messages.
|
|
70
|
+
|
|
71
|
+
### `violation_message` (Optional)
|
|
72
|
+
|
|
73
|
+
Message shown when a violation is found. If omitted, auto-generated as:
|
|
74
|
+
> "A match of the regular expression `<regex>` was found for rule '<RuleName>': <description>"
|
|
75
|
+
|
|
76
|
+
Good violation messages tell the user HOW to fix, not just WHAT's wrong:
|
|
77
|
+
- ❌ "Hardcoded ID found"
|
|
78
|
+
- ✅ "Replace hardcoded Salesforce ID with a Custom Label or Custom Metadata reference"
|
|
79
|
+
|
|
80
|
+
### `severity` (Optional, default: 3)
|
|
81
|
+
|
|
82
|
+
| Value | Name | Meaning |
|
|
83
|
+
|-------|------|---------|
|
|
84
|
+
| 1 | Critical | Security vulnerability, must fix before deploy |
|
|
85
|
+
| 2 | High | Significant quality issue, should fix |
|
|
86
|
+
| 3 | Moderate | Recommended improvement |
|
|
87
|
+
| 4 | Low | Minor style issue |
|
|
88
|
+
| 5 | Info | Informational, no action required |
|
|
89
|
+
|
|
90
|
+
Also accepts string names: `"Critical"`, `"High"`, `"Moderate"`, `"Low"`, `"Info"`
|
|
91
|
+
|
|
92
|
+
### `tags` (Optional, default: ['Recommended', 'Custom'])
|
|
93
|
+
|
|
94
|
+
Array of tag strings. Used for `--rule-selector` filtering. Common tags:
|
|
95
|
+
- `Custom` — auto-applied to user-created rules
|
|
96
|
+
- `Security` — security-related rules
|
|
97
|
+
- `BestPractices` — coding standards
|
|
98
|
+
- `Performance` — performance anti-patterns
|
|
99
|
+
- `CodeStyle` — formatting/naming rules
|
|
100
|
+
|
|
101
|
+
### `file_extensions` (Optional, default: all text files)
|
|
102
|
+
|
|
103
|
+
Array of file extensions to scan. Each must start with `.` and match: `/^([.][a-zA-Z0-9-_]+)+$/`
|
|
104
|
+
|
|
105
|
+
Common values for Salesforce:
|
|
106
|
+
- `.cls` — Apex classes
|
|
107
|
+
- `.trigger` — Apex triggers
|
|
108
|
+
- `.js` — JavaScript/LWC
|
|
109
|
+
- `.html` — LWC HTML templates
|
|
110
|
+
- `.xml` — Metadata files
|
|
111
|
+
- `.page` — Visualforce pages
|
|
112
|
+
- `.component` — Visualforce components
|
|
113
|
+
|
|
114
|
+
### `regex_ignore` (Optional)
|
|
115
|
+
|
|
116
|
+
A second regex pattern — matches that ALSO match this pattern are excluded. Useful for reducing false positives.
|
|
117
|
+
|
|
118
|
+
⚠️ **`regex_ignore` operates per-LINE, not per-FILE.** It only skips a match if the same line also matches the ignore pattern. It does NOT exclude entire files or classes. For example, adding `/@isTest/` only suppresses violations on lines that literally contain `@isTest` — a hardcoded ID on line 50 of a test class still flags because line 50 doesn't contain `@isTest`.
|
|
119
|
+
|
|
120
|
+
To exclude entire files, use `ignores.files` in `code-analyzer.yml`:
|
|
121
|
+
```yaml
|
|
122
|
+
ignores:
|
|
123
|
+
files:
|
|
124
|
+
- "**/*Test.cls"
|
|
125
|
+
- "**/test/**"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
# Flag System.debug everywhere EXCEPT in lines with "// OK" comment
|
|
130
|
+
regex: "/System\\.debug/g"
|
|
131
|
+
regex_ignore: "/\\/\\/ OK/"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Validation Rules
|
|
135
|
+
|
|
136
|
+
| Rule | Error if violated |
|
|
137
|
+
|------|-------------------|
|
|
138
|
+
| Name matches `/^[A-Za-z@][A-Za-z_0-9@\-/]*$/` | "Invalid rule name" |
|
|
139
|
+
| Regex starts and ends with `/` | "Invalid regex format" |
|
|
140
|
+
| File extensions start with `.` | "Invalid file extension" |
|
|
141
|
+
| Severity is 1-5 or valid name | "Invalid severity" |
|
|
142
|
+
| No duplicate rule names | "Rule already exists" |
|
|
143
|
+
|
|
144
|
+
## Multiple Rules Example
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
engines:
|
|
148
|
+
regex:
|
|
149
|
+
custom_rules:
|
|
150
|
+
NoHardcodedIds:
|
|
151
|
+
regex: "/['\"][0-9a-zA-Z]{15,18}['\"]/g"
|
|
152
|
+
description: "Detects hardcoded Salesforce record IDs"
|
|
153
|
+
violation_message: "Use Custom Labels or Custom Metadata instead of hardcoded IDs"
|
|
154
|
+
severity: 2
|
|
155
|
+
tags: ["Custom", "Security"]
|
|
156
|
+
file_extensions: [".cls", ".trigger"]
|
|
157
|
+
|
|
158
|
+
NoTodos:
|
|
159
|
+
regex: "/TODO|FIXME|HACK/gi"
|
|
160
|
+
description: "Flags TODO/FIXME/HACK comments that should be resolved"
|
|
161
|
+
violation_message: "Resolve or remove this TODO/FIXME/HACK before merging"
|
|
162
|
+
severity: 4
|
|
163
|
+
tags: ["Custom", "BestPractices"]
|
|
164
|
+
|
|
165
|
+
NoSuppressWarnings:
|
|
166
|
+
regex: "/(?<target>@SuppressWarnings\\([^)]*\\))/g"
|
|
167
|
+
description: "Bans @SuppressWarnings annotations — violations must be fixed, not suppressed"
|
|
168
|
+
violation_message: "Remove @SuppressWarnings and fix the underlying violation instead"
|
|
169
|
+
severity: 2
|
|
170
|
+
tags: ["Recommended", "Custom", "BestPractices"]
|
|
171
|
+
file_extensions: [".cls", ".trigger"]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
⚠️ **Note on test-class exclusion:** The `regex_ignore` field is per-LINE only. Adding `regex_ignore: "/@isTest/i"` does NOT exclude entire test classes — it only skips lines that literally contain `@isTest`. For SOQL rules that should skip test classes, use PMD/XPath instead (which can structurally check `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]`). See the "Excluding Test Classes" section in SKILL.md.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Troubleshooting Custom Rules
|
|
2
|
+
|
|
3
|
+
## Regex Rule Issues
|
|
4
|
+
|
|
5
|
+
| Problem | Cause | Fix |
|
|
6
|
+
|---------|-------|-----|
|
|
7
|
+
| "Invalid rule name" | Name starts with number or contains spaces | Use PascalCase: `NoHardcodedIds`, `BanTodoComments` |
|
|
8
|
+
| "Invalid regex" | Missing `/` delimiters | Must be `/pattern/flags` format |
|
|
9
|
+
| YAML parse error when adding regex | Quotes/backslashes in regex conflict with YAML syntax | **Use `create-regex-rule.js` script** — do not manually write regex into YAML. The script handles serialization correctly. |
|
|
10
|
+
| Rule not listed | YAML indentation wrong | Must be under `engines.regex.custom_rules` with correct nesting |
|
|
11
|
+
| No violations found | Regex doesn't match content | Test regex at regex101.com with your file content |
|
|
12
|
+
| Matches in wrong files | `file_extensions` not set | Add `file_extensions: [".cls"]` to limit scope |
|
|
13
|
+
| Matches in comments | Regex is text-based | Consider using PMD/XPath instead for code-aware matching |
|
|
14
|
+
| Too many matches | Regex too broad | Tighten pattern first (e.g., require leading `0` for IDs), then add `regex_ignore` for remaining edge cases |
|
|
15
|
+
| `regex_ignore` doesn't exclude test classes | `regex_ignore` is per-LINE, not per-FILE | It only skips lines where the ignore pattern matches. To exclude entire test files, use `ignores.files: ["**/*Test.cls"]` in `code-analyzer.yml` |
|
|
16
|
+
| Hardcoded ID regex flags normal words | Pattern `{15,18}` matches 16/17 char words | Salesforce IDs are exactly 15 OR 18 chars, start with `0`. Use: `0[a-zA-Z0-9]{14}(?:[a-zA-Z0-9]{3})?` |
|
|
17
|
+
| Special chars not working | YAML escaping issue | Use `create-regex-rule.js` script to avoid escaping issues. If manual: double-escape (`\\d` → `\d`) |
|
|
18
|
+
|
|
19
|
+
### Common Regex Escaping in YAML
|
|
20
|
+
|
|
21
|
+
| Character | In YAML | In regex |
|
|
22
|
+
|-----------|---------|----------|
|
|
23
|
+
| Backslash | `\\\\` | `\\` |
|
|
24
|
+
| Dot | `\\.` | `.` as literal |
|
|
25
|
+
| Quote | `\\"` or use single quotes | `"` |
|
|
26
|
+
| Newline | `\\n` | Line break |
|
|
27
|
+
|
|
28
|
+
## PMD/XPath Rule Issues
|
|
29
|
+
|
|
30
|
+
| Problem | Cause | Fix |
|
|
31
|
+
|---------|-------|-----|
|
|
32
|
+
| "Could not locate Java" | Java not installed or not on PATH | Install Java 11+ or set `engines.pmd.java_command` in config |
|
|
33
|
+
| "Language not supported" | Invalid language identifier | Use: apex, visualforce, html, xml, javascript |
|
|
34
|
+
| "File not found" for ruleset | Wrong path in `custom_rulesets` | Path is relative to `code-analyzer.yml` location |
|
|
35
|
+
| Rule not listed after creation | XML format wrong | Verify against template in `assets/pmd-ruleset-template.xml` |
|
|
36
|
+
| XPath returns 0 matches | Node name or attribute wrong | Re-run `sf code-analyzer ast-dump` and compare |
|
|
37
|
+
| XPath too broad | Missing attribute filter | Add `[@attribute='value']` conditions |
|
|
38
|
+
| XPath `@WithSharing='false'` returns 0 matches | PMD 7 boolean attributes are always present, not string-typed | Use XPath boolean function: `@WithSharing = false()`. NOT string `'false'`, NOT `not(@WithSharing)`. Same for `@Abstract`, `@Final`, etc. |
|
|
39
|
+
| Loop rule flags `for (x : [SELECT...])` | Used `//ForEachStatement//SoqlExpression` (matches iterable) | Scope to body: `//ForEachStatement/BlockStatement//SoqlExpression` |
|
|
40
|
+
| PMD parse error on sample code | Invalid Apex syntax in sample | Ensure sample compiles (doesn't need to deploy) |
|
|
41
|
+
|
|
42
|
+
### Debugging XPath Step by Step
|
|
43
|
+
|
|
44
|
+
1. **Dump the AST:** `sf code-analyzer ast-dump --file sample.cls`
|
|
45
|
+
2. **Find the target node:** Search the XML for the pattern (e.g., grep for "System.debug")
|
|
46
|
+
3. **Note the exact node name:** e.g., `MethodCallExpression` (not `MethodCall`)
|
|
47
|
+
4. **Note the exact attribute:** e.g., `FullMethodName='System.debug'` (not `Name` or `Image`)
|
|
48
|
+
5. **Check ancestry:** What's the parent chain? Does your XPath's `ancestor::` match?
|
|
49
|
+
6. **Simplify first:** Start with `//MethodCallExpression` → verify it matches → then add filters
|
|
50
|
+
|
|
51
|
+
### Verifying Rule Registration
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Check if rule appears in the rule list
|
|
55
|
+
sf code-analyzer rules --rule-selector regex:MyRuleName
|
|
56
|
+
sf code-analyzer rules --rule-selector pmd:MyRuleName
|
|
57
|
+
|
|
58
|
+
# Check all custom rules
|
|
59
|
+
sf code-analyzer rules --rule-selector Custom
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Verifying Rule Execution
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Test against a file that SHOULD violate
|
|
66
|
+
sf code-analyzer run --rule-selector regex:MyRuleName --target ./sample-violation.cls
|
|
67
|
+
|
|
68
|
+
# Test against a file that should NOT violate (expect 0 results)
|
|
69
|
+
sf code-analyzer run --rule-selector regex:MyRuleName --target ./clean-file.cls
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Metadata XML Rule Issues
|
|
73
|
+
|
|
74
|
+
| Problem | Cause | Fix |
|
|
75
|
+
|---------|-------|-----|
|
|
76
|
+
| XPath matches nothing on metadata | Missing `local-name()` | MUST use `*[local-name()='element']` — namespace blocks bare names |
|
|
77
|
+
| XPath matches nothing (text values) | Used `text()` — broken in PMD 7 | Use `@Text` attribute: `//*[@Text='value']` |
|
|
78
|
+
| Rule fires on wrong file type | XPath not scoped to root element | Add root check or `ancestor::*[local-name()='PermissionSet']` |
|
|
79
|
+
| `text()` comparison always returns 0 | PMD 7 XML language change | Replace ALL `text()='value'` with `@Text='value'`. This is a PMD 7 breaking change. |
|
|
80
|
+
| `@Text` matches but can't navigate up | `@Text` matches TEXT NODES, not elements | Navigate up: `//*[@Text='value']/..` (element), `/../..` (grandparent) |
|
|
81
|
+
| `[@Text]` predicate on element returns 0 matches | `@Text` lives on CHILD text nodes, not on elements | Use `/*[@Text...]` to navigate to child text node. E.g., `//*[local-name()='apiVersion']/*[number(@Text) < 60]` — NOT `//*[local-name()='apiVersion'][number(@Text) < 60]` |
|
|
82
|
+
| Child predicate `[*[local-name()='x'][@Text='y']]` returns 0 | PMD 7 doesn't support `@Text` in child predicates | Use bottom-up navigation: start from `@Text` match, go up with `../..` |
|
|
83
|
+
| Number comparison doesn't work | Need to use `@Text` not `text()` | `number(substring-before(@Text, '.'))` |
|
|
84
|
+
| Rule doesn't fire on metadata files | PMD only scans `.xml` extension by default | Add `file_extensions: { xml: [".xml"] }` under `engines.pmd`. This covers all compound extensions (`.permissionset-meta.xml`, etc.). Do NOT add compound extensions — the validator rejects them. |
|
|
85
|
+
| "Too many violations" on metadata | Rule too broad, scanning all XML | Scope to specific file types via root element check |
|
|
86
|
+
| ast-dump for XML fails with "XmlEncoding" error | Known bug in some versions | Read the raw XML file — the file content IS the DOM structure |
|
|
87
|
+
| ast-dump for XML shows flat DOM | Expected — XML AST is the literal DOM tree | Elements, text nodes, and attributes — not like Apex AST |
|
|
88
|
+
|
|
89
|
+
### Debugging Metadata XML XPath (PMD 7)
|
|
90
|
+
|
|
91
|
+
1. **Dump the AST** (or read raw file if ast-dump fails): `sf code-analyzer ast-dump --file MyField.field-meta.xml --language xml`
|
|
92
|
+
2. **Check namespace:** If file has `xmlns="..."`, ALL XPath must use `local-name()`
|
|
93
|
+
3. **Use `@Text` for text values:** Never use `text()` — it does not work in PMD 7 XML
|
|
94
|
+
4. **Start from the text node, navigate up:** `//*[@Text='TargetValue']/../..` to reach parent elements
|
|
95
|
+
5. **Test with `//*` first:** Confirm PMD is scanning the file at all (should return many violations)
|
|
96
|
+
6. **Test `@Text` matching:** `//*[@Text='YourValue']` — confirm the text node is found
|
|
97
|
+
7. **Add navigation one step at a time:** `../..`, then `[local-name()='parent']`, then sibling checks
|
|
98
|
+
8. **Check file extensions:** Ensure `file_extensions: { xml: [".xml"] }` is in your `engines.pmd` config. Just `.xml` is enough — it covers all Salesforce compound extensions.
|
|
99
|
+
|
|
100
|
+
## ESLint Rule Issues
|
|
101
|
+
|
|
102
|
+
| Problem | Cause | Fix |
|
|
103
|
+
|---------|-------|-----|
|
|
104
|
+
| Custom rules not appearing | Plugin missing `meta.docs.description` + `meta.docs.url` | Ensure plugin exports rule metadata |
|
|
105
|
+
| "Cannot find module" for plugin | Plugin not installed | Run `npm install --save-dev eslint-plugin-<name>` |
|
|
106
|
+
| Base rules conflict with custom | Both Code Analyzer base + custom config active | Set `disable_<type>_base_config: true` to suppress built-in rules |
|
|
107
|
+
| ESLint config not found | Wrong path or auto-discover off | Set `eslint_config_file` explicitly or enable `auto_discover_eslint_config` |
|
|
108
|
+
| Deprecated rules excluded | ESLint engine filters deprecated rules | Use replacement rule name (check ESLint docs) |
|
|
109
|
+
| `eslint_config_file` path in `code-analyzer.yml` fails at startup | Code Analyzer validates the path at load time | **Create `eslint.config.js` BEFORE adding it to `code-analyzer.yml`.** File must exist first. |
|
|
110
|
+
| Core rule (e.g., `no-restricted-globals`) not appearing in `sf code-analyzer rules` | Rule not enabled in `eslint.config.js` | Core ESLint rules only appear after you enable them in config AND Code Analyzer loads it. Always validate after configuration. |
|
|
111
|
+
| Created same rule in both Regex AND PMD — double-flagging | Rule defined in two engines | A rule should live in ONE engine only. Use PMD when structural exclusions (e.g., test classes) are needed; Regex for simple text patterns with no exclusions. |
|
|
112
|
+
|
|
113
|
+
## PMD Apex Rule Issues
|
|
114
|
+
|
|
115
|
+
> XPath authoring gotchas (loop-type coverage, PMD 7 boolean attributes like `@WithSharing`, etc.) live in the SKILL.md **Gotchas** table — that is the single source. This section covers PMD issues outside XPath authoring.
|
|
116
|
+
|
|
117
|
+
| Problem | Cause | Fix |
|
|
118
|
+
|---------|-------|-----|
|
|
119
|
+
| Rule works but wrong severity shows | Severity set in wrong place | Regex: edit `severity` under `engines.regex.custom_rules.<RuleName>` in `code-analyzer.yml`. PMD: set `<priority>` in the ruleset XML. |
|
|
120
|
+
| Too many false positives | Pattern too broad | Regex: tighten pattern, then add `regex_ignore` for edge cases. PMD: tighten XPath with `[not(...)]` predicates. |
|
|
121
|
+
| Want to customize threshold on a built-in rule | Can't modify built-in ruleset files | Use `<rule ref="category/...">` with nested `<properties>` override — see `references/advanced-pmd-patterns.md`. |
|
|
122
|
+
|
|
123
|
+
## Config File Issues
|
|
124
|
+
|
|
125
|
+
| Problem | Cause | Fix |
|
|
126
|
+
|---------|-------|-----|
|
|
127
|
+
| Config not picked up | File named wrong or in wrong directory | Must be `code-analyzer.yml` or `code-analyzer.yaml` at project root |
|
|
128
|
+
| YAML parse error | Invalid YAML syntax | Check indentation (2 spaces), colon spacing, quote balance |
|
|
129
|
+
| "engines.regex.custom_rules" not recognized | Wrong nesting level | Ensure correct hierarchy: `engines` → `regex` → `custom_rules` → `RuleName` |
|
|
130
|
+
| Multiple config files conflict | Both `.yml` and `.yaml` exist | Delete one — `.yaml` takes precedence over `.yml` |
|
|
131
|
+
| Custom ruleset path not found | Path not relative to config_root | `custom_rulesets` paths resolve relative to `code-analyzer.yml` directory |
|
|
132
|
+
| Rule severity override not working | Wrong config location | Use `rules.pmd.RuleName.severity` in config, not inside ruleset XML |
|
|
133
|
+
| Can't disable a specific rule | Wrong field name | Use `rules.pmd.RuleName.disabled: true` in `code-analyzer.yml` |
|
|
134
|
+
|
|
135
|
+
## Exclusion Issues
|
|
136
|
+
|
|
137
|
+
| Problem | Cause | Fix |
|
|
138
|
+
|---------|-------|-----|
|
|
139
|
+
| Rule still fires on excluded files | Glob pattern wrong | Use `**` for recursive: `"**/fflib_*"` not `"fflib_*"` |
|
|
140
|
+
| PMD exclude-pattern ignored | Pattern syntax differs from glob | PMD uses Java regex in `<exclude-pattern>`, not glob |
|
|
141
|
+
| Want to exclude one rule from a category | Missing `<exclude>` in ref | Use `<rule ref="category/..."><exclude name="RuleName"/></rule>` |
|