CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-net-sourceforge-pmd--pmd-java

Java language support module for the PMD static code analyzer with AST processing, symbol resolution, type system, metrics, and 400+ built-in rules

Pending
Overview
Eval results
Files

rule-framework.mddocs/

Rule Framework

PMD Java includes a comprehensive rule framework with over 400 built-in rules across 8 categories for detecting code quality issues, security vulnerabilities, and performance problems. The framework supports visitor-based rules, XPath-based custom rules, and provides infrastructure for developing custom rules.

Capabilities

Rule Categories

PMD Java organizes its 400+ built-in rules into eight main categories, each targeting specific aspects of code quality:

Best Practices (80+ rules)

Rules that enforce coding best practices and prevent common mistakes.

Code Style (60+ rules)

Rules that enforce consistent coding style and formatting conventions.

Design (30+ rules)

Rules that detect design problems and architectural issues.

Documentation (5+ rules)

Rules that enforce proper documentation and commenting standards.

Error Prone (120+ rules)

Rules that detect patterns that are likely to lead to bugs or runtime errors.

Multithreading (15+ rules)

Rules that detect concurrency and thread safety issues.

Performance (25+ rules)

Rules that identify performance problems and inefficient code patterns.

Security (5+ rules)

Rules that detect security vulnerabilities and unsafe practices.

Rule Framework Architecture

/**
 * Base interface for all PMD rules
 */
public interface Rule {
    /**
     * Returns the name of this rule
     */
    String getName();
    
    /**
     * Returns the description of this rule
     */
    String getDescription();
    
    /**
     * Returns the message displayed when this rule is violated
     */
    String getMessage();
    
    /**
     * Returns the external info URL for this rule
     */
    String getExternalInfoUrl();
    
    /**
     * Returns the priority/severity of this rule
     */
    RulePriority getPriority();
    
    /**
     * Applies this rule to the given node and reports violations
     */
    void apply(List<? extends Node> nodes, RuleContext ctx);
    
    /**
     * Returns the language this rule applies to
     */
    Language getLanguage();
    
    /**
     * Returns the minimum language version required for this rule
     */
    LanguageVersion getMinimumLanguageVersion();
    
    /**
     * Returns the maximum language version supported by this rule
     */
    LanguageVersion getMaximumLanguageVersion();
}

Java Rule Base Class

/**
 * Base class for Java-specific rules using the visitor pattern
 */
public abstract class AbstractJavaRule extends AbstractRule implements JavaParserVisitor {
    /**
     * Default implementation that visits child nodes
     */
    public Object visit(JavaNode node, Object data);
    
    /**
     * Visit method for compilation units (root nodes)
     */
    public Object visit(ASTCompilationUnit node, Object data);
    
    /**
     * Visit method for class declarations
     */
    public Object visit(ASTClassDeclaration node, Object data);
    
    /**
     * Visit method for method declarations
     */
    public Object visit(ASTMethodDeclaration node, Object data);
    
    /**
     * Visit method for field declarations
     */
    public Object visit(ASTFieldDeclaration node, Object data);
    
    /**
     * Visit method for method calls
     */
    public Object visit(ASTMethodCall node, Object data);
    
    /**
     * Visit method for variable access
     */
    public Object visit(ASTVariableAccess node, Object data);
    
    // 180+ additional visit methods for all AST node types...
    
    /**
     * Convenience method to add a rule violation
     */
    protected final void addViolation(Object data, JavaNode node);
    
    /**
     * Convenience method to add a rule violation with custom message
     */
    protected final void addViolation(Object data, JavaNode node, String message);
    
    /**
     * Convenience method to add a rule violation with message arguments
     */
    protected final void addViolation(Object data, JavaNode node, Object[] args);
}

Rule Context and Violations

/**
 * Context provided to rules during execution
 */
public interface RuleContext {
    /**
     * Reports a rule violation
     */
    void addViolation(RuleViolation violation);
    
    /**
     * Gets the current source file being analyzed
     */
    TextFile getCurrentFile();
    
    /**
     * Gets the language version being processed
     */
    LanguageVersion getLanguageVersion();
    
    /**
     * Gets configuration for the current rule
     */
    RuleSetReferenceId getCurrentRuleSetReferenceId();
}

/**
 * Represents a rule violation found in source code
 */
public interface RuleViolation {
    /**
     * Gets the rule that was violated
     */
    Rule getRule();
    
    /**
     * Gets the violation message
     */
    String getDescription();
    
    /**
     * Gets the source file containing the violation
     */
    String getFilename();
    
    /**
     * Gets the line number of the violation
     */
    int getBeginLine();
    
    /**
     * Gets the column number of the violation
     */
    int getBeginColumn();
    
    /**
     * Gets the end line number of the violation
     */
    int getEndLine();
    
    /**
     * Gets the end column number of the violation
     */
    int getEndColumn();
    
    /**
     * Gets the priority/severity of the violation
     */
    RulePriority getPriority();
}

Visitor-Based Rules

Simple Rule Example

/**
 * Example rule that detects empty catch blocks
 */
public class EmptyCatchBlockRule extends AbstractJavaRule {
    
    @Override
    public Object visit(ASTTryStatement node, Object data) {
        // Check each catch clause
        for (ASTCatchClause catchClause : node.getCatchClauses()) {
            ASTBlock catchBlock = catchClause.getBody();
            
            // Check if catch block is empty
            if (catchBlock.isEmpty()) {
                addViolation(data, catchClause, "Empty catch block");
            }
        }
        
        return super.visit(node, data);
    }
}

Complex Rule with Symbol Resolution

/**
 * Example rule that detects unused private methods
 */
public class UnusedPrivateMethodRule extends AbstractJavaRule {
    private Set<JMethodSymbol> privateMethods = new HashSet<>();
    private Set<JMethodSymbol> calledMethods = new HashSet<>();
    
    @Override
    public Object visit(ASTCompilationUnit node, Object data) {
        // Reset state for new compilation unit
        privateMethods.clear();
        calledMethods.clear();
        
        // First pass: collect all private methods
        super.visit(node, data);
        
        // Report unused private methods
        for (JMethodSymbol method : privateMethods) {
            if (!calledMethods.contains(method) && !isSpecialMethod(method)) {
                JavaNode methodNode = method.tryGetNode();
                if (methodNode != null) {
                    addViolation(data, methodNode, 
                        "Unused private method: " + method.getSimpleName());
                }
            }
        }
        
        return data;
    }
    
    @Override
    public Object visit(ASTMethodDeclaration node, Object data) {
        JMethodSymbol symbol = node.getSymbol();
        
        // Collect private methods
        if (symbol != null && node.getVisibility() == Visibility.PRIVATE) {
            privateMethods.add(symbol);
        }
        
        return super.visit(node, data);
    }
    
    @Override
    public Object visit(ASTMethodCall node, Object data) {
        JMethodSymbol calledMethod = node.getMethodType();
        
        // Track method calls
        if (calledMethod != null && !calledMethod.isUnresolved()) {
            calledMethods.add(calledMethod);
        }
        
        return super.visit(node, data);
    }
    
    private boolean isSpecialMethod(JMethodSymbol method) {
        String name = method.getSimpleName();
        return "readObject".equals(name) || 
               "writeObject".equals(name) ||
               "readResolve".equals(name) ||
               "writeReplace".equals(name);
    }
}

Rules Using Type System

/**
 * Rule that detects assignment to parameters
 */
public class AvoidReassigningParametersRule extends AbstractJavaRule {
    
    @Override
    public Object visit(ASTAssignmentExpression node, Object data) {
        // Check if left side is parameter assignment
        ASTExpression leftSide = node.getLeftOperand();
        
        if (leftSide instanceof ASTVariableAccess) {
            ASTVariableAccess varAccess = (ASTVariableAccess) leftSide;
            JVariableSymbol variable = varAccess.getReferencedSym();
            
            // Check if it's a formal parameter
            if (variable instanceof JFormalParamSymbol) {
                addViolation(data, node, 
                    "Avoid reassigning parameter: " + variable.getSimpleName());
            }
        }
        
        return super.visit(node, data);
    }
}

XPath-Based Rules

PMD supports XPath expressions for creating rules without writing Java code.

XPath Rule Configuration

/**
 * XPath-based rule implementation
 */
public class XPathRule extends AbstractRule implements JavaParserVisitor {
    /**
     * Sets the XPath expression for this rule
     */
    public void setXPath(String xpath);
    
    /**
     * Gets the XPath expression
     */
    public String getXPath();
    
    /**
     * Sets XPath version (1.0, 2.0, 3.1)
     */
    public void setVersion(String version);
    
    /**
     * Executes the XPath query against the AST
     */
    @Override
    public Object visit(JavaNode node, Object data);
}

XPath Rule Examples

Detect System.out.println usage:

//ASTMethodCall[
    @MethodName = 'println' and 
    ASTFieldAccess[@Name = 'out' and ASTFieldAccess[@Name = 'System']]
]

Find methods with too many parameters:

//ASTMethodDeclaration[count(ASTFormalParameters/ASTFormalParameter) > 5]

Detect empty if blocks:

//ASTIfStatement[ASTBlock[count(*) = 0]]

Find catch blocks that only log and rethrow:

//ASTCatchClause[
    count(ASTBlock/*) = 2 and
    ASTBlock/ASTExpressionStatement/ASTMethodCall[@MethodName = 'log'] and
    ASTBlock/ASTThrowStatement
]

Rule Execution Framework

Rule Processing Pipeline

/**
 * Main entry point for rule execution
 */
public class JavaLanguageModule extends LanguageModuleBase {
    /**
     * Creates a language processor for executing rules
     */
    @Override
    public LanguageProcessor createProcessor(LanguagePropertyBundle bundle) {
        return new JavaLanguageProcessor(bundle);
    }
}

/**
 * Language processor that coordinates rule execution
 */
public class JavaLanguageProcessor implements LanguageProcessor {
    /**
     * Processes source files and applies rules
     */
    public void processSource(TextFile textFile, RuleSet ruleSet, RuleContext ruleContext);
    
    /**
     * Creates services for symbol resolution and type checking
     */
    public AnalysisServices services();
}

/**
 * Services available during rule execution
 */
public interface AnalysisServices {
    /**
     * Gets the symbol resolver for the current analysis
     */
    SymbolResolver getSymbolResolver();
    
    /**
     * Gets the type resolver for the current analysis
     */
    TypeResolver getTypeResolver();
    
    /**
     * Gets the semantic model (symbol table + type system)
     */
    SemanticModel getSemanticModel();
}

Rule Execution Context

/**
 * Extended context for Java rule execution
 */
public interface JavaRuleContext extends RuleContext {
    /**
     * Gets the parsed AST for the current file
     */
    ASTCompilationUnit getCompilationUnit();
    
    /**
     * Gets the symbol table for symbol resolution
     */
    JSymbolTable getSymbolTable();
    
    /**
     * Gets the type system for type analysis
     */
    TypeSystem getTypeSystem();
    
    /**
     * Gets semantic analysis services
     */
    AnalysisServices getAnalysisServices();
}

Rule Configuration

Rule Properties

/**
 * Base class for rule properties
 */
public abstract class PropertyDescriptor<T> {
    /**
     * Gets the name of this property
     */
    public String name();
    
    /**
     * Gets the description of this property
     */
    public String description();
    
    /**
     * Gets the default value
     */
    public T defaultValue();
    
    /**
     * Validates a property value
     */
    public String errorFor(T value);
}

/**
 * Property descriptor for integer values
 */
public class IntegerPropertyDescriptor extends PropertyDescriptor<Integer> {
    /**
     * Creates descriptor with range validation
     */
    public static IntegerPropertyDescriptor named(String name)
        .desc(String description)
        .range(int min, int max)
        .defaultValue(int defaultValue);
}

/**
 * Property descriptor for string values
 */
public class StringPropertyDescriptor extends PropertyDescriptor<String> {
    /**
     * Creates descriptor with regex validation
     */
    public static StringPropertyDescriptor named(String name)
        .desc(String description)
        .validValues(String... values)
        .defaultValue(String defaultValue);
}

Configurable Rule Example

/**
 * Rule with configurable properties
 */
public class CyclomaticComplexityRule extends AbstractJavaRule {
    
    // Property descriptors
    private static final IntegerPropertyDescriptor CYCLO_THRESHOLD = 
        IntegerPropertyDescriptor.named("cyclomaticComplexity")
            .desc("Maximum cyclomatic complexity before reporting violation")
            .range(1, 50)
            .defaultValue(10);
    
    private static final BooleanPropertyDescriptor IGNORE_BOOLEAN_PATHS =
        BooleanPropertyDescriptor.named("ignoreBooleanPaths")
            .desc("Ignore boolean expressions in complexity calculation")
            .defaultValue(false);
    
    // Define properties for this rule
    public CyclomaticComplexityRule() {
        definePropertyDescriptor(CYCLO_THRESHOLD);
        definePropertyDescriptor(IGNORE_BOOLEAN_PATHS);
    }
    
    @Override
    public Object visit(ASTMethodDeclaration node, Object data) {
        // Get configured property values
        int threshold = getProperty(CYCLO_THRESHOLD);
        boolean ignoreBooleanPaths = getProperty(IGNORE_BOOLEAN_PATHS);
        
        // Create metric options based on properties
        MetricOptions options = ignoreBooleanPaths ? 
            MetricOptions.ofOption(CycloOption.IGNORE_BOOLEAN_PATHS) :
            MetricOptions.emptyOptions();
        
        // Calculate complexity
        int complexity = JavaMetrics.CYCLO.computeFor(node, options);
        
        if (complexity > threshold) {
            addViolation(data, node, new Object[] { 
                node.getName(), 
                complexity, 
                threshold 
            });
        }
        
        return super.visit(node, data);
    }
}

Built-in Rule Categories

Best Practices Rules

Key rules in the best practices category:

  • AvoidReassigningParameters: Avoid reassigning method parameters
  • OneDeclarationPerLine: Declare variables one per line
  • UseCollectionIsEmpty: Use isEmpty() instead of size() == 0
  • SystemPrintln: Avoid System.out.println in production code
  • UnusedImports: Remove unused import statements
  • UnusedLocalVariable: Remove unused local variables
  • UnusedPrivateField: Remove unused private fields
  • UnusedPrivateMethod: Remove unused private methods
  • AvoidStringBufferField: StringBuffer fields should be local variables

Error Prone Rules

Critical rules that detect likely bugs:

  • AvoidBranchingStatementAsLastInLoop: Avoid break/continue as last statement
  • AvoidDecimalLiteralsInBigDecimalConstructor: Use string constructor for BigDecimal
  • BrokenNullCheck: Detect broken null checks
  • EmptyCatchBlock: Avoid empty catch blocks
  • NullPointerException: Detect potential NPE scenarios
  • UseEqualsToCompareStrings: Use equals() for string comparison
  • MissingBreakInSwitch: Detect missing break in switch cases
  • OverrideBothEqualsAndHashcode: Override both equals() and hashCode()

Performance Rules

Rules that identify performance issues:

  • AvoidInstantiatingObjectsInLoops: Avoid creating objects in loops
  • StringInstantiation: Use string literals instead of new String()
  • UseStringBufferForStringAppends: Use StringBuilder for concatenation
  • AvoidArrayLoops: Use System.arraycopy() for array copying
  • BigIntegerInstantiation: Use BigInteger constants for common values
  • BooleanInstantiation: Use Boolean.TRUE/FALSE constants

Security Rules

Rules that detect security vulnerabilities:

  • HardCodedCryptoKey: Avoid hard-coded cryptographic keys
  • InsecureCryptoIv: Use secure initialization vectors
  • AvoidFileStream: Use nio.file APIs for file operations
  • StaticEJBFieldShouldBeFinal: EJB static fields should be final
  • DoNotCallGarbageCollection: Avoid System.gc() calls

Usage Examples

Creating a Custom Rule

// Example: Rule to detect long parameter lists
public class TooManyParametersRule extends AbstractJavaRule {
    
    private static final IntegerPropertyDescriptor MAX_PARAMETERS = 
        IntegerPropertyDescriptor.named("maxParameters")
            .desc("Maximum number of parameters allowed")
            .range(1, 20)
            .defaultValue(7);
    
    public TooManyParametersRule() {
        definePropertyDescriptor(MAX_PARAMETERS);
    }
    
    @Override
    public Object visit(ASTMethodDeclaration node, Object data) {
        int maxParams = getProperty(MAX_PARAMETERS);
        ASTFormalParameters params = node.getFormalParameters();
        
        int paramCount = params.size();
        if (paramCount > maxParams) {
            addViolation(data, node, new Object[] {
                node.getName(),
                paramCount,
                maxParams
            });
        }
        
        return super.visit(node, data);
    }
    
    @Override
    public Object visit(ASTConstructorDeclaration node, Object data) {
        int maxParams = getProperty(MAX_PARAMETERS);
        ASTFormalParameters params = node.getFormalParameters();
        
        int paramCount = params.size();
        if (paramCount > maxParams) {
            addViolation(data, node, new Object[] {
                "constructor",
                paramCount,
                maxParams
            });
        }
        
        return super.visit(node, data);
    }
}

Rule with Symbol Analysis

// Example: Rule to detect fields that should be local variables
public class FieldShouldBeLocalRule extends AbstractJavaRule {
    private Map<JFieldSymbol, ASTFieldDeclaration> privateFields = new HashMap<>();
    private Set<JFieldSymbol> accessedFields = new HashSet<>();
    
    @Override
    public Object visit(ASTCompilationUnit node, Object data) {
        privateFields.clear();
        accessedFields.clear();
        
        // First pass: collect field info and usage
        super.visit(node, data);
        
        // Report fields that are only used in one method
        for (Map.Entry<JFieldSymbol, ASTFieldDeclaration> entry : privateFields.entrySet()) {
            JFieldSymbol field = entry.getKey();
            ASTFieldDeclaration fieldDecl = entry.getValue();
            
            if (!field.isStatic() && !accessedFields.contains(field)) {
                Set<ASTMethodDeclaration> usingMethods = findMethodsUsingField(node, field);
                
                if (usingMethods.size() == 1) {
                    addViolation(data, fieldDecl, 
                        "Field '" + field.getSimpleName() + 
                        "' is only used in one method and should be a local variable");
                }
            }
        }
        
        return data;
    }
    
    @Override
    public Object visit(ASTFieldDeclaration node, Object data) {
        if (node.getVisibility() == Visibility.PRIVATE) {
            for (ASTVariableId varId : node.getVarIds()) {
                JVariableSymbol symbol = varId.getSymbol();
                if (symbol instanceof JFieldSymbol) {
                    privateFields.put((JFieldSymbol) symbol, node);
                }
            }
        }
        
        return super.visit(node, data);
    }
    
    @Override
    public Object visit(ASTFieldAccess node, Object data) {
        JFieldSymbol field = node.getReferencedSym();
        if (field != null && privateFields.containsKey(field)) {
            accessedFields.add(field);
        }
        
        return super.visit(node, data);
    }
    
    private Set<ASTMethodDeclaration> findMethodsUsingField(ASTCompilationUnit cu, JFieldSymbol field) {
        FieldUsageVisitor visitor = new FieldUsageVisitor(field);
        cu.acceptVisitor(visitor, null);
        return visitor.getUsingMethods();
    }
    
    private static class FieldUsageVisitor extends JavaVisitorBase<Void, Void> {
        private final JFieldSymbol targetField;
        private final Set<ASTMethodDeclaration> usingMethods = new HashSet<>();
        private ASTMethodDeclaration currentMethod;
        
        public FieldUsageVisitor(JFieldSymbol field) {
            this.targetField = field;
        }
        
        @Override
        public Void visit(ASTMethodDeclaration node, Void data) {
            ASTMethodDeclaration oldMethod = currentMethod;
            currentMethod = node;
            super.visit(node, data);
            currentMethod = oldMethod;
            return null;
        }
        
        @Override
        public Void visit(ASTFieldAccess node, Void data) {
            if (targetField.equals(node.getReferencedSym()) && currentMethod != null) {
                usingMethods.add(currentMethod);
            }
            return super.visit(node, data);
        }
        
        public Set<ASTMethodDeclaration> getUsingMethods() {
            return usingMethods;
        }
    }
}

XPath Rule Registration

// Example: Creating XPath rules programmatically
public class CustomXPathRules {
    
    public static Rule createEmptyIfRule() {
        XPathRule rule = new XPathRule();
        rule.setName("EmptyIfStatement");
        rule.setMessage("Empty if statement");
        rule.setDescription("Detects empty if statements");
        rule.setXPath("//ASTIfStatement[ASTBlock[count(*) = 0]]");
        rule.setLanguage(JavaLanguageModule.getInstance());
        return rule;
    }
    
    public static Rule createTooManyParametersRule() {
        XPathRule rule = new XPathRule();
        rule.setName("TooManyParameters");  
        rule.setMessage("Too many parameters: {0}");
        rule.setDescription("Methods should not have too many parameters");
        rule.setXPath("//ASTMethodDeclaration[count(.//ASTFormalParameter) > 7]");
        rule.setLanguage(JavaLanguageModule.getInstance());
        return rule;
    }
    
    public static Rule createStringLiteralEqualityRule() {
        XPathRule rule = new XPathRule();
        rule.setName("StringLiteralEquality");
        rule.setMessage("Use equals() instead of == for string comparison");
        rule.setDescription("String literals should be compared using equals() method");
        rule.setXPath("""
            //ASTEqualityExpression[@Operator = '==' or @Operator = '!=']
            [ASTStringLiteral or 
             .//*[self::ASTMethodCall[@MethodName = 'toString'] or 
                  self::ASTMethodCall[@MethodName = 'trim'] or
                  self::ASTMethodCall[@MethodName = 'toLowerCase'] or
                  self::ASTMethodCall[@MethodName = 'toUpperCase']]]
            """);
        rule.setLanguage(JavaLanguageModule.getInstance());
        return rule;
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-net-sourceforge-pmd--pmd-java

docs

ast-processing.md

index.md

language-support.md

metrics.md

rule-framework.md

symbols-types.md

tile.json