Added new NpathComplexity rule from Jason Bennett

git-svn-id: https://pmd.svn.sourceforge.net/svnroot/pmd/trunk@4636 51baf565-9d33-0410-a72c-fc3788e3496d
This commit is contained in:
Tom Copeland
2006-10-13 13:30:21 +00:00
parent 36803bc9f6
commit 730234d7ae
6 changed files with 267 additions and 1 deletions

View File

@ -1,6 +1,7 @@
?????, 2006 - 3.9:
New rules:
Basic ruleset: BigIntegerInstantiation(1.4 and 1.5)
Codesize ruleset: NPathComplexity
PMD now requires JDK 1.4 to run. Note that, however, PMD will still analyze code from earlier JDKs.
SummaryHTML Report changes from Brent Fisher - now contains linePrefix to support source output from javadoc using "linksource"
Fixed bug 1570915 - AvoidRethrowingException no longer reports a false positive for certain nested exceptions.

View File

@ -10,6 +10,16 @@
The Code Size Ruleset contains a collection of rules that find code size related problems.
</description>
<rule name="NPathComplexity" message="The method {0}() has an NPath complexity of {1}"
class="net.sourceforge.pmd.rules.design.NpathComplexity">
<description>
The NPath complexity of a method is the number of acyclic execution paths through that method.
A threshold of 200 is generally considered the point where measures should be taken to reduce complexity.
</description>
<properties>
<property name="minimum" description="The npath reporting threshold" value="200"/>
</properties>
</rule>
<rule name="ExcessiveMethodLength"
message="Avoid really long methods."

View File

@ -7,4 +7,5 @@ This ruleset contains links to rules that are new in PMD v3.9
</description>
<rule ref="rulesets/basic.xml/BigIntegerInstantiation_1.5"/>
<rule ref="rulesets/codesize.xml/NPathComplexity"/>
</ruleset>

View File

@ -72,5 +72,6 @@ public class SimpleRuleSetNameMapper {
nameMap.put("36", "rulesets/releases/36.xml");
nameMap.put("37", "rulesets/releases/37.xml");
nameMap.put("38", "rulesets/releases/38.xml");
nameMap.put("39", "rulesets/releases/39.xml");
}
}

View File

@ -0,0 +1,253 @@
package net.sourceforge.pmd.rules.design;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.ast.ASTConditionalAndExpression;
import net.sourceforge.pmd.ast.ASTConditionalExpression;
import net.sourceforge.pmd.ast.ASTConditionalOrExpression;
import net.sourceforge.pmd.ast.ASTDoStatement;
import net.sourceforge.pmd.ast.ASTExpression;
import net.sourceforge.pmd.ast.ASTForStatement;
import net.sourceforge.pmd.ast.ASTIfStatement;
import net.sourceforge.pmd.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.ast.ASTReturnStatement;
import net.sourceforge.pmd.ast.ASTStatement;
import net.sourceforge.pmd.ast.ASTSwitchLabel;
import net.sourceforge.pmd.ast.ASTSwitchStatement;
import net.sourceforge.pmd.ast.ASTTryStatement;
import net.sourceforge.pmd.ast.ASTWhileStatement;
import net.sourceforge.pmd.ast.SimpleJavaNode;
import net.sourceforge.pmd.stat.DataPoint;
import net.sourceforge.pmd.stat.StatisticalRule;
import net.sourceforge.pmd.util.NumericConstants;
/**
* NPath complexity is a measurement of the acyclic execution paths through a
* function. See Nejmeh, Communications of the ACM Feb 1988 pp 188-200.
*
* @author Jason Bennett
*/
public class NpathComplexity extends StatisticalRule {
public Object visit(ASTMethodDeclaration node, Object data) {
int npath = 1;
// Basic NPath functionality multiplies the complexity of peer nodes
for ( int i = 0; i < node.jjtGetNumChildren(); i++ ) {
SimpleJavaNode simpleNode = (SimpleJavaNode) node.jjtGetChild( i );
Integer complexity = (Integer) simpleNode.jjtAccept( this, data );
npath *= complexity.intValue();
}
DataPoint point = new DataPoint();
point.setNode( node );
point.setScore( 1.0 * npath );
point.setMessage( getMessage() );
addDataPoint( point );
return new Integer( npath );
}
public Object visit(SimpleJavaNode node, Object data) {
int npath = 1;
for ( int i = 0; i < node.jjtGetNumChildren(); i++ ) {
SimpleJavaNode simpleNode = (SimpleJavaNode) node.jjtGetChild( i );
Integer complexity = (Integer) simpleNode.jjtAccept( this, data );
npath *= complexity.intValue();
}
return new Integer( npath );
}
public Object visit(ASTIfStatement node, Object data) {
// (npath of if + npath of else (or 1) + bool_comp of if) * npath of next
int boolCompIf = sumNpathExpression( (ASTExpression) node.getFirstChildOfType( ASTExpression.class ) );
int complexity = 0;
List statementChildren = new ArrayList();
for ( int i = 0; i < node.jjtGetNumChildren(); i++ ) {
if ( node.jjtGetChild( i ).getClass() == ASTStatement.class ) {
statementChildren.add( node.jjtGetChild( i ) );
}
}
if ( statementChildren.isEmpty()
|| ( statementChildren.size() == 1 && node.hasElse() )
|| ( statementChildren.size() != 1 && !node.hasElse() ) ) {
throw new IllegalStateException( "If node has wrong number of children" );
}
// add path for not taking if
if ( !node.hasElse() ) {
complexity++;
}
for ( Iterator iter = statementChildren.iterator(); iter.hasNext(); ) {
SimpleJavaNode element = (SimpleJavaNode) iter.next();
complexity += ( (Integer) element.jjtAccept( this, data ) ).intValue();
}
return new Integer( boolCompIf + complexity );
}
public Object visit(ASTWhileStatement node, Object data) {
// (npath of while + bool_comp of while + 1) * npath of next
int boolCompWhile = sumNpathExpression( (ASTExpression) node.getFirstChildOfType( ASTExpression.class ) );
Integer nPathWhile = (Integer) ( (SimpleJavaNode) node.getFirstChildOfType( ASTStatement.class ) ).jjtAccept(
this, data );
return new Integer( boolCompWhile + nPathWhile.intValue() + 1 );
}
public Object visit(ASTDoStatement node, Object data) {
// (npath of do + bool_comp of do + 1) * npath of next
int boolCompDo = sumNpathExpression( (ASTExpression) node.getFirstChildOfType( ASTExpression.class ) );
Integer nPathDo = (Integer) ( (SimpleJavaNode) node.getFirstChildOfType( ASTStatement.class ) ).jjtAccept(
this, data );
return new Integer( boolCompDo + nPathDo.intValue() + 1 );
}
public Object visit(ASTForStatement node, Object data) {
// (npath of for + bool_comp of for + 1) * npath of next
int boolCompFor = sumNpathExpression( (ASTExpression) node.getFirstChildOfType( ASTExpression.class ) );
Integer nPathFor = (Integer) ( (SimpleJavaNode) node.getFirstChildOfType( ASTStatement.class ) ).jjtAccept(
this, data );
return new Integer( boolCompFor + nPathFor.intValue() + 1 );
}
public Object visit(ASTReturnStatement node, Object data) {
// return statements are valued at 1, or the value of the boolean expression
ASTExpression expr = (ASTExpression) node.getFirstChildOfType( ASTExpression.class );
if ( expr == null ) {
return NumericConstants.ONE;
}
List andNodes = expr.findChildrenOfType( ASTConditionalAndExpression.class );
List orNodes = expr.findChildrenOfType( ASTConditionalOrExpression.class );
int boolCompReturn = andNodes.size() + orNodes.size();
if ( boolCompReturn > 0 ) {
return new Integer( boolCompReturn );
}
return NumericConstants.ONE;
}
public Object visit(ASTSwitchStatement node, Object data) {
// bool_comp of switch + sum(npath(case_range))
int boolCompSwitch = sumNpathExpression( (ASTExpression) node.getFirstChildOfType( ASTExpression.class ) );
int npath = 0;
int caseRange = 0;
for ( int i = 0; i < node.jjtGetNumChildren(); i++ ) {
SimpleJavaNode simpleNode = (SimpleJavaNode) node.jjtGetChild( i );
// Fall-through labels count as 1 for complexity
if ( simpleNode instanceof ASTSwitchLabel ) {
npath += caseRange;
caseRange = 1;
} else {
Integer complexity = (Integer) simpleNode.jjtAccept( this, data );
caseRange *= complexity.intValue();
}
}
// add in npath of last label
npath += caseRange;
return new Integer( boolCompSwitch + npath );
}
public Object visit(ASTTryStatement node, Object data) {
/*
* This scenario was not addressed by the original paper. Based on the
* principles outlined in the paper, as well as the Checkstyle NPath
* implementation, this code will add the complexity of the try to the
* complexities of the catch and finally blocks.
*/
int npath = 0;
for ( int i = 0; i < node.jjtGetNumChildren(); i++ ) {
SimpleJavaNode simpleNode = (SimpleJavaNode) node.jjtGetChild( i );
Integer complexity = (Integer) simpleNode.jjtAccept( this, data );
npath += complexity.intValue();
}
return new Integer( npath );
}
public Object visit(ASTConditionalExpression node, Object data) {
if ( node.isTernary() ) {
int npath = 0;
for ( int i = 0; i < node.jjtGetNumChildren(); i++ ) {
SimpleJavaNode simpleNode = (SimpleJavaNode) node.jjtGetChild( i );
Integer complexity = (Integer) simpleNode.jjtAccept( this, data );
npath += complexity.intValue();
}
npath += 2;
return new Integer( npath );
}
return NumericConstants.ONE;
}
/**
* Calculate the boolean complexity of the given expression. NPath boolean
* complexity is the sum of && and || tokens. This is calculated by summing
* the number of children of the &&'s (minus one) and the children of the ||'s
* (minus one).
*
* @param expr
* control structure expression
* @return complexity of the boolean expression
*/
private int sumNpathExpression(ASTExpression expr) {
List andNodes = expr.findChildrenOfType( ASTConditionalAndExpression.class );
List orNodes = expr.findChildrenOfType( ASTConditionalOrExpression.class );
int children = 0;
for ( Iterator iter = orNodes.iterator(); iter.hasNext(); ) {
ASTConditionalOrExpression element = (ASTConditionalOrExpression) iter.next();
children += element.jjtGetNumChildren();
children--;
}
for ( Iterator iter = andNodes.iterator(); iter.hasNext(); ) {
ASTConditionalAndExpression element = (ASTConditionalAndExpression) iter.next();
children += element.jjtGetNumChildren();
children--;
}
return children;
}
protected void makeViolations(RuleContext ctx, Set p) {
Iterator points = p.iterator();
while ( points.hasNext() ) {
DataPoint point = (DataPoint) points.next();
addViolation( ctx, point.getNode(), new String[] {
( (ASTMethodDeclaration) point.getNode() ).getMethodName(),
String.valueOf( (int) point.getScore() ) } );
}
}
}

View File

@ -52,8 +52,8 @@
</subsection>
<subsection name="Contributors">
<ul>
<li>Jason Bennett - Wrote NPathPatches to improve CyclomaticComplexity rule</li>
<li>Brent Fisher - SummaryHTML report improvements</li>
<li>Jason Bennett - Patches to improve CyclomaticComplexity rule</li>
<li>George Thomas - Wrote AvoidRethrowingException rule</li>
<li>Robert Simmons - Reported bug in optimizations package along with suggestions for fix</li>
<li>Brian Remedios - display cleanup of CPD GUI, code cleanup of StringUtil and various rules, cleanup of rule designer, code cleanup of net.sourceforge.pmd.ant.Formatter.java, code improvements to Eclipse plugin, created AbstractPoorMethodCall and refactored UseIndexOfChar</li>