How to write a PMD ruleWriting PMD rules is cool because you don't have to wait for us to get around to implementing feature requests. Figure out what you want to look forThere are two way to write rules:
We'll cover the Java class first and the XPath second. Most of this documentation is applicable to both methods, too, so read on. Figure out what you want to look forLets's figure out what problem we want to spot. We can use "While loops must use braces" as an example. In the source code below, it's easy to get lost visually - it's kind of hard to tell what the curly braces belong to. public class Example { public void bar() { while (baz) buz.doSomething(); } } So we know what an example in source code looks like, which is half the battle. Write a test-data example and look at the AST
PMD doesn't use the source code directly; it uses a CompilationUnit TypeDeclaration ClassDeclaration UnmodifiedClassDeclaration ClassBody ClassBodyDeclaration MethodDeclaration ResultType MethodDeclarator FormalParameters Block BlockStatement Statement WhileStatement Expression PrimaryExpression PrimaryPrefix Name Statement StatementExpression PrimaryExpression PrimaryPrefix Name PrimarySuffix Arguments You can generate this yourself by:
So you can see in the example above that the AST for a WhileStatement Expression Statement StatementExpression If you were to add curly braces and click "Go" again, you'd see that the AST would change a bit. It'd look like this: WhileStatement Expression Statement Block BlockStatement Statement StatementExpression
Ah ha! We see that the curly braces add a couple more AST nodes - a By the way, all this structural information - i.e., the fact that a Statement may be followed a Block - is concisely defined in the EBNF grammar . So, for example, the Statement definition looks like this: void Statement() : {} { LOOKAHEAD(2) LabeledStatement() | Block() | EmptyStatement() | StatementExpression() ";" | SwitchStatement() | IfStatement() | WhileStatement() | DoStatement() | ForStatement() | BreakStatement() | ContinueStatement() | ReturnStatement() | ThrowStatement() | SynchronizedStatement() | TryStatement() | AssertStatement() } Write a rule class
Create a new Java class that extends public class WhileLoopsMustUseBracesRule extends AbstractRule { }
That was easy. PMD works by creating the AST and then traverses it recursively so a rule can get a callback
for any type it's interested in. So let's make sure our rule gets called whenever
the AST traversal finds a public class WhileLoopsMustUseBracesRule extends AbstractRule { public Object visit(ASTWhileStatement node, Object data) { System.out.println("hello world"); return data; } }
We stuck a Put the WhileLoopsMustUseBracesRule rule in a ruleset file
Now our rule is written - at least, the shell of it is - and now we need to tell PMD about it. We need
to add it to a rule set XML file. Look at
The whole ruleset file should look something like this: <?xml version="1.0"?> <rule name="WhileLoopsMustUseBracesRule" message="Avoid using 'while' statements without curly braces" class="net.sourceforge.pmd.rules.XPathRule"> <description> Avoid using 'while' statements without using curly braces </description> <priority>3</priority> <example> public void doSomething() { while (true) x++; } </example> </rule> </ruleset> OK, well, it won't look exactly like that - you'll need to wrap the example in a CDATA tag. But I couldn't figure out how to do that without Maven getting confused. If you know how to do this, please let me know. Run PMD using your new rulesetOK, let's run the new rule so we can see something work. Like this: run.bat c:\path\to\my\src xml c:\path\to\mycustomrules.xml This time your "hello world" will show up right after the AST gets printed out. If it doesn't, post a message to the forum so we can improve this document :-) Write code to add rule violations where appropriate
Now that we've identified our problem, recognized the AST pattern that
illustrates the problem, written a new rule, and plugged
it into a ruleset, we need to actually make our rule find the problem, create a public class WhileLoopsMustUseBracesRule extends AbstractRule { public Object visit(ASTWhileStatement node, Object data) { SimpleNode firstStmt = (SimpleNode)node.jjtGetChild(1); if (!hasBlockAsFirstChild(firstStmt)) { RuleContext ctx = (RuleContext)data; ctx.getReport().addRuleViolation(createRuleViolation(ctx, node.getBeginLine())); } return super.visit(node,data); } private boolean hasBlockAsFirstChild(SimpleNode node) { return (node.jjtGetNumChildren() != 0 && (node.jjtGetChild(0) instanceof ASTBlock)); } } TODO - if you don't understand the code for the rule, post a message to the forum so we can improve this document :-) Writing a rule as an XPath expressionRecently Daniel Sheppard integrated an XPath engine into PMD, so now you can write rules as XPath expressions. For example, the XPath expression for our WhileLoopsMustUseBracesRule looks like this: //WhileStatement[not(Statement/Block)] Concise, eh? Here's an article with a lot more detail. Note that for XPath rules you'll need to set the
<rule name="EmptyCatchBlock" message="Avoid empty catch blocks" class="net.sourceforge.pmd.rules.XPathRule"> <description> etc., etc. Note that access modifiers are held as attributes, so, for example, //FieldDeclaration[@Private='true'] Bundle it upTo use your rules as part of a nightly build or whatever, it's helpful to bundle up both the rule and the ruleset.xml file in a jar file. Then you can put that jar file on the CLASSPATH of your build. Setting up a script or an Ant task to do this can save you some tedious typing. Repeat as necessaryI've found that my rules usually don't work the first time, and so I have to go back and tweak them a couple times. That's OK, if we were perfect programmers PMD would be useless anyhow :-). As an acceptance test of sorts, I usually run a rule on the JDK 1.4 source code and make sure that a random sampling of the problems found are in fact legitimate rule violations. This also ensures that the rule doesn't get confused by nested inner classes or any of the other oddities that appear at various points in the JDK source. You're rolling now. If you think a rule would benefit the Java development community as a whole, post a message to the forum so we can get the rule moved into one of the core rulesets. Or, if you can improve one of the existing rules, that'd be great too! Thanks! |