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

visitor-pattern.mddocs/

Visitor Pattern

The JSP AST uses the visitor pattern to enable traversal and analysis of JSP documents. The visitor pattern provides a clean way to implement operations on AST nodes without modifying the node classes themselves.

Visitor Interface

JspVisitor

Core visitor interface for JSP AST traversal. This interface is generated by JavaCC during the build process and provides type-safe visit methods for all AST node types.

public interface JspVisitor<P, R> {
    // Generated interface with visit methods for all AST node types
    // This interface is generated by JavaCC during the build process
    // and contains visit methods for each concrete AST node class
    
    R visit(ASTCompilationUnit node, P data);
    R visit(ASTElement node, P data);
    R visit(ASTAttribute node, P data);
    R visit(ASTAttributeValue node, P data);
    R visit(ASTCData node, P data);
    R visit(ASTCommentTag node, P data);
    R visit(ASTContent node, P data);
    R visit(ASTDeclaration node, P data);
    R visit(ASTDoctypeDeclaration node, P data);
    R visit(ASTDoctypeExternalId node, P data);
    R visit(ASTElExpression node, P data);
    R visit(ASTHtmlScript node, P data);
    R visit(ASTJspComment node, P data);
    R visit(ASTJspDeclaration node, P data);
    R visit(ASTJspDirective node, P data);
    R visit(ASTJspDirectiveAttribute node, P data);
    R visit(ASTJspExpression node, P data);
    R visit(ASTJspExpressionInAttribute node, P data);
    R visit(ASTJspScriptlet node, P data);
    R visit(ASTText node, P data);
    R visit(ASTUnparsedText node, P data);
    R visit(ASTValueBinding node, P data);
}

Type Parameters:

  • P: Parameter type passed to visit methods (often RuleContext for rules)
  • R: Return type from visit methods (often the same as P, or void)

Base Visitor Implementation

JspVisitorBase

Base implementation providing default visitor behavior.

public class JspVisitorBase<P, R> extends AstVisitorBase<P, R> implements JspVisitor<P, R> {
}

Usage:

import net.sourceforge.pmd.lang.jsp.ast.JspVisitorBase;
import net.sourceforge.pmd.lang.jsp.ast.*;

public class MyJspVisitor extends JspVisitorBase<Void, Void> {
    
    @Override
    public Void visit(ASTElement node, Void data) {
        System.out.println("Found element: " + node.getName());
        return super.visit(node, data); // Continue traversal
    }
    
    @Override  
    public Void visit(ASTJspExpression node, Void data) {
        System.out.println("Found JSP expression: " + node.getContent());
        return super.visit(node, data);
    }
}

Visitor Dispatch

AbstractJspNode Visitor Support

All JSP AST nodes support visitor dispatch through the visitor pattern.

public abstract class AbstractJspNode extends AbstractJjtreeNode<AbstractJspNode, JspNode> implements JspNode {
    public final <P, R> R acceptVisitor(AstVisitor<? super P, ? extends R> visitor, P data);
    protected abstract <P, R> R acceptVisitor(JspVisitor<? super P, ? extends R> visitor, P data);
}

Usage:

import net.sourceforge.pmd.lang.jsp.ast.ASTCompilationUnit;

// Apply visitor to AST
MyJspVisitor visitor = new MyJspVisitor();
compilationUnit.acceptVisitor(visitor, null);

Methods:

  • acceptVisitor(AstVisitor, P): Generic visitor dispatch that checks for JSP visitor capability
  • acceptVisitor(JspVisitor, P): Direct JSP visitor dispatch implemented by each node type

Visitor Implementation Patterns

Simple Analysis Visitor

import net.sourceforge.pmd.lang.jsp.ast.*;

public class JspAnalysisVisitor extends JspVisitorBase<Void, Void> {
    
    private int elementCount = 0;
    private int expressionCount = 0;
    
    @Override
    public Void visit(ASTElement node, Void data) {
        elementCount++;
        
        // Analyze element properties
        if (node.isUnclosed()) {
            System.out.println("Warning: Unclosed element " + node.getName());
        }
        
        if ("script".equals(node.getName())) {
            System.out.println("Found script element");
        }
        
        return super.visit(node, data);
    }
    
    @Override
    public Void visit(ASTJspExpression node, Void data) {
        expressionCount++;
        
        String content = node.getContent();
        if (content.contains("request.")) {
            System.out.println("Found request access: " + content);
        }
        
        return super.visit(node, data);
    }
    
    @Override
    public Void visit(ASTElExpression node, Void data) {
        expressionCount++;
        
        String content = node.getContent();
        System.out.println("EL Expression: " + content);
        
        return super.visit(node, data);
    }
    
    public void printStats() {
        System.out.println("Elements found: " + elementCount);
        System.out.println("Expressions found: " + expressionCount);
    }
}

Data Collection Visitor

import java.util.*;

public class JspDataCollector extends JspVisitorBase<Void, Void> {
    
    private List<String> elementNames = new ArrayList<>();
    private List<String> jspExpressions = new ArrayList<>();
    private List<String> elExpressions = new ArrayList<>();
    private Map<String, List<String>> elementAttributes = new HashMap<>();
    
    @Override
    public Void visit(ASTElement node, Void data) {
        String elementName = node.getName();
        elementNames.add(elementName);
        
        // Collect attributes for this element
        List<String> attrs = node.children(ASTAttribute.class)
            .map(ASTAttribute::getName)
            .collect(Collectors.toList());
        
        if (!attrs.isEmpty()) {
            elementAttributes.put(elementName, attrs);
        }
        
        return super.visit(node, data);
    }
    
    @Override
    public Void visit(ASTJspExpression node, Void data) {
        jspExpressions.add(node.getContent());
        return super.visit(node, data);
    }
    
    @Override
    public Void visit(ASTElExpression node, Void data) {
        elExpressions.add(node.getContent());
        return super.visit(node, data);
    }
    
    // Getters for collected data
    public List<String> getElementNames() { return elementNames; }
    public List<String> getJspExpressions() { return jspExpressions; }
    public List<String> getElExpressions() { return elExpressions; }
    public Map<String, List<String>> getElementAttributes() { return elementAttributes; }
}

Conditional Visitor

public class SecurityAnalysisVisitor extends JspVisitorBase<List<String>, List<String>> {
    
    @Override
    public List<String> visit(ASTElExpression node, List<String> issues) {
        String content = node.getContent();
        
        // Check for potentially unsafe EL expressions
        if (!isInTaglib(node) && !isSanitized(content)) {
            issues.add("Potentially unsafe EL expression: " + content + 
                      " at line " + node.getBeginLine());
        }
        
        return super.visit(node, issues);
    }
    
    @Override
    public List<String> visit(ASTElement node, List<String> issues) {
        // Check for inline event handlers
        if (hasInlineEventHandler(node)) {
            issues.add("Inline event handler found in " + node.getName() + 
                      " at line " + node.getBeginLine());
        }
        
        return super.visit(node, issues);
    }
    
    private boolean isInTaglib(ASTElExpression node) {
        // Check if EL expression is within a taglib element
        return node.ancestors(ASTElement.class)
            .anyMatch(elem -> elem.getNamespacePrefix() != null);
    }
    
    private boolean isSanitized(String content) {
        return content.matches("^fn:escapeXml\\(.+\\)$");
    }
    
    private boolean hasInlineEventHandler(ASTElement element) {
        return element.children(ASTAttribute.class)
            .anyMatch(attr -> attr.getName().startsWith("on")); // onclick, onload, etc.
    }
}

Advanced Visitor Techniques

Visitor with Context

public class ContextAwareVisitor extends JspVisitorBase<VisitorContext, Void> {
    
    public static class VisitorContext {
        private Stack<String> elementStack = new Stack<>();
        private Set<String> seenIds = new HashSet<>();
        
        public void pushElement(String name) { elementStack.push(name); }
        public String popElement() { return elementStack.pop(); }
        public String getCurrentElement() { 
            return elementStack.isEmpty() ? null : elementStack.peek(); 
        }
        public boolean addId(String id) { return seenIds.add(id); }
    }
    
    @Override
    public Void visit(ASTElement node, VisitorContext context) {
        String elementName = node.getName();
        context.pushElement(elementName);
        
        // Check for duplicate IDs
        node.children(ASTAttribute.class)
            .filter(attr -> "id".equals(attr.getName()))
            .forEach(attr -> {
                String id = getAttributeValue(attr);
                if (!context.addId(id)) {
                    System.out.println("Duplicate ID found: " + id);
                }
            });
        
        // Continue traversal
        super.visit(node, context);
        
        context.popElement();
        return null;
    }
    
    private String getAttributeValue(ASTAttribute attr) {
        return attr.children(ASTAttributeValue.class)
            .findFirst()
            .map(ASTAttributeValue::getValue)
            .orElse("");
    }
}

Visitor Composition

public class CompositeVisitor extends JspVisitorBase<Void, Void> {
    
    private List<JspVisitor<Void, Void>> visitors = new ArrayList<>();
    
    public void addVisitor(JspVisitor<Void, Void> visitor) {
        visitors.add(visitor);
    }
    
    @Override
    public Void visit(ASTElement node, Void data) {
        // Apply all visitors to this node
        for (JspVisitor<Void, Void> visitor : visitors) {
            node.acceptVisitor(visitor, data);
        }
        return super.visit(node, data);
    }
    
    // Override other visit methods similarly...
}

Integration with PMD Rules

The visitor pattern integrates seamlessly with PMD's rule framework:

import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;

public class CustomJspRule extends AbstractJspRule {
    
    @Override
    public Object visit(ASTElExpression node, Object data) {
        // Rule-specific logic
        if (violatesRule(node)) {
            asCtx(data).addViolation(node, "Rule violation message");
        }
        
        return super.visit(node, data);
    }
    
    private boolean violatesRule(ASTElExpression node) {
        // Custom rule logic
        return node.getContent().contains("unsafeMethod");
    }
}

Best Practices

Performance Considerations

// Avoid creating objects in visit methods for frequently called nodes
@Override
public Void visit(ASTText node, Void data) {
    // Good: Direct operations
    if (node.getContent().trim().isEmpty()) {
        // Handle empty text
    }
    
    // Avoid: Creating unnecessary objects
    // String trimmed = node.getContent().trim(); // Creates new string
    
    return super.visit(node, data);
}

Null Safety

@Override
public Void visit(ASTElement node, Void data) {
    String name = node.getName();
    if (name != null && name.equals("script")) {
        // Safe null check
    }
    
    return super.visit(node, data);
}

Selective Traversal

@Override
public Void visit(ASTElement node, Void data) {
    if ("script".equals(node.getName())) {
        // Don't traverse into script elements
        return null;
    }
    
    return super.visit(node, data); // Continue normal traversal
}

The visitor pattern provides a powerful and flexible way to analyze JSP AST structures while keeping analysis logic separate from the AST node implementations.

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