@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,87 @@
1
+ # Metadata XML Examples: Fields and API Version
2
+
3
+ [← Back to Metadata XML Examples Index](metadata-xml-examples.md)
4
+
5
+ ## Example 2: Require Description on Custom Fields
6
+
7
+ **Problem:** Custom fields without descriptions make orgs hard to maintain.
8
+
9
+ **Target file:** `*.field-meta.xml`
10
+
11
+ **Sample violating metadata:**
12
+ ```xml
13
+ <CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
14
+ <fullName>Revenue__c</fullName>
15
+ <label>Revenue</label>
16
+ <type>Currency</type>
17
+ <!-- No <description> element! -->
18
+ </CustomField>
19
+ ```
20
+
21
+ **XPath (PMD 7):**
22
+ ```xpath
23
+ //*[local-name()='CustomField'][not(*[local-name()='description']) or *[local-name()='description'][not(@Text)]]
24
+ ```
25
+
26
+ **How it works:** Matches `CustomField` elements that either lack a `<description>` child entirely, or have one with no text content (`@Text` absent means empty).
27
+
28
+ **PMD ruleset:**
29
+ ```xml
30
+ <rule name="FieldRequiresDescription"
31
+ language="xml"
32
+ message="Custom field must have a non-empty description"
33
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
34
+ <description>Enforces that all custom fields have a description for documentation</description>
35
+ <priority>3</priority>
36
+ <properties>
37
+ <property name="xpath">
38
+ <value><![CDATA[
39
+ //*[local-name()='CustomField'][not(*[local-name()='description']) or *[local-name()='description'][not(@Text)]]
40
+ ]]></value>
41
+ </property>
42
+ </properties>
43
+ </rule>
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Example 3: Enforce Minimum API Version
49
+
50
+ **Problem:** Metadata using API versions older than 60.0 should be updated.
51
+
52
+ **Target file:** Any `*-meta.xml` with `<apiVersion>`
53
+
54
+ **Sample violating metadata:**
55
+ ```xml
56
+ <ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
57
+ <apiVersion>52.0</apiVersion>
58
+ <status>Active</status>
59
+ </ApexClass>
60
+ ```
61
+
62
+ **XPath (PMD 7):**
63
+ ```xpath
64
+ //*[local-name()='apiVersion']/*[number(@Text) < 60]
65
+ ```
66
+
67
+ **How it works:** Finds `<apiVersion>` elements, then navigates to their child text node with `/*`. The `@Text` attribute on the text node holds the value (e.g., "52.0"). `number(@Text)` converts it to a float, and flags if < 60.
68
+
69
+ ⚠️ **Key insight:** `@Text` lives on CHILD text nodes, not on elements. You must use `/*` to reach the text node — putting `[@Text]` directly on the element (after `local-name()='apiVersion'`) returns 0 matches.
70
+
71
+ **PMD ruleset:**
72
+ ```xml
73
+ <rule name="MinimumApiVersion"
74
+ language="xml"
75
+ message="API version must be 60.0 or higher — update this metadata"
76
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
77
+ <description>Enforces minimum API version of 60.0 across all metadata</description>
78
+ <priority>3</priority>
79
+ <properties>
80
+ <property name="xpath">
81
+ <value><![CDATA[
82
+ //*[local-name()='apiVersion']/*[number(@Text) < 60]
83
+ ]]></value>
84
+ </property>
85
+ </properties>
86
+ </rule>
87
+ ```
@@ -0,0 +1,105 @@
1
+ # Metadata XML Examples: Flows
2
+
3
+ [← Back to Metadata XML Examples Index](metadata-xml-examples.md)
4
+
5
+ ## Example 5: Require Flow Auto-Layout
6
+
7
+ **Problem:** Flows should use Auto-Layout canvas for consistency and maintainability.
8
+
9
+ **Target file:** `*.flow-meta.xml`
10
+
11
+ **Sample violating metadata:**
12
+ ```xml
13
+ <Flow xmlns="http://soap.sforce.com/2006/04/metadata">
14
+ <apiVersion>58.0</apiVersion>
15
+ <label>My Flow</label>
16
+ <processMetadataValues>
17
+ <name>CanvasMode</name>
18
+ <value><stringValue>FREE_FORM_CANVAS</stringValue></value>
19
+ </processMetadataValues>
20
+ </Flow>
21
+ ```
22
+
23
+ **XPath (PMD 7 — flags FREE_FORM_CANVAS):**
24
+ ```xpath
25
+ //*[@Text='FREE_FORM_CANVAS']/../..
26
+ [local-name()='value']
27
+ [ancestor::*[local-name()='processMetadataValues'][.//*[@Text='CanvasMode']]]
28
+ ```
29
+
30
+ **How it works:** Finds text nodes with "FREE_FORM_CANVAS", navigates up to the `<value>` element, then confirms it's inside a `processMetadataValues` block that also contains "CanvasMode".
31
+
32
+ **PMD ruleset:**
33
+ ```xml
34
+ <rule name="FlowMustUseAutoLayout"
35
+ language="xml"
36
+ message="Flows must use AUTO_LAYOUT_CANVAS mode"
37
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
38
+ <description>Enforces auto-layout canvas mode on all flows for consistency</description>
39
+ <priority>3</priority>
40
+ <properties>
41
+ <property name="xpath">
42
+ <value><![CDATA[
43
+ //*[@Text='FREE_FORM_CANVAS']/../..
44
+ [local-name()='value']
45
+ [ancestor::*[local-name()='processMetadataValues'][.//*[@Text='CanvasMode']]]
46
+ ]]></value>
47
+ </property>
48
+ </properties>
49
+ </rule>
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Example 6: Flow Missing Fault Handler
55
+
56
+ **Problem:** Flow actions without fault connectors can fail silently.
57
+
58
+ **Target file:** `*.flow-meta.xml`
59
+
60
+ **Sample violating metadata:**
61
+ ```xml
62
+ <Flow xmlns="http://soap.sforce.com/2006/04/metadata">
63
+ <recordCreates>
64
+ <name>Create_Account</name>
65
+ <object>Account</object>
66
+ <connector>
67
+ <targetReference>Next_Step</targetReference>
68
+ </connector>
69
+ <!-- No <faultConnector> element! -->
70
+ </recordCreates>
71
+ </Flow>
72
+ ```
73
+
74
+ **XPath:**
75
+ ```xpath
76
+ //*[local-name()='Flow']/*[
77
+ local-name()='recordCreates' or local-name()='recordUpdates'
78
+ or local-name()='recordDeletes' or local-name()='recordLookups'
79
+ ][
80
+ not(*[local-name()='faultConnector'])
81
+ ]
82
+ ```
83
+
84
+ **PMD ruleset:**
85
+ ```xml
86
+ <rule name="FlowActionRequiresFaultHandler"
87
+ language="xml"
88
+ message="Flow DML/query actions must have a fault connector for error handling"
89
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
90
+ <description>Ensures all data operations in flows have fault paths</description>
91
+ <priority>2</priority>
92
+ <properties>
93
+ <property name="xpath">
94
+ <value><![CDATA[
95
+ //*[local-name()='Flow']/*[
96
+ local-name()='recordCreates' or local-name()='recordUpdates'
97
+ or local-name()='recordDeletes' or local-name()='recordLookups'
98
+ ][
99
+ not(*[local-name()='faultConnector'])
100
+ ]
101
+ ]]></value>
102
+ </property>
103
+ </properties>
104
+ </rule>
105
+ ```
@@ -0,0 +1,95 @@
1
+ # Metadata XML Examples: Permissions
2
+
3
+ [← Back to Metadata XML Examples Index](metadata-xml-examples.md)
4
+
5
+ ## Example 1: Flag ModifyAllData / ViewAllData Permissions
6
+
7
+ **Problem:** Permission sets granting ModifyAllData or ViewAllData are a security risk.
8
+
9
+ **Target file:** `*.permissionset-meta.xml`
10
+
11
+ **Sample violating metadata:**
12
+ ```xml
13
+ <PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
14
+ <label>Power User</label>
15
+ <userPermissions>
16
+ <enabled>true</enabled>
17
+ <name>ModifyAllData</name>
18
+ </userPermissions>
19
+ </PermissionSet>
20
+ ```
21
+
22
+ **XPath (PMD 7):**
23
+ ```xpath
24
+ //*[@Text='ModifyAllData' or @Text='ViewAllData']/../..
25
+ [local-name()='userPermissions']
26
+ [.//*[@Text='true']]
27
+ ```
28
+
29
+ **How it works:**
30
+ 1. `//*[@Text='ModifyAllData' or @Text='ViewAllData']` — find text node with dangerous permission name
31
+ 2. `/../..` — navigate up: text node → `<name>` element → `<userPermissions>` parent
32
+ 3. `[local-name()='userPermissions']` — confirm we're at the right element
33
+ 4. `[.//*[@Text='true']]` — check that a descendant text node contains "true" (the `<enabled>` value)
34
+
35
+ **PMD ruleset:**
36
+ ```xml
37
+ <rule name="NoDangerousPermissions"
38
+ language="xml"
39
+ message="Permission set grants ModifyAllData or ViewAllData — use specific object permissions instead"
40
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
41
+ <description>Flags permission sets that grant ModifyAllData or ViewAllData</description>
42
+ <priority>1</priority>
43
+ <properties>
44
+ <property name="xpath">
45
+ <value><![CDATA[
46
+ //*[@Text='ModifyAllData' or @Text='ViewAllData']/../..
47
+ [local-name()='userPermissions']
48
+ [.//*[@Text='true']]
49
+ ]]></value>
50
+ </property>
51
+ </properties>
52
+ </rule>
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Example 4: No Field Permissions in Profiles
58
+
59
+ **Problem:** Field permissions should be managed via Permission Sets, not Profiles.
60
+
61
+ **Target file:** `*.profile-meta.xml`
62
+
63
+ **Sample violating metadata:**
64
+ ```xml
65
+ <Profile xmlns="http://soap.sforce.com/2006/04/metadata">
66
+ <fieldPermissions>
67
+ <editable>true</editable>
68
+ <field>Account.Revenue__c</field>
69
+ <readable>true</readable>
70
+ </fieldPermissions>
71
+ </Profile>
72
+ ```
73
+
74
+ **XPath:**
75
+ ```xpath
76
+ //*[local-name()='Profile']/*[local-name()='fieldPermissions']
77
+ ```
78
+
79
+ **PMD ruleset:**
80
+ ```xml
81
+ <rule name="NoFieldPermissionsInProfile"
82
+ language="xml"
83
+ message="Field permissions should be in Permission Sets, not Profiles"
84
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
85
+ <description>Profiles should not contain field-level security — use Permission Sets</description>
86
+ <priority>2</priority>
87
+ <properties>
88
+ <property name="xpath">
89
+ <value><![CDATA[
90
+ //*[local-name()='Profile']/*[local-name()='fieldPermissions']
91
+ ]]></value>
92
+ </property>
93
+ </properties>
94
+ </rule>
95
+ ```
@@ -0,0 +1,84 @@
1
+ # Metadata XML Rule Examples
2
+
3
+ Real-world custom PMD rules targeting Salesforce metadata XML files for org governance.
4
+
5
+ ⚠️ **PMD 7 Note:** All examples use `@Text` for text content matching. The `text()` function does NOT work in PMD 7's XML language. See `references/metadata-xml-rules.md` for full details.
6
+
7
+ ## Example Index
8
+
9
+ | # | Example | File |
10
+ |---|---------|------|
11
+ | 1 | Flag ModifyAllData / ViewAllData Permissions | [metadata-xml-example-permissions.md](metadata-xml-example-permissions.md) |
12
+ | 2 | Require Description on Custom Fields | [metadata-xml-example-fields-api.md](metadata-xml-example-fields-api.md) |
13
+ | 3 | Enforce Minimum API Version | [metadata-xml-example-fields-api.md](metadata-xml-example-fields-api.md) |
14
+ | 4 | No Field Permissions in Profiles | [metadata-xml-example-permissions.md](metadata-xml-example-permissions.md) |
15
+ | 5 | Require Flow Auto-Layout | [metadata-xml-example-flows.md](metadata-xml-example-flows.md) |
16
+ | 6 | Flow Missing Fault Handler | [metadata-xml-example-flows.md](metadata-xml-example-flows.md) |
17
+
18
+ ---
19
+
20
+ ## How to Create These
21
+
22
+ 1. Run `sf code-analyzer ast-dump --file your-file.xml --language xml` to see the DOM structure. If ast-dump fails, read the raw XML file directly — the file content IS the AST.
23
+ 2. Write XPath using `local-name()` for element matching and `@Text` for text content (NOT `text()`)
24
+ 3. Navigate from text nodes to parent elements using `../..`
25
+ 4. Scope to the correct metadata type by checking the root element or using `ancestor::`
26
+ 5. Configure `file_extensions: { xml: [".xml"] }` in `code-analyzer.yml` (just `.xml` covers all compound metadata extensions)
27
+ 6. Add the rule to a ruleset XML file with `language="xml"`
28
+ 7. Reference the ruleset in `code-analyzer.yml` under `engines.pmd.custom_rulesets`
29
+ 8. Validate: `sf code-analyzer rules --rule-selector pmd:RuleName`
30
+ 9. Test positive + negative: run against both a violating and clean metadata file
31
+
32
+ ## Complete Multi-Rule Ruleset Example
33
+
34
+ ```xml
35
+ <?xml version="1.0" encoding="UTF-8"?>
36
+ <ruleset name="MetadataGovernance"
37
+ xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
38
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
39
+ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
40
+
41
+ <description>Org governance rules for Salesforce metadata</description>
42
+
43
+ <rule name="NoDangerousPermissions" language="xml"
44
+ message="Permission set grants ModifyAllData or ViewAllData"
45
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
46
+ <priority>1</priority>
47
+ <properties>
48
+ <property name="xpath">
49
+ <value><![CDATA[
50
+ //*[@Text='ModifyAllData' or @Text='ViewAllData']/../..
51
+ [local-name()='userPermissions']
52
+ [.//*[@Text='true']]
53
+ ]]></value>
54
+ </property>
55
+ </properties>
56
+ </rule>
57
+
58
+ <rule name="FieldRequiresDescription" language="xml"
59
+ message="Custom field must have a non-empty description"
60
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
61
+ <priority>3</priority>
62
+ <properties>
63
+ <property name="xpath">
64
+ <value><![CDATA[
65
+ //*[local-name()='CustomField'][not(*[local-name()='description']) or *[local-name()='description'][not(@Text)]]
66
+ ]]></value>
67
+ </property>
68
+ </properties>
69
+ </rule>
70
+
71
+ <rule name="MinimumApiVersion" language="xml"
72
+ message="API version must be 60.0 or higher"
73
+ class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
74
+ <priority>3</priority>
75
+ <properties>
76
+ <property name="xpath">
77
+ <value><![CDATA[
78
+ //*[local-name()='apiVersion']/*[number(@Text) < 60]
79
+ ]]></value>
80
+ </property>
81
+ </properties>
82
+ </rule>
83
+ </ruleset>
84
+ ```
@@ -0,0 +1,127 @@
1
+ # Regex Rule Examples
2
+
3
+ Real-world custom regex rules solving problems from the Code Analyzer community.
4
+
5
+ ## Example 1: Ban Hardcoded Salesforce IDs
6
+
7
+ **Problem:** Developers hardcode record IDs that differ between orgs.
8
+ (GitHub issue #738 — community request for detecting bad patterns)
9
+
10
+ ```yaml
11
+ engines:
12
+ regex:
13
+ custom_rules:
14
+ NoHardcodedSalesforceIds:
15
+ regex: "/['\"](?<target>0[a-zA-Z0-9]{14}(?:[a-zA-Z0-9]{3})?)['\"]/g"
16
+ description: "Detects hardcoded 15 or 18 character Salesforce record IDs"
17
+ violation_message: "Replace hardcoded ID with Custom Label, Custom Metadata, or Custom Setting"
18
+ severity: 2
19
+ tags: ["Custom", "Security"]
20
+ file_extensions: [".cls", ".trigger"]
21
+ ```
22
+
23
+ **Why this pattern:**
24
+ - Salesforce IDs are exactly **15 or 18 characters** (never 16 or 17)
25
+ - They always **start with `0`** (the key prefix)
26
+ - `0[a-zA-Z0-9]{14}` matches 15-char IDs (0 + 14 more)
27
+ - `(?:[a-zA-Z0-9]{3})?` optionally matches the 3-char case-safe suffix (making 18-char)
28
+ - `(?<target>...)` narrows the violation highlight to just the ID, not the surrounding quotes
29
+ - ⚠️ Do NOT use `{15,18}` — this also matches 16/17 char strings and false-positives on normal words like `'BusinessAccount'` or `'RecordTypeInfos'`
30
+
31
+ **Note on `regex_ignore`:** This field works **per-line**, not per-file. Adding `/@isTest/` only skips lines that literally contain `@isTest` — it does NOT exclude entire test classes. To exclude test files entirely, use `ignores.files` in `code-analyzer.yml` with a glob like `**/*Test.cls`.
32
+
33
+ ---
34
+
35
+ ## Example 2: Flag TODO/FIXME Before Merge
36
+
37
+ **Problem:** Developers leave TODOs that never get resolved.
38
+
39
+ ```yaml
40
+ engines:
41
+ regex:
42
+ custom_rules:
43
+ NoUnresolvedTodos:
44
+ regex: "/(?:TODO|FIXME|HACK|XXX)\\b/gi"
45
+ description: "Flags unresolved TODO/FIXME comments"
46
+ violation_message: "Resolve this TODO/FIXME before merging to main"
47
+ severity: 4
48
+ tags: ["Custom", "BestPractices"]
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Example 3: Enforce WITH USER_MODE on SOQL
54
+
55
+ **Problem:** SOQL queries without FLS enforcement are a security risk.
56
+
57
+ ```yaml
58
+ engines:
59
+ regex:
60
+ custom_rules:
61
+ RequireUserModeOnSoql:
62
+ regex: "/\\[\\s*SELECT(?![^\\]]*WITH\\s+(?:USER_MODE|SECURITY_ENFORCED))[^\\]]*\\]/gi"
63
+ description: "SOQL queries must use WITH USER_MODE or WITH SECURITY_ENFORCED"
64
+ violation_message: "Add WITH USER_MODE to this SOQL query for FLS enforcement"
65
+ severity: 2
66
+ tags: ["Custom", "Security"]
67
+ file_extensions: [".cls", ".trigger"]
68
+ regex_ignore: "/@isTest|@IsTest|@testSetup|@TestSetup/"
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Example 4: Ban @SuppressWarnings and NOPMD
74
+
75
+ **Problem:** Teams want to prevent suppression of violations.
76
+ (GitHub issue #1972 — real user request)
77
+
78
+ ```yaml
79
+ engines:
80
+ regex:
81
+ custom_rules:
82
+ ProhibitSuppressWarnings:
83
+ regex: "/@SuppressWarnings\\([^)]*\\)|\\/\\/\\s*NOPMD/gi"
84
+ description: "Prohibits suppression of code analysis warnings"
85
+ violation_message: "Fix the underlying issue instead of suppressing warnings"
86
+ severity: 2
87
+ tags: ["Custom", "BestPractices"]
88
+ file_extensions: [".cls", ".trigger"]
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Example 5: Detect Old API Versions in Metadata
94
+
95
+ **Problem:** Metadata files using API versions more than 2 years old.
96
+
97
+ ```yaml
98
+ engines:
99
+ regex:
100
+ custom_rules:
101
+ NoOldApiVersions:
102
+ regex: "/<apiVersion>(4[0-9]|5[0-5])\\.0<\\/apiVersion>/g"
103
+ description: "Detects metadata files using API version 55.0 or below"
104
+ violation_message: "Update API version to 60.0 or higher"
105
+ severity: 3
106
+ tags: ["Custom", "BestPractices"]
107
+ file_extensions: [".xml"]
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Example 6: Enforce Test Class Naming Convention
113
+
114
+ **Problem:** Test classes should end with `_TEST` or `Test`.
115
+
116
+ ```yaml
117
+ engines:
118
+ regex:
119
+ custom_rules:
120
+ TestClassNaming:
121
+ regex: "/@(?:isTest|IsTest)(?:\\([^)]*\\))?\\s*(?:public|private)\\s+class\\s+(?!.*(?:_TEST|Test)\\b)\\w+/g"
122
+ description: "Test classes must end with _TEST or Test suffix"
123
+ violation_message: "Rename this test class to end with _TEST or Test (e.g., MyServiceTest)"
124
+ severity: 3
125
+ tags: ["Custom", "CodeStyle"]
126
+ file_extensions: [".cls"]
127
+ ```