diff --git a/docs/pages/pmd/languages/java_metrics_index.md b/docs/pages/pmd/languages/java_metrics_index.md index c093fa46bb..84fdf4a809 100644 --- a/docs/pages/pmd/languages/java_metrics_index.md +++ b/docs/pages/pmd/languages/java_metrics_index.md @@ -226,9 +226,15 @@ between blocks 4 and 5 before jumping to block 6. The first `if` offers 2 choices, the second offers 3, so the cyclomatic complexity of this method is 2 + 3 = 5. NPath, however, sees 2 * 3 = 6 full paths from the beginning to the end. +## Number Of Public Attributes (NOPA) +*Class metric.* Can be computed on classes. + +## Number Of Accessor Methods (NOAM) +*Class metric.* Can be computed on classes. + ## Weighted Method Count (WMC) -*Class metric.* Can be computed on classes and enums +*Class metric.* Can be computed on classes and enums. ### Description @@ -241,6 +247,25 @@ Sum of the statistical complexity of the operations in the class. We use WMC uses the same options as CYCLO, which are provided to CYCLO when computing it. +## Weight Of Class (WOC) + +*Class metric.* Can be computed on classes. + +### Description + +Number of "functional" public methods divided by the total number of +public methods. Our definition of "functional method" excludes +constructors, getters, and setters. + +This metric tries to quantify whether the measured class' interface reveals +more data than behaviour. Low values (less than 30%) indicate that the class +reveals much more data than behaviour, which is a sign of poor encapsulation. + +This metric is used to detect Data Classes, in conjunction with [WMC](#weighted-method-count-wmc), +[NOPA](#number-of-public-attributes-nopa) and [NOAM](#number-of-accessor-methods-noam). + + + # References Lanza05: Lanza, Marinescu; Object-Oriented Metrics in Practice, 2005. @@ -249,4 +274,4 @@ computing it. Sonarqube: [Sonarqube online documentation.](https://docs.sonarqube.org/display/SONAR/Metric+Definitions) -JavaNcss: [JavaNCSS online documentation.](http://www.kclee.de/clemens/java/javancss/) \ No newline at end of file +JavaNcss: [JavaNCSS online documentation.](http://www.kclee.de/clemens/java/javancss/) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 2226557e7a..200439eb31 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -232,5 +232,5 @@ All existing rules have been updated to reflect these changes. If you have custo * [#557](https://github.com/pmd/pmd/pull/557): \[java] Fix NPath metric not counting ternaries correctly - [Clément Fournier](https://github.com/oowekyala) * [#563](https://github.com/pmd/pmd/pull/563): \[java] Add support for basic method type inference for strict invocation - [Bendegúz Nagy](https://github.com/WinterGrascph) * [#567](https://github.com/pmd/pmd/pull/567): \[java] Last API change for metrics (metric options) - [Clément Fournier](https://github.com/oowekyala) +* [#573](https://github.com/pmd/pmd/pull/573): \[java] Data class rule - [Clément Fournier](https://github.com/oowekyala) * [#576](https://github.com/pmd/pmd/pull/576): \[doc][java] Add hint for Guava users in InefficientEmptyStringCheck - [mmoehring](https://github.com/mmoehring) - diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java index 9447de8024..1461eba2d2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java @@ -224,7 +224,7 @@ public interface Node { * @param xpathString * the expression to check * @return List of all matching nodes. Returns an empty list if none found. - * @throws JaxenException + * @throws JaxenException if the xpath is incorrect or fails altogether */ List findChildNodesWithXPath(String xpathString) throws JaxenException; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java index 19670c6b90..843fb567e8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java @@ -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(), diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java index 44ee6f5c5d..e59007d938 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java @@ -23,6 +23,7 @@ import net.sourceforge.pmd.lang.metrics.Metric; */ public abstract class AbstractJavaMetric implements Metric { + protected List findAllCalls(ASTMethodOrConstructorDeclaration node) { List result = new ArrayList<>(); // TODO:cf findAllCalls @@ -32,6 +33,18 @@ public abstract class AbstractJavaMetric implements Metric { } + @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 implements Metric { 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(); - } - } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java index 544af36631..9ef5fd0ed2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java @@ -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 { * * @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; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java index 142a2e06b1..045b7a8759 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java @@ -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 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 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 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 getFields(ASTAnyTypeDeclaration node) { + return getDeclarationsOfType(node, ASTFieldDeclaration.class); + } + + + private List getDeclarationsOfType(ASTAnyTypeDeclaration node, Class tClass) { + + List result = new ArrayList<>(); + List decls = node.getDeclarations(); + + for (ASTAnyTypeBodyDeclaration decl : decls) { + if (tClass.isInstance(decl.jjtGetChild(0))) { + result.add(tClass.cast(decl.jjtGetChild(0))); + } + } + + return result; + } + + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java new file mode 100644 index 0000000000..c94237d35c --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java @@ -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); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java new file mode 100644 index 0000000000..e19fa2761b --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java @@ -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); + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java new file mode 100644 index 0000000000..b3be183fdf --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java @@ -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; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java new file mode 100644 index 0000000000..777b9df2d6 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java @@ -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; + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java index 144945ab9b..2a6d615d8e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java @@ -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 + + + 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. + + 3 + + + + + + diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java index 631725eea7..6092bd905a 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java @@ -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; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AllMetricsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AllMetricsTest.java index b09a8a4b67..3ae9def4a4 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AllMetricsTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AllMetricsTest.java @@ -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"); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamTestRule.java new file mode 100644 index 0000000000..eeec1f4851 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamTestRule.java @@ -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; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaTestRule.java new file mode 100644 index 0000000000..444f69de00 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaTestRule.java @@ -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; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/WocTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/WocTestRule.java new file mode 100644 index 0000000000..123f81bf45 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/WocTestRule.java @@ -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; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java index 22148254aa..9c6c90d235 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java @@ -28,5 +28,6 @@ public class MetricsRulesTest extends SimpleAggregatorTst { addRule(RULESET, "CyclomaticComplexity"); addRule(RULESET, "NcssCount"); addRule(RULESET, "NPathComplexity"); + addRule(RULESET, "DataClass"); } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NoamTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NoamTest.xml new file mode 100644 index 0000000000..653da8018a --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NoamTest.xml @@ -0,0 +1,79 @@ + + + + + + + + Full example + 1 + + '.Foo' has value 3. + + + + + + Test empty class + 1 + + '.Foo' has value 0. + + + + + + NOPA doesn't support enums, interfaces or annotations + 0 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NopaTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NopaTest.xml new file mode 100644 index 0000000000..4aca132041 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NopaTest.xml @@ -0,0 +1,65 @@ + + + + + + + + Full example + 1 + + '.Foo' has value 7. + + + + + + Test empty class + 1 + + '.Foo' has value 0. + + + + + + NOPA doesn't support enums, interfaces or annotations + 0 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WocTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WocTest.xml new file mode 100644 index 0000000000..303509b4ed --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WocTest.xml @@ -0,0 +1,103 @@ + + + + + + + + Full example + 1 + + '.Property' has value 11.11%. + + + + + + Test empty class + 0 + + + + + NOPA doesn't support enums, interfaces or annotations + 0 + + + + \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml new file mode 100644 index 0000000000..3a73eb3c0f --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml @@ -0,0 +1,214 @@ + + + + + + + + + ArgoUML data class + 1 + + The class 'Property' is suspected to be a Data Class + + + + + + + expectedMessages = new ArrayList<>(); + private List 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 messages) { + expectedMessages.clear(); + expectedMessages.addAll(messages); + } + + public List getExpectedMessages() { + return expectedMessages; + } + + public void setExpectedLineNumbers(List expectedLineNumbers) { + this.expectedLineNumbers.clear(); + this.expectedLineNumbers.addAll(expectedLineNumbers); + } + + public List 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 false if system property "pmd.regress" is set to + * false, true 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; + } +} + ]]> + + + + PMD data class + 1 + + The class 'TestDescriptor' is suspected to be a Data Class + + + + + \ No newline at end of file diff --git a/pmd-java/src/test/resources/rulesets/java/metrics_test.xml b/pmd-java/src/test/resources/rulesets/java/metrics_test.xml index d3b6ecd4c7..e6424a61a7 100644 --- a/pmd-java/src/test/resources/rulesets/java/metrics_test.xml +++ b/pmd-java/src/test/resources/rulesets/java/metrics_test.xml @@ -39,4 +39,22 @@ metrics="true"> + + + + + + + + +