forked from phoedos/pmd
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:
@ -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>
|
||||
|
||||
|
||||
|
||||
|
248
pmd/src/net/sourceforge/pmd/rules/CyclomaticComplexityRule.java
Normal file
248
pmd/src/net/sourceforge/pmd/rules/CyclomaticComplexityRule.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user