@salesforce/afv-skills 1.28.0 → 1.30.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/README.md +2 -0
- package/package.json +1 -1
- package/skills/agentforce-architecture-analyze/README.md +2 -8
- package/skills/agentforce-architecture-analyze/SKILL.md +2 -2
- package/skills/agentforce-architecture-analyze/references/contract.json +4 -4
- package/skills/agentforce-architecture-analyze/references/soql_fields.md +1 -1
- package/skills/data360-activate/README.md +0 -1
- package/skills/data360-activate/SKILL.md +0 -2
- package/skills/data360-connect/README.md +0 -2
- package/skills/data360-connect/SKILL.md +0 -2
- package/skills/data360-harmonize/SKILL.md +0 -1
- package/skills/data360-orchestrate/README.md +0 -7
- package/skills/data360-orchestrate/SKILL.md +0 -2
- package/skills/data360-prepare/README.md +0 -1
- package/skills/data360-prepare/SKILL.md +0 -1
- package/skills/data360-query/README.md +0 -1
- package/skills/data360-query/SKILL.md +0 -1
- package/skills/data360-segment/README.md +0 -1
- package/skills/data360-segment/SKILL.md +0 -2
- 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/experience-cms-brand-apply/SKILL.md +47 -16
- package/skills/experience-lwc-generate/README.md +3 -2
- package/skills/experience-lwc-generate/references/async-notification-patterns.md +1 -1
- package/skills/experience-lwc-generate/references/component-patterns.md +3 -3
- package/skills/experience-lwc-generate/references/flow-integration-guide.md +1 -1
- package/skills/experience-lwc-generate/references/lwc-best-practices.md +4 -4
- package/skills/experience-lwc-generate/references/scoring-and-testing.md +1 -1
- package/skills/experience-lwc-generate/references/state-management.md +2 -2
- package/skills/experience-lwc-generate/references/triangle-pattern.md +3 -3
- package/skills/experience-lwc-generate/scripts/post-tool-validate.py +2 -2
- package/skills/experience-lwc-generate/scripts/template_validator.py +1 -1
- package/skills/external-diagram-mermaid-generate/README.md +2 -2
- package/skills/external-diagram-mermaid-generate/SKILL.md +1 -3
- package/skills/external-diagram-mermaid-generate/assets/agentforce/agent-flow.md +4 -4
- package/skills/external-diagram-mermaid-generate/assets/architecture/system-landscape.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/datamodel/b2b-commerce-erd.md +3 -3
- package/skills/external-diagram-mermaid-generate/assets/datamodel/campaigns-erd.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/datamodel/consent-erd.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/datamodel/files-erd.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/datamodel/forecasting-erd.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/datamodel/fsl-erd.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/datamodel/party-model-erd.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/datamodel/quote-order-erd.md +3 -3
- package/skills/external-diagram-mermaid-generate/assets/datamodel/revenue-cloud-erd.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/datamodel/sales-cloud-erd.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/datamodel/salesforce-erd.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/datamodel/scheduler-erd.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/datamodel/service-cloud-erd.md +3 -3
- package/skills/external-diagram-mermaid-generate/assets/datamodel/territory-management-erd.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/integration/api-sequence.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/oauth/authorization-code-pkce.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/oauth/authorization-code.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/oauth/client-credentials.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/oauth/device-authorization.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/oauth/jwt-bearer.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/oauth/refresh-token.md +1 -1
- package/skills/external-diagram-mermaid-generate/assets/oauth/user-agent-social-sign-on.md +2 -2
- package/skills/external-diagram-mermaid-generate/assets/role-hierarchy/user-hierarchy.md +1 -1
- package/skills/external-diagram-mermaid-generate/references/color-palette.md +13 -13
- package/skills/external-diagram-mermaid-generate/references/diagram-conventions.md +5 -5
- package/skills/external-diagram-mermaid-generate/references/erd-conventions.md +1 -1
- package/skills/external-diagram-mermaid-generate/references/mermaid-styling.md +2 -2
- package/skills/external-diagram-mermaid-generate/references/preview-guide.md +1 -1
- package/skills/external-diagram-mermaid-generate/references/usage-examples.md +9 -9
- package/skills/external-diagram-mermaid-generate/scripts/README.md +1 -1
- package/skills/integration-connectivity-connected-app-configure/README.md +4 -5
- package/skills/integration-connectivity-connected-app-configure/SKILL.md +2 -2
- package/skills/integration-connectivity-connected-app-configure/references/example-usage.md +7 -7
- package/skills/integration-connectivity-connected-app-configure/references/migration-guide.md +1 -1
- package/skills/integration-connectivity-connected-app-configure/references/oauth-flows-reference.md +1 -1
- package/skills/integration-connectivity-connected-app-configure/references/testing-validation-guide.md +3 -3
- package/skills/integration-connectivity-generate/README.md +1 -1
- package/skills/integration-connectivity-generate/SKILL.md +6 -0
- package/skills/integration-connectivity-generate/assets/callouts/rest-queueable-callout.cls +1 -1
- package/skills/integration-connectivity-generate/assets/external-services/external-service-operations.md +1 -1
- package/skills/integration-connectivity-generate/assets/soap/wsdl2apex-guide.md +1 -1
- package/skills/integration-connectivity-generate/references/callout-patterns.md +4 -0
- package/skills/integration-connectivity-generate/references/cdc-guide.md +2 -2
- package/skills/integration-connectivity-generate/references/event-driven-architecture-guide.md +5 -5
- package/skills/integration-connectivity-generate/references/event-patterns.md +3 -2
- package/skills/integration-connectivity-generate/references/external-services-guide.md +4 -1
- package/skills/integration-connectivity-generate/references/messaging-api-v2.md +2 -2
- package/skills/integration-connectivity-generate/references/named-credentials-automation.md +1 -1
- package/skills/integration-connectivity-generate/references/named-credentials-guide.md +12 -2
- package/skills/integration-connectivity-generate/references/platform-events-guide.md +3 -3
- package/skills/integration-connectivity-generate/references/rest-callout-patterns.md +1 -1
- package/skills/integration-connectivity-generate/references/scoring-rubric.md +1 -1
- package/skills/integration-connectivity-generate/references/security-best-practices.md +5 -2
- package/skills/integration-connectivity-generate/scripts/configure-named-credential.sh +16 -5
- package/skills/integration-connectivity-generate/scripts/set-api-credential.sh +11 -2
- package/skills/integration-connectivity-generate/scripts/suggest_credential_setup.py +4 -3
- package/skills/integration-connectivity-generate/scripts/validate_integration.py +1 -1
- package/skills/mobile-platform-native-capabilities-integrate/SKILL.md +3 -3
- package/skills/omnistudio-callable-apex-generate/README.md +1 -5
- package/skills/omnistudio-callable-apex-generate/SKILL.md +14 -4
- package/skills/omnistudio-callable-apex-generate/assets/pattern_callable_vanilla.cls +8 -1
- package/skills/omnistudio-callable-apex-generate/examples/Test_VlocityOpenInterface2Conversion/TRANSCRIPT.md +1 -1
- package/skills/omnistudio-callable-apex-generate/examples/Test_VlocityOpenInterfaceConversion/TRANSCRIPT.md +1 -1
- package/skills/omnistudio-datamapper-generate/SKILL.md +1 -1
- package/skills/omnistudio-datamapper-generate/assets/completion-summary-template.md +2 -2
- package/skills/omnistudio-datamapper-generate/references/best-practices.md +9 -9
- package/skills/omnistudio-datamapper-generate/references/naming-conventions.md +6 -6
- package/skills/omnistudio-datapacks-deploy/README.md +1 -1
- package/skills/omnistudio-datapacks-deploy/examples/business-internet-plus-bundle/deploy-business-internet-plus-bundle.yaml +1 -1
- package/skills/omnistudio-dependencies-analyze/SKILL.md +12 -5
- package/skills/omnistudio-dependencies-analyze/references/dependency-patterns.md +9 -9
- package/skills/omnistudio-dependencies-analyze/references/namespace-guide.md +1 -1
- package/skills/omnistudio-epc-catalog-generate/README.md +1 -1
- package/skills/omnistudio-flexcard-generate/SKILL.md +11 -4
- package/skills/omnistudio-flexcard-generate/references/best-practices.md +6 -6
- package/skills/omnistudio-flexcard-generate/references/data-binding-guide.md +9 -9
- package/skills/omnistudio-integration-procedure-generate/SKILL.md +9 -1
- package/skills/omnistudio-integration-procedure-generate/references/element-types.md +3 -3
- package/skills/omnistudio-omniscript-generate/SKILL.md +2 -2
- package/skills/omnistudio-omniscript-generate/references/best-practices.md +1 -1
- package/skills/omnistudio-omniscript-generate/scripts/check-duplicate-omniscript.sh +8 -0
- package/skills/omnistudio-omniscript-generate/scripts/deploy-omniscript.sh +8 -0
- package/skills/platform-apex-logs-debug/README.md +2 -2
- package/skills/platform-apex-logs-debug/references/cli-commands.md +2 -2
- package/skills/platform-apex-logs-debug/references/debug-log-reference.md +16 -16
- package/skills/platform-apex-logs-debug/references/log-analysis-tools.md +2 -2
- package/skills/platform-apex-test-run/README.md +1 -1
- package/skills/platform-apex-test-run/references/test-fix-loop.md +2 -2
- package/skills/platform-apex-test-run/references/testing-best-practices.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-data-manage/README.md +4 -4
- package/skills/platform-data-manage/references/bulk-testing-example.md +1 -1
- package/skills/platform-data-manage/references/crud-workflow-example.md +2 -2
- package/skills/platform-data-manage/references/orchestration.md +6 -6
- package/skills/platform-flexipage-generate/SKILL.md +281 -346
- package/skills/platform-flexipage-generate/references/cli_commands.md +39 -0
- package/skills/platform-flexipage-generate/references/flexipage_fieldSection.md +162 -0
- package/skills/platform-flexipage-generate/references/flexipage_richText.md +84 -0
- package/skills/platform-flexipage-generate/references/identifiers_and_regions.md +167 -0
- package/skills/platform-flexipage-generate/references/mcp_action_examples.md +104 -0
- package/skills/platform-flexipage-generate/references/record_flexipage_dynamicHighlights.md +119 -0
- package/skills/platform-flexipage-generate/references/xml_rules.md +335 -0
- package/skills/platform-flexipage-generate/scripts/resolve-component-instructions.sh +42 -0
- package/skills/platform-flexipage-generate/scripts/scan-lwc-components.sh +113 -0
- package/skills/platform-metadata-deploy/README.md +2 -2
- package/skills/platform-metadata-deploy/references/agent-deployment-guide.md +2 -2
- package/skills/platform-metadata-deploy/references/deployment-workflows.md +16 -16
- package/skills/platform-metadata-deploy/references/orchestration.md +3 -3
- package/skills/platform-metadata-deploy/references/trigger-deployment-safety.md +2 -2
- package/skills/platform-models-api-configure/SKILL.md +140 -0
- package/skills/platform-models-api-configure/scripts/get-orgjwt.sh +48 -0
- package/skills/platform-policy-rule-generate/SKILL.md +260 -0
- package/skills/platform-policy-rule-generate/references/deploy-errors.md +54 -0
- package/skills/platform-policy-rule-generate/references/fixtures-index.md +29 -0
- package/skills/platform-policy-rule-generate/references/policy-schema-full.md +250 -0
- package/skills/platform-policy-rule-generate/references/templates-access.md +132 -0
- package/skills/platform-policy-rule-generate/references/templates-advanced.md +58 -0
- package/skills/platform-policy-rule-generate/references/templates-record.md +75 -0
- package/skills/platform-policy-rule-generate/references/templates-transform.md +70 -0
- package/skills/platform-policy-rule-generate/references/templates.md +10 -0
- package/skills/platform-policy-rule-generate/references/ui-compatibility.md +142 -0
- package/skills/platform-soql-query/README.md +1 -1
- package/skills/platform-soql-query/SKILL.md +1 -1
- package/skills/platform-soql-query/references/anti-patterns.md +1 -1
- package/skills/platform-soql-query/references/field-coverage-rules.md +1 -1
- package/skills/platform-soql-query/references/query-optimization.md +1 -1
- package/skills/platform-soql-query/scripts/post-tool-validate.py +3 -3
- package/skills/platform-value-set-generate/SKILL.md +305 -0
- package/skills/data360-activate/CREDITS.md +0 -5
- package/skills/data360-connect/CREDITS.md +0 -5
- package/skills/data360-harmonize/CREDITS.md +0 -3
- package/skills/data360-orchestrate/CREDITS.md +0 -15
- package/skills/data360-orchestrate/UPSTREAM.md +0 -45
- package/skills/data360-prepare/CREDITS.md +0 -7
- package/skills/data360-query/CREDITS.md +0 -7
- package/skills/data360-segment/CREDITS.md +0 -3
- package/skills/experience-lwc-generate/CREDITS.md +0 -5
- package/skills/external-diagram-mermaid-generate/CREDITS.md +0 -46
- package/skills/external-diagram-visual-generate/CREDITS.md +0 -80
- package/skills/external-diagram-visual-generate/README.md +0 -83
- package/skills/external-diagram-visual-generate/SKILL.md +0 -207
- package/skills/external-diagram-visual-generate/assets/architecture/integration-flow.md +0 -55
- package/skills/external-diagram-visual-generate/assets/erd/core-objects.md +0 -131
- package/skills/external-diagram-visual-generate/assets/erd/custom-objects.md +0 -60
- package/skills/external-diagram-visual-generate/assets/lwc/dashboard-card.md +0 -45
- package/skills/external-diagram-visual-generate/assets/lwc/data-table.md +0 -57
- package/skills/external-diagram-visual-generate/assets/lwc/record-form.md +0 -60
- package/skills/external-diagram-visual-generate/assets/review/apex-review.md +0 -57
- package/skills/external-diagram-visual-generate/assets/review/lwc-review.md +0 -48
- package/skills/external-diagram-visual-generate/references/architect-aesthetic-guide.md +0 -257
- package/skills/external-diagram-visual-generate/references/examples-index.md +0 -35
- package/skills/external-diagram-visual-generate/references/gemini-cli-setup.md +0 -65
- package/skills/external-diagram-visual-generate/references/interview-questions.md +0 -529
- package/skills/external-diagram-visual-generate/references/iteration-workflow.md +0 -173
- package/skills/external-diagram-visual-generate/scripts/check-prerequisites.sh +0 -101
- package/skills/external-diagram-visual-generate/scripts/generate_image.py +0 -243
- package/skills/integration-connectivity-connected-app-configure/CREDITS.md +0 -3
- package/skills/integration-connectivity-generate/CREDITS.md +0 -5
- package/skills/omnistudio-callable-apex-generate/CREDITS.md +0 -9
- package/skills/omnistudio-datamapper-generate/CREDITS.md +0 -5
- package/skills/omnistudio-datapacks-deploy/CREDITS.md +0 -5
- package/skills/omnistudio-dependencies-analyze/CREDITS.md +0 -5
- package/skills/omnistudio-epc-catalog-generate/CREDITS.md +0 -14
- package/skills/omnistudio-flexcard-generate/CREDITS.md +0 -5
- package/skills/omnistudio-integration-procedure-generate/CREDITS.md +0 -5
- package/skills/omnistudio-omniscript-generate/CREDITS.md +0 -5
- package/skills/platform-apex-generate/CREDITS.md +0 -5
- package/skills/platform-apex-logs-debug/CREDITS.md +0 -22
- package/skills/platform-apex-test-generate/CREDITS.md +0 -5
- package/skills/platform-apex-test-run/CREDITS.md +0 -22
- package/skills/platform-data-manage/CREDITS.md +0 -5
- package/skills/platform-metadata-deploy/CREDITS.md +0 -25
- package/skills/platform-soql-query/CREDITS.md +0 -21
- /package/skills/omnistudio-callable-apex-generate/examples/Test_VlocityOpenInterfaceConversion/{MyCustomVlocityOpenInterface2.cls → MyCustomClass.cls} +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# XPath Patterns for Apex Custom Rules
|
|
2
|
+
|
|
3
|
+
Pre-validated PMD XPath patterns for Salesforce Apex code. Every pattern below has been verified against actual `sf code-analyzer ast-dump` output. Use these directly — but still run `ast-dump` on YOUR code to confirm node names haven't changed in newer PMD versions.
|
|
4
|
+
|
|
5
|
+
## XPath Syntax Quick Reference
|
|
6
|
+
|
|
7
|
+
| Syntax | Meaning |
|
|
8
|
+
|--------|---------|
|
|
9
|
+
| `//Node` | Find Node anywhere in tree |
|
|
10
|
+
| `//Node[@attr='value']` | Node with specific string attribute value |
|
|
11
|
+
| `//Node[@attr = true()]` | Node with boolean attribute = true (**PMD 7 requirement**) |
|
|
12
|
+
| `//Parent//Child` | Child anywhere inside Parent (any descendant) |
|
|
13
|
+
| `//Parent/Child` | **Direct child only** — use this to avoid false positives |
|
|
14
|
+
| `//Node[not(...)]` | Node where condition is NOT true |
|
|
15
|
+
| `//Node[ancestor::Other]` | Node that has Other as an ancestor |
|
|
16
|
+
| `//Node[.//Other]` | Node that contains Other somewhere inside |
|
|
17
|
+
| `@Image` | The name/text of the node |
|
|
18
|
+
| `@FullMethodName` | Full qualified method name (e.g., 'System.debug') |
|
|
19
|
+
| `@LiteralType` | Type of literal: STRING, INTEGER, BOOLEAN, TRUE, FALSE, NULL |
|
|
20
|
+
|
|
21
|
+
### PMD 7 Boolean Attributes — MUST use `true()` / `false()`
|
|
22
|
+
|
|
23
|
+
In PMD 7, many node attributes are **boolean typed**, not strings — even though `ast-dump` renders them as `Nested='false'` or `Test='true'` in the XML output. String attributes (`@FullMethodName`, `@Name`, `@Image`, `@LiteralType`, etc.) are safe to copy from the AST dump as-is. But **attributes whose values are `true` or `false` in the dump are boolean-typed** — comparing them with string literals (`@Nested='false'`) causes the error: "Cannot compare xs:boolean to xs:string".
|
|
24
|
+
|
|
25
|
+
Known boolean attributes by node:
|
|
26
|
+
|
|
27
|
+
| Node | Boolean Attributes |
|
|
28
|
+
|------|--------------------|
|
|
29
|
+
| `ModifierNode` | `@Test`, `@Public`, `@Private`, `@Protected`, `@Static`, `@Abstract`, `@Final`, `@Global`, `@WithSharing`, `@WithoutSharing`, `@InheritedSharing` |
|
|
30
|
+
| `UserClass` | `@Nested` |
|
|
31
|
+
| `Method` | `@Constructor` |
|
|
32
|
+
|
|
33
|
+
| WRONG | CORRECT |
|
|
34
|
+
|-------|---------|
|
|
35
|
+
| `UserClass[@Nested='false']` | `UserClass[@Nested = false()]` |
|
|
36
|
+
| `UserClass[@Nested='true']` | `UserClass[@Nested = true()]` |
|
|
37
|
+
| `ModifierNode[@Test='true']` | `ModifierNode[@Test = true()]` |
|
|
38
|
+
| `ModifierNode[@Static='false']` | `ModifierNode[@Static = false()]` |
|
|
39
|
+
| `Method[@Constructor = false()]` | `Method[@Constructor = false()]` |
|
|
40
|
+
|
|
41
|
+
`true()` and `false()` are XPath boolean functions. If you see an attribute that looks boolean in the AST dump, assume it is typed as boolean and use `true()`/`false()`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### `/` vs `//` — The Most Common Source of False Positives
|
|
46
|
+
|
|
47
|
+
| XPath | Meaning | Risk |
|
|
48
|
+
|-------|---------|------|
|
|
49
|
+
| `//ForEachStatement//SoqlExpression` | SOQL anywhere inside ForEachStatement | ❌ Matches iterable position too |
|
|
50
|
+
| `//ForEachStatement/BlockStatement//SoqlExpression` | SOQL inside loop **body** only | ✅ Correct |
|
|
51
|
+
|
|
52
|
+
**Rule of thumb:** When matching inside a structural node (loop, if, try), always scope to `/BlockStatement//` to target the body, not sibling children like iterables or conditions.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Pattern Categories (By Topic)
|
|
57
|
+
|
|
58
|
+
For detailed patterns in each category, see the dedicated files below:
|
|
59
|
+
|
|
60
|
+
| Category | File | Contents |
|
|
61
|
+
|----------|------|----------|
|
|
62
|
+
| Governor Limits | [xpath-patterns-governor-limits.md](xpath-patterns-governor-limits.md) | SOQL/DML in loops, Database methods in loops |
|
|
63
|
+
| Method Calls & Annotations | [xpath-patterns-method-calls.md](xpath-patterns-method-calls.md) | Ban specific methods, @AuraEnabled, @future, @IsTest, @SuppressWarnings patterns |
|
|
64
|
+
| Security | [xpath-patterns-security.md](xpath-patterns-security.md) | Sharing declarations, SOQL security modes, hardcoded IDs |
|
|
65
|
+
| Code Structure, Tests & Naming | [xpath-patterns-structure.md](xpath-patterns-structure.md) | DML error handling, empty catches, test assertions, naming conventions |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Key Apex AST Node Names
|
|
70
|
+
|
|
71
|
+
| Apex Construct | AST Node | Key Attributes |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Class | `UserClass` | `Image`, `SuperClassName`, `InterfaceNames`, `Nested` |
|
|
74
|
+
| Interface | `UserInterface` | `Image`, `SuperInterfaceName` |
|
|
75
|
+
| Method | `Method` | `Image`, `Arity`, `ReturnType`, `Constructor` |
|
|
76
|
+
| Trigger | `UserTrigger` | `Image`, `TargetName` |
|
|
77
|
+
| SOQL query | `SoqlExpression` | `Query`, `CanonicalQuery` |
|
|
78
|
+
| DML insert | `DmlInsertStatement` | |
|
|
79
|
+
| DML update | `DmlUpdateStatement` | |
|
|
80
|
+
| DML delete | `DmlDeleteStatement` | |
|
|
81
|
+
| DML upsert | `DmlUpsertStatement` | |
|
|
82
|
+
| Method call | `MethodCallExpression` | `FullMethodName`, `MethodName`, `InputParametersSize` |
|
|
83
|
+
| For-each loop | `ForEachStatement` | children: VariableDeclarationStatements, VariableExpression, **BlockStatement** (body), iterable |
|
|
84
|
+
| For loop | `ForLoopStatement` | children: init, condition, update, **BlockStatement** (body) |
|
|
85
|
+
| While loop | `WhileLoopStatement` | children: condition, **BlockStatement** (body) |
|
|
86
|
+
| If/else | `IfElseBlockStatement` > `IfBlockStatement` | `ElseStatement` |
|
|
87
|
+
| Try-catch | `TryCatchFinallyBlockStatement` | |
|
|
88
|
+
| Catch block | `CatchBlockStatement` | `ExceptionType`, `VariableName` |
|
|
89
|
+
| RunAs | `RunAsBlockStatement` | |
|
|
90
|
+
| String literal | `LiteralExpression` | `@LiteralType='STRING'`, `@Image` (value without quotes) |
|
|
91
|
+
| Integer literal | `LiteralExpression` | `@LiteralType='INTEGER'`, `@Image` |
|
|
92
|
+
| Boolean true | `LiteralExpression` | `@LiteralType='TRUE'` |
|
|
93
|
+
| Boolean false | `LiteralExpression` | `@LiteralType='FALSE'` |
|
|
94
|
+
| Null | `LiteralExpression` | `@LiteralType='NULL'` |
|
|
95
|
+
| Variable | `VariableExpression` | `@Image` (name) |
|
|
96
|
+
| Assignment | `AssignmentExpression` | `@Op` (=, +=, etc.) |
|
|
97
|
+
| Binary expression | `BinaryExpression` | `@Op` (+, -, *, /) |
|
|
98
|
+
| Boolean expression | `BooleanExpression` | `@Op` (>, <, ==, !=, >=, <=) |
|
|
99
|
+
| New object | `NewKeyValueObjectExpression` | `@Type` |
|
|
100
|
+
| New object (no-arg) | `NewObjectExpression` | `@Type` |
|
|
101
|
+
| Return | `ReturnStatement` | |
|
|
102
|
+
| Annotation | `Annotation` | `@Name` (IsTest, AuraEnabled, Future, SuppressWarnings) |
|
|
103
|
+
| Annotation param | `AnnotationParameter` | `@Name`, `@Value` |
|
|
104
|
+
| Modifier | `ModifierNode` | `Public`, `Private`, `Static`, `Test`, `WithSharing`, `Global`, etc. |
|
|
105
|
+
| Parameter | `Parameter` | `@Image` (name), `@Type` |
|
|
106
|
+
| New list | `NewListInitExpression` | |
|
|
107
|
+
| New map | `NewMapInitExpression` | |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## XPath Best Practices
|
|
112
|
+
|
|
113
|
+
1. **ALWAYS run `ast-dump` first** — never guess node names, even for patterns listed here
|
|
114
|
+
2. **Use `/BlockStatement//` for loop/if body** — avoids matching iterables, conditions, etc.
|
|
115
|
+
3. **Use `@FullMethodName`** for method calls — not `@Image` or `@MethodName` alone
|
|
116
|
+
4. **Exclude test classes** with `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]`
|
|
117
|
+
5. **Test with BOTH positive AND negative cases** — ensure no false positives
|
|
118
|
+
6. **Prefer `//Node` over absolute paths** — code structure varies
|
|
119
|
+
7. **Use `ancestor::` / `not(ancestor::)`** for structural exclusions (try-catch, test class)
|
|
120
|
+
8. **Keep XPath simple** — complex expressions are fragile and hard to maintain
|
|
121
|
+
|
|
122
|
+
## Common False Positive Traps
|
|
123
|
+
|
|
124
|
+
| Pattern | Trap | Fix |
|
|
125
|
+
|---------|------|-----|
|
|
126
|
+
| SOQL in loop | `//ForEachStatement//SoqlExpression` matches iterable | Use `/BlockStatement//` |
|
|
127
|
+
| DML in loop | Same as above | Use `/BlockStatement//` |
|
|
128
|
+
| Ban method in all code | Flags test code too | Add `[not(ancestor::UserClass[ModifierNode[@Test = true()]])]` |
|
|
129
|
+
| Empty block detection | Matches intentional empty constructors | Add `[@Constructor = false()]` or exclude specific patterns |
|
|
130
|
+
| No sharing declaration | Flags inner classes (which inherit) | Add `[@Nested = false()]` |
|
|
131
|
+
| String literal length check | Matches test data strings | Exclude test classes |
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Creates a PMD XPath custom rule (XML ruleset file + config reference)
|
|
3
|
+
// Usage: node create-pmd-rule.js --name <name> --xpath <expression> --message <msg> [options]
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
function printUsage() {
|
|
9
|
+
console.error(`Usage: node create-pmd-rule.js --name <name> --xpath <expression> --message <msg> [options]
|
|
10
|
+
|
|
11
|
+
Required:
|
|
12
|
+
--name <name> Rule name (PascalCase, no spaces)
|
|
13
|
+
--xpath <expression> XPath expression to match violations
|
|
14
|
+
--message <msg> Violation message shown to users
|
|
15
|
+
|
|
16
|
+
Optional:
|
|
17
|
+
--description <desc> Detailed rule description (default: same as message)
|
|
18
|
+
--language <lang> PMD language (default: apex)
|
|
19
|
+
--priority <1-5> PMD priority (default: 3)
|
|
20
|
+
--example <code> Example violating code snippet
|
|
21
|
+
--config-file <path> Path to code-analyzer.yml (default: ./code-analyzer.yml)
|
|
22
|
+
--ruleset-dir <dir> Directory for ruleset XML (default: ./custom-rules)
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
node create-pmd-rule.js --name NoSystemDebug --xpath "//MethodCallExpression[@FullMethodName='System.debug']" --message "System.debug not allowed" --priority 3
|
|
26
|
+
node create-pmd-rule.js --name SoqlInLoop --xpath "//ForEachStatement//SoqlExpression" --message "SOQL inside loop" --priority 2`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Parse arguments
|
|
31
|
+
const args = process.argv.slice(2);
|
|
32
|
+
if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
|
|
33
|
+
printUsage();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const options = {
|
|
37
|
+
name: null,
|
|
38
|
+
xpath: null,
|
|
39
|
+
message: null,
|
|
40
|
+
description: null,
|
|
41
|
+
language: "apex",
|
|
42
|
+
priority: 3,
|
|
43
|
+
example: null,
|
|
44
|
+
configFile: "./code-analyzer.yml",
|
|
45
|
+
rulesetDir: "./custom-rules",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < args.length; i++) {
|
|
49
|
+
switch (args[i]) {
|
|
50
|
+
case "--name": options.name = args[++i]; break;
|
|
51
|
+
case "--xpath": options.xpath = args[++i]; break;
|
|
52
|
+
case "--message": options.message = args[++i]; break;
|
|
53
|
+
case "--description": options.description = args[++i]; break;
|
|
54
|
+
case "--language": options.language = args[++i]; break;
|
|
55
|
+
case "--priority": options.priority = parseInt(args[++i], 10); break;
|
|
56
|
+
case "--example": options.example = args[++i]; break;
|
|
57
|
+
case "--config-file": options.configFile = args[++i]; break;
|
|
58
|
+
case "--ruleset-dir": options.rulesetDir = args[++i]; break;
|
|
59
|
+
default:
|
|
60
|
+
console.error(`Unknown option: ${args[i]}`);
|
|
61
|
+
printUsage();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validate required fields
|
|
66
|
+
if (!options.name) { console.error("Error: --name is required"); process.exit(1); }
|
|
67
|
+
if (!options.xpath) { console.error("Error: --xpath is required"); process.exit(1); }
|
|
68
|
+
if (!options.message) { console.error("Error: --message is required"); process.exit(1); }
|
|
69
|
+
|
|
70
|
+
// Validate rule name
|
|
71
|
+
const RULE_NAME_PATTERN = /^[A-Za-z@][A-Za-z_0-9@\-/]*$/;
|
|
72
|
+
if (!RULE_NAME_PATTERN.test(options.name)) {
|
|
73
|
+
console.error(`Error: Invalid rule name "${options.name}". Must match: ${RULE_NAME_PATTERN}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Validate priority
|
|
78
|
+
if (options.priority < 1 || options.priority > 5) {
|
|
79
|
+
console.error("Error: Priority must be 1-5");
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Validate language
|
|
84
|
+
const VALID_LANGUAGES = ["apex", "visualforce", "html", "xml", "javascript"];
|
|
85
|
+
if (!VALID_LANGUAGES.includes(options.language.toLowerCase())) {
|
|
86
|
+
console.error(`Error: Invalid language "${options.language}". Supported: ${VALID_LANGUAGES.join(", ")}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Set defaults
|
|
91
|
+
if (!options.description) {
|
|
92
|
+
options.description = options.message;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Generate the PMD ruleset XML
|
|
96
|
+
function buildRulesetXml() {
|
|
97
|
+
const escXpath = options.xpath.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
98
|
+
const escMessage = options.message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
99
|
+
const escDescription = options.description.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
100
|
+
|
|
101
|
+
let xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
102
|
+
<ruleset name="${options.name}CustomRules"
|
|
103
|
+
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
|
104
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
105
|
+
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
|
106
|
+
|
|
107
|
+
<description>Custom rules for ${options.name}</description>
|
|
108
|
+
|
|
109
|
+
<rule name="${options.name}"
|
|
110
|
+
language="${options.language}"
|
|
111
|
+
message="${escMessage}"
|
|
112
|
+
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
|
113
|
+
|
|
114
|
+
<description>${escDescription}</description>
|
|
115
|
+
<priority>${options.priority}</priority>
|
|
116
|
+
|
|
117
|
+
<properties>
|
|
118
|
+
<property name="xpath">
|
|
119
|
+
<value><![CDATA[
|
|
120
|
+
${options.xpath}
|
|
121
|
+
]]></value>
|
|
122
|
+
</property>
|
|
123
|
+
</properties>`;
|
|
124
|
+
|
|
125
|
+
if (options.example) {
|
|
126
|
+
xml += `
|
|
127
|
+
|
|
128
|
+
<example>
|
|
129
|
+
<![CDATA[
|
|
130
|
+
${options.example}
|
|
131
|
+
]]>
|
|
132
|
+
</example>`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
xml += `
|
|
136
|
+
</rule>
|
|
137
|
+
</ruleset>
|
|
138
|
+
`;
|
|
139
|
+
|
|
140
|
+
return xml;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Create ruleset directory if needed
|
|
144
|
+
const rulesetDir = path.resolve(options.rulesetDir);
|
|
145
|
+
if (!fs.existsSync(rulesetDir)) {
|
|
146
|
+
fs.mkdirSync(rulesetDir, { recursive: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Write ruleset XML
|
|
150
|
+
const rulesetFileName = `${options.name}-pmd-ruleset.xml`;
|
|
151
|
+
const rulesetPath = path.join(rulesetDir, rulesetFileName);
|
|
152
|
+
|
|
153
|
+
if (fs.existsSync(rulesetPath)) {
|
|
154
|
+
console.error(`Error: Ruleset file already exists: ${rulesetPath}`);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const rulesetXml = buildRulesetXml();
|
|
159
|
+
fs.writeFileSync(rulesetPath, rulesetXml, "utf8");
|
|
160
|
+
|
|
161
|
+
// Update code-analyzer.yml to reference the ruleset
|
|
162
|
+
const configPath = path.resolve(options.configFile);
|
|
163
|
+
const rulesetRelativePath = path.relative(path.dirname(configPath), rulesetPath);
|
|
164
|
+
let configContent = "";
|
|
165
|
+
|
|
166
|
+
if (fs.existsSync(configPath)) {
|
|
167
|
+
configContent = fs.readFileSync(configPath, "utf8");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if this ruleset path is already referenced in config (deduplication)
|
|
171
|
+
const alreadyReferenced = configContent.includes(rulesetRelativePath);
|
|
172
|
+
|
|
173
|
+
if (!configContent) {
|
|
174
|
+
// Create new config
|
|
175
|
+
configContent = `engines:\n pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n`;
|
|
176
|
+
} else if (alreadyReferenced) {
|
|
177
|
+
// Ruleset path already in config — skip adding duplicate entry
|
|
178
|
+
// (This happens when the XML was deleted and recreated during iteration)
|
|
179
|
+
} else if (configContent.includes("custom_rulesets:") && configContent.includes("pmd:")) {
|
|
180
|
+
// Add to existing custom_rulesets
|
|
181
|
+
const insertPoint = configContent.indexOf("custom_rulesets:");
|
|
182
|
+
const afterLine = configContent.indexOf("\n", insertPoint) + 1;
|
|
183
|
+
configContent = configContent.slice(0, afterLine) + ` - "${rulesetRelativePath}"\n` + configContent.slice(afterLine);
|
|
184
|
+
} else if (configContent.includes("pmd:")) {
|
|
185
|
+
// Add custom_rulesets under pmd
|
|
186
|
+
const insertPoint = configContent.indexOf("pmd:");
|
|
187
|
+
const afterLine = configContent.indexOf("\n", insertPoint) + 1;
|
|
188
|
+
configContent = configContent.slice(0, afterLine) + ` custom_rulesets:\n - "${rulesetRelativePath}"\n` + configContent.slice(afterLine);
|
|
189
|
+
} else if (configContent.includes("engines:")) {
|
|
190
|
+
// Add pmd section under engines
|
|
191
|
+
const insertPoint = configContent.indexOf("engines:");
|
|
192
|
+
const afterLine = configContent.indexOf("\n", insertPoint) + 1;
|
|
193
|
+
configContent = configContent.slice(0, afterLine) + ` pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n` + configContent.slice(afterLine);
|
|
194
|
+
} else {
|
|
195
|
+
// Append engines section
|
|
196
|
+
configContent += `\nengines:\n pmd:\n custom_rulesets:\n - "${rulesetRelativePath}"\n`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fs.writeFileSync(configPath, configContent, "utf8");
|
|
200
|
+
|
|
201
|
+
console.log(JSON.stringify({
|
|
202
|
+
status: "success",
|
|
203
|
+
ruleName: options.name,
|
|
204
|
+
engine: "pmd",
|
|
205
|
+
language: options.language,
|
|
206
|
+
rulesetFile: rulesetPath,
|
|
207
|
+
configFile: configPath,
|
|
208
|
+
message: `Rule "${options.name}" created. Validate with: sf code-analyzer rules --rule-selector pmd:${options.name}`
|
|
209
|
+
}));
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Creates a regex custom rule in code-analyzer.yml
|
|
3
|
+
// Usage: node create-regex-rule.js --name <name> --regex <pattern> --description <desc> [options]
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
function printUsage() {
|
|
9
|
+
console.error(`Usage: node create-regex-rule.js --name <name> --regex <pattern> --description <desc> [options]
|
|
10
|
+
|
|
11
|
+
Required:
|
|
12
|
+
--name <name> Rule name (PascalCase, no spaces)
|
|
13
|
+
--regex <pattern> Regex in /pattern/flags format
|
|
14
|
+
--description <desc> What the rule checks
|
|
15
|
+
|
|
16
|
+
Optional:
|
|
17
|
+
--violation-message <msg> Message shown on violation
|
|
18
|
+
--severity <1-5> Severity level (default: 3)
|
|
19
|
+
--tags <tag1,tag2> Comma-separated tags (default: Recommended,Custom)
|
|
20
|
+
--file-extensions <exts> Comma-separated extensions (e.g., .cls,.trigger)
|
|
21
|
+
--regex-ignore <pattern> Negative pattern to exclude matches
|
|
22
|
+
--config-file <path> Path to code-analyzer.yml (default: ./code-analyzer.yml)
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
node create-regex-rule.js --name NoHardcodedIds --regex "/[0-9a-zA-Z]{18}/g" --description "Detects hardcoded IDs" --severity 2 --file-extensions ".cls,.trigger"
|
|
26
|
+
node create-regex-rule.js --name NoTodos --regex "/TODO|FIXME/gi" --description "Flags TODO comments" --severity 4`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Parse arguments
|
|
31
|
+
const args = process.argv.slice(2);
|
|
32
|
+
if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
|
|
33
|
+
printUsage();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const options = {
|
|
37
|
+
name: null,
|
|
38
|
+
regex: null,
|
|
39
|
+
description: null,
|
|
40
|
+
violationMessage: null,
|
|
41
|
+
severity: 3,
|
|
42
|
+
tags: ["Recommended", "Custom"],
|
|
43
|
+
fileExtensions: null,
|
|
44
|
+
regexIgnore: null,
|
|
45
|
+
configFile: "./code-analyzer.yml",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < args.length; i++) {
|
|
49
|
+
switch (args[i]) {
|
|
50
|
+
case "--name": options.name = args[++i]; break;
|
|
51
|
+
case "--regex": options.regex = args[++i]; break;
|
|
52
|
+
case "--description": options.description = args[++i]; break;
|
|
53
|
+
case "--violation-message": options.violationMessage = args[++i]; break;
|
|
54
|
+
case "--severity": options.severity = parseInt(args[++i], 10); break;
|
|
55
|
+
case "--tags": options.tags = args[++i].split(",").map(t => t.trim()); break;
|
|
56
|
+
case "--file-extensions": options.fileExtensions = args[++i].split(",").map(e => e.trim()); break;
|
|
57
|
+
case "--regex-ignore": options.regexIgnore = args[++i]; break;
|
|
58
|
+
case "--config-file": options.configFile = args[++i]; break;
|
|
59
|
+
default:
|
|
60
|
+
console.error(`Unknown option: ${args[i]}`);
|
|
61
|
+
printUsage();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validate required fields
|
|
66
|
+
if (!options.name) { console.error("Error: --name is required"); process.exit(1); }
|
|
67
|
+
if (!options.regex) { console.error("Error: --regex is required"); process.exit(1); }
|
|
68
|
+
if (!options.description) { console.error("Error: --description is required"); process.exit(1); }
|
|
69
|
+
|
|
70
|
+
// Validate rule name
|
|
71
|
+
const RULE_NAME_PATTERN = /^[A-Za-z@][A-Za-z_0-9@\-/]*$/;
|
|
72
|
+
if (!RULE_NAME_PATTERN.test(options.name)) {
|
|
73
|
+
console.error(`Error: Invalid rule name "${options.name}". Must match: ${RULE_NAME_PATTERN}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Validate regex format — must be /pattern/flags with no surrounding whitespace
|
|
78
|
+
// and the flags portion must contain only valid JavaScript regex flag characters.
|
|
79
|
+
options.regex = options.regex.trim();
|
|
80
|
+
if (!options.regex.startsWith("/") || options.regex.lastIndexOf("/") <= 0) {
|
|
81
|
+
console.error(`Error: Regex must be in /pattern/flags format. Got: "${options.regex}"`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const lastSlash = options.regex.lastIndexOf("/");
|
|
85
|
+
const flags = options.regex.slice(lastSlash + 1);
|
|
86
|
+
if (!flags) {
|
|
87
|
+
console.error(`Error: Regex must include flags after the closing /. Use /pattern/g at minimum. Got: "${options.regex}"`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
// Strict flags validation — only valid JS regex flag chars allowed, no spaces, no junk.
|
|
91
|
+
if (!/^[gimsuy]+$/.test(flags)) {
|
|
92
|
+
console.error(`Error: Invalid regex flags "${flags}". Allowed: g, i, m, s, u, y (no spaces, no other characters). Got: "${options.regex}"`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
// Code Analyzer regex rules require the global flag.
|
|
96
|
+
if (!flags.includes("g")) {
|
|
97
|
+
console.error(`Error: Regex must include the global flag 'g'. Got flags: "${flags}"`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Validate severity
|
|
102
|
+
if (options.severity < 1 || options.severity > 5) {
|
|
103
|
+
console.error("Error: Severity must be 1-5");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Validate file extensions
|
|
108
|
+
if (options.fileExtensions) {
|
|
109
|
+
for (const ext of options.fileExtensions) {
|
|
110
|
+
if (!ext.startsWith(".")) {
|
|
111
|
+
console.error(`Error: File extension must start with dot: "${ext}"`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Safely quote a string for YAML output.
|
|
118
|
+
// CRITICAL: regex patterns contain backslashes (`\.`, `\d`, `\\`, etc.).
|
|
119
|
+
// Double-quoted YAML treats `\` as an escape introducer — `\.` is an unknown
|
|
120
|
+
// escape and YAML rejects the file with "unknown escape sequence". Single-quoted
|
|
121
|
+
// YAML treats backslash as literal, which is exactly what regex needs.
|
|
122
|
+
// Strategy:
|
|
123
|
+
// - If value contains a backslash → ALWAYS use single quotes (escape ' as '')
|
|
124
|
+
// - Else if value has no quotes → double quotes (simplest, most readable)
|
|
125
|
+
// - Else → single quotes (escape ' as '')
|
|
126
|
+
function yamlQuote(value) {
|
|
127
|
+
const hasBackslash = value.includes("\\");
|
|
128
|
+
const hasSingle = value.includes("'");
|
|
129
|
+
const hasDouble = value.includes('"');
|
|
130
|
+
|
|
131
|
+
if (!hasBackslash && !hasSingle && !hasDouble) {
|
|
132
|
+
return `"${value}"`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Single-quoted YAML: only ' needs escaping (as ''). Backslashes pass through.
|
|
136
|
+
const escaped = value.replace(/'/g, "''");
|
|
137
|
+
return `'${escaped}'`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Build the rule YAML block
|
|
141
|
+
function buildRuleYaml() {
|
|
142
|
+
const indent = " ";
|
|
143
|
+
const lines = [];
|
|
144
|
+
lines.push(` ${options.name}:`);
|
|
145
|
+
lines.push(`${indent} regex: ${yamlQuote(options.regex)}`);
|
|
146
|
+
lines.push(`${indent} description: ${yamlQuote(options.description)}`);
|
|
147
|
+
|
|
148
|
+
if (options.violationMessage) {
|
|
149
|
+
lines.push(`${indent} violation_message: ${yamlQuote(options.violationMessage)}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
lines.push(`${indent} severity: ${options.severity}`);
|
|
153
|
+
|
|
154
|
+
if (options.tags && options.tags.length > 0) {
|
|
155
|
+
lines.push(`${indent} tags:`);
|
|
156
|
+
options.tags.forEach(tag => lines.push(`${indent} - "${tag}"`));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (options.fileExtensions && options.fileExtensions.length > 0) {
|
|
160
|
+
lines.push(`${indent} file_extensions:`);
|
|
161
|
+
options.fileExtensions.forEach(ext => lines.push(`${indent} - "${ext}"`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (options.regexIgnore) {
|
|
165
|
+
lines.push(`${indent} regex_ignore: ${yamlQuote(options.regexIgnore)}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Read or create config file
|
|
172
|
+
const configPath = path.resolve(options.configFile);
|
|
173
|
+
let configContent = "";
|
|
174
|
+
|
|
175
|
+
if (fs.existsSync(configPath)) {
|
|
176
|
+
configContent = fs.readFileSync(configPath, "utf8");
|
|
177
|
+
|
|
178
|
+
// Check if rule already exists
|
|
179
|
+
if (configContent.includes(`${options.name}:`)) {
|
|
180
|
+
console.error(`Error: Rule "${options.name}" already exists in ${configPath}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const ruleYaml = buildRuleYaml();
|
|
186
|
+
|
|
187
|
+
// Upsert into config
|
|
188
|
+
if (!configContent) {
|
|
189
|
+
// Create new file
|
|
190
|
+
configContent = `engines:\n regex:\n custom_rules:\n${ruleYaml}\n`;
|
|
191
|
+
} else if (configContent.includes("custom_rules:") && configContent.includes("regex:")) {
|
|
192
|
+
// Add to existing custom_rules section
|
|
193
|
+
const insertPoint = configContent.indexOf("custom_rules:");
|
|
194
|
+
const afterCustomRules = configContent.indexOf("\n", insertPoint) + 1;
|
|
195
|
+
configContent = configContent.slice(0, afterCustomRules) + ruleYaml + "\n" + configContent.slice(afterCustomRules);
|
|
196
|
+
} else if (configContent.includes("regex:")) {
|
|
197
|
+
// Add custom_rules section under regex
|
|
198
|
+
const insertPoint = configContent.indexOf("regex:");
|
|
199
|
+
const afterRegex = configContent.indexOf("\n", insertPoint) + 1;
|
|
200
|
+
configContent = configContent.slice(0, afterRegex) + " custom_rules:\n" + ruleYaml + "\n" + configContent.slice(afterRegex);
|
|
201
|
+
} else if (configContent.includes("engines:")) {
|
|
202
|
+
// Add regex section under engines
|
|
203
|
+
const insertPoint = configContent.indexOf("engines:");
|
|
204
|
+
const afterEngines = configContent.indexOf("\n", insertPoint) + 1;
|
|
205
|
+
configContent = configContent.slice(0, afterEngines) + " regex:\n custom_rules:\n" + ruleYaml + "\n" + configContent.slice(afterEngines);
|
|
206
|
+
} else {
|
|
207
|
+
// Append engines section
|
|
208
|
+
configContent += "\nengines:\n regex:\n custom_rules:\n" + ruleYaml + "\n";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Write config
|
|
212
|
+
fs.writeFileSync(configPath, configContent, "utf8");
|
|
213
|
+
|
|
214
|
+
console.log(JSON.stringify({
|
|
215
|
+
status: "success",
|
|
216
|
+
ruleName: options.name,
|
|
217
|
+
engine: "regex",
|
|
218
|
+
configFile: configPath,
|
|
219
|
+
message: `Rule "${options.name}" created. Validate with: sf code-analyzer rules --rule-selector regex:${options.name}`
|
|
220
|
+
}));
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dx-code-analyzer-run
|
|
3
|
-
description: "Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violations. Supports all engines (PMD, ESLint, CPD, RetireJS, Flow, SFGE, ApexGuru), targets (files, folders, git diff), categories, and severities. Also handles post-scan exploration: filtering results by engine/severity/category/file, and explaining what
|
|
4
|
-
allowed-tools: Read, Bash(sf code-analyzer), Bash(node), Bash(git diff), Bash(date), Write, Edit
|
|
3
|
+
description: "Run Salesforce Code Analyzer to scan code for security, performance, best practice, and code style violations. Supports all engines (PMD, ESLint, CPD, RetireJS, Flow, SFGE, ApexGuru), targets (files, folders, git diff), categories, and severities. Also handles post-scan exploration: filtering results by engine/severity/category/file, and explaining what rules mean. TRIGGER when: user says 'scan my code', 'check security issues', 'run PMD/ESLint', 'find duplicates', 'analyze Flows', 'check vulnerable libraries', 'AppExchange review', 'lint my LWC', 'static analysis', 'code quality', 'show security violations', 'what is this rule', 'explain ApexCRUDViolation', 'filter results', or mentions engines/file types (.cls, .trigger, .js, .flow-meta.xml). Use this skill for scanning, exploring results, and listing rules. DO NOT TRIGGER when: user asks only about installation/configuration (use dx-code-analyzer-configure), or wants to create a custom rule (use dx-code-analyzer-custom-rule-create)."
|
|
5
4
|
metadata:
|
|
6
5
|
version: "1.0"
|
|
7
|
-
|
|
6
|
+
relatedSkills:
|
|
7
|
+
- "dx-code-analyzer-configure"
|
|
8
|
+
- "dx-code-analyzer-custom-rule-create"
|
|
9
|
+
cliTools:
|
|
10
|
+
- tool: ["sf"]
|
|
11
|
+
semver: ">=2.0.0"
|
|
12
|
+
- tool: ["node"]
|
|
13
|
+
semver: ">=18.0.0"
|
|
14
|
+
- tool: ["git"]
|
|
15
|
+
semver: ">=2.0.0"
|
|
8
16
|
---
|
|
9
17
|
|
|
10
18
|
# Running Code Analyzer Skill
|
|
@@ -67,11 +75,13 @@ Any aggregation, filter, or rank question ("which file has the most violations?"
|
|
|
67
75
|
|
|
68
76
|
## Overview
|
|
69
77
|
|
|
78
|
+
> **Ecosystem:** This skill is part of a 3-skill Code Analyzer suite — `dx-code-analyzer-run` (scans & results) · `dx-code-analyzer-configure` (setup, config, CI/CD) · `dx-code-analyzer-custom-rule-create` (custom rule authoring).
|
|
79
|
+
|
|
70
80
|
This skill translates natural-language requests ("scan for security issues", "check my changes") into the correct `sf code-analyzer run` command, executes scans across any combination of engines/targets/severities, and presents actionable results. When engine-provided fixes are available, it discovers them, asks for user confirmation, applies them safely, and offers verification. Use it for static analysis, security reviews, AppExchange certification, code-quality checks, and finding duplicates/vulnerabilities in Salesforce projects.
|
|
71
81
|
|
|
72
82
|
**In scope:** running scans, parsing/filtering/ranking results, applying engine auto-fixes, diff-based scans, all output formats (JSON/HTML/SARIF/CSV/XML), describing/listing rules, scan-failure troubleshooting.
|
|
73
83
|
|
|
74
|
-
**Out of scope:** installing/configuring `sf` or the plugin (→ `dx-code-analyzer-configure`), writing custom rules/engines, AI-generated fixes beyond engine-provided ones, deep refactoring, CI/CD setup (→ `dx-code-analyzer-configure`).
|
|
84
|
+
**Out of scope:** installing/configuring `sf` or the plugin (→ `dx-code-analyzer-configure`), writing custom rules/engines (→ `dx-code-analyzer-custom-rule-create`), AI-generated fixes beyond engine-provided ones, deep refactoring, CI/CD setup (→ `dx-code-analyzer-configure`).
|
|
75
85
|
|
|
76
86
|
**Allowed tools:** Bash (`sf code-analyzer`, `node`, `git diff`, `date`), Read, Write, Edit. **Forbidden:** any MCP tool, Agent tool, web tools, other skills, Python, `jq`, inline scripts/heredocs. This skill owns the complete scan-fix-verify-query-explain workflow end-to-end.
|
|
77
87
|
|
|
@@ -203,7 +213,7 @@ Use the **Bash tool only** — never the `run_code_analyzer` MCP tool.
|
|
|
203
213
|
|
|
204
214
|
1. Generate the timestamp via Bash: `date +%Y%m%d-%H%M%S` → e.g. `20260512-143022`.
|
|
205
215
|
2. Tell the user:
|
|
206
|
-
```
|
|
216
|
+
```text
|
|
207
217
|
Starting scan...
|
|
208
218
|
Results: ./code-analyzer-results-20260512-143022.json
|
|
209
219
|
Log: ./code-analyzer-results-20260512-143022.log
|
|
@@ -237,7 +247,7 @@ node "<skill_dir>/scripts/parse-results.js" "./code-analyzer-results-TIMESTAMP.j
|
|
|
237
247
|
|
|
238
248
|
### Presentation template
|
|
239
249
|
|
|
240
|
-
```
|
|
250
|
+
```text
|
|
241
251
|
## Scan Complete
|
|
242
252
|
|
|
243
253
|
**Found X violations** across Y files.
|
|
@@ -296,7 +306,7 @@ node "<skill_dir>/scripts/discover-fixes.js" "./code-analyzer-results-TIMESTAMP.
|
|
|
296
306
|
|
|
297
307
|
### 6.3 Present + ASK (then STOP)
|
|
298
308
|
|
|
299
|
-
```
|
|
309
|
+
```text
|
|
300
310
|
### Engine-Provided Fixes Available
|
|
301
311
|
**X of Y violations** have auto-fixes provided by the analysis engine:
|
|
302
312
|
|
|
@@ -327,7 +337,7 @@ node "<skill_dir>/scripts/summarize-fixes.js" "./code-analyzer-results-TIMESTAMP
|
|
|
327
337
|
|
|
328
338
|
Then present:
|
|
329
339
|
|
|
330
|
-
```
|
|
340
|
+
```text
|
|
331
341
|
### Engine-Provided Fixes Applied Successfully ✓
|
|
332
342
|
**Applied X auto-fixes across Y files.**
|
|
333
343
|
|
|
@@ -439,6 +449,29 @@ node "<skill_dir>/scripts/list-rules.js" "<selector>" [options]
|
|
|
439
449
|
|
|
440
450
|
Filters: `--engine`, `--severity`, `--top` (default 100), `--count-only`. The script pre-validates selector tokens (catches typos like `secruity`) before calling the CLI. Presentation: `<skill_dir>/references/post-scan-workflows.md`.
|
|
441
451
|
|
|
452
|
+
---
|
|
453
|
+
## Cross-Skill Integration
|
|
454
|
+
|
|
455
|
+
This skill is part of a 3-skill Code Analyzer ecosystem. Hand off cleanly rather than attempting work that belongs to another skill.
|
|
456
|
+
|
|
457
|
+
### When THIS skill delegates to `dx-code-analyzer-configure`:
|
|
458
|
+
|
|
459
|
+
- Pre-flight check fails (CLI missing, plugin not installed, engine prereqs broken) → stop, delegate, return here after fix
|
|
460
|
+
- User asks to set up CI/CD, edit `code-analyzer.yml`, change severities, or disable engines → delegate entirely
|
|
461
|
+
|
|
462
|
+
### When THIS skill delegates to `dx-code-analyzer-custom-rule-create`:
|
|
463
|
+
|
|
464
|
+
- User asks to create a new rule, write XPath, write a regex rule, or enforce a pattern not covered by built-in rules → delegate entirely. Do NOT attempt to create rules here.
|
|
465
|
+
|
|
466
|
+
### When other skills hand off HERE:
|
|
467
|
+
|
|
468
|
+
- `dx-code-analyzer-configure` completes setup → proceed with scan (Step 1–5)
|
|
469
|
+
- `dx-code-analyzer-custom-rule-create` finishes creating a rule → proceed with scan targeting the new rule (e.g., `--rule-selector pmd:<RuleName>`) to verify it works
|
|
470
|
+
|
|
471
|
+
### Ownership boundary
|
|
472
|
+
|
|
473
|
+
This skill owns the complete **scan → explore → fix** workflow end-to-end. It does NOT own installation, config file management, or rule authoring.
|
|
474
|
+
|
|
442
475
|
---
|
|
443
476
|
|
|
444
477
|
## Constraints & Gotchas
|