Added Cyclomatic Complexity rule.

git-svn-id: https://pmd.svn.sourceforge.net/svnroot/pmd/trunk@1341 51baf565-9d33-0410-a72c-fc3788e3496d
This commit is contained in:
Don Leckie
2003-01-16 18:45:20 +00:00
parent 7c15bde289
commit bb284207e5
2 changed files with 321 additions and 3 deletions

View File

@ -177,7 +177,77 @@ public class Foo {
</example>
</rule>
<rule name="CyclomaticComplexityRule"
message = "The {0} ''{1}'' has a Cyclomatic Complexity of {2}."
class="net.sourceforge.pmd.rules.CyclomaticComplexityRule">
<description>
Complexity is determined by the number of decision points in a method plus one for the
method entry. The decision points are 'if', 'while', 'for', and 'case labels'. Scale:
1-4 (low complexity) 5-7 (moderate complexity) 8-10 (high complexity) 10+ (very high complexity)
</description>
<priority>3</priority>
<example>
<![CDATA[
Cyclomatic Complexity = 12
public class Foo
{
1 public void example()
{
2 if (a == b)
{
3 if (a1 == b1)
{
do something;
}
4 else if a2 == b2)
{
do something;
}
else
{
do something;
}
}
5 else if (c == d)
{
6 while (c == d)
{
do something;
}
}
7 else if (e == f)
{
8 for (int n = 0; n < h; n++)
{
do something;
}
}
else
{
switch (z)
{
9 case 1:
do something;
break;
10 case 2:
do something;
break;
11 case 3:
do something;
break;
12 default:
do something;
break;
}
}
}
}
]]>
</example>
</rule>
</ruleset>

View File

@ -0,0 +1,248 @@
package net.sourceforge.pmd.rules;
import java.text.MessageFormat;
import java.util.Stack;
import net.sourceforge.pmd.AbstractRule;
import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.ast.ASTForStatement;
import net.sourceforge.pmd.ast.ASTIfStatement;
import net.sourceforge.pmd.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.ast.ASTNestedClassDeclaration;
import net.sourceforge.pmd.ast.ASTSwitchLabel;
import net.sourceforge.pmd.ast.ASTWhileStatement;
import net.sourceforge.pmd.ast.ASTUnmodifiedClassDeclaration;
import net.sourceforge.pmd.ast.Node;
import net.sourceforge.pmd.ast.SimpleNode;
import net.sourceforge.pmd.Report;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleViolation;
/**
*
* @author Donald A. Leckie
* @since January 14, 2003
* @version $Revision$, $Date$
*/
public class CyclomaticComplexityRule extends AbstractRule
{
private Stack m_entryStack = new Stack();
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTIfStatement node, Object data)
{
Entry entry = (Entry) m_entryStack.peek();
entry.m_decisionPoints++;
super.visit(node, data);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTForStatement node, Object data)
{
Entry entry = (Entry) m_entryStack.peek();
entry.m_decisionPoints++;
super.visit(node, data);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTSwitchLabel node, Object data)
{
Entry entry = (Entry) m_entryStack.peek();
// *******
// Needs work: don't count label if there is no block under it.
entry.m_decisionPoints++;
super.visit(node, data);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTWhileStatement node, Object data)
{
Entry entry = (Entry) m_entryStack.peek();
entry.m_decisionPoints++;
super.visit(node, data);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTUnmodifiedClassDeclaration node, Object data)
{
m_entryStack.push(new Entry(node));
super.visit(node, data);
// The {0} "{1}" has a cyclomatic complexity of {2}.
Entry classEntry = (Entry) m_entryStack.pop();
double decisionPoints = (double) classEntry.m_decisionPoints;
double methodCount = (double) classEntry.m_methodCount;
int complexityAverage = (int) (Math.rint(decisionPoints / methodCount));
RuleContext ruleContext = (RuleContext) data;
String template = getMessage();
String className = node.getImage();
String complexityHighest = String.valueOf(classEntry.m_highestDecisionPoints);
String complexity = String.valueOf(complexityAverage)
+ " (Highest = "
+ complexityHighest
+ ")";
String[] args = {"class", className, complexity};
String message = MessageFormat.format(template, args);
int lineNumber = node.getBeginLine();
RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
ruleContext.getReport().addRuleViolation(ruleViolation);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTMethodDeclaration node, Object data)
{
m_entryStack.push(new Entry(node));
super.visit(node, data);
Entry methodEntry = (Entry) m_entryStack.pop();
int methodDecisionPoints = methodEntry.m_decisionPoints;
Entry classEntry = (Entry) m_entryStack.peek();
classEntry.m_methodCount++;
classEntry.m_decisionPoints += methodDecisionPoints;
if (methodDecisionPoints > classEntry.m_highestDecisionPoints)
{
classEntry.m_highestDecisionPoints = methodDecisionPoints;
}
ASTMethodDeclarator methodDeclarator = null;
for (int n = 0; n < node.jjtGetNumChildren(); n++)
{
Node childNode = node.jjtGetChild(n);
if (childNode instanceof ASTMethodDeclarator)
{
methodDeclarator = (ASTMethodDeclarator) childNode;
break;
}
}
// The {0} "{1}" has a cyclomatic complexity of {2}.
RuleContext ruleContext = (RuleContext) data;
String template = getMessage();
String methodName = (methodDeclarator == null) ? "" : methodDeclarator.getImage();
String complexity = String.valueOf(methodEntry.m_decisionPoints);
String[] args = {"method", methodName, complexity};
String message = MessageFormat.format(template, args);
int lineNumber = node.getBeginLine();
RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
ruleContext.getReport().addRuleViolation(ruleViolation);
return data;
}
/**
**************************************************************************
*
* @param node
* @param data
*
* @return
*/
public Object visit(ASTConstructorDeclaration node, Object data)
{
m_entryStack.push(new Entry(node));
super.visit(node, data);
Entry constructorEntry = (Entry) m_entryStack.pop();
int constructorDecisionPointCount = constructorEntry.m_decisionPoints;
Entry classEntry = (Entry) m_entryStack.peek();
classEntry.m_methodCount++;
classEntry.m_decisionPoints += constructorDecisionPointCount;
if (constructorDecisionPointCount > classEntry.m_highestDecisionPoints)
{
classEntry.m_highestDecisionPoints = constructorDecisionPointCount;
}
// The {0} "{1}" has a cyclomatic complexity of {2}.
RuleContext ruleContext = (RuleContext) data;
String template = getMessage();
String constructorName = classEntry.m_node.getImage();
String complexity = String.valueOf(constructorDecisionPointCount);
String[] args = {"constructor", constructorName, complexity};
String message = MessageFormat.format(template, args);
int lineNumber = node.getBeginLine();
RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
ruleContext.getReport().addRuleViolation(ruleViolation);
return data;
}
/**
***************************************************************************
***************************************************************************
***************************************************************************
*/
private class Entry
{
// ASTUnmodifedClassDeclaration or ASTMethodDeclarator or ASTConstructorDeclaration
private SimpleNode m_node;
private int m_decisionPoints = 1;
private int m_highestDecisionPoints;
private int m_methodCount;
/**
***********************************************************************
*
* @param node
*/
private Entry(SimpleNode node)
{
m_node = node;
}
}
}