@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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/skills/dx-code-analyzer-configure/SKILL.md +31 -13
  3. package/skills/dx-code-analyzer-custom-rule-create/SKILL.md +484 -0
  4. package/skills/dx-code-analyzer-custom-rule-create/assets/pmd-ruleset-template.xml +31 -0
  5. package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-fields-api.md +87 -0
  6. package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-flows.md +105 -0
  7. package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-example-permissions.md +95 -0
  8. package/skills/dx-code-analyzer-custom-rule-create/examples/metadata-xml-examples.md +84 -0
  9. package/skills/dx-code-analyzer-custom-rule-create/examples/regex-examples.md +127 -0
  10. package/skills/dx-code-analyzer-custom-rule-create/examples/xpath-examples.md +227 -0
  11. package/skills/dx-code-analyzer-custom-rule-create/references/advanced-pmd-patterns.md +288 -0
  12. package/skills/dx-code-analyzer-custom-rule-create/references/apex-ast-reference.md +127 -0
  13. package/skills/dx-code-analyzer-custom-rule-create/references/eslint-custom-plugins.md +247 -0
  14. package/skills/dx-code-analyzer-custom-rule-create/references/eslint-rules-discovery.md +188 -0
  15. package/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier2-configurable.md +114 -0
  16. package/skills/dx-code-analyzer-custom-rule-create/references/eslint-tier3-custom-plugins.md +113 -0
  17. package/skills/dx-code-analyzer-custom-rule-create/references/metadata-xml-rules.md +285 -0
  18. package/skills/dx-code-analyzer-custom-rule-create/references/regex-rule-schema.md +174 -0
  19. package/skills/dx-code-analyzer-custom-rule-create/references/troubleshooting.md +141 -0
  20. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-governor-limits.md +83 -0
  21. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-method-calls.md +108 -0
  22. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-security.md +45 -0
  23. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns-structure.md +127 -0
  24. package/skills/dx-code-analyzer-custom-rule-create/references/xpath-patterns.md +131 -0
  25. package/skills/dx-code-analyzer-custom-rule-create/scripts/create-pmd-rule.js +209 -0
  26. package/skills/dx-code-analyzer-custom-rule-create/scripts/create-regex-rule.js +220 -0
  27. package/skills/dx-code-analyzer-run/SKILL.md +41 -8
  28. package/skills/mobile-platform-native-capabilities-integrate/SKILL.md +3 -3
  29. package/skills/platform-custom-field-generate/SKILL.md +86 -126
  30. package/skills/platform-custom-field-generate/references/advanced-picklists.md +590 -0
  31. 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>` |