Merge branch 'pr-523'
This commit is contained in:
@ -4,6 +4,8 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.api;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.AtfdMetric.AtfdClassMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloClassMetric;
|
||||
@ -101,7 +103,20 @@ public enum JavaClassMetricKey implements MetricKey<ASTAnyTypeDeclaration> {
|
||||
public boolean supports(ASTAnyTypeDeclaration node) {
|
||||
return metric.supports(node);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj != null && getClass() == obj.getClass()
|
||||
&& Objects.equals(name(), getClass().cast(obj).name())
|
||||
&& Objects.equals(getCalculator(), getClass().cast(obj).getCalculator());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (metric != null ? metric.hashCode() * 31 : 0) + (name != null ? name.hashCode() : 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,11 +4,14 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.api;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.AtfdMetric.AtfdOperationMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloOperationMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.LocMetric.LocOperationMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.NcssMetric.NcssOperationMetric;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.NpathMetric;
|
||||
import net.sourceforge.pmd.lang.metrics.api.Metric;
|
||||
import net.sourceforge.pmd.lang.metrics.api.MetricKey;
|
||||
|
||||
@ -43,7 +46,18 @@ public enum JavaOperationMetricKey implements MetricKey<ASTMethodOrConstructorDe
|
||||
*
|
||||
* @see net.sourceforge.pmd.lang.java.metrics.impl.LocMetric
|
||||
*/
|
||||
LOC(new LocOperationMetric());
|
||||
LOC(new LocOperationMetric()),
|
||||
|
||||
|
||||
/**
|
||||
* N-path complexity.
|
||||
*
|
||||
* @see NpathMetric
|
||||
*/
|
||||
NPATH(new NpathMetric());
|
||||
|
||||
|
||||
|
||||
|
||||
private final JavaOperationMetric calculator;
|
||||
|
||||
@ -93,6 +107,21 @@ public enum JavaOperationMetricKey implements MetricKey<ASTMethodOrConstructorDe
|
||||
public boolean supports(ASTMethodOrConstructorDeclaration node) {
|
||||
return metric.supports(node);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj != null && getClass() == obj.getClass()
|
||||
&& Objects.equals(name(), getClass().cast(obj).name())
|
||||
&& Objects.equals(getCalculator(), getClass().cast(obj).getCalculator());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (metric != null ? metric.hashCode() * 31 : 0) + (name != null ? name.hashCode() : 0);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,14 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableInt;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaParserVisitor;
|
||||
import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
|
||||
@ -38,6 +43,7 @@ import net.sourceforge.pmd.lang.metrics.api.ResultOption;
|
||||
* fall-through cases in {@code switch} statements are not counted as well.
|
||||
*
|
||||
* <p>References:
|
||||
*
|
||||
* <ul>
|
||||
* <li> [1] Lanza, Object-Oriented Metrics in Practice, 2005.
|
||||
* <li> [2] McCabe, A Complexity Measure, in Proceedings of the 2nd ICSE (1976).
|
||||
@ -49,8 +55,43 @@ import net.sourceforge.pmd.lang.metrics.api.ResultOption;
|
||||
*/
|
||||
public final class CycloMetric {
|
||||
|
||||
private CycloMetric() {
|
||||
|
||||
}
|
||||
|
||||
// TODO:cf Cyclo should develop factorized boolean operators to count them
|
||||
|
||||
|
||||
/**
|
||||
* Evaluates the number of paths through a boolean expression. This is the total number of {@code &&} and {@code ||}
|
||||
* operators appearing in the expression. This is used in the calculation of cyclomatic and n-path complexity.
|
||||
*
|
||||
* @param expr Expression to analyse
|
||||
*
|
||||
* @return The number of paths through the expression
|
||||
*/
|
||||
public static int booleanExpressionComplexity(ASTExpression expr) {
|
||||
if (expr == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<ASTConditionalAndExpression> andNodes = expr.findDescendantsOfType(ASTConditionalAndExpression.class);
|
||||
List<ASTConditionalOrExpression> orNodes = expr.findDescendantsOfType(ASTConditionalOrExpression.class);
|
||||
|
||||
int complexity = 0;
|
||||
|
||||
for (ASTConditionalOrExpression element : orNodes) {
|
||||
complexity += element.jjtGetNumChildren() - 1;
|
||||
}
|
||||
|
||||
for (ASTConditionalAndExpression element : andNodes) {
|
||||
complexity += element.jjtGetNumChildren() - 1;
|
||||
}
|
||||
|
||||
return complexity;
|
||||
}
|
||||
|
||||
|
||||
/** Variants of CYCLO. */
|
||||
public enum CycloVersion implements MetricVersion {
|
||||
/** Do not count the paths in boolean expressions as decision points. */
|
||||
@ -78,4 +119,5 @@ public final class CycloMetric {
|
||||
return 1 + JavaMetrics.get(JavaOperationMetricKey.CYCLO, node, version, ResultOption.AVERAGE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.impl;
|
||||
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.visitors.DefaultNpathVisitor;
|
||||
import net.sourceforge.pmd.lang.metrics.api.MetricVersion;
|
||||
|
||||
/**
|
||||
* 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 Clément Fournier
|
||||
*/
|
||||
public class NpathMetric extends AbstractJavaOperationMetric {
|
||||
|
||||
@Override
|
||||
public double computeFor(ASTMethodOrConstructorDeclaration node, MetricVersion version) {
|
||||
return (Integer) node.jjtAccept(new DefaultNpathVisitor(), null);
|
||||
}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.impl.visitors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaNode;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorReducedAdapter;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric;
|
||||
|
||||
/**
|
||||
* Visitor for the default n-path complexity version.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @author Jason Bennett
|
||||
*/
|
||||
public class DefaultNpathVisitor extends JavaParserVisitorReducedAdapter {
|
||||
|
||||
/* Multiplies the complexity of the children of this node. */
|
||||
private int multiplyChildrenComplexities(JavaNode node, Object data) {
|
||||
int product = 1;
|
||||
|
||||
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
|
||||
JavaNode n = (JavaNode) node.jjtGetChild(i);
|
||||
product *= (Integer) n.jjtAccept(this, data);
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
|
||||
/* Sums the complexity of the children of the node. */
|
||||
private int sumChildrenComplexities(JavaNode node, Object data) {
|
||||
int sum = 0;
|
||||
|
||||
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
|
||||
JavaNode n = (JavaNode) node.jjtGetChild(i);
|
||||
sum += (Integer) n.jjtAccept(this, data);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTMethodOrConstructorDeclaration node, Object data) {
|
||||
return multiplyChildrenComplexities(node, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(JavaNode node, Object data) {
|
||||
return multiplyChildrenComplexities(node, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTIfStatement node, Object data) {
|
||||
// (npath of if + npath of else (or 1) + bool_comp of if) * npath of next
|
||||
|
||||
List<ASTStatement> statementChildren = node.findChildrenOfType(ASTStatement.class);
|
||||
|
||||
// add path for not taking if
|
||||
int complexity = node.hasElse() ? 0 : 1;
|
||||
|
||||
for (ASTStatement element : statementChildren) {
|
||||
complexity += (Integer) element.jjtAccept(this, data);
|
||||
}
|
||||
|
||||
int boolCompIf = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
return boolCompIf + complexity;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTWhileStatement node, Object data) {
|
||||
// (npath of while + bool_comp of while + 1) * npath of next
|
||||
|
||||
int boolCompWhile = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
|
||||
int nPathWhile = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
|
||||
|
||||
return boolCompWhile + nPathWhile + 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTDoStatement node, Object data) {
|
||||
// (npath of do + bool_comp of do + 1) * npath of next
|
||||
|
||||
int boolCompDo = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
|
||||
int nPathDo = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
|
||||
|
||||
return boolCompDo + nPathDo + 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTForStatement node, Object data) {
|
||||
// (npath of for + bool_comp of for + 1) * npath of next
|
||||
|
||||
int boolCompFor = CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class));
|
||||
|
||||
int nPathFor = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data);
|
||||
|
||||
return boolCompFor + nPathFor + 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTReturnStatement node, Object data) {
|
||||
// return statements are valued at 1, or the value of the boolean expression
|
||||
|
||||
ASTExpression expr = node.getFirstChildOfType(ASTExpression.class);
|
||||
|
||||
if (expr == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int boolCompReturn = CycloMetric.booleanExpressionComplexity(expr);
|
||||
int conditionalExpressionComplexity = multiplyChildrenComplexities(expr, data);
|
||||
|
||||
if (conditionalExpressionComplexity > 1) {
|
||||
boolCompReturn += conditionalExpressionComplexity;
|
||||
}
|
||||
|
||||
return (boolCompReturn > 0) ? boolCompReturn : 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTSwitchStatement node, Object data) {
|
||||
// bool_comp of switch + sum(npath(case_range))
|
||||
|
||||
int boolCompSwitch = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
|
||||
int npath = 0;
|
||||
int caseRange = 0;
|
||||
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
|
||||
JavaNode n = (JavaNode) node.jjtGetChild(i);
|
||||
|
||||
// Fall-through labels count as 1 for complexity
|
||||
if (n instanceof ASTSwitchLabel) {
|
||||
npath += caseRange;
|
||||
caseRange = 1;
|
||||
} else {
|
||||
Integer complexity = (Integer) n.jjtAccept(this, data);
|
||||
caseRange *= complexity;
|
||||
}
|
||||
}
|
||||
// add in npath of last label
|
||||
npath += caseRange;
|
||||
return boolCompSwitch + npath;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTConditionalExpression node, Object data) {
|
||||
return node.isTernary() ? sumChildrenComplexities(node, data) + 2 : 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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.
|
||||
*/
|
||||
return sumChildrenComplexities(node, data);
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaNode;
|
||||
import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric;
|
||||
import net.sourceforge.pmd.lang.java.rule.codesize.NPathComplexityRule;
|
||||
|
||||
/**
|
||||
@ -29,7 +30,7 @@ public class StandardCycloVisitor extends CycloPathUnawareOperationVisitor {
|
||||
public Object visit(ASTIfStatement node, Object data) {
|
||||
super.visit(node, data);
|
||||
|
||||
int boolCompIf = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
int boolCompIf = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
((MutableInt) data).add(boolCompIf);
|
||||
return data;
|
||||
}
|
||||
@ -39,8 +40,7 @@ public class StandardCycloVisitor extends CycloPathUnawareOperationVisitor {
|
||||
public Object visit(ASTForStatement node, Object data) {
|
||||
super.visit(node, data);
|
||||
|
||||
int boolCompFor = NPathComplexityRule
|
||||
.sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class));
|
||||
int boolCompFor = CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class));
|
||||
((MutableInt) data).add(boolCompFor);
|
||||
return data;
|
||||
}
|
||||
@ -50,7 +50,7 @@ public class StandardCycloVisitor extends CycloPathUnawareOperationVisitor {
|
||||
public Object visit(ASTDoStatement node, Object data) {
|
||||
super.visit(node, data);
|
||||
|
||||
int boolCompDo = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
int boolCompDo = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
((MutableInt) data).add(boolCompDo);
|
||||
return data;
|
||||
}
|
||||
@ -60,7 +60,7 @@ public class StandardCycloVisitor extends CycloPathUnawareOperationVisitor {
|
||||
public Object visit(ASTSwitchStatement node, Object data) {
|
||||
super.visit((JavaNode) node, data); // skip the superclass' treatment
|
||||
|
||||
int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
int boolCompSwitch = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
((MutableInt) data).add(boolCompSwitch);
|
||||
return data;
|
||||
}
|
||||
@ -80,7 +80,7 @@ public class StandardCycloVisitor extends CycloPathUnawareOperationVisitor {
|
||||
public Object visit(ASTWhileStatement node, Object data) {
|
||||
super.visit(node, data);
|
||||
|
||||
int boolCompWhile = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
int boolCompWhile = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class));
|
||||
((MutableInt) data).add(boolCompWhile);
|
||||
return data;
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.rule;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
|
||||
import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule;
|
||||
import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
|
||||
|
||||
/**
|
||||
* Simple n-path complexity rule.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public class NPathComplexityRule extends AbstractJavaMetricsRule {
|
||||
|
||||
private static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty(
|
||||
"reportLevel", "N-Path Complexity reporting threshold", 1, 30, 200, 1.0f);
|
||||
|
||||
|
||||
private static int reportLevel = 200;
|
||||
|
||||
|
||||
public NPathComplexityRule() {
|
||||
definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTCompilationUnit node, Object data) {
|
||||
reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
|
||||
|
||||
super.visit(node, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Object visit(ASTMethodOrConstructorDeclaration node, Object data) {
|
||||
int npath = (int) JavaMetrics.get(JavaOperationMetricKey.NPATH, node);
|
||||
if (npath >= reportLevel) {
|
||||
addViolation(data, node, new String[] {node instanceof ASTMethodDeclaration ? "method" : "constructor",
|
||||
node.getQualifiedName().getOperation(), "" + npath, });
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
@ -21,12 +21,12 @@ Complexity directly affects maintenance costs is determined by the number of dec
|
||||
plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls.
|
||||
Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote
|
||||
high complexity, and 11+ is very high complexity.
|
||||
]]>
|
||||
]]>
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo { // This has a Cyclomatic Complexity = 12
|
||||
public class Foo { // This has a Cyclomatic Complexity = 12
|
||||
1 public void example() {
|
||||
2 if (a == b) {
|
||||
3 if (a1 == b1) {
|
||||
@ -66,7 +66,6 @@ public class Foo { // This has a Cyclomatic Complexity = 12
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<!-- TODO -->
|
||||
<rule name="NcssCount"
|
||||
message="The {0} ''{1}'' has a NCSS line count of {2}."
|
||||
since="3.9"
|
||||
@ -82,19 +81,65 @@ public class Foo { // This has a Cyclomatic Complexity = 12
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo extends Bar {
|
||||
public Foo() {
|
||||
//this class only has 4 NCSS lines
|
||||
super();
|
||||
public Foo() { //this class only has 4 NCSS lines
|
||||
super();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
super.foo();
|
||||
}
|
||||
super.foo();
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="NPathComplexity"
|
||||
since="3.9"
|
||||
message="The {0} ''{1}'' has an NPath complexity of {2}"
|
||||
class="net.sourceforge.pmd.lang.java.metrics.rule.NPathComplexityRule"
|
||||
metrics="true"
|
||||
externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#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 and increase readability.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
void bar() { // This is something more complex than it needs to be,
|
||||
if (y) { // it should be broken down into smaller methods or functions
|
||||
for (j = 0; j < m; j++) {
|
||||
if (j > r) {
|
||||
doSomething();
|
||||
while (f < 5 ) {
|
||||
anotherThing();
|
||||
f -= 27;
|
||||
}
|
||||
} else {
|
||||
tryThis();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( r - n > 45) {
|
||||
while (doMagic()) {
|
||||
findRabbits();
|
||||
}
|
||||
}
|
||||
try {
|
||||
doSomethingDangerous();
|
||||
} catch (Exception ex) {
|
||||
makeAmends();
|
||||
} finally {
|
||||
dontDoItAgain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
||||
|
@ -9,6 +9,8 @@ import net.sourceforge.pmd.lang.java.metrics.MetricsHook;
|
||||
import net.sourceforge.pmd.testframework.SimpleAggregatorTst;
|
||||
|
||||
/**
|
||||
* Executes the metrics testing rules.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public class AllMetricsTest extends SimpleAggregatorTst {
|
||||
@ -30,6 +32,7 @@ public class AllMetricsTest extends SimpleAggregatorTst {
|
||||
addRule(RULESET, "NcssTest");
|
||||
addRule(RULESET, "WmcTest");
|
||||
addRule(RULESET, "LocTest");
|
||||
addRule(RULESET, "NPathTest");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.metrics.impl;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey;
|
||||
import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey;
|
||||
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public class NPathTestRule extends AbstractMetricTestRule {
|
||||
|
||||
@Override
|
||||
protected JavaClassMetricKey getClassKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected JavaOperationMetricKey getOpKey() {
|
||||
return JavaOperationMetricKey.NPATH;
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ import net.sourceforge.pmd.testframework.SimpleAggregatorTst;
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
|
||||
public class MetricsRulesTest extends SimpleAggregatorTst {
|
||||
|
||||
private static final String RULESET = "java-metrics";
|
||||
@ -28,5 +27,6 @@ public class MetricsRulesTest extends SimpleAggregatorTst {
|
||||
public void setUp() {
|
||||
addRule(RULESET, "CyclomaticComplexity");
|
||||
addRule(RULESET, "NcssCount");
|
||||
addRule(RULESET, "NPathComplexity");
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,196 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<test-data>
|
||||
|
||||
<code-fragment id="full-example"><![CDATA[
|
||||
public class Foo {
|
||||
public static void bar() {
|
||||
boolean a, b = true;
|
||||
try { // 2 * 2 + 2
|
||||
if (true) { // 2
|
||||
List buz = new ArrayList();
|
||||
}
|
||||
|
||||
for(int i = 0; i < 19; i++) { // 2
|
||||
List buz = new ArrayList();
|
||||
}
|
||||
} catch(Exception e) {
|
||||
if (true) { // 2
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
int j = 0;
|
||||
if (true || a && b) { // 4
|
||||
j = 10;
|
||||
return;
|
||||
}
|
||||
|
||||
while (j++ < 20) { // 2
|
||||
List buz = new ArrayList();
|
||||
}
|
||||
|
||||
switch(j) { // 7
|
||||
case 1:
|
||||
case 2: break;
|
||||
case 3: j = 5; break;
|
||||
case 4: if (b && a) { bar(); } break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
do { // 3
|
||||
List buz = new ArrayList();
|
||||
} while (a && j++ < 30);
|
||||
|
||||
if (b) { // 2
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code-fragment>
|
||||
|
||||
|
||||
<test-code>
|
||||
<description>Full example</description>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>The method 'bar()' has an NPath complexity of 2016</message>
|
||||
</expected-messages>
|
||||
<code-ref id="full-example"/>
|
||||
</test-code>
|
||||
|
||||
|
||||
<test-code>
|
||||
<description>Test default report level - report 200</description>
|
||||
<rule-property name="reportLevel">0</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>The method 'bar()' has an NPath complexity of 200</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
public class Foo {
|
||||
public static void bar() {
|
||||
boolean a, b = true;
|
||||
int j = 0;
|
||||
|
||||
switch (j) { // 5
|
||||
case 0:
|
||||
case 1:
|
||||
case 3: if (a || b) {} break;
|
||||
}
|
||||
|
||||
switch (j) { // * 5
|
||||
case 0:
|
||||
case 1:
|
||||
case 3: if (a || b) {} break;
|
||||
}
|
||||
|
||||
if (true || a && b); // * 4
|
||||
while (j++ < 20); // * 2
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>Test default report level - ignore 199</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
class Foo {
|
||||
void bar() {
|
||||
boolean a, b;
|
||||
try {
|
||||
switch(j) { // 7
|
||||
case 1:
|
||||
case 2: break;
|
||||
case 3: j = 5; break;
|
||||
case 4: if (b && a) { bar(); } break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
switch(j) { // * 7
|
||||
case 1:
|
||||
case 2: break;
|
||||
case 3: j = 5; break;
|
||||
case 4: if (b && a) { bar(); } break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (true || a || b); // * 4
|
||||
|
||||
} catch (ScaryException e) {
|
||||
if (true || a); // + 3
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
<test-code>
|
||||
<description>Boolean operators</description>
|
||||
<rule-property name="reportLevel">2</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>The method 'bar()' has an NPath complexity of 4</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
class Foo {
|
||||
void bar() {
|
||||
if (a && b || c);
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
|
||||
|
||||
<code-fragment id="bug3484404"><![CDATA[
|
||||
class Bar {
|
||||
public void x(boolean x, boolean y) {
|
||||
z((x ? 1 : 2), (y ? 3 : 4));
|
||||
}
|
||||
|
||||
public int y(boolean x, boolean y) {
|
||||
return z((x ? 1 : 2), (y ? 3 : 4));
|
||||
}
|
||||
|
||||
public int z(int x, int y) {
|
||||
return x + y;
|
||||
}
|
||||
}
|
||||
]]></code-fragment>
|
||||
|
||||
<test-code>
|
||||
<description>test case for bug 3484404 (Invalid NPath calculation in return statement)</description>
|
||||
<rule-property name="reportLevel">5</rule-property>
|
||||
<expected-problems>2</expected-problems>
|
||||
<expected-messages>
|
||||
<message>The method 'x(boolean, boolean)' has an NPath complexity of 25</message>
|
||||
<message>The method 'y(boolean, boolean)' has an NPath complexity of 25</message>
|
||||
</expected-messages>
|
||||
<code-ref id="bug3484404"/>
|
||||
</test-code>
|
||||
|
||||
|
||||
<test-code>
|
||||
<description>NPath supports constructors</description>
|
||||
<rule-property name="reportLevel">6</rule-property>
|
||||
<expected-problems>1</expected-problems>
|
||||
<expected-messages>
|
||||
<message>The constructor 'Foo()' has an NPath complexity of 7</message>
|
||||
</expected-messages>
|
||||
<code><![CDATA[
|
||||
class Foo {
|
||||
Foo() {
|
||||
boolean a, b;
|
||||
int j = 23;
|
||||
switch(j) {
|
||||
case 1:
|
||||
case 2: break;
|
||||
case 3: j = 5; break;
|
||||
case 4: if (b && a) { bar(); } break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</test-code>
|
||||
</test-data>
|
@ -33,4 +33,10 @@
|
||||
metrics="true">
|
||||
</rule>
|
||||
|
||||
<rule name="NPathTest"
|
||||
message = "''{0}'' has value {1}."
|
||||
class="net.sourceforge.pmd.lang.java.metrics.impl.NPathTestRule"
|
||||
metrics="true">
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
||||
|
@ -103,4 +103,5 @@ Based on those metrics, rules like "GodClass" detection can be implemented more
|
||||
* [#512](https://github.com/pmd/pmd/pull/512): \[java] Add incorporation to type inference - [Bendegúz Nagy](https://github.com/WinterGrascph)
|
||||
* [#513](https://github.com/pmd/pmd/pull/513): \[java] Fix for maximally specific method selection - [Bendegúz Nagy](https://github.com/WinterGrascph)
|
||||
* [#514](https://github.com/pmd/pmd/pull/514): \[java] Add static method type resolution - [Bendegúz Nagy](https://github.com/WinterGrascph)
|
||||
* [#523](https://github.com/pmd/pmd/pull/523): \[java] Npath complexity metric and rule - [Clément Fournier](https://github.com/oowekyala)
|
||||
|
||||
|
Reference in New Issue
Block a user