Additional PMD and Regex rules for testing Salesforce Apex code using Salesforce Code Analyzer. Most rules are implemented as XPath 3.1 expressions that operate on the PMD Apex AST. Some rules use the Regex engine for pattern-based matching.
Repository: https://github.com/starch-uk/sca-extra
Note: This entire project has been vibe coded. The plan for the project can be found in PLAN.md.
This project provides a comprehensive set of PMD and Regex rules for Salesforce
Apex code analysis. Most rules are implemented using XPath 3.1 expressions only
(no custom Java classes), making them easy to understand, modify, and maintain.
Some rules use the Regex engine for pattern-based matching (e.g.,
NoConsecutiveBlankLines, ProhibitPrettierIgnore, ProhibitSuppressWarnings,
NoLongLines).
The code-analyzer.yml file is where you configure which rules to enable,
customize rule properties, and disable default rules. This section explains how
to customize your configuration.
Where the rulesets live:
- Most rules are defined as XML rulesets under the
rulesets/directory, grouped by category (see Rule Categories below for an overview). - Some rules (like
NoConsecutiveBlankLines,ProhibitPrettierIgnore,ProhibitSuppressWarnings, andNoLongLines) are defined as Regex rules in the repository'scode-analyzer.ymlunderengines.regex.custom_rules. - You can copy the entire
rulesets/folder into your Salesforce project, or just the specific category folders you want to enable.
Add the rulesets to code-analyzer.yml:
- Create or update a
code-analyzer.ymlfile in the root of your Salesforce project. - Reference the ruleset XML files you want to enable under
engines.pmd.custom_rulesets, using paths relative to your project root (for full examples, see Installation and Usage below). - Important: Do NOT copy the repository's
code-analyzer.ymlfile in its entirety, as it hasrulesets: [](disabled). Instead, copy only the Regex rules configuration from theengines.regex.custom_rulessection into your owncode-analyzer.yml(see Regex Rules section below).
Here's a complete code-analyzer.yml configuration that includes all 48 PMD
rules and 4 Regex rules from this repository:
engines:
pmd:
disable_engine: false
file_extensions:
apex:
- .apex
- .cls
- .trigger
custom_rulesets:
- rulesets/bestpractices/FinalVariablesMustBeFinal.xml
- rulesets/bestpractices/RegexPatternsMustBeStaticFinal.xml
- rulesets/bestpractices/StaticMethodsMustBeStatic.xml
- rulesets/bestpractices/StaticVariablesMustBeFinalAndScreamingSnakeCase.xml
- rulesets/bestpractices/TestClassIsParallel.xml
- rulesets/codestyle/AvoidMagicNumbers.xml
- rulesets/codestyle/AvoidOneLinerMethods.xml
- rulesets/codestyle/InnerClassesMustBeOneWord.xml
- rulesets/codestyle/MapShouldBeInitializedWithValues.xml
- rulesets/codestyle/MultipleStringContainsCalls.xml
- rulesets/codestyle/NoMethodCallsAsArguments.xml
- rulesets/codestyle/NoMethodCallsInConditionals.xml
- rulesets/codestyle/NoSingleLetterVariableNames.xml
- rulesets/codestyle/PreferBuilderPatternChaining.xml
- rulesets/codestyle/PreferConcatenationOverStringJoinWithEmpty.xml
- rulesets/codestyle/PreferMethodCallsInLoopConditions.xml
- rulesets/codestyle/PreferNullCoalescingOverTernary.xml
- rulesets/codestyle/PreferSafeNavigationOperator.xml
- rulesets/codestyle/PreferStringJoinOverConcatenation.xml
- rulesets/codestyle/PreferStringJoinOverMultipleNewlines.xml
- rulesets/codestyle/PreferStringJoinWithSeparatorOverEmpty.xml
- rulesets/codestyle/SingleArgumentMustBeSingleLine.xml
- rulesets/codestyle/VariablesMustNotShareNamesWithClasses.xml
- rulesets/design/AvoidLowValueWrapperMethods.xml
- rulesets/design/AvoidTrivialPropertyGetters.xml
- rulesets/design/ClassesMustHaveMethods.xml
- rulesets/design/CombineNestedIfStatements.xml
- rulesets/design/EnumMinimumValues.xml
- rulesets/design/InnerClassesCannotBeStatic.xml
- rulesets/design/InnerClassesCannotHaveStaticMembers.xml
- rulesets/design/NoCustomParameterObjects.xml
- rulesets/design/NoInterfacesEndingWithCallback.xml
- rulesets/design/NoParameterClasses.xml
- rulesets/design/NoThisOutsideConstructors.xml
- rulesets/design/NoUnnecessaryAttributeVariables.xml
- rulesets/design/NoUnnecessaryReturnVariables.xml
- rulesets/design/UnusedVariableNotReturnedOrPassed.xml
- rulesets/design/PreferPropertySyntaxOverGetterMethods.xml
- rulesets/design/PreferSwitchOverIfElseChains.xml
- rulesets/design/SingleParameterMustBeSingleLine.xml
- rulesets/design/DmlInLoopAcrossMethods.xml
- rulesets/documentation/ExceptionDocumentationRequired.xml
- rulesets/documentation/MethodsRequireExampleTag.xml
- rulesets/documentation/ProhibitAuthorSinceVersionTags.xml
- rulesets/documentation/SingleLineDocumentationFormat.xml
- rulesets/documentation/ValidGroupTagValues.xml
regex:
custom_rules:
NoConsecutiveBlankLines:
regex: /\n\s*\n\s*\n/g
file_extensions: ['.apex', '.cls', '.trigger']
description:
'Prevents two or more consecutive blank lines in Apex code.
Code should have at most one blank line between statements,
methods, or other code elements.'
violation_message:
'Two or more consecutive blank lines are not allowed. Use at
most one blank line between statements.'
severity: 'Moderate'
tags: ['CodeStyle', 'Recommended']
ProhibitSuppressWarnings:
regex: /@SuppressWarnings\([^)]*\)|\/\/\s*NOPMD/gi
file_extensions: ['.apex', '.cls', '.trigger']
description:
'Prohibits the use of @SuppressWarnings annotations and
NOPMD comments in Apex code. Suppressions hide code quality
issues; prefer fixing the underlying problems or improving
rules instead.'
violation_message:
'Suppression of warnings is not allowed. Fix the underlying
issue or improve the rule instead of suppressing violations.'
severity: 'High'
tags: ['CodeStyle', 'Recommended']
NoLongLines:
regex: /.{81,}/gm
file_extensions: ['.apex', '.cls', '.trigger']
description:
'Enforces a maximum line length of 80 characters. Lines
longer than 80 characters reduce readability and make code
harder to review. Use shorter class, attribute, method, and
variable names to improve readability.'
violation_message:
'Line exceeds 80 characters. Use shorter class, attribute,
method, and variable names to improve readability.'
severity: 'Moderate'
tags: ['CodeStyle', 'Recommended']
ProhibitPrettierIgnore:
regex: /\/\/\s*prettier-ignore/gi
file_extensions: ['.apex', '.cls', '.trigger']
description:
'Prohibits the use of prettier-ignore comments in Apex code.
Code should be formatted consistently without exceptions.
Using prettier-ignore comments undermines code formatting
standards and makes the codebase less maintainable.'
violation_message:
'Prettier-ignore comments are not allowed. Code should be
formatted consistently without exceptions.'
severity: 'Moderate'
tags: ['CodeStyle', 'Recommended']Note: You can copy this entire configuration into your project's
code-analyzer.yml file, or select only the rules you want to enable. See the
sections below for information on customizing rules and selecting specific
categories.
The code-analyzer.yml file uses YAML syntax and typically includes:
engines:
pmd:
custom_rulesets:
# List of ruleset files to include
- rulesets/design/InnerClassesCannotBeStatic.xml
rules:
# Rule-specific configuration (severity and tags only)
# Note: Property overrides are NOT supported via code-analyzer.yml
# Use custom ruleset XML files with ref= syntax instead (see "Overriding Rule Properties" below)
RuleName:
severity: 'High'
tags: ['Recommended']To add rules from this repository, reference the ruleset XML files under
engines.pmd.custom_rulesets using paths relative to your project root:
engines:
pmd:
custom_rulesets:
# Design rules
- rulesets/design/InnerClassesCannotBeStatic.xml
- rulesets/design/InnerClassesCannotHaveStaticMembers.xml
# Best practices rules
- rulesets/bestpractices/FinalVariablesMustBeFinal.xml
- rulesets/bestpractices/StaticMethodsMustBeStatic.xml
# Code style rules (including naming)
- rulesets/codestyle/NoSingleLetterVariableNames.xml
# More code style rules
- rulesets/codestyle/NoMethodCallsInConditionals.xml
- rulesets/codestyle/PreferSafeNavigationOperator.xmlImportant:
- Copy the
rulesets/directory (or specific category folders) to your Salesforce project root first - Paths in
code-analyzer.ymlare relative to your project root - You can include as many or as few rules as needed
- Use
engines.pmd.custom_rulesets(notrulesets:) to reference PMD rulesets
Important: PMD 7+ does not support dynamic properties for XPath rules via
code-analyzer.yml. However, rules are designed with configurable variables at
the top of their XPath expressions, making customization straightforward.
How to Customize a Rule:
-
Locate the rule file in the
rulesets/directory (e.g.,rulesets/design/EnumMinimumValues.xml) -
Edit the configurable variables at the top of the XPath expression (usually in a
letstatement). You don't need to edit the entire XPath - just change the variable values. -
Update the rule description to reflect your changes (optional but recommended)
Example 1 - Change EnumMinimumValues threshold from 3 to 4:
Open rulesets/design/EnumMinimumValues.xml and find the variable at the top of
the XPath expression:
<property name="xpath">
<value>
<![CDATA[
let $minValues := 3 <!-- Change this value -->
return //UserEnum[
count(Field) < $minValues
]
]]>
</value>
</property>Change 3 to 4:
<property name="xpath">
<value>
<![CDATA[
let $minValues := 4 <!-- Changed from 3 to 4 -->
return //UserEnum[
count(Field) < $minValues
]
]]>
</value>
</property>Best Practices:
- Look for variables at the top - Most rules have configurable variables in
letstatements at the beginning of the XPath expression - Only change variable values - You typically don't need to modify the rest of the XPath expression
- Keep a backup of the original rule file before making changes
- Document your changes in comments or commit messages
- Test your changes by running
sf code-analyzer runon your codebase - Consider version control - if you customize rules, you may want to maintain your own fork or keep custom rules in a separate directory
- Update rule descriptions to reflect your customizations (optional)
Note: If you need different behavior for different parts of your codebase, you can:
- Create multiple copies of the rule with different names (e.g.,
EnumMinimumValues4.xml,EnumMinimumValues5.xml) - Reference both in your
code-analyzer.yml - Use PMD's exclusion patterns if needed
Rule Examples: All rules include <example> sections in their XML files
showing violations and valid code patterns. These examples help clarify the
rule's intent. Rules with configurable thresholds use easy-to-edit variables at
the top of the XPath expression, making customization straightforward - you only
need to change the variable values, not the entire XPath logic.
To disable default PMD rules provided by Salesforce Code Analyzer, you can exclude them from standard PMD category rulesets. Create a custom ruleset XML file that references the category but excludes specific rules:
<?xml version="1.0" ?>
<ruleset name="CustomPMDRules" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0">
<description>Custom PMD rules with some defaults disabled</description>
<!-- Include standard security rules, but exclude ApexCRUDViolation -->
<rule ref="category/apex/security.xml">
<exclude name="ApexCRUDViolation" />
</rule>
<!-- Include best practices, but exclude specific rules -->
<rule ref="category/apex/bestpractices.xml">
<exclude name="DebugsShouldUseLoggingLevel" />
<exclude name="ApexUnitTestClassShouldHaveRunAs" />
</rule>
</ruleset>Save this as rulesets/custom-disabled-defaults.xml and reference it in
code-analyzer.yml:
engines:
pmd:
custom_rulesets:
- rulesets/custom-disabled-defaults.xml # Custom ruleset with disabled defaults
- rulesets/design/InnerClassesCannotBeStatic.xml
- rulesets/codestyle/NoSingleLetterVariableNames.xmlAlternatively, if you're creating a comprehensive custom ruleset, you can
combine standard PMD rules with exclusions in a single XML file. Note that
custom rulesets from this repository are typically referenced separately in
code-analyzer.yml (as shown in the example below):
<?xml version="1.0" ?>
<ruleset
name="ComprehensiveRules"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
>
<description>Standard PMD rules with custom exclusions</description>
<!-- Standard PMD rules with some disabled -->
<rule ref="category/apex/security.xml">
<priority>1</priority>
<exclude name="ApexCRUDViolation" />
</rule>
<rule ref="category/apex/bestpractices.xml">
<priority>2</priority>
<exclude name="DebugsShouldUseLoggingLevel" />
</rule>
<!-- Standard PMD category rulesets -->
<rule ref="category/apex/design.xml">
<priority>2</priority>
</rule>
</ruleset>Then reference both this comprehensive ruleset and your custom rulesets in
code-analyzer.yml:
engines:
pmd:
custom_rulesets:
- rulesets/custom-comprehensive-rules.xml # Standard rules with exclusions
- rulesets/design/InnerClassesCannotBeStatic.xml # Custom rules
- rulesets/codestyle/NoSingleLetterVariableNames.xmlStandard PMD category rulesets available:
category/apex/security.xml- Security-related rulescategory/apex/bestpractices.xml- Best practice rulescategory/apex/design.xml- Design pattern rulescategory/apex/performance.xml- Performance rulescategory/apex/codestyle.xml- Code style rulescategory/apex/errorprone.xml- Errorprone patterns
Some rules are implemented using the Regex engine for pattern-based matching.
These rules are defined in the repository's code-analyzer.yml under
engines.regex.custom_rules.
To use Regex rules in your project:
Copy the engines.regex.custom_rules section from the repository's
code-analyzer.yml into your own code-analyzer.yml file. Do NOT copy the
entire code-analyzer.yml file, as the repository version has rulesets: []
(disabled) and is only meant as a reference for Regex rules.
Example - Add to your code-analyzer.yml:
engines:
pmd:
custom_rulesets:
- rulesets/design/InnerClassesCannotBeStatic.xml
# ... other rulesets ...
regex:
custom_rules:
NoConsecutiveBlankLines:
regex: /\n\s*\n\s*\n/g
file_extensions: ['.apex', '.cls', '.trigger']
description:
'Prevents two or more consecutive blank lines in Apex code.
Code should have at most one blank line between statements,
methods, or other code elements.'
violation_message:
'Two or more consecutive blank lines are not allowed. Use at
most one blank line between statements.'
severity: 'Moderate'
tags: ['CodeStyle', 'Recommended']
ProhibitSuppressWarnings:
regex: /@SuppressWarnings\([^)]*\)|\/\/\s*NOPMD/gi
file_extensions: ['.apex', '.cls', '.trigger']
description:
'Prohibits the use of @SuppressWarnings annotations and
NOPMD comments in Apex code. Suppressions hide code quality
issues; prefer fixing the underlying problems or improving
rules instead.'
violation_message:
'Suppression of warnings is not allowed. Fix the underlying
issue or improve the rule instead of suppressing violations.'
severity: 'High'
tags: ['CodeStyle', 'Recommended']
NoLongLines:
regex: /.{81,}/gm
file_extensions: ['.apex', '.cls', '.trigger']
description:
'Enforces a maximum line length of 80 characters. Lines
longer than 80 characters reduce readability and make code
harder to review. Use shorter class, attribute, method, and
variable names to improve readability.'
violation_message:
'Line exceeds 80 characters. Use shorter class, attribute,
method, and variable names to improve readability.'
severity: 'Moderate'
tags: ['CodeStyle', 'Recommended']
ProhibitPrettierIgnore:
regex: /\/\/\s*prettier-ignore/gi
file_extensions: ['.apex', '.cls', '.trigger']
description:
'Prohibits the use of prettier-ignore comments in Apex code.
Code should be formatted consistently without exceptions.
Using prettier-ignore comments undermines code formatting
standards and makes the codebase less maintainable.'
violation_message:
'Prettier-ignore comments are not allowed. Code should be
formatted consistently without exceptions.'
severity: 'Moderate'
tags: ['CodeStyle', 'Recommended']Regex rules are useful for:
- Pattern-based text matching (e.g., consecutive blank lines)
- Finding patterns in comments (PMD ignores comments)
- Simple string/pattern detection
For more information on creating Regex rules, see the Code Analyzer Configuration Regex Engine section.
Here's a minimal code-analyzer.yml example showing how to enable a few rules
and customize their severity:
engines:
pmd:
custom_rulesets:
# Design rules
- rulesets/design/InnerClassesCannotBeStatic.xml
- rulesets/design/InnerClassesCannotHaveStaticMembers.xml
# Best practices rules
- rulesets/bestpractices/FinalVariablesMustBeFinal.xml
# Code style rules
- rulesets/codestyle/NoSingleLetterVariableNames.xml
- rulesets/codestyle/NoMethodCallsInConditionals.xml
# Documentation rules
- rulesets/documentation/ExceptionDocumentationRequired.xml
- rulesets/documentation/ValidGroupTagValues.xml
- rulesets/documentation/ProhibitAuthorSinceVersionTags.xml
- rulesets/documentation/SingleLineDocumentationFormat.xml
- rulesets/documentation/MethodsRequireExampleTag.xml
rules:
# Override severity and tags
NoSingleLetterVariableNames:
severity: 'High'
tags: ['Recommended', 'Naming']For a comprehensive example with all 48 PMD rules and 4 Regex rules, see the Comprehensive Example (All Rules) section above.
For more examples: See the unhappy-soup ruleset for a comprehensive example of combining standard PMD rules with custom rules and configuration.
To customize rule behavior: See the Customizing Rules section above for instructions on customizing rules by editing configurable variables at the top of XPath expressions.
-
Copy rulesets to your project
- Copy the
rulesets/directory (or specific category folders) to your Salesforce project root - Rules are organized by category (see Rule Categories below)
- Copy the
-
Configure
code-analyzer.yml- Create or update
code-analyzer.ymlin your project root - Add rulesets and disable default rules as needed
- See Configuring code-analyzer.yml for detailed instructions
- Create or update
-
Run the analyzer
- Use the command line:
sf code-analyzer run - Or use the VS Code extension for real-time analysis
- See Usage for details
- Use the command line:
Important: PMD rules should focus on code quality, logic, and best practices, not formatting. Formatting concerns should be handled by Prettier or similar formatters.
-
Code Quality & Logic
- Detecting anti-patterns and code smells
- Enforcing best practices and design patterns
- Identifying potential bugs or logic errors
- Examples:
NoMethodCallsInConditionals- Prevents side effects in conditionalsInnerClassesCannotBeStatic- Enforces Apex language constraints
-
Structural Issues
- Code organization and architecture
- Class and method structure
- Inheritance and composition patterns
- Examples:
ClassesMustHaveMethods- Ensures classes have purposeAvoidTrivialPropertyGetters- Prevents unnecessary wrappersNoParameterClasses- Enforces proper class design
-
Naming & Conventions
- Meaningful names that affect code readability and maintainability
- Naming patterns that indicate intent
- Examples:
NoSingleLetterVariableNames- Ensures descriptive variable namesVariablesMustNotShareNamesWithClasses- Prevents confusion
-
Modifiers & Access Control
- Appropriate use of modifiers (final, static, etc.)
- Access control best practices
- Examples:
FinalVariablesMustBeFinal- Detects method-local variables that are never reassigned and should be declared as finalStaticMethodsMustBeStatic- Prevents unnecessary instance methodsRegexPatternsMustBeStaticFinal- Ensures regex patterns are static final constantsTestClassIsParallel- Enforces test best practices
-
Documentation Quality
- Meaningful documentation, not formatting
- Documentation completeness
- Examples:
ExceptionDocumentationRequired- Ensures exceptions are documentedValidGroupTagValues- Ensures @group tags use valid valuesProhibitAuthorSinceVersionTags- Prohibits @author, @since, and @version tagsMethodsRequireExampleTag- Requires methods to have @example tags in ApexDoc
-
Formatting & Style
- Indentation and spacing
- Line breaks and line length
- Brace placement
- Quote style (single vs. double)
- Trailing commas
- Examples of what NOT to create rules for:
- ❌ "Methods must be on separate lines" (formatting)
- ❌ "Indentation must be 4 spaces" (formatting)
- ❌ "No trailing whitespace" (formatting)
- ❌ "Line length must be < 120 characters" (formatting)
-
Whitespace & Spacing
- Spaces around operators
- Spaces in function calls
- Blank lines between methods
- Examples:
- ❌ "No consecutive blank lines" (formatting - though this could be a code quality rule if it's about readability)
- ❌ "Spaces around operators" (formatting)
-
Syntax Formatting
- Semicolon placement
- Comma placement
- Parentheses spacing
- Examples:
- ❌ "Always use semicolons" (formatting)
- ❌ "No space before opening parenthesis" (formatting)
Create a PMD Rule When:
- The issue affects code quality, maintainability, or correctness
- The check requires understanding code semantics (not just syntax)
- The rule helps prevent bugs or enforces architectural decisions
- The rule is about "what" the code does, not "how" it looks
Use Prettier When:
- The issue is purely about code appearance
- The check is about spacing, indentation, or line breaks
- The rule would just reformat code without changing meaning
- The rule is about "how" the code looks, not "what" it does
Good Rule (PMD):
// Rule: NoMethodCallsInConditionals
// Why: Prevents side effects and makes code more predictable
if (getValue() > 0) { // ❌ Method call in conditional
// ...
}Formatting (Prettier):
// Prettier handles: Indentation, spacing, line breaks
if(getValue()>0){ // Prettier formats to:
if (getValue() > 0) { // Proper spacingGood Rule (PMD):
// Rule: NoSingleLetterVariableNames
// Why: Ensures code is readable and maintainable
Integer x = 5; // ❌ Unclear what 'x' representsFormatting (Prettier):
// Prettier handles: Spacing around operators
Integer x=5; // Prettier formats to:
Integer x = 5; // Proper spacingNote: Some rules may appear to overlap with formatting (e.g.,
NoConsecutiveBlankLines), but if they're about code readability and structure
rather than pure formatting, they can be appropriate rules.
NoConsecutiveBlankLines is implemented as a Regex rule for efficient pattern
matching. The key distinction is: Does this affect code quality and
understanding, or just appearance?
-
Clone or download the repository
git clone https://github.com/starch-uk/sca-extra.git
-
Copy rulesets to your Salesforce project
- Copy the
rulesets/directory to your Salesforce project root - Or copy specific category folders (e.g.,
rulesets/design/) as needed - Maintain the directory structure for organization
- Copy the
-
Reference rulesets in your project
- Rulesets can be referenced by relative path from your project root
- Example:
rulesets/design/InnerClassesCannotBeStatic.xml
Once you've configured your code-analyzer.yml file (see
Configuring code-analyzer.yml above), you can
run Salesforce Code Analyzer in two ways:
1. Command Line:
# Install the plugin (once per environment)
sf plugins install code-analyzer
# Run the analyzer from your project root
sf code-analyzer run2. VS Code Extension:
- Install the Salesforce Code Analyzer extension in VS Code
- Open your Salesforce project with
code-analyzer.ymland therulesets/folder present - The extension will automatically analyze your code and surface issues in the editor
For detailed configuration instructions, including how to add rules, customize rules, disable default rules, and use Regex rules, see the Configuring code-analyzer.yml section above.
Priority: P1 (Critical)
Description: Inner classes in Apex cannot be static. Remove the static
modifier from inner class declarations.
Violations:
public class Outer {
public static class Inner { // ❌ Static inner class not allowed
}
}Valid Code:
public class Outer {
public class Inner { // ✅ Non-static inner class
}
}Priority: P1 (Critical)
Description: Inner classes in Apex cannot have static attributes or methods.
Remove static modifiers from inner class members.
Violations:
public class Outer {
public class Inner {
public static Integer value; // ❌ Static field not allowed
public static void method() { } // ❌ Static method not allowed
}
}Valid Code:
public class Outer {
public class Inner {
public Integer value; // ✅ Non-static field
public void method() { } // ✅ Non-static method
}
}Priority: P3 (Medium)
Description: Interfaces should not be named with the suffix 'Callback'.
Callbacks are an anti-pattern that should be avoided in favor of better design
patterns such as event-driven architectures or observer patterns. Callbacks are
often used to hide circular dependencies without fixing them, create tight
coupling, make code harder to test, and reduce maintainability.
Violations:
public interface PaymentCallback { // ❌ Interface ending with 'Callback'
void onSuccess();
void onError(String error);
}Valid Code:
public interface PaymentHandler { // ✅ Descriptive name without 'Callback'
void onSuccess();
void onError(String error);
}
public interface EventListener { // ✅ Alternative pattern
void handleEvent(String event);
}Priority: P1 (Critical)
Description: DML operations should not be performed inside loops, even when
the DML operation is in a separate method called from the loop. This pattern
leads to governor limit issues and poor performance. Collect records in a list
and perform bulk DML operations outside the loop instead.
This rule detects when a for loop or for-each loop calls a method that performs DML operations (insert, update, delete, upsert, merge, or Database class methods). The rule flags the loop that calls the method, not the method that performs the DML.
Violations:
// Violation: Loop calls method that performs DML
public void processAccounts(List<Account> accounts) {
for (Account acc : accounts) {
saveAccount(acc); // ❌ Method performs insert
}
}
private void saveAccount(Account acc) {
insert acc; // DML operation
}
// Violation: Loop calls method that performs DML via Database class
public void updateContacts(List<Contact> contacts) {
for (Contact con : contacts) {
updateContact(con); // ❌ Method performs Database.update
}
}
private void updateContact(Contact con) {
Database.update(con); // DML via Database class
}Valid Code:
// Valid: Collect records and perform bulk DML outside loop
public void processAccounts(List<Account> accounts) {
List<Account> accountsToInsert = new List<Account>();
for (Account acc : accounts) {
// Process account without DML
acc.Name = 'Processed';
accountsToInsert.add(acc);
}
insert accountsToInsert; // ✅ Bulk DML outside loop
}
// Valid: Method called from loop does not perform DML
public void processAccounts(List<Account> accounts) {
for (Account acc : accounts) {
validateAccount(acc); // ✅ No DML in this method
}
}
private void validateAccount(Account acc) {
if (acc.Name == null) {
acc.Name = 'Unknown';
}
}Priority: P3 (Medium)
Description: Detects variables that are assigned but never returned or
passed to a method or constructor. Variables should either be used in return
statements or passed as arguments to methods or constructors. This helps
identify dead code and unused variables that can be removed.
This rule flags variables that are assigned but not used in:
- Return statements
- Method call arguments
- Constructor arguments (both positional and named parameters)
Variables that are reassigned, incremented, or decremented after being used are still flagged if the final value is not returned or passed.
Violations:
// Violation: Variable assigned but never used
public void method1() {
String unused = 'test'; // ❌ Variable never returned or passed
}
// Violation: Variable assigned after being used but not returned or passed
public void method2() {
String value = 'test';
process(value);
value = 'test2'; // ❌ Variable reassigned but not returned or passed
}
// Violation: Variable incremented but not returned or passed
public void method3() {
Integer value = 0;
process(value);
value++; // ❌ Variable incremented but not returned or passed
}
// Violation: Variable used in constructor then reassigned but not returned or passed
public void method4() {
String name = 'test';
Account acc = new Account(Name = name);
name = 'test2'; // ❌ Variable reassigned but not returned or passed
}Valid Code:
// Valid: Variable returned
public String method1() {
String result = 'test';
return result; // ✅ Variable is returned
}
// Valid: Variable passed to method
public void method2() {
String value = 'test';
process(value); // ✅ Variable is passed to method
}
// Valid: Variable passed to constructor with named parameters
public void method3() {
String name = 'Test';
Account acc = new Account(Name = name); // ✅ Variable is passed to constructor
process(acc);
}
// Valid: Variable passed to constructor with positional parameters
public Account method4() {
String name = 'Test';
Account acc = new Account(name); // ✅ Variable is passed to constructor
return acc; // ✅ Result is returned
}
// Valid: Variable used in multiple ways
public void method5() {
String value = 'test';
process(value);
log(value); // ✅ Variable is passed to multiple methods
}
// Valid: Variable returned after being used
public String method6() {
String result = 'test';
process(result);
return result; // ✅ Variable is returned after being used
}Priority: P2 (High)
Description: Single-letter variable names are not allowed except for loop
counters (i, c) or exception variables (e).
Violations:
Integer x = 5; // ❌ Single-letter variable name
String s = 'test'; // ❌ Single-letter variable nameValid Code:
Integer index = 5; // ✅ Descriptive name
for (Integer i = 0; i < 10; i++) { } // ✅ Loop counter allowed
catch (Exception e) { } // ✅ Exception variable allowedPriority: P2 (High)
Description: Method calls should not be used in conditional expressions to
prevent side effects and make code more predictable.
Violations:
if (getValue() > 0) { // ❌ Method call in conditional
// ...
}Valid Code:
Integer value = getValue(); // ✅ Extract to variable
if (value > 0) {
// ...
}Priority: P3 (Medium)
Description: Method calls should be used directly in loop conditionals
rather than testing inside the loop with a break statement. This makes the loop
condition explicit and improves readability. Applies to both while and do-while
loops.
Violations:
// while loop with break inside
while (true) { // ❌ Should use method call in condition
if (!hasMore()) {
break;
}
processItem();
}
// do-while loop with break inside
do { // ❌ Should use method call in condition
if (!hasMore()) {
break;
}
processItem();
} while (true);Valid Code:
// Method call in while loop condition
while (hasMore()) { // ✅ Method call in condition
processItem();
}
// Method call in do-while loop condition
do { // ✅ Method call in condition
processItem();
} while (hasMore());
// Break without method call (not flagged)
while (true) {
if (flag) { // ✅ No method call, so not flagged
break;
}
process();
}Priority: P3 (Medium)
Description: Prefer the null coalescing operator (??) over ternary
operators when checking for null and providing a default value. The null
coalescing operator is more concise and clearly expresses the intent of
providing a default value when a variable is null.
The null coalescing operator (??) returns its left-hand operand if it's not
null, otherwise returns the right-hand operand. It's a binary operator that's
left-associative. The left-hand operand is evaluated only once, and the
right-hand operand is only evaluated if the left-hand operand is null.
This rule flags ternary expressions that check for null using == or !=
operators and follow patterns that can be converted to the null coalescing
operator, where the same variable or expression appears in both the condition
and one of the branches.
Patterns where the branches contain different variables than the one being
checked (e.g., x != null ? y : z where y differs from x) cannot be
converted to null coalescing, as they represent different logic. This rule also
does not flag ternary expressions that check conditions other than null.
When converting ternary operators to null coalescing, you may also need to use
the safe navigation operator (?.) to safely access properties or methods when
the left operand might be null.
Both operands must be of compatible types. The null coalescing operator cannot be used as the left-hand side of an assignment, and SOQL bind expressions don't support the null coalescing operator.
Violations:
// Basic variable null check
String result = data != null ? data : 'default'; // ❌ Should use ??
Integer value = num == null ? 0 : num; // ❌ Should use ??
// Method call with null check
String value = obj != null ? obj.getName() : 'default'; // ❌ Should use ?. and ??
// Property access with null check
String name = account == null ? 'Unknown' : account.Name; // ❌ Should use ?. and ??
// Null on left side of comparison
String result = null == data ? 'default' : data; // ❌ Should use ??
// SOQL query with null check
Account defaultAccount = new Account(Name = 'Default');
Account a = [SELECT Id FROM Account WHERE Id = :accountId] != null
? [SELECT Id FROM Account WHERE Id = :accountId]
: defaultAccount; // ❌ Should use ??Valid Code:
// Basic variable null coalescing
String result = data ?? 'default'; // ✅ Uses null coalescing operator
Integer value = num ?? 0; // ✅ Uses null coalescing operator
// Method call with safe navigation and null coalescing
String value = obj?.getName() ?? 'default'; // ✅ Uses ?. and ??
// Property access with safe navigation and null coalescing
String name = account?.Name ?? 'Unknown'; // ✅ Uses ?. and ??
// Chained null coalescing operators (left-associative)
String city = account?.BillingCity ?? account?.ShippingCity ?? 'N/A'; // ✅ Chained ??
// SOQL query with null coalescing
Account defaultAccount = new Account(Name = 'Default');
Account a = [SELECT Id FROM Account WHERE Id = :accountId] ?? defaultAccount; // ✅ Uses ??
// Ternary for non-null checks (not flagged by this rule)
String result = status == 'active' ? 'yes' : 'no'; // ✅ Not a null check
Integer value = count > 0 ? count : 1; // ✅ Not a null checkPriority: P3 (Medium)
Description: Magic numbers are hardcoded numeric literals that should be
replaced with named constants for better readability and maintainability. This
rule flags numeric literals that are not common "safe" numbers (like 0, 1, -1)
and are not used in array indices or loop conditions.
Test classes annotated with @IsTest are exempt from this rule when SeeAllData is false (the default) or explicitly set to false, as they often need to use hardcoded values for test data setup and assertions.
Violations:
// Magic numbers in calculations
Integer total = price * 1.15; // ❌ What is 1.15?
Integer timeout = 30000; // ❌ What is 30000?
Decimal discount = amount * 0.10; // ❌ What is 0.10?
// Magic numbers in method calls
setMaxRetries(3); // ❌ What is 3?
setBatchSize(200); // ❌ What is 200?
// Magic numbers in assignments
Integer maxRetries = 3; // ❌ Should use a constant
Integer batchSize = 200; // ❌ Should use a constant
// Magic numbers in return statements
public Integer getDefaultTimeout() {
return 5000; // ❌ What is 5000?
}
// Magic numbers in conditions (not safe numbers)
if (count > 5) { } // ❌ 5 is not a safe number
if (size >= 10) { } // ❌ 10 is not a safe numberValid Code:
// Use named constants
private static final Decimal TAX_RATE = 1.15;
private static final Integer TIMEOUT_MS = 30000;
private static final Decimal DISCOUNT_PERCENTAGE = 0.10;
private static final Integer MAX_RETRIES = 3;
private static final Integer BATCH_SIZE = 200;
private static final Integer DEFAULT_TIMEOUT = 5000;
Integer total = price * TAX_RATE; // ✅ Uses constant
Integer timeout = TIMEOUT_MS; // ✅ Uses constant
Decimal discount = amount * DISCOUNT_PERCENTAGE; // ✅ Uses constant
// Safe numbers (0, 1, -1) are allowed
Integer count = 0; // ✅ Safe number
Integer index = 1; // ✅ Safe number
Integer offset = -1; // ✅ Safe number
// Array/list indices are allowed
List<String> items = new List<String>();
String first = items[0]; // ✅ Array index
String second = items[1]; // ✅ Array index
// Loop conditions are allowed
for (Integer i = 0; i < 10; i++) { } // ✅ Loop condition
while (count < 100) { } // ✅ Loop condition
// Comparisons with safe numbers (0, 1, -1) are allowed
if (count == 0) { } // ✅ Safe number comparison
if (size > 1) { } // ✅ Safe number comparison
if (result != -1) { } // ✅ Safe number comparison
// Test classes with @IsTest are exempt (SeeAllData defaults to false)
@IsTest
private class TestClassDefault {
static void testSomeMethod() {
Integer batchSize = 200; // ✅ Allowed in test classes
Decimal taxRate = 0.08; // ✅ Allowed in test classes
Account acc = new Account(AnnualRevenue = 50000); // ✅ Allowed
System.assertEquals(3, records.size()); // ✅ Allowed
}
}
// Test classes with @IsTest(SeeAllData=false) are exempt
@IsTest(SeeAllData=false)
private class TestClassExplicitFalse {
static void testSomeMethod() {
Integer timeout = 30000; // ✅ Allowed when SeeAllData=false
Decimal discount = amount * 0.10; // ✅ Allowed when SeeAllData=false
}
}Priority: P2 (High)
Description: Regular expression patterns must be declared as static final
constants instead of inline. This improves performance by avoiding repeated
pattern compilation, makes patterns reusable, and improves maintainability by
centralizing pattern definitions.
This rule applies to:
Pattern.compile()andPattern.matches()static methodsString.split(),String.matches(),String.replaceAll(), andString.replaceFirst()instance methods
For String replace operations, consider using the Matcher class with a static
final Pattern instead of String.replaceAll() or String.replaceFirst() for
better performance when the same pattern is used multiple times.
Violations:
// Pattern.compile() with inline regex pattern
Pattern pattern = Pattern.compile('^[A-Z]+$'); // ❌ Inline pattern
// Pattern.matches() with inline regex pattern
Boolean isValid = Pattern.matches('^[A-Z]+$', input); // ❌ Inline pattern
// String.replaceAll() with inline regex pattern
String replaced = input.replaceAll('\\d', 'X'); // ❌ Inline pattern
// String.replaceFirst() with inline regex pattern
String replaced = input.replaceFirst('\\d', 'X'); // ❌ Inline pattern
// String.split() with inline regex pattern
List<String> parts = input.split(','); // ❌ Inline pattern
// String.matches() with inline regex pattern
Boolean matches = input.matches('test.*'); // ❌ Inline patternValid Code:
// Static final regex pattern with Pattern.matches()
private static final String PATTERN = '^[A-Z]+$';
public Boolean isValid(String input) {
return Pattern.matches(PATTERN, input); // ✅ Uses static final constant
}
// Static final regex pattern with Pattern.compile()
private static final String EMAIL_PATTERN = '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$';
public Boolean isValidEmail(String input) {
Pattern pattern = Pattern.compile(EMAIL_PATTERN); // ✅ Uses static final constant
return pattern.matcher(input).matches();
}
// Using Matcher class with static final Pattern for replace operations
private static final Pattern DIGIT_PATTERN = Pattern.compile('\\d');
public String replaceDigits(String input) {
Matcher m = DIGIT_PATTERN.matcher(input); // ✅ Uses static final Pattern
return m.replaceAll('X');
}
// Pattern from method/constructor argument (exception)
public void processPattern(String pattern) {
Pattern p = Pattern.compile(pattern); // ✅ Pattern from parameter - allowed
}
// Pattern from method output (exception)
public void useDynamicPattern() {
Pattern p = Pattern.compile(getPattern()); // ✅ Pattern from method - allowed
}Priority: P3 (Moderate)
Description: Method-local variables that are never reassigned must be
declared as final to enforce immutability and improve code clarity.
Violations:
public void method() {
Integer value = 5; // ❌ Should be final (never reassigned)
String name = 'test'; // ❌ Should be final (never reassigned)
Boolean isValid = true; // ❌ Should be final (never reassigned)
// value, name, and isValid are never reassigned
}Valid Code:
public void method() {
final Integer value = 5; // ✅ Declared as final
final String name = 'test'; // ✅ Declared as final
Integer counter = 0;
counter++; // ✅ Not final (incremented)
Integer result = 10;
result = result + 5; // ✅ Not final (reassigned)
Integer sum = 0;
sum += 5; // ✅ Not final (compound assignment)
}
// Loop variables and parameters are excluded
public void method(Integer param) {
for (Integer i = 0; i < 10; i++) {
// Loop variable 'i' is excluded
}
// Parameter 'param' is excluded
}Priority: P2 (High)
Description: Methods that don't use instance state should be declared as
static.
Violations:
public class Utils {
public Integer add(Integer a, Integer b) { // ❌ Should be static
return a + b;
}
}Valid Code:
public class Utils {
public static Integer add(Integer a, Integer b) { // ✅ Static method
return a + b;
}
}Priority: P3 (Medium)
Version: 1.0.2
Source: rulesets/documentation/MethodsRequireExampleTag.xml
Description:
Methods must have at least one @example tag in their ApexDoc comments
containing a properly formatted {@code} block (with both opening { and
closing }), unless they have annotations, are override methods, are interface
method declarations, are constructors, or return void and take no arguments.
This helps developers understand how to use methods correctly. According to the
Salesforce ApexDoc format specification,
@example tags must contain {@code} blocks to properly format code examples.
Violations:
/**
* Processes the account.
* @param acc The account to process
* @return The processed account
*/
public Account processAccount(Account acc) { // ❌ Missing @example tag
return acc;
}
/**
* Processes the account.
* @param acc The account to process
* @example
* Account acc = new Account(Name = 'Test');
*/
public Account processAccount(Account acc) { // ❌ @example without {@code} block
return acc;
}
/**
* Processes the account.
* @param acc The account to process
* @example
* @code
* Account acc = new Account(Name = 'Test');
*/
public Account processAccount(Account acc) { // ❌ @code without curly braces (should be {@code})
return acc;
}
/**
* Processes the account.
* @param acc The account to process
* @example
* {@code
* Account acc = new Account(Name = 'Test');
*/
public Account processAccount(Account acc) { // ❌ {@code without closing }
return acc;
}Valid Code:
/**
* Processes the account.
* @param acc The account to process
* @return The processed account
* @example
* {@code
* Account acc = new Account(Name = 'Test');
* Account result = processor.processAccount(acc);
* }
*/
public Account processAccount(Account acc) { // ✅ Has @example with {@code} block
return acc;
}
// Valid: Method with annotation (exempt)
/**
* Test method for account processing.
*/
@IsTest
public static void testProcessAccount() { // ✅ Annotated method exempt
// Test code
}
// Valid: Override method (exempt)
/**
* String representation.
*/
@Override
public String toString() { // ✅ Override method exempt
return 'Example';
}
// Valid: Interface method declaration (exempt)
public interface MyInterface {
/**
* Interface method without @example.
*/
void doSomething(); // ✅ Interface method exempt
}
// Valid: Constructor (exempt)
/**
* Creates a new instance.
*/
public MyClass() { // ✅ Constructor exempt
// Constructor implementation
}
// Valid: Void method with no parameters (exempt)
/**
* Initializes the instance.
*/
public void initialize() { // ✅ Void method with no parameters exempt
// Method implementation
}Rules are organized into PMD's 8 standard categories (consistent across languages):
- bestpractices/ - Generally accepted best practices (5 PMD rules: modifier rules, test class rules)
- codestyle/ - Coding style enforcement (21 PMD rules: formatting, naming conventions, code style patterns)
- design/ - Design issue detection (18 PMD rules: code structure, method signatures, class organization)
- documentation/ - Code documentation rules (5 PMD rules)
- errorprone/ - Broken/confusing/runtime-error-prone constructs (currently empty)
- multithreading/ - Multi-threaded execution issues (currently empty)
- performance/ - Suboptimal code detection (currently empty)
- security/ - Potential security flaws (currently empty)
Total: 48 PMD rules + 4 Regex rules = 52 rules
In addition to the PMD rules above, 4 Regex rules are provided:
NoConsecutiveBlankLines- Prevents consecutive blank linesProhibitPrettierIgnore- Prohibits prettier-ignore commentsProhibitSuppressWarnings- Prohibits suppression annotations and commentsNoLongLines- Enforces maximum line length of 80 characters
For a complete list of all rules, see the rulesets/ directory. Each rule XML
file contains detailed descriptions and XPath expressions. Regex rules are
defined in code-analyzer.yml under engines.regex.custom_rules.
- Node.js 18+
- pnpm
pnpm installpnpm test # Run all tests
pnpm test:watch # Run tests in watch mode
pnpm test:coverage # Run tests with coverageFor Jest API reference, see Jest Reference.
pnpm format # Format all files
pnpm format:check # Check formatting without modifyingpnpm lint # Lint JavaScript files
pnpm lint:fix # Fix linting issues automaticallypnpm validate # Validate all rulesets
pnpm check-xml-order # Check XML element orderPMD ruleset XML files must follow the
PMD Ruleset XML Schema, which
requires elements within <rule> to be in a specific order:
<description>(optional)<priority>(optional)<properties>(optional)<exclude>(optional, can appear multiple times)<example>(optional, can appear multiple times)
Scripts available:
pnpm check-xml-order- Check if all XML files have correct element orderpnpm fix-xml-order- Automatically fix element order in all XML filespnpm add-version-info- Add version information to all rule descriptions
These scripts use XML libraries (@xmldom/xmldom) to properly parse and
manipulate XML, ensuring all elements (including multiple examples) are
preserved.
pnpm benchmark # Run performance benchmarks
pnpm benchmark -- --baseline # Generate baseline for regression detection
pnpm benchmark -- --json # JSON output for CI/CD integration
pnpm benchmark -- --compare # Compare mode (doesn't fail on regressions)
pnpm check-regressions # Check for performance regressionsResults are saved to benchmarks/results/:
results-{timestamp}.json- Latest benchmark resultsbaseline.json- Baseline for regression comparison
All scripts in the scripts/ directory have convenience commands in
package.json:
Development:
pnpm test- Run all testspnpm test:watch- Run tests in watch modepnpm test:coverage- Run tests with coveragepnpm validate- Validate all rulesetspnpm format- Format all files with Prettierpnpm format:check- Check formatting without modifyingpnpm lint- Lint JavaScript filespnpm lint:fix- Fix linting issues automatically
XML Management:
pnpm check-xml-order- Check XML element order in all ruleset filespnpm fix-xml-order- Automatically fix XML element orderpnpm add-version-info- Add version information to all rule descriptions
Testing & Utilities:
pnpm list-test-files- List all test files to verify Jest discoverypnpm generate-test-ruleset- Generate test ruleset for validationpnpm ast-dump- Dump PMD AST for Apex files (usage:pnpm ast-dump <file>)
Performance:
pnpm benchmark- Run performance benchmarkspnpm check-regressions- Check for performance regressions
Project Management:
pnpm version:bump- Bump package.json version numberpnpm bump-rule-versions- Automatically bump rule versions based on changes:- Major bump: Rules with failing existing tests (tests that existed at HEAD)
- Minor bump: Rules with new tests
- Patch bump: Other changed rules
pnpm changelog- Generate changelogpnpm clean- Clean build artifacts and temporary files
CI/CD:
pnpm ci- Run all CI checks (format, lint, test)
All scripts in the scripts/ directory implement security best practices:
- File Descriptors: Scripts use file descriptors (
fs.openSync,fs.writeFileSyncwith file descriptor) instead of file paths to prevent time-of-check to time-of-use (TOCTOU) race conditions - No Existence Checks: Scripts never use
fs.existsSync()before opening files - files are opened directly and errors are handled if they don't exist - Atomic Operations: File operations are atomic with respect to file descriptors, preventing race conditions
- execFileSync: Scripts use
execFileSyncinstead ofexecSyncwhen passing dynamic paths or arguments to prevent shell command injection vulnerabilities - Separate Arguments: Commands and arguments are passed separately (e.g.,
execFileSync('git', ['show', path], {...}))
Scripts that accept file paths from user input implement additional security measures:
- Path Sanitization: The
sanitize-filenamepackage is used to sanitize file paths, eliminating dangerous characters - Path Validation: Paths are validated to ensure they don't contain path
traversal sequences (
..) and are relative paths - Symbolic Link Resolution:
fs.realpathSync()is used to resolve symbolic links and ensure files are within expected directories - Defense in Depth: Multiple validation layers ensure robust security
Scripts implementing these measures:
scripts/benchmark.js- File descriptor usage for read/write operationsscripts/bump-rule-versions.js- File descriptors andexecFileSyncfor git operationsscripts/check-performance-regressions.js- Path validation for user inputscripts/ast-dump.sh- Path validation for user input
For more details, see SECURITY.md and CONTRIBUTING.md.
We welcome contributions! Please see CONTRIBUTING.md for guidelines on:
- Development setup
- Adding new rules
- Testing requirements
- Pull request process
- Code style guidelines
Please note that this project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.
For security vulnerabilities, please see SECURITY.md for reporting instructions.
- PMD Quick Reference - Condensed guide to PMD essentials (rulesets, CLI, CPD, configuration)
- Code Analyzer Configuration - Quick reference for
configuring
code-analyzer.ymlwith all engines and properties (includes Regex engine configuration) - XPath 3.1 Reference - XPath 3.1 syntax and functions
- PMD Quick Reference - PMD essentials including Apex AST reference (see Apex AST Reference section)
- ApexDoc Reference - ApexDoc syntax, tags, and documentation format for Apex code
- PMD Quick Reference - How to suppress PMD rule violations using annotations, comments, and rule properties (see Suppressing Warnings section)
- Jest Reference - Jest 30.0 API reference for writing and running tests
- pnpm Reference - pnpm package manager reference for dependency management and workspace configuration
- Husky Reference - Husky Git hooks manager reference for setting up pre-commit and other Git hooks
- PMD Quick Reference - Copy/Paste Detector (CPD) engine for duplicate code detection across multiple languages (see CPD section)
- ESLint Reference - ESLint engine configuration for JavaScript, TypeScript, and LWC static analysis
- Graph Engine Reference - Salesforce Graph Engine configuration and usage
- Gremlin Query Language Reference - Gremlin query language reference for graph traversal
- Apache TinkerPop Reference - Apache TinkerPop framework reference for graph computing
- GraphML Reference - GraphML format for graph serialization
- GraphSON Reference - GraphSON format for graph serialization
- Gryo Reference - Gryo binary format for graph serialization
- GraphBinary Reference - GraphBinary format for graph serialization
The Code Analyzer Configuration and PMD Quick Reference provide machine-readable reference for AI coding assistants (like Cursor's Agent) to help developers configure PMD rules. This guide contains structured information about each rule including violations, valid code examples, and configuration properties.
When setting up AI agent rules, you should reference these documentation files:
Core PMD & Rule Development:
- PMD Quick Reference - PMD essentials (rulesets, CLI, CPD, configuration)
- Code Analyzer Configuration - Complete
code-analyzer.ymlconfiguration reference (includes Regex engine configuration) - XPath 3.1 Reference - XPath 3.1 syntax and functions for writing rule queries
- PMD Quick Reference - PMD essentials including Apex AST reference (see Apex AST Reference section)
Documentation & Suppression:
- ApexDoc Reference - ApexDoc syntax, tags, and documentation format for Apex code
- PMD Quick Reference - How to suppress PMD rule violations using annotations, comments, and rule properties (see Suppressing Warnings section)
Testing & Development:
- Jest Reference - Jest 30.0 API reference for writing and running tests
- pnpm Reference - pnpm package manager reference for dependency management and workspace configuration
- Husky Reference - Husky Git hooks manager reference for setting up pre-commit and other Git hooks
Additional Engines:
- PMD Quick Reference - Copy/Paste Detector (CPD) engine for duplicate code detection (see CPD section)
- ESLint Reference - ESLint engine configuration for JavaScript, TypeScript, and LWC
Graph Engine Documentation:
- Graph Engine Reference - Salesforce Graph Engine configuration and usage
- Gremlin Query Language Reference - Gremlin query language reference for graph traversal
- Apache TinkerPop Reference - Apache TinkerPop framework reference
- GraphML Reference - GraphML format for graph serialization
- GraphSON Reference - GraphSON format for graph serialization
- Gryo Reference - Gryo binary format for graph serialization
- GraphBinary Reference - GraphBinary format for graph serialization
To configure Cursor's Agent to use this guide, create a Cursor Project Rule:
-
Create a Project Rule:
- Open Cursor Settings → Rules, Commands
- Click
+ Add Rulenext toProject Rules - Or use the
New Cursor Rulecommand - This creates a new rule folder in
.cursor/rules
-
Rule Structure: Create a folder like
.cursor/rules/salesforce-pmd-guide/containingRULE.md:--- description: 'Guide for configuring PMD rules for Salesforce Apex code analysis. Reference @docs/CODEANALYZER.md and @docs/PMD.md when helping with code-analyzer.yml configuration or creating new rules.' alwaysApply: false --- When helping with Salesforce Code Analyzer configuration or creating PMD rules: - Reference @docs/CODEANALYZER.md for `code-analyzer.yml` configuration and engine settings (includes Regex engine configuration) - Reference @docs/XPATH31.md for XPath 3.1 syntax when writing rule queries - Reference @docs/PMD.md for PMD Apex AST node types and patterns (see Apex AST Reference section) - Reference @docs/PMD.md for suppressing rule violations when needed (see Suppressing Warnings section) - Use the structured format to provide accurate rule information - Include code examples from the guides - When configuring code-analyzer.yml, use property examples from the guides - Suggest appropriate property values based on the rule's purpose - Provide complete YAML examples when needed
-
Rule Types:
- Always Apply: Apply to every chat session
- Apply Intelligently: When Agent decides it's relevant based on description
- Apply to Specific Files: When file matches a specified pattern (use
globsin frontmatter) - Apply Manually: When @-mentioned in chat (e.g.,
@salesforce-pmd-guide)
Cursor supports four types of rules:
- Project Rules: Stored in
.cursor/rules, version-controlled and scoped to your codebase - User Rules: Global to your Cursor environment, used by Agent (Chat)
- Team Rules: Team-wide rules managed from the dashboard (Team/Enterprise plans)
- AGENTS.md: Agent instructions in markdown format, simple alternative to
.cursor/rules
- Keep rules focused and under 500 lines
- Split large rules into multiple, composable rules
- Provide concrete examples or reference files using
@filename - Write rules like clear internal documentation
- Use
@filenamesyntax to include relevant files in rule context
For more information about Cursor rules, see the Cursor Rules Documentation.
This project is licensed under the MIT License. See the LICENSE.md file for details.
- Issues: GitHub Issues
- Repository: https://github.com/starch-uk/sca-extra