CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-net-sourceforge-pmd--pmd-jsp

PMD JSP language module providing static code analysis capabilities for JavaServer Pages files with lexical analysis, AST parsing, and rule-based code quality checks.

Pending
Overview
Eval results
Files

rule-development.mddocs/

Rule Development

PMD JSP provides a framework for creating custom JSP analysis rules. Rules implement the visitor pattern to analyze JSP AST and report code quality violations.

Rule Base Class

AbstractJspRule

Base class for all JSP rules, implementing both PMD's rule interface and JSP visitor pattern.

public abstract class AbstractJspRule extends AbstractRule implements JspVisitor<Object, Object> {
    public void apply(Node target, RuleContext ctx);
    public Object visitNode(Node node, Object param);
}

Usage:

import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
import net.sourceforge.pmd.lang.jsp.ast.*;
import net.sourceforge.pmd.reporting.RuleContext;

public class MyJspRule extends AbstractJspRule {
    
    @Override
    public Object visit(ASTElExpression node, Object data) {
        // Rule implementation
        if (violatesRule(node)) {
            ((RuleContext) data).addViolation(node, "Violation message");
        }
        return super.visit(node, data);
    }
    
    private boolean violatesRule(ASTElExpression node) {
        // Custom rule logic
        return node.getContent().contains("dangerousFunction");
    }
}

Methods:

  • apply(Node, RuleContext): Entry point called by PMD framework
  • visitNode(Node, Object): Default visitor implementation that traverses all children

Note: The data parameter in visit methods is the RuleContext passed from the PMD framework and should be cast to RuleContext when reporting violations.

Rule Implementation Patterns

Simple Violation Detection

import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
import net.sourceforge.pmd.lang.jsp.ast.ASTJspScriptlet;
import net.sourceforge.pmd.reporting.RuleContext;

public class NoScriptletsRule extends AbstractJspRule {
    
    @Override
    public Object visit(ASTJspScriptlet node, Object data) {
        // Report violation for any scriptlet
        ((RuleContext) data).addViolation(node, 
            "Avoid using JSP scriptlets. Use JSTL or EL expressions instead.");
        
        return super.visit(node, data);
    }
}

Conditional Rules

import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
import net.sourceforge.pmd.lang.jsp.ast.*;
import net.sourceforge.pmd.reporting.RuleContext;

public class NoInlineStyleRule extends AbstractJspRule {
    
    @Override
    public Object visit(ASTElement node, Object data) {
        // Check for style attributes
        node.children(ASTAttribute.class)
            .filter(attr -> "style".equals(attr.getName()))
            .forEach(attr -> {
                ((RuleContext) data).addViolation(attr, 
                    "Avoid inline styles. Use CSS classes instead.");
            });
        
        return super.visit(node, data);
    }
}

Content Analysis Rules

import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
import net.sourceforge.pmd.lang.jsp.ast.ASTElExpression;
import net.sourceforge.pmd.reporting.RuleContext;

public class UnsanitizedExpressionRule extends AbstractJspRule {
    
    @Override
    public Object visit(ASTElExpression node, Object data) {
        String content = node.getContent();
        
        // Check if expression is in a taglib context
        boolean inTaglib = node.ancestors(ASTElement.class)
            .anyMatch(elem -> elem.getNamespacePrefix() != null);
        
        // Check if expression is sanitized
        boolean sanitized = content.matches("^fn:escapeXml\\(.+\\)$");
        
        if (!inTaglib && !sanitized) {
            ((RuleContext) data).addViolation(node, 
                "EL expression may be vulnerable to XSS attacks. " + 
                "Use fn:escapeXml() or place within a taglib element.");
        }
        
        return super.visit(node, data);
    }
}

Multi-Node Analysis

import java.util.*;

public class DuplicateImportsRule extends AbstractJspRule {
    
    private Set<String> seenImports = new HashSet<>();
    
    @Override
    public Object visit(ASTJspDirective node, Object data) {
        if ("page".equals(node.getName())) {
            // Find import attributes
            node.children(ASTJspDirectiveAttribute.class)
                .filter(attr -> "import".equals(attr.getName()))
                .forEach(attr -> checkDuplicateImport(attr, data));
        }
        
        return super.visit(node, data);
    }
    
    private void checkDuplicateImport(ASTJspDirectiveAttribute attr, Object data) {
        String importValue = attr.getValue();
        
        if (!seenImports.add(importValue)) {
            ((RuleContext) data).addViolation(attr, 
                "Duplicate import: " + importValue);
        }
    }
    
    @Override
    public void start(RuleContext ctx) {
        super.start(ctx);
        seenImports.clear(); // Reset for each file
    }
}

Structural Analysis Rules

public class NestedJsfInLoopRule extends AbstractJspRule {
    
    @Override
    public Object visit(ASTElement node, Object data) {
        // Check for JSTL iteration tags
        if (isJstlIterationTag(node)) {
            // Look for JSF components within iteration
            node.descendants(ASTElement.class)
                .filter(this::isJsfComponent)
                .forEach(jsfElement -> {
                    ((RuleContext) data).addViolation(jsfElement,
                        "JSF component found within JSTL iteration. " +
                        "This can cause performance issues.");
                });
        }
        
        return super.visit(node, data);
    }
    
    private boolean isJstlIterationTag(ASTElement element) {
        return "c".equals(element.getNamespacePrefix()) && 
               ("forEach".equals(element.getLocalName()) || 
                "forTokens".equals(element.getLocalName()));
    }
    
    private boolean isJsfComponent(ASTElement element) {
        String prefix = element.getNamespacePrefix();
        return "h".equals(prefix) || "f".equals(prefix);
    }
}

Built-in Rule Examples

Security Rule Implementation

Based on the existing NoUnsanitizedJSPExpressionRule:

public class NoUnsanitizedJSPExpressionRule extends AbstractJspRule {
    public Object visit(ASTElExpression node, Object data);
}

Implementation pattern:

@Override
public Object visit(ASTElExpression node, Object data) {
    if (elOutsideTaglib(node)) {
        ((RuleContext) data).addViolation(node);
    }
    return super.visit(node, data);
}

private boolean elOutsideTaglib(ASTElExpression node) {
    ASTElement parentElement = node.ancestors(ASTElement.class).first();
    
    boolean elInTaglib = parentElement != null && 
        parentElement.getName() != null &&
        parentElement.getName().contains(":");
    
    boolean elWithFnEscapeXml = node.getContent() != null && 
        node.getContent().matches("^fn:escapeXml\\(.+\\)$");
    
    return !elInTaglib && !elWithFnEscapeXml;
}

Design Rules

public class NoInlineStyleInformationRule extends AbstractJspRule {
    // Implementation for detecting inline style attributes
}

public class NoLongScriptsRule extends AbstractJspRule {
    // Implementation for detecting overly long scriptlets
}

public class NoScriptletsRule extends AbstractJspRule {
    // Implementation for detecting JSP scriptlets
}

Code Style Rules

public class DuplicateJspImportsRule extends AbstractJspRule {
    // Implementation for detecting duplicate import directives
}

Rule Configuration

XML Rule Definition

Rules are defined in XML files under /category/jsp/:

<rule name="NoUnsanitizedJSPExpression"
      language="jsp"
      since="3.6"
      message="Avoid unsanitized JSP expressions"
      class="net.sourceforge.pmd.lang.jsp.rule.security.NoUnsanitizedJSPExpressionRule"
      externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_security.html#nounsanitizedjspexpression">
    <description>
    Avoid unsanitized JSP expressions as they can lead to Cross Site Scripting (XSS) attacks.
    </description>
    <priority>2</priority>
    <example>
<![CDATA[
<!-- Bad: Unsanitized EL expression -->
<p>${userInput}</p>

<!-- Good: Sanitized with fn:escapeXml -->
<p>${fn:escapeXml(userInput)}</p>

<!-- Good: Within taglib element -->
<c:out value="${userInput}"/>
]]>
    </example>
</rule>

XPath-based Rules

Some rules use XPath expressions instead of Java implementations:

<rule name="NoClassAttribute"
      class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
    <properties>
        <property name="xpath">
            <value><![CDATA[
//Attribute[ upper-case(@Name)="CLASS" ]
            ]]></value>
        </property>
    </properties>
</rule>

Advanced Rule Techniques

Rule with Properties

public class ConfigurableRule extends AbstractJspRule {
    
    private static final StringProperty MAX_LENGTH = 
        StringProperty.named("maxLength")
            .desc("Maximum allowed length")
            .defaultValue("100")
            .build();
    
    public ConfigurableRule() {
        definePropertyDescriptor(MAX_LENGTH);
    }
    
    @Override
    public Object visit(ASTJspScriptlet node, Object data) {
        int maxLength = Integer.parseInt(getProperty(MAX_LENGTH));
        
        if (node.getContent().length() > maxLength) {
            ((RuleContext) data).addViolation(node, 
                "Scriptlet exceeds maximum length of " + maxLength);
        }
        
        return super.visit(node, data);
    }
}

Rule with State Tracking

public class StatefulRule extends AbstractJspRule {
    
    private Map<String, Integer> elementCounts = new HashMap<>();
    
    @Override
    public void start(RuleContext ctx) {
        super.start(ctx);
        elementCounts.clear();
    }
    
    @Override
    public Object visit(ASTElement node, Object data) {
        String elementName = node.getName();
        int count = elementCounts.getOrDefault(elementName, 0) + 1;
        elementCounts.put(elementName, count);
        
        if (count > 10) {
            ((RuleContext) data).addViolation(node, 
                "Too many " + elementName + " elements (" + count + ")");
        }
        
        return super.visit(node, data);
    }
}

Complex Analysis Rule

public class ComplexAnalysisRule extends AbstractJspRule {
    
    private Stack<String> contextStack = new Stack<>();
    private boolean inFormContext = false;
    
    @Override
    public Object visit(ASTElement node, Object data) {
        String elementName = node.getName();
        
        // Track context
        if ("form".equals(elementName)) {
            inFormContext = true;
            contextStack.push("form");
        }
        
        // Analyze based on context
        if ("input".equals(elementName) && !inFormContext) {
            ((RuleContext) data).addViolation(node, 
                "Input element found outside of form context");
        }
        
        // Continue traversal
        Object result = super.visit(node, data);
        
        // Clean up context
        if ("form".equals(elementName)) {
            contextStack.pop();
            inFormContext = !contextStack.contains("form");
        }
        
        return result;
    }
}

Testing Rules

Unit Testing Pattern

import net.sourceforge.pmd.test.SimpleAggregatorTst;

public class MyJspRuleTest extends SimpleAggregatorTst {
    
    private static final String TEST_JSP = 
        "<%@ page language=\"java\" %>\n" +
        "<html>\n" +
        "  <body>\n" +
        "    ${unsafeExpression}\n" +
        "  </body>\n" +
        "</html>";
    
    @Test
    public void testUnsafeExpression() {
        Rule rule = new NoUnsanitizedJSPExpressionRule();
        RuleViolation[] violations = getViolations(rule, TEST_JSP);
        
        assertEquals(1, violations.length);
        assertTrue(violations[0].getDescription().contains("unsafe"));
    }
}

Rule Categories

Rules are organized into categories:

  • bestpractices: General best practices
  • security: Security-related issues
  • design: Design and architecture issues
  • codestyle: Code style and formatting
  • errorprone: Error-prone patterns
  • performance: Performance issues
  • multithreading: Multithreading issues
  • documentation: Documentation issues

Each category has its own XML ruleset file that can be included or excluded in PMD analysis configurations.

Integration with PMD Analysis

Rules integrate with PMD's analysis engine:

  1. Rule Discovery: Rules are discovered through XML ruleset files
  2. AST Application: Rules are applied to JSP AST nodes via visitor pattern
  3. Violation Reporting: Violations are reported through RuleContext
  4. Result Aggregation: PMD aggregates violations across all rules and files

The rule framework provides a powerful foundation for implementing custom JSP code quality checks that integrate seamlessly with PMD's analysis pipeline.

Install with Tessl CLI

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

docs

ast-node-types.md

copy-paste-detection.md

index.md

jsp-parser-ast.md

language-module.md

rule-development.md

visitor-pattern.md

tile.json