Merge branch 'pr-573'

This commit is contained in:
Andreas Dangel
2017-08-23 20:40:08 +02:00
24 changed files with 972 additions and 23 deletions

View File

@ -55,7 +55,7 @@ public final class JavaQualifiedName implements QualifiedName {
* @return The qualified name of the node
*/
/* default */ static JavaQualifiedName ofOperation(ASTConstructorDeclaration node) {
ASTClassOrInterfaceDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class);
ASTAnyTypeDeclaration parent = node.getFirstParentOfType(ASTAnyTypeDeclaration.class);
return ofOperation(parent.getQualifiedName(),
parent.getImage(),

View File

@ -23,6 +23,7 @@ import net.sourceforge.pmd.lang.metrics.Metric;
*/
public abstract class AbstractJavaMetric<N extends Node> implements Metric<N> {
protected List<JavaQualifiedName> findAllCalls(ASTMethodOrConstructorDeclaration node) {
List<JavaQualifiedName> result = new ArrayList<>();
// TODO:cf findAllCalls
@ -32,6 +33,18 @@ public abstract class AbstractJavaMetric<N extends Node> implements Metric<N> {
}
@Override
public final boolean equals(Object o) {
return o != null && o.getClass() == this.getClass();
}
@Override
public final int hashCode() {
return getClass().hashCode();
}
/**
* Gives access to a signature matcher to metrics. They can use it to perform signature matching.
*
@ -41,14 +54,4 @@ public abstract class AbstractJavaMetric<N extends Node> implements Metric<N> {
return JavaMetrics.getFacade().getTopLevelPackageStats();
}
@Override
public final boolean equals(Object o) {
return o != null && o.getClass() == this.getClass();
}
@Override
public final int hashCode() {
return getClass().hashCode();
}
}

View File

@ -8,7 +8,10 @@ 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.LocMetric.LocClassMetric;
import net.sourceforge.pmd.lang.java.metrics.impl.NcssMetric.NcssClassMetric;
import net.sourceforge.pmd.lang.java.metrics.impl.NoamMetric;
import net.sourceforge.pmd.lang.java.metrics.impl.NopaMetric;
import net.sourceforge.pmd.lang.java.metrics.impl.WmcMetric;
import net.sourceforge.pmd.lang.java.metrics.impl.WocMetric;
import net.sourceforge.pmd.lang.metrics.MetricKey;
/**
@ -42,7 +45,29 @@ public enum JavaClassMetricKey implements MetricKey<ASTAnyTypeDeclaration> {
*
* @see net.sourceforge.pmd.lang.java.metrics.impl.LocMetric
*/
LOC(new LocClassMetric());
LOC(new LocClassMetric()),
/**
* Number of Public Attributes.
*
* @see NopaMetric
*/
NOPA(new NopaMetric()),
/**
* Number of Accessor Methods.
*
* @see NopaMetric
*/
NOAM(new NoamMetric()),
/**
* Weight of class.
*
* @see WocMetric
*/
WOC(new WocMetric());
private final JavaClassMetric calculator;

View File

@ -4,10 +4,19 @@
package net.sourceforge.pmd.lang.java.metrics.impl;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
import net.sourceforge.pmd.lang.java.metrics.AbstractJavaMetric;
import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetric;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask;
/**
* Base class for class metrics.
@ -28,4 +37,89 @@ public abstract class AbstractJavaClassMetric extends AbstractJavaMetric<ASTAnyT
public boolean supports(ASTAnyTypeDeclaration node) {
return node.getTypeKind() != TypeKind.ANNOTATION && node.getTypeKind() != TypeKind.INTERFACE;
}
/**
* Counts the operations matching the signature mask in this class.
*
* @param classNode The class on which to count
* @param mask The mask
*
* @return The number of operations matching the signature mask
*/
protected int countMatchingOpSigs(ASTAnyTypeDeclaration classNode, JavaOperationSigMask mask) {
int count = 0;
List<ASTMethodOrConstructorDeclaration> decls = getMethodsAndConstructors(classNode);
for (ASTMethodOrConstructorDeclaration decl : decls) {
if (mask.covers(decl.getSignature())) {
count++;
}
}
return count;
}
/**
* Counts the fields matching the signature mask in this class.
*
* @param classNode The class on which to count
* @param mask The mask
*
* @return The number of fields matching the signature mask
*/
protected int countMatchingFieldSigs(ASTAnyTypeDeclaration classNode, JavaFieldSigMask mask) {
int count = 0;
List<ASTFieldDeclaration> decls = getFields(classNode);
for (ASTFieldDeclaration decl : decls) {
if (mask.covers(decl.getSignature())) {
count++;
}
}
return count;
}
/**
* Gets a list of all methods and constructors declared in the class.
*
* @param node The class
*
* @return The list of all methods and constructors
*/
protected List<ASTMethodOrConstructorDeclaration> getMethodsAndConstructors(ASTAnyTypeDeclaration node) {
return getDeclarationsOfType(node, ASTMethodOrConstructorDeclaration.class);
}
/**
* Gets a list of all fields declared in the class.
*
* @param node The class
*
* @return The list of all fields
*/
protected List<ASTFieldDeclaration> getFields(ASTAnyTypeDeclaration node) {
return getDeclarationsOfType(node, ASTFieldDeclaration.class);
}
private <T extends Node> List<T> getDeclarationsOfType(ASTAnyTypeDeclaration node, Class<T> tClass) {
List<T> result = new ArrayList<>();
List<ASTAnyTypeBodyDeclaration> decls = node.getDeclarations();
for (ASTAnyTypeBodyDeclaration decl : decls) {
if (tClass.isInstance(decl.jjtGetChild(0))) {
result.add(tClass.cast(decl.jjtGetChild(0)));
}
}
return result;
}
}

View File

@ -0,0 +1,37 @@
/**
* 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.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility;
import net.sourceforge.pmd.lang.metrics.MetricOptions;
/**
* Number of accessor methods.
*
* @author Clément Fournier
* @since 6.0.0
*/
public class NoamMetric extends AbstractJavaClassMetric {
@Override
public boolean supports(ASTAnyTypeDeclaration node) {
return node.getTypeKind() == TypeKind.CLASS;
}
@Override
public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) {
JavaOperationSigMask mask = new JavaOperationSigMask();
mask.restrictRolesTo(Role.GETTER_OR_SETTER);
mask.restrictVisibilitiesTo(Visibility.PUBLIC);
return (double) countMatchingOpSigs(node, mask);
}
}

View File

@ -0,0 +1,36 @@
/**
* 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.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility;
import net.sourceforge.pmd.lang.metrics.MetricOptions;
/**
* Number of public attributes.
*
* @author Clément Fournier
* @since 6.0.0
*/
public class NopaMetric extends AbstractJavaClassMetric {
@Override
public boolean supports(ASTAnyTypeDeclaration node) {
return node.getTypeKind() == TypeKind.CLASS;
}
@Override
public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) {
JavaFieldSigMask mask = new JavaFieldSigMask();
mask.restrictVisibilitiesTo(Visibility.PUBLIC);
return (double) countMatchingFieldSigs(node, mask);
}
}

View File

@ -0,0 +1,43 @@
/**
* 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.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role;
import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility;
import net.sourceforge.pmd.lang.metrics.MetricOptions;
/**
* Weight of class.
*
* @author Clément Fournier
* @since 6.0.0
*/
public class WocMetric extends AbstractJavaClassMetric {
@Override
public boolean supports(ASTAnyTypeDeclaration node) {
return node.getTypeKind() == TypeKind.CLASS;
}
@Override
public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) {
JavaOperationSigMask mask = new JavaOperationSigMask();
mask.forbid(Role.CONSTRUCTOR, Role.GETTER_OR_SETTER);
mask.restrictVisibilitiesTo(Visibility.PUBLIC);
int functionalMethods = countMatchingOpSigs(node, mask);
mask.coverAllRoles();
int totalPublicMethods = countMatchingOpSigs(node, mask);
return functionalMethods / (double) totalPublicMethods;
}
}

View File

@ -0,0 +1,54 @@
/**
* 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.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule;
/**
* @author Clément Fournier
* @since 6.0.0
*/
public class DataClassRule extends AbstractJavaMetricsRule {
// probably not worth using properties
private static final int ACCESSOR_OR_FIELD_FEW_LEVEL = 3;
private static final int ACCESSOR_OR_FIELD_MANY_LEVEL = 5;
private static final double WOC_LEVEL = 1. / 3.;
private static final int WMC_HIGH_LEVEL = 31;
private static final int WMC_VERY_HIGH_LEVEL = 47;
@Override
public Object visit(ASTAnyTypeDeclaration node, Object data) {
boolean isDataClass = interfaceRevealsData(node) && classRevealsDataAndLacksComplexity(node);
if (isDataClass) {
addViolation(data, node, new String[] {node.getImage()});
}
return super.visit(node, data);
}
private boolean interfaceRevealsData(ASTAnyTypeDeclaration node) {
double woc = JavaMetrics.get(JavaClassMetricKey.WOC, node);
return woc < WOC_LEVEL;
}
private boolean classRevealsDataAndLacksComplexity(ASTAnyTypeDeclaration node) {
int nopa = (int) JavaMetrics.get(JavaClassMetricKey.NOPA, node);
int noam = (int) JavaMetrics.get(JavaClassMetricKey.NOAM, node);
int wmc = (int) JavaMetrics.get(JavaClassMetricKey.WMC, node);
return nopa + noam > ACCESSOR_OR_FIELD_FEW_LEVEL && wmc < WMC_HIGH_LEVEL
|| nopa + noam > ACCESSOR_OR_FIELD_MANY_LEVEL && wmc < WMC_VERY_HIGH_LEVEL;
}
}

View File

@ -8,6 +8,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
@ -79,6 +81,8 @@ public final class JavaOperationSignature extends JavaSignature<ASTMethodOrConst
public enum Role {
GETTER_OR_SETTER, CONSTRUCTOR, METHOD, STATIC;
private static final Pattern FIELD_NAME_PATTERN = Pattern.compile("(?:m_|_)?(\\w+)");
public static Role get(ASTMethodOrConstructorDeclaration node) {
return node instanceof ASTConstructorDeclaration ? CONSTRUCTOR : get((ASTMethodDeclaration) node);
@ -110,7 +114,11 @@ public final class JavaOperationSignature extends JavaSignature<ASTMethodOrConst
.getNode()
.getFirstParentOfType(ASTFieldDeclaration.class);
fieldNames.put(field.getVariableName(), field.getFirstChildOfType(ASTType.class).getTypeImage());
Matcher matcher = FIELD_NAME_PATTERN.matcher(field.getVariableName());
String varName = matcher.find() ? matcher.group(1) : field.getVariableName();
fieldNames.put(varName, field.getFirstChildOfType(ASTType.class).getTypeImage());
}
return isGetter(node, fieldNames) || isSetter(node, fieldNames);

View File

@ -163,4 +163,42 @@ public class Foo {
</example>
</rule>
<rule name="DataClass"
since="6.0"
message="The class ''{0}'' is suspected to be a Data Class"
class="net.sourceforge.pmd.lang.java.metrics.rule.DataClassRule"
metrics="true"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_metrics.html#DataClass">
<description>
Data Classes are simple data holders, which reveal most of their state, and
without complex functionality. The lack of functionality may indicate that
their behaviour is defined elsewhere, which is a sign of poor data-behaviour
proximity. By directly exposing their internals, Data Classes break encapsulation,
and therefore reduce the system's maintainability and understandability. Moreover,
classes tend to strongly rely on their data representation, which makes for a brittle
design.
Refactoring a Data Class should focus on restoring a good data-behaviour proximity. In
most cases, that means moving the operations defined on the data back into the class.
In some other cases it may make sense to remove entirely the class and move the data
into the former client classes.
</description>
<priority>3</priority>
<example>
<![CDATA[
public class DataClass {
public int bar = 0;
public int na = 0;
private int bee = 0;
public void setBee(int n) {
bee = n;
}
}
]]>
</example>
</rule>
</ruleset>

View File

@ -126,16 +126,40 @@ public abstract class AbstractMetricTestRule extends AbstractJavaMetricsRule {
}
/** Gets a string representation rounded to the nearest half. */
private String presentableString(double val) {
boolean isInt = Math.floor(val) == val;
if (!isInt && val >= 0 && val <= 1) { // percentage
return roundedString(100 * val) + "%";
} else if (!isInt) {
return String.valueOf(roundedString(val));
} else {
return String.valueOf((int) val);
}
}
private String roundedString(double val) {
double truncated = Math.floor(100 * val) / 100;
if (truncated == Math.floor(truncated)) {
return String.valueOf((int) truncated);
} else {
return String.valueOf(truncated);
}
}
@Override
public Object visit(ASTAnyTypeDeclaration node, Object data) {
if (classKey != null && reportClasses && classKey.supports(node)) {
int classValue = (int) JavaMetrics.get(classKey, node, metricOptions);
double classValue = JavaMetrics.get(classKey, node, metricOptions);
String valueReport = String.valueOf(classValue);
String valueReport = presentableString(classValue);
if (opKey != null) {
int highest = (int) JavaMetrics.get(opKey, node, metricOptions, ResultOption.HIGHEST);
valueReport += " highest " + highest;
double highest = JavaMetrics.get(opKey, node, metricOptions, ResultOption.HIGHEST);
valueReport += " highest " + presentableString(highest);
}
if (classValue >= reportLevel) {
addViolation(data, node, new String[] {node.getQualifiedName().toString(), valueReport, });
@ -148,9 +172,10 @@ public abstract class AbstractMetricTestRule extends AbstractJavaMetricsRule {
@Override
public Object visit(ASTMethodOrConstructorDeclaration node, Object data) {
if (opKey != null && reportMethods && opKey.supports(node)) {
int methodValue = (int) JavaMetrics.get(opKey, node, metricOptions);
double methodValue = JavaMetrics.get(opKey, node, metricOptions);
if (methodValue >= reportLevel) {
addViolation(data, node, new String[] {node.getQualifiedName().toString(), "" + methodValue, });
addViolation(data, node, new String[] {node.getQualifiedName().toString(),
"" + presentableString(methodValue), });
}
}
return data;

View File

@ -33,6 +33,9 @@ public class AllMetricsTest extends SimpleAggregatorTst {
addRule(RULESET, "WmcTest");
addRule(RULESET, "LocTest");
addRule(RULESET, "NPathTest");
addRule(RULESET, "NopaTest");
addRule(RULESET, "NoamTest");
addRule(RULESET, "WocTest");
}
}

View File

@ -0,0 +1,26 @@
/**
* 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
* @since 6.0.0
*/
public class NoamTestRule extends AbstractMetricTestRule {
@Override
protected JavaClassMetricKey getClassKey() {
return JavaClassMetricKey.NOAM;
}
@Override
protected JavaOperationMetricKey getOpKey() {
return null;
}
}

View File

@ -0,0 +1,26 @@
/**
* 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
* @since 6.0.0
*/
public class NopaTestRule extends AbstractMetricTestRule {
@Override
protected JavaClassMetricKey getClassKey() {
return JavaClassMetricKey.NOPA;
}
@Override
protected JavaOperationMetricKey getOpKey() {
return null;
}
}

View File

@ -0,0 +1,26 @@
/**
* 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
* @since 6.0.0
*/
public class WocTestRule extends AbstractMetricTestRule {
@Override
protected JavaClassMetricKey getClassKey() {
return JavaClassMetricKey.WOC;
}
@Override
protected JavaOperationMetricKey getOpKey() {
return null;
}
}

View File

@ -28,5 +28,6 @@ public class MetricsRulesTest extends SimpleAggregatorTst {
addRule(RULESET, "CyclomaticComplexity");
addRule(RULESET, "NcssCount");
addRule(RULESET, "NPathComplexity");
addRule(RULESET, "DataClass");
}
}

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data>
<code-fragment id="full-example"><![CDATA[
public class Foo {
private int value;
private double speed;
private MutableInt mutX;
private boolean bool;
public int getValue() {
return value;
}
public boolean isBool() {
return bool;
}
public int value() {
return value;
}
protected int getValue() {
return value;
}
boolean isBool() {
return bool;
}
private int value() {
return value;
}
}
]]></code-fragment>
<test-code>
<description>Full example</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.Foo' has value 3.</message>
</expected-messages>
<code-ref id="full-example"/>
</test-code>
<test-code>
<description>Test empty class</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.Foo' has value 0.</message>
</expected-messages>
<code><![CDATA[
public class Foo {
}
]]></code>
</test-code>
<test-code>
<description>NOPA doesn't support enums, interfaces or annotations</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public interface Foo {
public final int h;
enum Bar{
FOO;
public int bel = 0;
}
@interface Tag {
public static final int num = 0;
}
}
]]></code>
</test-code>
</test-data>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data>
<code-fragment id="full-example"><![CDATA[
public class Foo {
public static int a;
public final int b;
protected int c;
public static int d;
public static final int e;
int f;
int g;
public int h;
public int i;
public int j;
public static void bar() {
}
}
]]></code-fragment>
<test-code>
<description>Full example</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.Foo' has value 7.</message>
</expected-messages>
<code-ref id="full-example"/>
</test-code>
<test-code>
<description>Test empty class</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.Foo' has value 0.</message>
</expected-messages>
<code><![CDATA[
public class Foo {
}
]]></code>
</test-code>
<test-code>
<description>NOPA doesn't support enums, interfaces or annotations</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public interface Foo {
public final int h;
enum Bar{
FOO;
public int bel = 0;
}
@interface Tag {
public static final int num = 0;
}
}
]]></code>
</test-code>
</test-data>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data>
<code-fragment id="full-example"><![CDATA[
// Data class from ArgoUML
/**
* A property that can be displayed and edited within a PropertyTable.
*
* @author Jeremy Jones
**/
public class Property implements Comparable {
private String _name;
private Class _valueType;
private Object _initialValue;
private Object _currentValue;
private Object[] _availableValues;
public Property(String name, Class valueType, Object initialValue) {
this(name, valueType, initialValue, null);
}
public Property(String name, Class valueType, Object initialValue, Object[] values) {
_name = name;
_valueType = valueType;
_initialValue = initialValue;
_availableValues = values;
_currentValue = _initialValue;
}
public String getName() {
return _name;
}
public Class getValueType() {
return _valueType;
}
public Object getInitialValue() {
return _initialValue;
}
public Object[] getAvailableValues() {
return _availableValues;
}
public Object getCurrentValue() {
return _currentValue;
}
public void setCurrentValue(Object value) {
_currentValue = value;
}
public int compareTo(Object o) {
return _name.compareTo(((Property) o)._name);
}
}
]]></code-fragment>
<test-code>
<description>Full example</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>'.Property' has value 11.11%.</message>
</expected-messages>
<code-ref id="full-example"/>
</test-code>
<test-code>
<description>Test empty class</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
}
]]></code>
</test-code>
<test-code>
<description>NOPA doesn't support enums, interfaces or annotations</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public interface Foo {
public final int h;
enum Bar{
FOO;
public int bel = 0;
}
@interface Tag {
public static final int num = 0;
}
}
]]></code>
</test-code>
</test-data>

View File

@ -0,0 +1,214 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data>
<code-fragment id="argoUML-dataclass">
<![CDATA[
public class Property implements Comparable { // Woc = 10%, Noam = 6, Nopa = 0, Wmc = 10
private String _name;
private Class _valueType;
private Object _initialValue;
private Object _currentValue;
private Object[] _availableValues;
public Property(String name, Class valueType, Object initialValue) {
this(name, valueType, initialValue, null);
}
public Property(String name, Class valueType, Object initialValue, Object[] values) {
_name = name;
_valueType = valueType;
_initialValue = initialValue;
_availableValues = values;
_currentValue = _initialValue;
}
public String getName() {
return _name;
}
public Class getValueType() {
return _valueType;
}
public Object getInitialValue() {
return _initialValue;
}
public Object[] getAvailableValues() {
return _availableValues;
}
public Object getCurrentValue() {
return _currentValue;
}
public void setCurrentValue(Object value) {
_currentValue = value;
}
public int compareTo(Object o) {
return _name.compareTo(((Property) o)._name);
}
}
]]>
</code-fragment>
<test-code>
<description>ArgoUML data class</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>The class 'Property' is suspected to be a Data Class</message>
</expected-messages>
<code-ref id="argoUML-dataclass"/>
</test-code>
<code-fragment id="testDescriptor">
<![CDATA[
public class TestDescriptor {
private Rule rule;
private Properties properties;
private String description;
private int numberOfProblemsExpected;
private List<String> expectedMessages = new ArrayList<>();
private List<Integer> expectedLineNumbers = new ArrayList<>();
private String code;
private LanguageVersion languageVersion;
// default, avoids unintentional mixing of state between test cases
private boolean reinitializeRule = true;
private boolean isRegressionTest = true;
private boolean useAuxClasspath = true;
private int numberInDocument = -1;
// Empty descriptor added to please mvn surefire plugin
public TestDescriptor() {
}
public TestDescriptor(String code, String description, int numberOfProblemsExpected, Rule rule) {
this(code, description, numberOfProblemsExpected, rule, rule.getLanguage().getDefaultVersion());
}
public TestDescriptor(String code, String description, int numberOfProblemsExpected, Rule rule,
LanguageVersion languageVersion) {
this.rule = rule;
this.code = code;
this.description = description;
this.numberOfProblemsExpected = numberOfProblemsExpected;
this.languageVersion = languageVersion;
}
public int getNumberInDocument() {
return numberInDocument;
}
public void setNumberInDocument(int numberInDocument) {
this.numberInDocument = numberInDocument;
}
public void setExpectedMessages(List<String> messages) {
expectedMessages.clear();
expectedMessages.addAll(messages);
}
public List<String> getExpectedMessages() {
return expectedMessages;
}
public void setExpectedLineNumbers(List<Integer> expectedLineNumbers) {
this.expectedLineNumbers.clear();
this.expectedLineNumbers.addAll(expectedLineNumbers);
}
public List<Integer> getExpectedLineNumbers() {
return expectedLineNumbers;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public Properties getProperties() {
return properties;
}
public String getCode() {
return code;
}
public LanguageVersion getLanguageVersion() {
return languageVersion;
}
public String getDescription() {
return description;
}
public int getNumberOfProblemsExpected() {
return numberOfProblemsExpected;
}
public Rule getRule() {
return rule;
}
public boolean getReinitializeRule() {
return reinitializeRule;
}
public void setReinitializeRule(boolean reinitializeRule) {
this.reinitializeRule = reinitializeRule;
}
/**
* Checks whether we are testing for regression problems only. Return value
* is based on the system property "pmd.regress".
*
* @return <code>false</code> if system property "pmd.regress" is set to
* <code>false</code>, <code>true</code> otherwise
*/
public static boolean inRegressionTestMode() {
boolean inRegressionMode = true; // default
try {
// get the "pmd.regress" System property
String property = System.getProperty("pmd.regress");
if (property != null) {
inRegressionMode = Boolean.parseBoolean(property);
}
} catch (IllegalArgumentException e) {
}
return inRegressionMode;
}
public boolean isRegressionTest() {
return isRegressionTest;
}
public void setRegressionTest(boolean isRegressionTest) {
this.isRegressionTest = isRegressionTest;
}
public void setUseAuxClasspath(boolean useAuxClasspath) {
this.useAuxClasspath = useAuxClasspath;
}
public boolean isUseAuxClasspath() {
return useAuxClasspath;
}
}
]]>
</code-fragment>
<test-code>
<description>PMD data class</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>The class 'TestDescriptor' is suspected to be a Data Class</message>
</expected-messages>
<code-ref id="testDescriptor"/>
</test-code>
</test-data>

View File

@ -39,4 +39,22 @@
metrics="true">
</rule>
<rule name="NopaTest"
message = "''{0}'' has value {1}."
class="net.sourceforge.pmd.lang.java.metrics.impl.NopaTestRule"
metrics="true">
</rule>
<rule name="NoamTest"
message = "''{0}'' has value {1}."
class="net.sourceforge.pmd.lang.java.metrics.impl.NoamTestRule"
metrics="true">
</rule>
<rule name="WocTest"
message = "''{0}'' has value {1}."
class="net.sourceforge.pmd.lang.java.metrics.impl.WocTestRule"
metrics="true">
</rule>
</ruleset>