PMD Core - The foundational library module providing essential infrastructure for PMD static code analysis including AST handling, rule execution, configuration management, and reporting mechanisms.
—
The AST (Abstract Syntax Tree) Processing module provides comprehensive navigation, querying, and analysis capabilities for parsed source code structures. It offers tree traversal, node searching, XPath querying, and streaming operations for efficient AST manipulation.
Core interface for Abstract Syntax Tree nodes providing navigation, querying, and content access capabilities.
/**
* Core interface for Abstract Syntax Tree nodes.
* Provides navigation, querying, and content access for parsed source code.
*/
public interface Node {
/**
* Get XPath node name for XPath queries
* @return Node name used in XPath expressions
*/
String getXPathNodeName();
/**
* Get parent node in the AST
* @return Parent Node, or null if this is the root
*/
Node getParent();
/**
* Get child node by index
* @param index Zero-based index of child
* @return Child Node at specified index
* @throws IndexOutOfBoundsException if index is invalid
*/
Node getChild(int index);
/**
* Get number of direct children
* @return Count of immediate child nodes
*/
int getNumChildren();
/**
* Get all direct children as list
* @return Unmodifiable list of child nodes
*/
List<? extends Node> getChildren();
/**
* Get index of this node within its parent's children
* @return Zero-based index in parent, or -1 if no parent
*/
int getIndexInParent();
/**
* Get first child node
* @return First child, or null if no children
*/
Node getFirstChild();
/**
* Get last child node
* @return Last child, or null if no children
*/
Node getLastChild();
/**
* Get next sibling node
* @return Next sibling, or null if this is the last child
*/
Node getNextSibling();
/**
* Get previous sibling node
* @return Previous sibling, or null if this is the first child
*/
Node getPreviousSibling();
/**
* Check if any descendant matches the given node type
* @param type Class of node type to search for
* @return true if at least one descendant of the type exists
*/
boolean hasDescendantOfType(Class<? extends Node> type);
/**
* Find all descendants of specific type
* @param type Class of node type to find
* @return List of all descendant nodes matching the type
*/
List<? extends Node> findDescendantsOfType(Class<? extends Node> type);
/**
* Find first descendant of specific type
* @param type Class of node type to find
* @return First descendant matching type, or null if none found
*/
Node getFirstDescendantOfType(Class<? extends Node> type);
/**
* Stream all descendants of this node
* @return NodeStream for efficient descendant traversal
*/
NodeStream<? extends Node> descendants();
/**
* Stream direct children of this node
* @return NodeStream for child iteration
*/
NodeStream<? extends Node> children();
/**
* Get text region in source file
* @return TextRegion with start/end positions
*/
TextRegion getTextRegion();
/**
* Get file location for reporting purposes
* @return FileLocation with file and position information
*/
FileLocation getReportLocation();
/**
* Get source text content of this node
* @return Original source code text for this node
*/
String getText();
}Usage Examples:
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.NodeStream;
import java.util.List;
// Basic node navigation
public void analyzeNode(Node node) {
System.out.printf("Node: %s (Children: %d)%n",
node.getXPathNodeName(),
node.getNumChildren());
// Navigate to parent
Node parent = node.getParent();
if (parent != null) {
System.out.printf("Parent: %s%n", parent.getXPathNodeName());
}
// Iterate through children
for (int i = 0; i < node.getNumChildren(); i++) {
Node child = node.getChild(i);
System.out.printf("Child %d: %s%n", i, child.getXPathNodeName());
}
// Or use the children list
List<? extends Node> children = node.getChildren();
for (Node child : children) {
analyzeNode(child); // Recursive analysis
}
// Navigate siblings
Node nextSibling = node.getNextSibling();
Node prevSibling = node.getPreviousSibling();
// Get source information
TextRegion region = node.getTextRegion();
System.out.printf("Text region: %d-%d%n",
region.getStartOffset(),
region.getEndOffset());
String sourceText = node.getText();
System.out.printf("Source text: %s%n", sourceText.trim());
}
// Finding specific node types (example with hypothetical Java nodes)
public void findMethodNodes(Node root) {
// Check if any method declarations exist
if (root.hasDescendantOfType(ASTMethodDeclaration.class)) {
System.out.println("Found method declarations");
// Find all method declarations
List<? extends Node> methods = root.findDescendantsOfType(ASTMethodDeclaration.class);
System.out.printf("Found %d methods%n", methods.size());
for (Node method : methods) {
System.out.printf("Method: %s%n", method.getText());
}
// Find first method only
Node firstMethod = root.getFirstDescendantOfType(ASTMethodDeclaration.class);
if (firstMethod != null) {
System.out.printf("First method: %s%n", firstMethod.getText());
}
}
}
// Using NodeStream for efficient traversal
public void streamExample(Node root) {
// Stream all descendants
root.descendants()
.filterIs(ASTVariableDeclarator.class)
.forEach(var -> System.out.printf("Variable: %s%n", var.getText()));
// Stream direct children only
root.children()
.filter(child -> child.getNumChildren() > 0)
.map(Node::getXPathNodeName)
.distinct()
.forEach(System.out::println);
// Count specific node types
long methodCount = root.descendants()
.filterIs(ASTMethodDeclaration.class)
.count();
// Find nodes with specific characteristics
List<Node> complexNodes = root.descendants()
.filter(node -> node.getNumChildren() > 5)
.toList();
}Efficient streaming API for traversing and filtering AST nodes with functional programming patterns.
/**
* Streaming API for efficient AST traversal and filtering.
* Provides functional programming patterns for node processing.
*/
interface NodeStream<T extends Node> {
/**
* Filter nodes by predicate
* @param predicate Function to test each node
* @return NodeStream containing only matching nodes
*/
NodeStream<T> filter(Predicate<? super T> predicate);
/**
* Filter nodes by specific type
* @param nodeType Class of target node type
* @return NodeStream containing only nodes of specified type
*/
<R extends Node> NodeStream<R> filterIs(Class<? extends R> nodeType);
/**
* Transform nodes to different type
* @param mapper Function to transform each node
* @return Stream containing transformed values
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
/**
* Transform nodes to NodeStream of different type
* @param mapper Function to transform each node to NodeStream
* @return Flattened NodeStream of transformed nodes
*/
<R extends Node> NodeStream<R> flatMap(Function<? super T, NodeStream<? extends R>> mapper);
/**
* Get descendants of each node in stream
* @return NodeStream containing all descendants
*/
NodeStream<Node> descendants();
/**
* Get children of each node in stream
* @return NodeStream containing all children
*/
NodeStream<Node> children();
/**
* Execute action for each node
* @param action Action to perform on each node
*/
void forEach(Consumer<? super T> action);
/**
* Collect nodes to list
* @return List containing all nodes in stream
*/
List<T> toList();
/**
* Count nodes in stream
* @return Number of nodes in stream
*/
long count();
/**
* Get first node in stream
* @return First node, or empty Optional if stream is empty
*/
Optional<T> first();
/**
* Get last node in stream
* @return Last node, or empty Optional if stream is empty
*/
Optional<T> last();
/**
* Check if any nodes match predicate
* @param predicate Function to test nodes
* @return true if at least one node matches
*/
boolean any(Predicate<? super T> predicate);
/**
* Check if no nodes match predicate
* @param predicate Function to test nodes
* @return true if no nodes match
*/
boolean none(Predicate<? super T> predicate);
/**
* Get nodes with distinct values based on key function
* @param keyExtractor Function to extract comparison key
* @return NodeStream with distinct nodes
*/
NodeStream<T> distinct(Function<? super T, ?> keyExtractor);
/**
* Limit stream to first N nodes
* @param maxSize Maximum number of nodes to include
* @return NodeStream limited to specified size
*/
NodeStream<T> take(long maxSize);
/**
* Skip first N nodes
* @param n Number of nodes to skip
* @return NodeStream with first N nodes skipped
*/
NodeStream<T> drop(long n);
}Usage Examples:
import net.sourceforge.pmd.lang.ast.*;
import java.util.List;
import java.util.Optional;
// Complex filtering and transformation examples
public class ASTAnalysisExamples {
public void analyzeComplexity(Node root) {
// Find all method declarations with high complexity
List<Node> complexMethods = root.descendants()
.filterIs(ASTMethodDeclaration.class)
.filter(method -> method.descendants()
.filterIs(ASTIfStatement.class)
.count() > 3)
.toList();
System.out.printf("Found %d complex methods%n", complexMethods.size());
}
public void findUnusedVariables(Node root) {
// Find variable declarations
NodeStream<ASTVariableDeclarator> variables = root.descendants()
.filterIs(ASTVariableDeclarator.class);
// Check for unused variables (simplified example)
variables.filter(var -> {
String varName = var.getName();
return root.descendants()
.filterIs(ASTName.class)
.none(name -> name.getImage().equals(varName));
}).forEach(unusedVar ->
System.out.printf("Unused variable: %s%n", unusedVar.getName())
);
}
public void collectStatistics(Node root) {
// Count different node types
long classCount = root.descendants()
.filterIs(ASTClassOrInterfaceDeclaration.class)
.count();
long methodCount = root.descendants()
.filterIs(ASTMethodDeclaration.class)
.count();
long lineCount = root.descendants()
.map(node -> node.getTextRegion().getEndLine() -
node.getTextRegion().getStartLine() + 1)
.mapToLong(Integer::longValue)
.sum();
System.out.printf("Classes: %d, Methods: %d, Lines: %d%n",
classCount, methodCount, lineCount);
}
public void findDesignPatterns(Node root) {
// Find potential singleton pattern usage
root.descendants()
.filterIs(ASTMethodDeclaration.class)
.filter(method -> method.getName().equals("getInstance"))
.filter(method -> method.isStatic())
.forEach(method -> System.out.printf("Potential singleton: %s%n",
method.getParent().getFirstChildOfType(ASTClassOrInterfaceDeclaration.class).getName()));
// Find builder pattern usage
root.descendants()
.filterIs(ASTMethodDeclaration.class)
.filter(method -> method.getName().startsWith("set") || method.getName().startsWith("with"))
.filter(method -> method.getReturnType().equals(method.getDeclaringClass().getName()))
.distinct(method -> method.getDeclaringClass())
.forEach(builder -> System.out.printf("Potential builder: %s%n",
builder.getDeclaringClass().getName()));
}
public void securityAnalysis(Node root) {
// Find potential SQL injection points
root.descendants()
.filterIs(ASTMethodCallExpression.class)
.filter(call -> call.getMethodName().contains("executeQuery") ||
call.getMethodName().contains("execute"))
.filter(call -> call.getArguments().stream()
.anyMatch(arg -> arg.hasDescendantOfType(ASTStringLiteral.class)))
.forEach(call -> System.out.printf("Potential SQL injection: %s%n",
call.getTextRegion()));
// Find hardcoded passwords/keys
root.descendants()
.filterIs(ASTStringLiteral.class)
.map(Node::getText)
.filter(text -> text.toLowerCase().contains("password") ||
text.toLowerCase().contains("key") ||
text.toLowerCase().contains("secret"))
.forEach(suspicious -> System.out.printf("Suspicious string: %s%n", suspicious));
}
}Visitor pattern implementation for traversing and processing AST nodes with type-safe method dispatch.
/**
* Visitor pattern for AST traversal with type-safe method dispatch.
* Provides structured way to process different node types.
*/
interface ASTVisitor<T, P> {
/**
* Visit any node (default implementation)
* @param node Node to visit
* @param data Additional data for processing
* @return Result of visiting the node
*/
T visit(Node node, P data);
/**
* Visit child nodes of given node
* @param node Parent node whose children should be visited
* @param data Additional data for processing
* @return Result of visiting children
*/
T visitChildren(Node node, P data);
}
/**
* Base visitor class with default traversal behavior
*/
abstract class ASTVisitorBase<T, P> implements ASTVisitor<T, P> {
@Override
public T visit(Node node, P data) {
return node.childrenAccept(this, data);
}
@Override
public T visitChildren(Node node, P data) {
T result = null;
for (Node child : node.getChildren()) {
T childResult = child.jjtAccept(this, data);
if (childResult != null) {
result = childResult;
}
}
return result;
}
}Usage Examples:
import net.sourceforge.pmd.lang.ast.*;
// Example visitor for collecting method information
public class MethodCollectorVisitor extends ASTVisitorBase<Void, List<String>> {
public Void visit(ASTMethodDeclaration node, List<String> methods) {
// Collect method information
String methodInfo = String.format("%s (line %d)",
node.getName(),
node.getTextRegion().getStartLine());
methods.add(methodInfo);
// Continue visiting children
return visitChildren(node, methods);
}
public Void visit(ASTClassOrInterfaceDeclaration node, List<String> methods) {
System.out.printf("Analyzing class: %s%n", node.getName());
return visitChildren(node, methods);
}
}
// Usage example
public void collectMethods(Node root) {
List<String> methods = new ArrayList<>();
MethodCollectorVisitor visitor = new MethodCollectorVisitor();
root.jjtAccept(visitor, methods);
System.out.printf("Found %d methods:%n", methods.size());
methods.forEach(System.out::println);
}
// Visitor for complexity calculation
public class ComplexityVisitor extends ASTVisitorBase<Integer, Void> {
public Integer visit(ASTMethodDeclaration node, Void data) {
int complexity = 1; // Base complexity
// Add complexity for control flow nodes
complexity += node.descendants()
.filterIs(ASTIfStatement.class)
.count();
complexity += node.descendants()
.filterIs(ASTWhileStatement.class)
.count();
complexity += node.descendants()
.filterIs(ASTForStatement.class)
.count();
complexity += node.descendants()
.filterIs(ASTSwitchStatement.class)
.children()
.filterIs(ASTSwitchLabel.class)
.count();
System.out.printf("Method %s complexity: %d%n", node.getName(), complexity);
return complexity;
}
}/**
* Text region representing position in source file
*/
interface TextRegion {
/**
* Get start offset in characters
* @return Zero-based start position
*/
int getStartOffset();
/**
* Get end offset in characters
* @return Zero-based end position (exclusive)
*/
int getEndOffset();
/**
* Get start line number
* @return One-based start line
*/
int getStartLine();
/**
* Get end line number
* @return One-based end line
*/
int getEndLine();
/**
* Get start column number
* @return One-based start column
*/
int getStartColumn();
/**
* Get end column number
* @return One-based end column
*/
int getEndColumn();
/**
* Get length in characters
* @return Number of characters in region
*/
int getLength();
/**
* Check if region contains given offset
* @param offset Character offset to check
* @return true if offset is within region
*/
boolean contains(int offset);
}
/**
* File location for reporting and error messages
*/
interface FileLocation {
/**
* Get file identifier
* @return FileId for the source file
*/
FileId getFileId();
/**
* Get text region within file
* @return TextRegion with position information
*/
TextRegion getTextRegion();
/**
* Get display string for location
* @return Human-readable location description
*/
String getDisplayName();
}
/**
* Text document interface for source content
*/
interface TextDocument {
/**
* Get complete document text
* @return Full source text content
*/
String getText();
/**
* Get text for specific region
* @param region TextRegion to extract
* @return Text content for the region
*/
String getText(TextRegion region);
/**
* Get line content by number
* @param lineNumber One-based line number
* @return Text content of the line
*/
String getLine(int lineNumber);
/**
* Get total number of lines
* @return Line count in document
*/
int getLineCount();
/**
* Get file identifier
* @return FileId for this document
*/
FileId getFileId();
}
/**
* XPath query capabilities for AST nodes
*/
interface XPathCapable {
/**
* Execute XPath query on node
* @param xpath XPath expression to evaluate
* @return List of matching nodes
*/
List<Node> findChildNodesWithXPath(String xpath);
/**
* Check if XPath query matches any nodes
* @param xpath XPath expression to test
* @return true if query matches at least one node
*/
boolean hasDescendantMatchingXPath(String xpath);
}
/**
* Abstract base node class providing common functionality
*/
abstract class AbstractNode implements Node {
/**
* Accept visitor with double dispatch
* @param visitor ASTVisitor to accept
* @param data Additional data for visitor
* @return Result from visitor
*/
public abstract <T, P> T jjtAccept(ASTVisitor<T, P> visitor, P data);
/**
* Have children accept visitor
* @param visitor ASTVisitor for children
* @param data Additional data for visitor
* @return Result from visiting children
*/
public <T, P> T childrenAccept(ASTVisitor<T, P> visitor, P data) {
T result = null;
for (Node child : getChildren()) {
T childResult = child.jjtAccept(visitor, data);
if (childResult != null) {
result = childResult;
}
}
return result;
}
}Install with Tessl CLI
npx tessl i tessl/maven-net-sourceforge-pmd--pmd-core