PMD JSP language module providing static code analysis capabilities for JavaServer Pages files with lexical analysis, AST parsing, and rule-based code quality checks.
—
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.
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 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);
}
}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 capabilityacceptVisitor(JspVisitor, P): Direct JSP visitor dispatch implemented by each node typeimport 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);
}
}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; }
}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.
}
}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("");
}
}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...
}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");
}
}// 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);
}@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);
}@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