Added NcssCountRule

This commit is contained in:
oowekyala
2017-06-27 09:33:19 +02:00
parent bc8637d23e
commit 40282e77ae
21 changed files with 573 additions and 293 deletions

View File

@ -28,6 +28,54 @@ import net.sourceforge.pmd.lang.java.oom.api.OperationMetricKey;
*/
public abstract class AbstractMetric implements Metric {
protected List<QualifiedName> findAllCalls(ASTMethodOrConstructorDeclaration node) {
List<QualifiedName> result = new ArrayList<>();
// TODO:cf findAllCalls
// Needs TypeRes
// Find the qualified names of all methods called in that method's block
return result;
}
/**
* Calls more specific methods that can be overriden by subclasses.
*
* @param node The node to check
*
* @return True if the metric can be computed on this node
*/
@Override
public final boolean supports(AccessNode node) {
return node instanceof ASTAnyTypeDeclaration && supports((ASTAnyTypeDeclaration) node)
|| node instanceof ASTMethodOrConstructorDeclaration && supports((ASTMethodOrConstructorDeclaration) node);
}
/**
* Returns true if the metric can be computed on this type declaration. By default, annotation and interface
* declarations are filtered out.
*
* @param node The type declaration
*
* @return True if the metric can be computed on this type declaration
*/
protected boolean supports(ASTAnyTypeDeclaration node) {
return node.getTypeKind() != TypeKind.ANNOTATION && node.getTypeKind() != TypeKind.INTERFACE;
}
/**
* Returns true if the metric can be computed on this operation. By default, abstract operations are filtered out.
*
* @param node The operation
*
* @return True if the metric can be computed on this operation
*/
protected boolean supports(ASTMethodOrConstructorDeclaration node) {
return !node.isAbstract();
}
/**
* Gives access to the toplevel package stats to metrics without having to pass them as a parameter to metrics.
*
@ -63,6 +111,40 @@ public abstract class AbstractMetric implements Metric {
}
/**
* Finds the declaration nodes of all methods or constructors that are declared inside a class.
*
* @param node The class in which to look for.
* @param includeNested Include operations found in nested classes?
*
* @return The list of all operations declared inside the specified class.
*
* TODO:cf this one is computed every time
*
* TODO:cf it might not be at the best place too (used by ClassStats)
*/
public static List<ASTMethodOrConstructorDeclaration> findOperations(ASTAnyTypeDeclaration node,
boolean includeNested) {
if (includeNested) {
return node.findDescendantsOfType(ASTMethodOrConstructorDeclaration.class);
}
List<ASTClassOrInterfaceBodyDeclaration> outerDecls
= node.jjtGetChild(0).findChildrenOfType(ASTClassOrInterfaceBodyDeclaration.class);
List<ASTMethodOrConstructorDeclaration> operations = new ArrayList<>();
for (ASTClassOrInterfaceBodyDeclaration decl : outerDecls) {
if (decl.jjtGetChild(0) instanceof ASTMethodOrConstructorDeclaration) {
operations.add((ASTMethodOrConstructorDeclaration) decl.jjtGetChild(0));
}
}
return operations;
}
/**
* Gets the average of the value of an operation metric over all operations in this class (excluding nested
* classes). The computation is not forced (memoized results are used if they can be found).
@ -115,86 +197,4 @@ public abstract class AbstractMetric implements Metric {
return highest;
}
/**
* Finds the declaration nodes of all methods or constructors that are declared inside a class.
*
* @param node The class in which to look for.
* @param includeNested Include operations found in nested classes?
*
* @return The list of all operations declared inside the specified class.
*
* TODO:cf this one is computed every time
*
* TODO:cf it might not be at the best place too (used by ClassStats)
*/
public static List<ASTMethodOrConstructorDeclaration> findOperations(ASTAnyTypeDeclaration node,
boolean includeNested) {
if (includeNested) {
return node.findDescendantsOfType(ASTMethodOrConstructorDeclaration.class);
}
List<ASTClassOrInterfaceBodyDeclaration> outerDecls
= node.jjtGetChild(0).findChildrenOfType(ASTClassOrInterfaceBodyDeclaration.class);
List<ASTMethodOrConstructorDeclaration> operations = new ArrayList<>();
for (ASTClassOrInterfaceBodyDeclaration decl : outerDecls) {
if (decl.jjtGetChild(0) instanceof ASTMethodOrConstructorDeclaration) {
operations.add((ASTMethodOrConstructorDeclaration) decl.jjtGetChild(0));
}
}
return operations;
}
protected List<QualifiedName> findAllCalls(ASTMethodOrConstructorDeclaration node) {
List<QualifiedName> result = new ArrayList<>();
// TODO:cf findAllCalls
// Needs TypeRes
// Find the qualified names of all methods called in that method's block
return result;
}
/**
* Calls more specific methods that can be overriden by subclasses.
*
* @param node The node to check
*
* @return True if the metric can be computed on this node
*/
@Override
public final boolean supports(AccessNode node) {
return node instanceof ASTAnyTypeDeclaration && supports((ASTAnyTypeDeclaration) node)
|| node instanceof ASTMethodOrConstructorDeclaration && supports((ASTMethodOrConstructorDeclaration) node);
}
/**
* Returns true if the metric can be computed on this type declaration. By default, annotation and interface
* declarations are filtered out.
*
* @param node The type declaration
*
* @return True if the metric can be computed on this type declaration
*/
protected boolean supports(ASTAnyTypeDeclaration node) {
return node.getTypeKind() != TypeKind.ANNOTATION && node.getTypeKind() != TypeKind.INTERFACE;
}
/**
* Returns true if the metric can be computed on this operation. By default, abstract operations are filtered out.
*
* @param node The operation
*
* @return True if the metric can be computed on this operation
*/
protected boolean supports(ASTMethodOrConstructorDeclaration node) {
return !node.isAbstract();
}
}

View File

@ -250,7 +250,7 @@ import net.sourceforge.pmd.lang.java.oom.signature.OperationSignature;
*/
/* default */ double compute(ClassMetricKey key, ASTAnyTypeDeclaration node, boolean force, MetricVersion version) {
ParameterizedMetricKey paramKey = ParameterizedMetricKey.build(key, version);
ParameterizedMetricKey paramKey = ParameterizedMetricKey.getInstance(key, version);
// if memo.get(key) == null then the metric has never been computed. NaN is a valid value.
Double prev = memo.get(paramKey);
if (!force && prev != null) {

View File

@ -35,8 +35,7 @@ public final class Metrics {
*
* @return The top level package stats
*/
/* default */
static PackageStats getTopLevelPackageStats() {
/* default */ static PackageStats getTopLevelPackageStats() {
return TOP_LEVEL_PACKAGE;
}

View File

@ -46,7 +46,7 @@ import net.sourceforge.pmd.lang.java.oom.api.OperationMetricKey;
/* default */ double compute(OperationMetricKey key, ASTMethodOrConstructorDeclaration node, boolean force,
MetricVersion version) {
ParameterizedMetricKey paramKey = ParameterizedMetricKey.build(key, version);
ParameterizedMetricKey paramKey = ParameterizedMetricKey.getInstance(key, version);
Double prev = memo.get(paramKey);
if (!force && prev != null) {
return prev;

View File

@ -39,6 +39,22 @@ public final class PackageStats {
}
/**
* Returns true if the signature of the operation designated by the qualified name is covered by
* the mask.
*
* @param qname The operation to test
* @param sigMask The signature mask to use
*
* @return True if the signature of the operation designated by the qualified name is covered by the mask
*/
public boolean hasMatchingSig(QualifiedName qname, OperationSigMask sigMask) {
ClassStats clazz = getClassStats(qname, false);
return clazz != null && clazz.hasMatchingSig(qname.getOperation(), sigMask);
}
/**
* Gets the ClassStats corresponding to the named resource. The class can be nested. If the
* createIfNotFound parameter is set, the method also creates the hierarchy if it doesn't exist.
@ -106,22 +122,6 @@ public final class PackageStats {
}
/**
* Returns true if the signature of the operation designated by the qualified name is covered by
* the mask.
*
* @param qname The operation to test
* @param sigMask The signature mask to use
*
* @return True if the signature of the operation designated by the qualified name is covered by the mask
*/
public boolean hasMatchingSig(QualifiedName qname, OperationSigMask sigMask) {
ClassStats clazz = getClassStats(qname, false);
return clazz != null && clazz.hasMatchingSig(qname.getOperation(), sigMask);
}
/**
* Returns true if the signature of the field designated by its name and the qualified name of its class is
* covered by the mask.

View File

@ -25,38 +25,20 @@ public final class ParameterizedMetricKey {
/** The version of the metric. */
public final MetricVersion version;
/** Used internally by the pooler. */
private ParameterizedMetricKey(MetricKey<? extends Metric> key, MetricVersion version) {
this.key = key;
this.version = version;
}
/** Builds a parameterized metric key. */
public static ParameterizedMetricKey build(MetricKey<? extends Metric> key, MetricVersion version) {
int code = code(key, version);
ParameterizedMetricKey paramKey = POOL.get(code);
if (paramKey == null) {
POOL.put(code, new ParameterizedMetricKey(key, version));
}
return POOL.get(code);
}
/** Used by the pooler. */
private static int code(MetricKey key, MetricVersion version) {
int result = key.hashCode();
result = 31 * result + version.hashCode();
return result;
}
@Override
public String toString() {
return "ParameterizedMetricKey{"
+ "key=" + key
+ ", version=" + version
+ '}';
return "ParameterizedMetricKey{key=" + key + ", version=" + version + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
@ -74,10 +56,28 @@ public final class ParameterizedMetricKey {
return version.equals(that.version);
}
@Override
public int hashCode() {
return code(key, version);
}
/** Used by the pooler. */
private static int code(MetricKey key, MetricVersion version) {
int result = key.hashCode();
result = 31 * result + version.hashCode();
return result;
}
/** Builds a parameterized metric key. */
public static ParameterizedMetricKey getInstance(MetricKey<? extends Metric> key, MetricVersion version) {
int code = code(key, version);
ParameterizedMetricKey paramKey = POOL.get(code);
if (paramKey == null) {
POOL.put(code, new ParameterizedMetricKey(key, version));
}
return POOL.get(code);
}
}

View File

@ -6,10 +6,7 @@ package net.sourceforge.pmd.lang.java.oom.api;
/**
* Version of a metric. Only one version can be active on a metric. Versions should typically be defined in an enum
* named 'Version' nested inside the implementation class of the metric. Versions <i>cannot</i> be shared between
* metrics, otherwise the internal memoization logic breaks down.
*
* TODO:cf Strictly enforce the non-sharing of versions, eg through generics.
* named 'Version' nested inside the implementation class of the metric.
*
* @author Clément Fournier
*/

View File

@ -1,7 +1,5 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*
* Object-Oriented Metrics module (Metrics framework).
*/
/**

View File

@ -4,110 +4,44 @@
package net.sourceforge.pmd.lang.java.oom.rule;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
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.oom.Metrics;
import net.sourceforge.pmd.lang.java.oom.api.ClassMetricKey;
import net.sourceforge.pmd.lang.java.oom.api.Metric;
import net.sourceforge.pmd.lang.java.oom.api.Metric.Version;
import net.sourceforge.pmd.lang.java.oom.api.MetricVersion;
import net.sourceforge.pmd.lang.java.oom.api.OperationMetricKey;
import net.sourceforge.pmd.lang.java.oom.api.ResultOption;
import net.sourceforge.pmd.lang.java.oom.metrics.CycloMetric;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule;
import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
import net.sourceforge.pmd.lang.java.rule.AbstractSimpleJavaMetricsRule;
import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty;
import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
/**
* Refactored to use metrics.
*
* @author Clément Fournier
*/
public class CyclomaticComplexityRule extends AbstractJavaMetricsRule {
private static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty(
"reportLevel", "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
private static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty(
"showClassesComplexity", "Add class average violations to the report", true, 2.0f);
private static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty(
"showMethodsComplexity", "Add method average violations to the report", true, 3.0f);
public class CyclomaticComplexityRule extends AbstractSimpleJavaMetricsRule {
// TODO:cf clean that up once the new property API is up
private static final String[] VERSION_LABELS = {"standard", "ignoreBooleanPaths"};
private static final MetricVersion[] CYCLO_VERSIONS = {Metric.Version.STANDARD, CycloMetric.Version.IGNORE_BOOLEAN_PATHS};
private static final EnumeratedProperty<MetricVersion> CYCLO_VERSION_DESCRIPTOR = new EnumeratedProperty<>(
"cycloVersion", "Choose a variant of Cyclo or the standard",
VERSION_LABELS, CYCLO_VERSIONS, 0, 3.0f);
private int reportLevel;
private boolean showClassesComplexity = true;
private boolean showMethodsComplexity = true;
private MetricVersion cycloVersion = Version.STANDARD;
public CyclomaticComplexityRule() {
definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
definePropertyDescriptor(CYCLO_VERSION_DESCRIPTOR);
@Override
protected EnumeratedProperty<MetricVersion> versionDescriptor() {
return CYCLO_VERSION_DESCRIPTOR;
}
@Override
public Object visit(ASTCompilationUnit node, Object data) {
reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
Object version = getProperty(CYCLO_VERSION_DESCRIPTOR);
cycloVersion = version instanceof MetricVersion ? (MetricVersion) version : Version.STANDARD;
super.visit(node, data);
return data;
protected ClassMetricKey classMetricKey() {
return ClassMetricKey.CYCLO;
}
@Override
public Object visit(ASTAnyTypeDeclaration node, Object data) {
TypeKind kind = node.getTypeKind();
if (kind == TypeKind.INTERFACE) {
return data;
}
super.visit(node, data);
if (showClassesComplexity) {
int classCyclo = (int) Metrics.get(ClassMetricKey.CYCLO, node, cycloVersion);
int classHighest = (int) Metrics.get(OperationMetricKey.CYCLO, node, cycloVersion, ResultOption.HIGHEST);
if (classCyclo >= reportLevel || classHighest >= reportLevel) {
String[] messageParams = {kind.name().toLowerCase(),
node.getImage(),
classCyclo + " (Highest = " + classHighest + ')',};
addViolation(data, node, messageParams);
}
}
return data;
protected OperationMetricKey operationMetricKey() {
return OperationMetricKey.CYCLO;
}
@Override
public Object visit(ASTMethodOrConstructorDeclaration node, Object data) {
int cyclo = (int) Metrics.get(OperationMetricKey.CYCLO, node, cycloVersion);
if (showMethodsComplexity && cyclo >= reportLevel) {
addViolation(data, node, new String[] {node instanceof ASTMethodDeclaration ? "method" : "constructor",
node.getQualifiedName().getOperation(), "" + cyclo,});
}
return data;
}
}

View File

@ -0,0 +1,27 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.oom.rule;
import net.sourceforge.pmd.lang.java.oom.api.ClassMetricKey;
import net.sourceforge.pmd.lang.java.oom.api.OperationMetricKey;
import net.sourceforge.pmd.lang.java.rule.AbstractSimpleJavaMetricsRule;
/**
* @author Clément Fournier
*/
public final class NcssCountRule extends AbstractSimpleJavaMetricsRule {
@Override
protected ClassMetricKey classMetricKey() {
return ClassMetricKey.NCSS;
}
@Override
protected OperationMetricKey operationMetricKey() {
return OperationMetricKey.NCSS;
}
}

View File

@ -29,6 +29,24 @@ public final class FieldSignature extends Signature {
}
@Override
public boolean equals(Object o) {
return this == o;
}
@Override
public int hashCode() {
return code(visibility, isStatic, isFinal);
}
/** Used internally by the pooler. */
private static int code(Visibility visibility, boolean isStatic, boolean isFinal) {
return (isFinal ? 1 : 0) + visibility.hashCode() << 2 + (isStatic ? 1 : 0) << 1;
}
/**
* Builds a field signature from its AST node.
*
@ -43,22 +61,4 @@ public final class FieldSignature extends Signature {
}
return POOL.get(code);
}
/** Used internally by the pooler. */
private static int code(Visibility visibility, boolean isStatic, boolean isFinal) {
return visibility.hashCode() * 31 + (isStatic ? 1 : 0) * 2 + (isFinal ? 1 : 0);
}
@Override
public boolean equals(Object o) {
return this == o;
}
@Override
public int hashCode() {
return (isFinal ? 1 : 0) + super.hashCode() << 3 + (isStatic ? 1 : 0) << 1;
}
}

View File

@ -25,6 +25,14 @@ public final class OperationSigMask extends SigMask<OperationSignature> {
}
/**
* Sets the mask to cover all roles.
*/
public void coverAllRoles() {
roleMask.addAll(Arrays.asList(OperationSignature.Role.values()));
}
/**
* Restricts the roles covered by the mask to the parameters.
*
@ -36,14 +44,6 @@ public final class OperationSigMask extends SigMask<OperationSignature> {
}
/**
* Sets the mask to cover all roles.
*/
public void coverAllRoles() {
roleMask.addAll(Arrays.asList(OperationSignature.Role.values()));
}
/**
* Forbid all mentioned roles.
*

View File

@ -48,7 +48,13 @@ public final class OperationSignature extends Signature {
@Override
public int hashCode() {
return (isAbstract ? 1 : 0) + super.hashCode() << 1 + role.hashCode() << 2;
return code(visibility, role, isAbstract);
}
/** Used internally by the pooler. */
private static int code(Visibility visibility, Role role, boolean isAbstract) {
return visibility.hashCode() << 2 + role.hashCode() << 1 + (isAbstract ? 1 : 0);
}
@ -68,12 +74,6 @@ public final class OperationSignature extends Signature {
}
/** Used internally by the pooler. */
private static int code(Visibility visibility, Role role, boolean isAbstract) {
return visibility.hashCode() * 31 + role.hashCode() * 2 + (isAbstract ? 1 : 0);
}
/**
* Role of an operation.
*/
@ -114,7 +114,8 @@ public final class OperationSignature extends Signature {
for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> decl
: scope.getVariableDeclarations().entrySet()) {
ASTFieldDeclaration field = decl.getKey().getNode()
ASTFieldDeclaration field = decl.getKey()
.getNode()
.getFirstParentOfType(ASTFieldDeclaration.class);
fieldNames.put(field.getVariableName(), field.getFirstChildOfType(ASTType.class).getTypeImage());
@ -139,11 +140,12 @@ public final class OperationSignature extends Signature {
/** Attempts to determine if the method is a setter. */
private static boolean isSetter(ASTMethodDeclaration node, Map<String, String> fieldNames) {
if (node.getFirstDescendantOfType(ASTFormalParameters.class).getParameterCount() != 1) {
if (node.getFirstDescendantOfType(ASTFormalParameters.class).getParameterCount() != 1
|| !node.getFirstDescendantOfType(ASTResultType.class).isVoid()) {
return false;
}
return node.getFirstDescendantOfType(ASTResultType.class).isVoid();
return fieldNames.containsKey(node.getName());
}
}
}

View File

@ -26,6 +26,14 @@ public abstract class SigMask<T extends Signature> {
}
/**
* Sets the mask to cover all visibilities.
*/
public void coverAllVisibilities() {
visMask.addAll(Arrays.asList(Signature.Visibility.values()));
}
/**
* Restricts the visibilities covered by the mask to the parameters.
*
@ -37,14 +45,6 @@ public abstract class SigMask<T extends Signature> {
}
/**
* Sets the mask to cover all visibilities.
*/
public void coverAllVisibilities() {
visMask.addAll(Arrays.asList(Signature.Visibility.values()));
}
/**
* Forbid all mentioned visibilities.
*

View File

@ -0,0 +1,140 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule;
import net.sourceforge.pmd.PropertyDescriptor;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
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.oom.Metrics;
import net.sourceforge.pmd.lang.java.oom.api.ClassMetricKey;
import net.sourceforge.pmd.lang.java.oom.api.Metric.Version;
import net.sourceforge.pmd.lang.java.oom.api.MetricVersion;
import net.sourceforge.pmd.lang.java.oom.api.OperationMetricKey;
import net.sourceforge.pmd.lang.java.oom.api.ResultOption;
import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
import net.sourceforge.pmd.lang.rule.properties.EnumeratedProperty;
import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
/**
* Reports values of a metric that cross a certain threshold.
*
* @author Clément Fournier
*/
public abstract class AbstractSimpleJavaMetricsRule extends AbstractJavaMetricsRule {
private static final PropertyDescriptor<Boolean> SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty(
"reportClasses", "Add class average violations to the report", true, 2.0f);
private static final PropertyDescriptor<Boolean> SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty(
"reportMethods", "Add method average violations to the report", true, 3.0f);
private static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty(
"reportLevel", "Metric reporting threshold", 1, 30, 10, 1.0f);
private ClassMetricKey classMetricKey;
private OperationMetricKey operationMetricKey;
private boolean reportClasses = true;
private boolean reportMethods = true;
private MetricVersion metricVersion = Version.STANDARD;
private int reportLevel;
public AbstractSimpleJavaMetricsRule() {
definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
if (versionDescriptor() != null) {
definePropertyDescriptor(versionDescriptor());
}
classMetricKey = classMetricKey();
operationMetricKey = operationMetricKey();
}
/**
* Return the property descriptor which selects the version of the metric, if the metric has versions.
*
* @return The property descriptor which selects the version of the metric
*/
protected EnumeratedProperty<MetricVersion> versionDescriptor() {
return null;
}
/**
* Returns the class metric key to use.
*
* @return The class metric key to use
*/
protected abstract ClassMetricKey classMetricKey();
/**
* Returns the operation metric key to use.
*
* @return The operation metric key to use
*/
protected abstract OperationMetricKey operationMetricKey();
@Override
public final Object visit(ASTCompilationUnit node, Object data) {
reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
reportClasses = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
reportMethods = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
if (versionDescriptor() != null) {
Object version = getProperty(versionDescriptor());
metricVersion = version instanceof MetricVersion ? (MetricVersion) version : Version.STANDARD;
}
super.visit(node, data);
return data;
}
@Override
public final Object visit(ASTAnyTypeDeclaration node, Object data) {
super.visit(node, data);
if (reportClasses && classMetricKey != null) {
int classValue = (int) Metrics.get(classMetricKey, node, metricVersion);
int classHighest = (int) Metrics.get(operationMetricKey, node, metricVersion, ResultOption.HIGHEST);
String valueReport = String.valueOf(classValue);
if (operationMetricKey != null) {
int highest = (int) Metrics.get(operationMetricKey, node, metricVersion, ResultOption.HIGHEST);
valueReport += " (Highest = " + highest + ")";
}
if (classValue >= reportLevel || classHighest >= reportLevel) {
String[] messageParams = {node.getTypeKind().name().toLowerCase(),
node.getImage(),
valueReport, };
addViolation(data, node, messageParams);
}
}
return data;
}
@Override
public final Object visit(ASTMethodOrConstructorDeclaration node, Object data) {
if (reportMethods && operationMetricKey != null) {
int cyclo = (int) Metrics.get(operationMetricKey, node, metricVersion);
if (cyclo >= reportLevel) {
addViolation(data, node, new String[] {node instanceof ASTMethodDeclaration ? "method" : "constructor",
node.getQualifiedName().getOperation(), "" + cyclo, });
}
}
return data;
}
}

View File

@ -1,17 +1,17 @@
<?xml version="1.0"?>
<ruleset name="Metrics temporary ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/3.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/3.0.0 http://pmd.sourceforge.net/ruleset_3_0_0.xsd">
xmlns="http://pmd.sourceforge.net/ruleset/3.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/3.0.0 http://pmd.sourceforge.net/ruleset_3_0_0.xsd">
<description>
These are rules which use the Metrics Framework to calculate metrics.
</description>
<description>
These are rules which use the Metrics Framework to calculate metrics.
</description>
<rule name="CyclomaticComplexity"
since="1.03"
message = "The {0} ''{1}'' has a Cyclomatic Complexity of {2}."
message="The {0} ''{1}'' has a Cyclomatic Complexity of {2}."
since="1.03"
class="net.sourceforge.pmd.lang.java.oom.rule.CyclomaticComplexityRule"
metrics="true"
externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#CyclomaticComplexity">
@ -65,4 +65,36 @@ public class Foo { // This has a Cyclomatic Complexity = 12
]]>
</example>
</rule>
</ruleset>
<!-- TODO -->
<rule name="NcssCount"
message="The {0} ''{1}'' has a NCSS line count of {2}."
since="3.9"
class="net.sourceforge.pmd.lang.java.oom.rule.NcssCountRule"
metrics="true"
externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#NcssTypeCount">
<description>
This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines
of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm,
lines of code that are split are counted as one.
</description>
<priority>3</priority>
<example>
<![CDATA[
public class Foo extends Bar {
public Foo() {
//this class only has 6 NCSS lines
super();
super.foo();
}
}
]]>
</example>
</rule>
</ruleset>

View File

@ -32,7 +32,7 @@ public class GetterDetection {
}
/* public double speedModified() {
/* public double speedModified() {
return speed * 12 + 1;
}
@ -55,5 +55,5 @@ public class GetterDetection {
public int mutableIntConditional() {
return mutX == null ? 0 : mutX.getValue();
}
*/
*/
}

View File

@ -20,33 +20,19 @@ public class SetterDetection {
value = x;
}
public void putNewValue(int x) {
value = x;
}
public void putNewValueComposed(int x) {
value += x;
}
public void putNewValueIf(int x) {
if (x > 0) {
value = x;
}
}
public void putNewValueConditional(int x) {
public void value(int x) {
value = x > 0 ? x : -x;
}
public void updateWithMethod(int x) {
public void speed(int x) {
mutX.setValue(x);
}
public void updateWithOtherMethod(int x) {
public void mutX(int x) {
mutX.increment();
}
public void updateHiddenVal(int value) {
public void bool(int value) {
this.value = value;
}
}

View File

@ -4,6 +4,7 @@
package net.sourceforge.pmd.lang.java.rule.metrics;
import net.sourceforge.pmd.lang.java.oom.MetricsForceHook;
import net.sourceforge.pmd.testframework.SimpleAggregatorTst;
/**
@ -14,8 +15,15 @@ public class MetricsRulesTest extends SimpleAggregatorTst {
private static final String RULESET = "java-metrics";
static {
MetricsForceHook.setForce(true); // TODO:cf get rid of that when you can
}
@Override
public void setUp() {
addRule(RULESET, "CyclomaticComplexity");
addRule(RULESET, "NcssCount");
}
}

View File

@ -117,20 +117,20 @@
<test-code>
<description>Testing new parameter showClassMethods</description>
<rule-property name="showClassesComplexity">false</rule-property>
<rule-property name="reportClasses">false</rule-property>
<expected-problems>1</expected-problems>
<code-ref id="basic-violation"/>
</test-code>
<test-code>
<description>Testing new parameter showMethodsMethods</description>
<rule-property name="showMethodsComplexity">false</rule-property>
<rule-property name="reportMethods">false</rule-property>
<expected-problems>1</expected-problems>
<code-ref id="basic-violation"/>
</test-code>
<test-code>
<description>Testing default value of showClassMethods and showClassesComplexity</description>
<description>Testing default value of showClassMethods and reportClasses</description>
<expected-problems>2</expected-problems>
<code-ref id="basic-violation"/>
</test-code>
@ -152,10 +152,10 @@
</code-fragment>
<test-code>
<description>#984 Cyclomatic complexity should treat constructors like methods: 1 - showMethodsComplexity=true
<description>#984 Cyclomatic complexity should treat constructors like methods: 1 - reportMethods=true
</description>
<rule-property name="showClassesComplexity">false</rule-property>
<rule-property name="showMethodsComplexity">true</rule-property>
<rule-property name="reportClasses">false</rule-property>
<rule-property name="reportMethods">true</rule-property>
<rule-property name="reportLevel">1</rule-property>
<expected-problems>1</expected-problems>
<code-ref id="constructor-violation"/>
@ -163,10 +163,10 @@
<test-code>
<description>#984 Cyclomatic complexity should treat constructors like methods: 2 -
showMethodsComplexity=false
reportMethods=false
</description>
<rule-property name="showClassesComplexity">false</rule-property>
<rule-property name="showMethodsComplexity">false</rule-property>
<rule-property name="reportClasses">false</rule-property>
<rule-property name="reportMethods">false</rule-property>
<rule-property name="reportLevel">1</rule-property>
<expected-problems>0</expected-problems>
<code-ref id="constructor-violation"/>
@ -211,8 +211,8 @@
<test-code>
<description>Standard Cyclo should count empty switch labels too</description>
<rule-property name="showClassesComplexity">false</rule-property>
<rule-property name="showMethodsComplexity">true</rule-property>
<rule-property name="reportClasses">false</rule-property>
<rule-property name="reportMethods">true</rule-property>
<rule-property name="reportLevel">2</rule-property>
<expected-problems>1</expected-problems>
<expected-messages>
@ -223,8 +223,8 @@
<test-code>
<description>IgnoreBooleanPaths Cyclo should not count empty switch labels</description>
<rule-property name="showClassesComplexity">false</rule-property>
<rule-property name="showMethodsComplexity">true</rule-property>
<rule-property name="reportClasses">false</rule-property>
<rule-property name="reportMethods">true</rule-property>
<rule-property name="cycloVersion">ignoreBooleanPaths</rule-property>
<rule-property name="reportLevel">2</rule-property>
<expected-problems>1</expected-problems>
@ -253,7 +253,7 @@
<test-code>
<description>Standard Cyclo should count boolean paths</description>
<rule-property name="showClassesComplexity">false</rule-property>
<rule-property name="reportClasses">false</rule-property>
<rule-property name="reportLevel">2</rule-property>
<expected-problems>1</expected-problems>
<expected-messages>
@ -264,7 +264,7 @@
<test-code>
<description>IgnoreBooleanPaths Cyclo should not count boolean paths</description>
<rule-property name="showClassesComplexity">false</rule-property>
<rule-property name="reportClasses">false</rule-property>
<rule-property name="cycloVersion">ignoreBooleanPaths</rule-property>
<rule-property name="reportLevel">2</rule-property>
<expected-problems>1</expected-problems>

View File

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data>
<code-fragment id="full-example">
<![CDATA[
public class Foo {
int y, z, t;
String q;
{
bar();
int x;
switch (x) {
case 1: foo(); break;
case 2:
case 3: bar(); break;
default: break;
}
}
public void foo() {}
public static void main(String args[]) {
String k, l, m;
bar();
do {
x++;
} while (x < 2);
while (x < 4) {
x++;
}
for (int i = 1; x < 6; ) {
x += i;
}
{
x++;;;;;
}
int p =
2 + 4 + 5;
try {
x++;
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} finally {
// Do nothing
}
bar();
x = (int) bar();
/*
* This is
* a comment
*/
x.foobar();
}
}
]]>
</code-fragment>
<test-code>
<description>Full example</description>
<expected-problems>2</expected-problems>
<expected-messages>
<message>The class 'Foo' has a NCSS line count of 35 (Highest = 18).</message>
<message>The method 'main(String)' has a NCSS line count of 18.</message>
</expected-messages>
<code-ref id="full-example"/>
</test-code>
<test-code>
<description><![CDATA[
short
]]></description>
<rule-property name="reportLevel">13</rule-property>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public static void main(String args[]) {
bar();
}
}
]]></code>
</test-code>
<test-code>
<description><![CDATA[
lots of comments
]]></description>
<rule-property name="reportLevel">13</rule-property>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public static void main(String args[]) {
//nothing to see here
//nothing to see here
//nothing to see here
//nothing to see here
bar();
//nothing to see here
//nothing to see here
//nothing to see here
//nothing to see here
foo();
}
}
]]></code>
</test-code>
<code-fragment id="long method"><![CDATA[
public class Foo {
public static void main(String args[]) {
//nothing to see here
//nothing to see here
//nothing to see here
//nothing to see here
bar();
bar();
bar();
bar();
bar();
bar();
//nothing to see here
//nothing to see here
//nothing to see here
//nothing to see here
foo();
foo();
foo();
foo();
foo();
foo();
}
}
]]></code-fragment>
<test-code>
<description><![CDATA[
long method
]]></description>
<rule-property name="reportLevel">13</rule-property>
<rule-property name="reportClasses">false</rule-property>
<expected-problems>1</expected-problems>
<code-ref id="long method"/>
</test-code>
<test-code>
<description><![CDATA[
long method - changed reportLevel
]]></description>
<!-- validated this number against NCSS -->
<rule-property name="reportLevel">15</rule-property>
<expected-problems>0</expected-problems>
<code-ref id="long method"/>
</test-code>
</test-data>