Learn how to write a custom rule for PMD
Table of ContentsNote: Ideally most of what is written in this document would be directly in the Javadocs of the relevant classes. This is not the case yet.
This page covers the specifics of writing a rule in Java. The basic development process is very similar to the process for XPath rules, which is described in Your First Rule.
Basically, you open the designer, look at the structure of the AST, and refine your rule as you add test cases.
In this page weâll talk about rules for the Java language, but the process is very similar for other languages.
Note: Please find an index of language-specific documentation here
BasicsTo write a rule in Java youâll have to:
Rule
. Each language implementation provides a base rule class to ease your pain, e.g. AbstractJavaRule
.Most base rule classes use a Visitor pattern to explore the AST.
Tree traversalWhen a rule is applied to a file, itâs handed the root of the AST and told to traverse all the tree to look for violations. Each rule defines a specific visit
method for each type of node for of the language, which by default just visits the children.
So the following rule would traverse the whole tree and do nothing:
public class MyRule extends AbstractJavaRule {
// all methods are default implementations!
}
Generally, a rule wants to check for only some node types. In our XPath example in Your First Rule, we wanted to check for some VariableId
nodes. Thatâs the XPath name, but in Java, youâll get access to the ASTVariableId
full API.
If you want to check for some specific node types, you can override the corresponding visit
method:
public class MyRule extends AbstractJavaRule {
@Override
public Object visit(ASTVariableId node, Object data) {
// This method is called on each node of type ASTVariableId
// in the AST
if (node.getType() == short.class) {
// reports a violation at the position of the node
// the "data" parameter is a context object handed to by your rule
// the message for the violation is the message defined in the rule declaration XML element
asCtx(data).addViolation(node);
}
// this calls back to the default implementation, which recurses further down the subtree
return super.visit(node, data);
}
}
The super.visit(node, data)
call is super common in rule implementations, because it makes the traversal continue by visiting all the descendants of the current node.
Sometimes you have checked all you needed and youâre sure that the descendants of a node may not contain violations. In that case, you can avoid calling the super
implementation and the traversal will not continue further down. This means that your callbacks (visit
implementations) wonât be called on the rest of the subtree. The siblings of the current node may be visited recursively nevertheless.
If you donât care about the order in which the nodes are traversed (e.g. your rule doesnât maintain any state between visits), then you can monumentally speed-up your rule by using the rulechain.
That mechanism doesnât recurse on all the tree, instead, your rule will only be passed the nodes it is interested in. To use the rulechain correctly:
buildTargetSelector
. This method should return a target selector, that selects all the node types you are interested in. E.g. the factory method forTypes
can be used to create such a selector.AbstractJavaRulechainRule
. Youâll need to call the super constructor and provide the node types you are interested in.super.visit
in the methods.In Java rule implementations, you often need to navigate the AST to find the interesting nodes. In your visit
implementation, you can start navigating the AST from the given node.
The Node
interface provides a couple of useful methods that return a NodeStream
and can be used to query the AST:
The returned NodeStream API provides easy to use methods that follow the Java Stream API (java.util.stream
).
Example:
NodeStream.of(someNode) // the stream here is empty if the node is null
.filterIs(ASTVariableDeclaratorId.class)// the stream here is empty if the node was not a variable declarator id
.followingSiblings() // the stream here contains only the siblings, not the original node
.filterIs(ASTVariableInitializer.class)
.children(ASTExpression.class)
.children(ASTPrimaryExpression.class)
.children(ASTPrimaryPrefix.class)
.children(ASTLiteral.class)
.filterMatching(Node::getImage, "0")
.filterNot(ASTLiteral::isStringLiteral)
.nonEmpty(); // If the stream is non empty here, then all the pipeline matched
The Node
interface provides also an alternative way to navigate the AST for convenience:
getParent
getNumChildren
getChild
getFirstChild
getLastChild
getPreviousSibling
getNextSibling
firstChild
Depending on the AST of the language, there might also be more specific methods that can be used to navigate. E.g. in Java there exists the method ASTIfStatement#getCondition
to get the condition of an If-statement.
In your visit method, you have access to the RuleContext
which is the entry point into reporting back during the analysis.
addViolation
reports a rule violation at the position of the given node with the message defined in the rule declaration XML element.{0}
. In that case, you need to call addViolation
and provide the values for the placeholders. The message is actually processed as a java.text.MessageFormat
.addViolationWithMessage
or addViolationWithMessage
. Using these methods, the message defined in the rule declaration XML element is not used.${propertyName}
.${methodName}
to insert the name of the method in which the violation occurred. See Java-specific features and guidance.When starting execution, PMD will instantiate a new instance of your rule. If PMD is executed in multiple threads, then each thread is using its own instance of the rule. This means, that the rule implementation does not need to care about threading issues, as PMD makes sure, that a single instance is not used concurrently by multiple threads.
However, for performance reasons, the rule instances are reused for multiple files. This means, that the constructor of the rule is only executed once (per thread) and the rule instance is reused. If you rely on a proper initialization of instance properties, you can do the initialization in the start
method of the rule (you need to override this method). The start method is called exactly once per file.
Some languages might support metrics.
Using symbol tableSome languages might support symbol table.
Using type resolutionSome languages might support type resolution.
Rule lifecycle reference ConstructionExactly once (per thread):
For each thread, a deep copy of the rule is created. Each thread is given a different set of files to analyse. Then, for each such file and for each rule copy:
start
is called once, before parsingapply
is called with the root of the AST. That method performs the AST traversal that ultimately calls visit methods. Itâs not called for RuleChain rules.end
is called when the rule is done processing the fileSee https://github.com/pmd/pmd-examples for a couple of example projects, that create custom PMD rules for different languages.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4