PMD Apex language module providing static code analysis support for Salesforce Apex programming language.
—
Base classes and framework for creating custom PMD rules. Includes abstract base rule class with visitor pattern integration and comprehensive set of built-in rules across 6 categories.
Abstract base class for all Apex PMD rules with visitor pattern integration.
/**
* Base class for all Apex PMD rules
* Implements ApexVisitor for AST traversal and rule logic
*/
public abstract class AbstractApexRule implements ApexVisitor<Object, Object> {
/**
* Apply rule to a node with rule context
* @param target - AST node to analyze
* @param ctx - Rule context with violation reporting capabilities
*/
public void apply(Node target, RuleContext ctx);
// Inherits all 80+ visit methods from ApexVisitor interface
// Override specific visit methods to implement rule logic
Object visit(ASTUserClass node, Object data);
Object visit(ASTMethod node, Object data);
Object visit(ASTVariableExpression node, Object data);
// ... and 80+ more visit methods
}Context object providing violation reporting and analysis capabilities.
/**
* Rule execution context
*/
public interface RuleContext {
/** Report a violation at the given node */
void addViolation(Node node, String message);
/** Report a violation with custom message parameters */
void addViolation(Node node, String message, Object... params);
/** Get current file name being analyzed */
String getFileDisplayName();
/** Get language processor for multifile analysis */
LanguageProcessor getLanguageProcessor();
/** Get rule being executed */
Rule getRule();
}Rules promoting Apex coding best practices and testing standards.
/**
* Base class for unit test related rules
*/
public abstract class AbstractApexUnitTestRule extends AbstractApexRule {
/** Check if class is a test class */
protected boolean isTestClass(ASTUserClass node);
}
/**
* Assertions should include descriptive messages
*/
public class ApexAssertionsShouldIncludeMessageRule extends AbstractApexRule {
// Checks System.assert() calls for message parameters
}
/**
* Test classes should contain assertions
*/
public class ApexUnitTestClassShouldHaveAssertsRule extends AbstractApexUnitTestRule {
// Verifies test methods contain assertion statements
}
/**
* Test classes should use RunAs for proper test isolation
*/
public class ApexUnitTestClassShouldHaveRunAsRule extends AbstractApexUnitTestRule {
// Checks for System.runAs() usage in test methods
}
/**
* Avoid SeeAllData=true in test classes
*/
public class ApexUnitTestShouldNotUseSeeAllDataTrueRule extends AbstractApexUnitTestRule {
// Detects @isTest(SeeAllData=true) annotations
}
/**
* Avoid using global modifier unnecessarily
*/
public class AvoidGlobalModifierRule extends AbstractApexRule {
// Flags unnecessary global access modifiers
}
/**
* Keep trigger logic simple, delegate to handler classes
*/
public class AvoidLogicInTriggerRule extends AbstractApexRule {
// Detects complex logic directly in trigger bodies
}
/**
* Queueable jobs should implement finalizer methods
*/
public class QueueableWithoutFinalizerRule extends AbstractApexRule {
// Checks Queueable classes for proper error handling
}
/**
* Detect unused local variables
*/
public class UnusedLocalVariableRule extends AbstractApexRule {
// Identifies local variables that are declared but never used
}Rules enforcing naming conventions and code organization.
/**
* Base class for naming convention rules
*/
public abstract class AbstractNamingConventionsRule extends AbstractApexRule {
/** Check if name matches expected pattern */
protected boolean matches(String name, Pattern pattern);
}
/**
* Enforce class naming conventions
*/
public class ClassNamingConventionsRule extends AbstractNamingConventionsRule {
// Enforces PascalCase for class names
}
/**
* Fields should be declared at the start of classes
*/
public class FieldDeclarationsShouldBeAtStartRule extends AbstractApexRule {
// Checks field placement within class body
}
/**
* Enforce field naming conventions
*/
public class FieldNamingConventionsRule extends AbstractNamingConventionsRule {
// Enforces camelCase for field names
}
/**
* Enforce method parameter naming conventions
*/
public class FormalParameterNamingConventionsRule extends AbstractNamingConventionsRule {
// Enforces camelCase for parameter names
}
/**
* Enforce local variable naming conventions
*/
public class LocalVariableNamingConventionsRule extends AbstractNamingConventionsRule {
// Enforces camelCase for local variable names
}
/**
* Enforce method naming conventions
*/
public class MethodNamingConventionsRule extends AbstractNamingConventionsRule {
// Enforces camelCase for method names
}
/**
* Enforce property naming conventions
*/
public class PropertyNamingConventionsRule extends AbstractNamingConventionsRule {
// Enforces PascalCase for property names
}Rules measuring code complexity and design quality.
/**
* Base class for NCSS (Non-Commenting Source Statements) counting rules
*/
public abstract class AbstractNcssCountRule extends AbstractApexRule {
/** Count NCSS for a node */
protected int countNcss(Node node);
}
/**
* Avoid deeply nested if statements
*/
public class AvoidDeeplyNestedIfStmtsRule extends AbstractApexRule {
// Detects excessive nesting depth in conditional statements
}
/**
* Measure cognitive complexity of methods
*/
public class CognitiveComplexityRule extends AbstractApexRule {
// Uses ApexMetrics.COGNITIVE_COMPLEXITY to measure understandability
}
/**
* Measure cyclomatic complexity of methods
*/
public class CyclomaticComplexityRule extends AbstractApexRule {
// Uses ApexMetrics.CYCLO to measure code paths
}
/**
* Limit class length
*/
public class ExcessiveClassLengthRule extends AbstractApexRule {
// Counts lines of code in class declarations
}
/**
* Limit method parameter count
*/
public class ExcessiveParameterListRule extends AbstractApexRule {
// Counts parameters in method signatures
}
/**
* Limit number of public members in classes
*/
public class ExcessivePublicCountRule extends AbstractApexRule {
// Counts public methods, fields, and properties
}
/**
* Measure constructor complexity using NCSS
*/
public class NcssConstructorCountRule extends AbstractNcssCountRule {
// Applies NCSS counting to constructor methods
}
/**
* Measure method complexity using NCSS
*/
public class NcssMethodCountRule extends AbstractNcssCountRule {
// Applies NCSS counting to regular methods
}
/**
* Measure type complexity using NCSS
*/
public class NcssTypeCountRule extends AbstractNcssCountRule {
// Applies NCSS counting to entire classes/interfaces
}
/**
* Standard cyclomatic complexity measurement
*/
public class StdCyclomaticComplexityRule extends AbstractApexRule {
// Standard implementation of cyclomatic complexity
}
/**
* Limit number of fields in classes
*/
public class TooManyFieldsRule extends AbstractApexRule {
// Counts field declarations in classes
}
/**
* Detect unused methods
*/
public class UnusedMethodRule extends AbstractApexRule {
// Identifies methods that are declared but never called
}Rules detecting common programming errors and potential bugs.
/**
* Detect CSRF vulnerabilities in Apex
*/
public class ApexCSRFRule extends AbstractApexRule {
// Identifies potential Cross-Site Request Forgery issues
}
/**
* Avoid hardcoded Salesforce record IDs
*/
public class AvoidHardcodingIdRule extends AbstractApexRule {
// Detects hardcoded 15 or 18 character Salesforce IDs
}
/**
* Check for non-existent annotations
*/
public class AvoidNonExistentAnnotationsRule extends AbstractApexRule {
// Validates annotation usage against known Apex annotations
}
/**
* Avoid stateful database operations that can cause issues
*/
public class AvoidStatefulDatabaseResultRule extends AbstractApexRule {
// Detects problematic database result handling patterns
}
/**
* Check Aura-enabled getter accessibility
*/
public class InaccessibleAuraEnabledGetterRule extends AbstractApexRule {
// Validates @AuraEnabled getter method accessibility
}
/**
* Method names should not match enclosing class name
*/
public class MethodWithSameNameAsEnclosingClassRule extends AbstractApexRule {
// Detects methods that should be constructors
}
/**
* Override both equals and hashCode together
*/
public class OverrideBothEqualsAndHashcodeRule extends AbstractApexRule {
// Ensures both methods are implemented when one is overridden
}
/**
* Avoid shadowing built-in namespace names
*/
public class TypeShadowsBuiltInNamespaceRule extends AbstractApexRule {
// Detects types that shadow Salesforce built-in namespaces
}Rules identifying performance bottlenecks and optimization opportunities.
/**
* Base class for rules detecting expensive operations in loops
*/
public abstract class AbstractAvoidNodeInLoopsRule extends AbstractApexRule {
/** Check if node is inside a loop construct */
protected boolean isInLoop(Node node);
}
/**
* Avoid non-selective SOQL queries
*/
public class AvoidNonRestrictiveQueriesRule extends AbstractApexRule {
// Detects SOQL queries without WHERE clauses or selective filters
}
/**
* Avoid expensive operations inside loops
*/
public class OperationWithHighCostInLoopRule extends AbstractAvoidNodeInLoopsRule {
// Detects DML, SOQL, and other expensive operations in loops
}
/**
* Avoid operations that consume governor limits inside loops
*/
public class OperationWithLimitsInLoopRule extends AbstractAvoidNodeInLoopsRule {
// Identifies operations that can hit Salesforce governor limits
}Rules detecting security vulnerabilities and unsafe practices.
/**
* Detect bad cryptographic practices
*/
public class ApexBadCryptoRule extends AbstractApexRule {
// Identifies weak encryption algorithms and practices
}
/**
* Detect CRUD and FLS security violations
*/
public class ApexCRUDViolationRule extends AbstractApexRule {
// Checks for proper CRUD/FLS security enforcement
}
/**
* Detect usage of dangerous methods
*/
public class ApexDangerousMethodsRule extends AbstractApexRule {
// Identifies potentially unsafe method calls
}
/**
* Detect insecure HTTP endpoints
*/
public class ApexInsecureEndpointRule extends AbstractApexRule {
// Checks for HTTP instead of HTTPS endpoints
}
/**
* Detect open redirect vulnerabilities
*/
public class ApexOpenRedirectRule extends AbstractApexRule {
// Identifies potential open redirect vulnerabilities
}
/**
* Detect SOQL injection vulnerabilities
*/
public class ApexSOQLInjectionRule extends AbstractApexRule {
// Checks for dynamic SOQL construction without proper sanitization
}
/**
* Detect sharing rule violations
*/
public class ApexSharingViolationsRule extends AbstractApexRule {
// Identifies bypass of Salesforce sharing rules
}
/**
* Suggest using Named Credentials for external calls
*/
public class ApexSuggestUsingNamedCredRule extends AbstractApexRule {
// Recommends Named Credentials over hardcoded endpoints
}
/**
* Detect XSS vulnerabilities from escape=false
*/
public class ApexXSSFromEscapeFalseRule extends AbstractApexRule {
// Identifies potential XSS from unescaped output
}
/**
* Detect XSS vulnerabilities from URL parameters
*/
public class ApexXSSFromURLParamRule extends AbstractApexRule {
// Checks for unsafe URL parameter handling
}Usage Examples:
// Create custom rule extending AbstractApexRule
public class CustomComplexityRule extends AbstractApexRule {
@Override
public Object visit(ASTMethod node, Object data) {
RuleContext ctx = (RuleContext) data;
// Use metrics to check complexity
int complexity = MetricsUtil.computeMetric(ApexMetrics.CYCLO, node);
if (complexity > 15) {
ctx.addViolation(node, "Method complexity too high: {0}", complexity);
}
// Check for specific patterns
List<ASTSoqlExpression> queries = node.findDescendantsOfType(ASTSoqlExpression.class);
if (queries.size() > 3) {
ctx.addViolation(node, "Too many SOQL queries in method: {0}", queries.size());
}
return super.visit(node, data);
}
@Override
public Object visit(ASTUserClass node, Object data) {
RuleContext ctx = (RuleContext) data;
// Check class naming
String className = node.getQualifiedName().getClassName();
if (!className.matches("^[A-Z][a-zA-Z0-9]*$")) {
ctx.addViolation(node, "Class name should follow PascalCase convention");
}
// Use multifile analysis if available
ApexMultifileAnalysis multifile = ctx.getLanguageProcessor().getMultiFileState();
if (!multifile.isFailed()) {
List<Issue> issues = multifile.getFileIssues(ctx.getFileDisplayName());
for (Issue issue : issues) {
if (issue.getSeverity() == Severity.ERROR) {
ctx.addViolation(node, "Multifile analysis error: {0}", issue.getMessage());
}
}
}
return super.visit(node, data);
}
}
// Rule that checks for test class patterns
public class TestClassValidationRule extends AbstractApexUnitTestRule {
@Override
public Object visit(ASTUserClass node, Object data) {
if (isTestClass(node)) {
RuleContext ctx = (RuleContext) data;
// Check for test methods
List<ASTMethod> methods = node.findDescendantsOfType(ASTMethod.class);
boolean hasTestMethods = methods.stream()
.anyMatch(method -> method.getName().toLowerCase().contains("test"));
if (!hasTestMethods) {
ctx.addViolation(node, "Test class should contain test methods");
}
// Check for assertions in test methods
for (ASTMethod method : methods) {
if (method.getName().toLowerCase().contains("test")) {
List<ASTMethodCallExpression> calls = method.findDescendantsOfType(ASTMethodCallExpression.class);
boolean hasAssertions = calls.stream()
.anyMatch(call -> call.getMethodName().startsWith("assert"));
if (!hasAssertions) {
ctx.addViolation(method, "Test method should contain assertions");
}
}
}
}
return super.visit(node, data);
}
}Install with Tessl CLI
npx tessl i tessl/maven-net-sourceforge-pmd--pmd-apex