Merge branch 'java-grammar' into grammar-flatten-body-declarations
This commit is contained in:
@ -182,7 +182,8 @@ Example:
|
||||
|
||||
* [apex](pmd_rules_apex.html) (Salesforce Apex)
|
||||
* [java](pmd_rules_java.html)
|
||||
* Supported Versions: 1.3, 1.4, 1.5, 5, 1.6, 6, 1.7, 7, 1.8, 8, 9, 1.9, 10, 1.10, 11, 12, 13 (default), 13-preview
|
||||
* Supported Versions: 1.3, 1.4, 1.5, 5, 1.6, 6, 1.7, 7, 1.8, 8, 9, 1.9, 10, 1.10, 11, 12,
|
||||
13, 13-preview, 14 (default), 14-preview
|
||||
* [ecmascript](pmd_rules_ecmascript.html) (JavaScript)
|
||||
* [jsp](pmd_rules_jsp.html)
|
||||
* [modelica](pmd_rules_modelica.html)
|
||||
|
@ -222,8 +222,10 @@ nested element. Possible values are:
|
||||
<sourceLanguage name="java" version="1.10"/> <!-- alias for 10 -->
|
||||
<sourceLanguage name="java" version="11"/>
|
||||
<sourceLanguage name="java" version="12"/>
|
||||
<sourceLanguage name="java" version="13"/> <!-- this is the default -->
|
||||
<sourceLanguage name="java" version="13"/>
|
||||
<sourceLanguage name="java" version="13-preview"/>
|
||||
<sourceLanguage name="java" version="14"/> <!-- this is the default -->
|
||||
<sourceLanguage name="java" version="14-preview"/>
|
||||
<sourceLanguage name="jsp" version=""/>
|
||||
<sourceLanguage name="modelica" version=""/>
|
||||
<sourceLanguage name="pom" version=""/>
|
||||
|
@ -19,27 +19,79 @@ This is a {{ site.pmd.release_type }} release.
|
||||
|
||||
### New and noteworthy
|
||||
|
||||
#### Java 14 Support
|
||||
|
||||
This release of PMD brings support for Java 14. PMD can parse [Switch Expressions](https://openjdk.java.net/jeps/361),
|
||||
which have been promoted to be a standard language feature of Java.
|
||||
|
||||
PMD also parses [Text Blocks](https://openjdk.java.net/jeps/368) as String literals, which is still a preview
|
||||
language feature in Java 14.
|
||||
|
||||
The new [Pattern Matching for instanceof](https://openjdk.java.net/jeps/305) can be used as well as
|
||||
[Records](https://openjdk.java.net/jeps/359).
|
||||
|
||||
Note: The Text Blocks, Pattern Matching for instanceof and Records are all preview language features of OpenJDK 14
|
||||
and are not enabled by default. In order to
|
||||
analyze a project with PMD that uses these language features, you'll need to enable it via the environment
|
||||
variable `PMD_JAVA_OPTS` and select the new language version `14-preview`:
|
||||
|
||||
export PMD_JAVA_OPTS=--enable-preview
|
||||
./run.sh pmd -language java -version 14-preview ...
|
||||
|
||||
Note: Support for the extended break statement introduced in Java 12 as a preview language feature
|
||||
has been removed from PMD with this version. The version "12-preview" is no longer available.
|
||||
|
||||
|
||||
#### Updated PMD Designer
|
||||
|
||||
This PMD release ships a new version of the pmd-designer.
|
||||
For the changes, see [PMD Designer Changelog](https://github.com/pmd/pmd-designer/releases/tag/6.21.0).
|
||||
|
||||
### Apex Suppressions
|
||||
#### Apex Suppressions
|
||||
|
||||
In addition to suppressing violation with the `@SuppressWarnings` annotation, Apex now also supports
|
||||
the suppressions with a `NOPMD` comment. See [Suppressing warnings](pmd_userdocs_suppressing_warnings.html).
|
||||
|
||||
#### Improved CPD support for C#
|
||||
|
||||
The C# tokenizer is now based on an antlr grammar instead of a manual written tokenizer. This
|
||||
should give more accurate results and especially fixes the problems with the using statement syntax
|
||||
(see [#2139](https://github.com/pmd/pmd/issues/2139)).
|
||||
|
||||
#### New Rules
|
||||
|
||||
* The Rule {% rule "apex/design/CognitiveComplexity" %} (`apex-design`) finds methods and classes
|
||||
that are highly complex and therefore difficult to read and more costly to maintain. In contrast
|
||||
to cyclomatic complexity, this rule uses "Cognitive Complexity", which is a measure of how
|
||||
difficult it is for humans to read and understand a method.
|
||||
|
||||
* The Rule {% rule "apex/errorprone/TestMethodsMustBeInTestClasses" %} (`apex-errorprone`) finds test methods
|
||||
that are not residing in a test class. The test methods should be moved to a proper test class.
|
||||
Support for tests inside functional classes was removed in Spring-13 (API Version 27.0), making classes
|
||||
that violate this rule fail compile-time. This rule is however useful when dealing with legacy code.
|
||||
|
||||
### Fixed Issues
|
||||
|
||||
* apex
|
||||
* [#1087](https://github.com/pmd/pmd/issues/1087): \[apex] Support suppression via //NOPMD
|
||||
* [#2306](https://github.com/pmd/pmd/issues/2306): \[apex] Switch statements are not parsed/supported
|
||||
* apex-design
|
||||
* [#2162](https://github.com/pmd/pmd/issues/2162): \[apex] Cognitive Complexity rule
|
||||
* apex-errorprone
|
||||
* [#639](https://github.com/pmd/pmd/issues/639): \[apex] Test methods should not be in classes other than test classes
|
||||
* cs
|
||||
* [#2139](https://github.com/pmd/pmd/issues/2139): \[cs] CPD doesn't understand alternate using statement syntax with C# 8.0
|
||||
* doc
|
||||
* [#2274](https://github.com/pmd/pmd/issues/2274): \[doc] Java API documentation for PMD
|
||||
* java
|
||||
* [#2159](https://github.com/pmd/pmd/issues/2159): \[java] Prepare for JDK 14
|
||||
* [#2268](https://github.com/pmd/pmd/issues/2268): \[java] Improve TypeHelper resilience
|
||||
* java-bestpractices
|
||||
* [#2277](https://github.com/pmd/pmd/issues/2277): \[java] FP in UnusedImports for ambiguous static on-demand imports
|
||||
* java-design
|
||||
* [#911](https://github.com/pmd/pmd/issues/911): \[java] UselessOverridingMethod false positive when elevating access modifier
|
||||
* java-errorprone
|
||||
* [#2242](https://github.com/pmd/pmd/issues/2242): \[java] False-positive MisplacedNullCheck reported
|
||||
* [#2250](https://github.com/pmd/pmd/issues/2250): \[java] InvalidLogMessageFormat flags logging calls using a slf4j-Marker
|
||||
* [#2255](https://github.com/pmd/pmd/issues/2255): \[java] InvalidLogMessageFormat false-positive for a lambda argument
|
||||
* java-performance
|
||||
@ -119,7 +171,7 @@ methods on {% jdoc apex::lang.apex.ast.ApexParserVisitor %} and its implementati
|
||||
* {% jdoc !!java::lang.java.ast.ASTFormalParameters#getParameterCount() %}.
|
||||
Use {% jdoc java::lang.java.ast.ASTFormalParameters#size() %} instead.
|
||||
* pmd-apex
|
||||
* {% jdoc java::lang.apex.metrics.ApexMetrics %}, {% jdoc java::lang.java.metrics.JavaMetricsComputer %}
|
||||
* {% jdoc apex::lang.apex.metrics.ApexMetrics %}, {% jdoc apex::lang.apex.metrics.ApexMetricsComputer %}
|
||||
|
||||
|
||||
### External Contributions
|
||||
@ -130,6 +182,10 @@ methods on {% jdoc apex::lang.apex.ast.ApexParserVisitor %} and its implementati
|
||||
* [#2276](https://github.com/pmd/pmd/pull/2276): \[java] AppendCharacterWithCharRule ignore literals in expressions - [Kris Scheibe](https://github.com/kris-scheibe)
|
||||
* [#2278](https://github.com/pmd/pmd/pull/2278): \[java] fix UnusedImports rule for ambiguous static on-demand imports - [Kris Scheibe](https://github.com/kris-scheibe)
|
||||
* [#2279](https://github.com/pmd/pmd/pull/2279): \[apex] Add support for suppressing violations using the // NOPMD comment - [Gwilym Kuiper](https://github.com/gwilymatgearset)
|
||||
* [#2280](https://github.com/pmd/pmd/pull/2280): \[cs] CPD: Replace C# tokenizer by an Antlr-based one - [Maikel Steneker](https://github.com/maikelsteneker)
|
||||
* [#2297](https://github.com/pmd/pmd/pull/2297): \[apex] Cognitive complexity metrics - [Gwilym Kuiper](https://github.com/gwilymatgearset)
|
||||
* [#2317](https://github.com/pmd/pmd/pull/2317): \[apex] New Rule - Test Methods Must Be In Test Classes - [Brian Nørremark](https://github.com/noerremark)
|
||||
* [#2321](https://github.com/pmd/pmd/pull/2321): \[apex] Support switch statements correctly in Cognitive Complexity - [Gwilym Kuiper](https://github.com/gwilymatgearset)
|
||||
|
||||
{% endtocmaker %}
|
||||
|
||||
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import apex.jorje.semantic.ast.statement.ElseWhenBlock;
|
||||
|
||||
public final class ASTElseWhenBlock extends AbstractApexNode<ElseWhenBlock> {
|
||||
|
||||
|
||||
ASTElseWhenBlock(ElseWhenBlock node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object jjtAccept(ApexParserVisitor visitor, Object data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import apex.jorje.semantic.ast.expression.EmptyReferenceExpression;
|
||||
|
||||
public final class ASTEmptyReferenceExpression extends AbstractApexNode<EmptyReferenceExpression> {
|
||||
|
||||
|
||||
ASTEmptyReferenceExpression(EmptyReferenceExpression node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object jjtAccept(ApexParserVisitor visitor, Object data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import apex.jorje.semantic.ast.statement.WhenCases.IdentifierCase;
|
||||
|
||||
public final class ASTIdentifierCase extends AbstractApexNode<IdentifierCase> {
|
||||
|
||||
|
||||
ASTIdentifierCase(IdentifierCase node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object jjtAccept(ApexParserVisitor visitor, Object data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import apex.jorje.semantic.ast.statement.WhenCases.LiteralCase;
|
||||
|
||||
public final class ASTLiteralCase extends AbstractApexNode<LiteralCase> {
|
||||
|
||||
|
||||
ASTLiteralCase(LiteralCase node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object jjtAccept(ApexParserVisitor visitor, Object data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import apex.jorje.semantic.ast.statement.SwitchStatement;
|
||||
|
||||
public final class ASTSwitchStatement extends AbstractApexNode<SwitchStatement> {
|
||||
|
||||
|
||||
ASTSwitchStatement(SwitchStatement node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object jjtAccept(ApexParserVisitor visitor, Object data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import apex.jorje.semantic.ast.statement.TypeWhenBlock;
|
||||
|
||||
public final class ASTTypeWhenBlock extends AbstractApexNode<TypeWhenBlock> {
|
||||
|
||||
|
||||
ASTTypeWhenBlock(TypeWhenBlock node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return String.valueOf(node.getTypeRef());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
// unfortunately the name is not exposed...
|
||||
try {
|
||||
Field nameField = TypeWhenBlock.class.getDeclaredField("name");
|
||||
nameField.setAccessible(true);
|
||||
return String.valueOf(nameField.get(node));
|
||||
} catch (SecurityException | ReflectiveOperationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jjtAccept(ApexParserVisitor visitor, Object data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import apex.jorje.semantic.ast.statement.ValueWhenBlock;
|
||||
|
||||
public final class ASTValueWhenBlock extends AbstractApexNode<ValueWhenBlock> {
|
||||
|
||||
|
||||
ASTValueWhenBlock(ValueWhenBlock node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object jjtAccept(ApexParserVisitor visitor, Object data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
@ -191,4 +191,18 @@ public interface ApexParserVisitor {
|
||||
Object visit(ASTVariableExpression node, Object data);
|
||||
|
||||
Object visit(ASTWhileLoopStatement node, Object data);
|
||||
|
||||
Object visit(ASTSwitchStatement node, Object data);
|
||||
|
||||
Object visit(ASTElseWhenBlock node, Object data);
|
||||
|
||||
Object visit(ASTTypeWhenBlock node, Object data);
|
||||
|
||||
Object visit(ASTValueWhenBlock node, Object data);
|
||||
|
||||
Object visit(ASTLiteralCase node, Object data);
|
||||
|
||||
Object visit(ASTIdentifierCase node, Object data);
|
||||
|
||||
Object visit(ASTEmptyReferenceExpression node, Object data);
|
||||
}
|
||||
|
@ -468,4 +468,39 @@ public class ApexParserVisitorAdapter implements ApexParserVisitor {
|
||||
public Object visit(ASTFormalComment node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTSwitchStatement node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTElseWhenBlock node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTTypeWhenBlock node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTValueWhenBlock node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTIdentifierCase node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTLiteralCase node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTEmptyReferenceExpression node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ import apex.jorje.semantic.ast.expression.BindExpressions;
|
||||
import apex.jorje.semantic.ast.expression.BooleanExpression;
|
||||
import apex.jorje.semantic.ast.expression.CastExpression;
|
||||
import apex.jorje.semantic.ast.expression.ClassRefExpression;
|
||||
import apex.jorje.semantic.ast.expression.EmptyReferenceExpression;
|
||||
import apex.jorje.semantic.ast.expression.Expression;
|
||||
import apex.jorje.semantic.ast.expression.IllegalStoreExpression;
|
||||
import apex.jorje.semantic.ast.expression.InstanceOfExpression;
|
||||
@ -93,6 +94,7 @@ import apex.jorje.semantic.ast.statement.DmlUndeleteStatement;
|
||||
import apex.jorje.semantic.ast.statement.DmlUpdateStatement;
|
||||
import apex.jorje.semantic.ast.statement.DmlUpsertStatement;
|
||||
import apex.jorje.semantic.ast.statement.DoLoopStatement;
|
||||
import apex.jorje.semantic.ast.statement.ElseWhenBlock;
|
||||
import apex.jorje.semantic.ast.statement.ExpressionStatement;
|
||||
import apex.jorje.semantic.ast.statement.FieldDeclaration;
|
||||
import apex.jorje.semantic.ast.statement.FieldDeclarationStatements;
|
||||
@ -106,10 +108,15 @@ import apex.jorje.semantic.ast.statement.ReturnStatement;
|
||||
import apex.jorje.semantic.ast.statement.RunAsBlockStatement;
|
||||
import apex.jorje.semantic.ast.statement.Statement;
|
||||
import apex.jorje.semantic.ast.statement.StatementExecuted;
|
||||
import apex.jorje.semantic.ast.statement.SwitchStatement;
|
||||
import apex.jorje.semantic.ast.statement.ThrowStatement;
|
||||
import apex.jorje.semantic.ast.statement.TryCatchFinallyBlockStatement;
|
||||
import apex.jorje.semantic.ast.statement.TypeWhenBlock;
|
||||
import apex.jorje.semantic.ast.statement.ValueWhenBlock;
|
||||
import apex.jorje.semantic.ast.statement.VariableDeclaration;
|
||||
import apex.jorje.semantic.ast.statement.VariableDeclarationStatements;
|
||||
import apex.jorje.semantic.ast.statement.WhenCases.IdentifierCase;
|
||||
import apex.jorje.semantic.ast.statement.WhenCases.LiteralCase;
|
||||
import apex.jorje.semantic.ast.statement.WhileLoopStatement;
|
||||
import apex.jorje.semantic.ast.visitor.AdditionalPassScope;
|
||||
import apex.jorje.semantic.ast.visitor.AstVisitor;
|
||||
@ -117,7 +124,8 @@ import apex.jorje.semantic.exception.Errors;
|
||||
|
||||
public final class ApexTreeBuilder extends AstVisitor<AdditionalPassScope> {
|
||||
|
||||
private static final Map<Class<? extends AstNode>, Constructor<? extends AbstractApexNode<?>>> NODE_TYPE_TO_NODE_ADAPTER_TYPE = new HashMap<>();
|
||||
private static final Map<Class<? extends AstNode>, Constructor<? extends AbstractApexNode<?>>>
|
||||
NODE_TYPE_TO_NODE_ADAPTER_TYPE = new HashMap<>();
|
||||
|
||||
static {
|
||||
register(Annotation.class, ASTAnnotation.class);
|
||||
@ -208,11 +216,19 @@ public final class ApexTreeBuilder extends AstVisitor<AdditionalPassScope> {
|
||||
register(VariableDeclarationStatements.class, ASTVariableDeclarationStatements.class);
|
||||
register(VariableExpression.class, ASTVariableExpression.class);
|
||||
register(WhileLoopStatement.class, ASTWhileLoopStatement.class);
|
||||
register(SwitchStatement.class, ASTSwitchStatement.class);
|
||||
register(ElseWhenBlock.class, ASTElseWhenBlock.class);
|
||||
register(TypeWhenBlock.class, ASTTypeWhenBlock.class);
|
||||
register(ValueWhenBlock.class, ASTValueWhenBlock.class);
|
||||
register(LiteralCase.class, ASTLiteralCase.class);
|
||||
register(IdentifierCase.class, ASTIdentifierCase.class);
|
||||
register(EmptyReferenceExpression.class, ASTEmptyReferenceExpression.class);
|
||||
}
|
||||
|
||||
private static <T extends AstNode> void register(Class<T> nodeType, Class<? extends AbstractApexNode<T>> nodeAdapterType) {
|
||||
private static <T extends AstNode> void register(Class<T> nodeType,
|
||||
Class<? extends AbstractApexNode<T>> nodeAdapterType) {
|
||||
try {
|
||||
NODE_TYPE_TO_NODE_ADAPTER_TYPE.put(nodeType, nodeAdapterType.getConstructor(nodeType));
|
||||
NODE_TYPE_TO_NODE_ADAPTER_TYPE.put(nodeType, nodeAdapterType.getDeclaredConstructor(nodeType));
|
||||
} catch (SecurityException | NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -805,4 +821,39 @@ public final class ApexTreeBuilder extends AstVisitor<AdditionalPassScope> {
|
||||
public boolean visit(NewKeyValueObjectExpression node, AdditionalPassScope scope) {
|
||||
return visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(SwitchStatement node, AdditionalPassScope scope) {
|
||||
return visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ElseWhenBlock node, AdditionalPassScope scope) {
|
||||
return visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeWhenBlock node, AdditionalPassScope scope) {
|
||||
return visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ValueWhenBlock node, AdditionalPassScope scope) {
|
||||
return visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(LiteralCase node, AdditionalPassScope scope) {
|
||||
return visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(IdentifierCase node, AdditionalPassScope scope) {
|
||||
return visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(EmptyReferenceExpression node, AdditionalPassScope scope) {
|
||||
return visit(node);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package net.sourceforge.pmd.lang.apex.metrics.api;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.impl.ClassCognitiveComplexityMetric;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.impl.WmcMetric;
|
||||
import net.sourceforge.pmd.lang.metrics.MetricKey;
|
||||
|
||||
@ -12,6 +13,7 @@ import net.sourceforge.pmd.lang.metrics.MetricKey;
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public enum ApexClassMetricKey implements MetricKey<ASTUserClassOrInterface<?>> {
|
||||
COGNITIVE(new ClassCognitiveComplexityMetric()),
|
||||
WMC(new WmcMetric());
|
||||
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
package net.sourceforge.pmd.lang.apex.metrics.api;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.impl.CognitiveComplexityMetric;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.impl.CycloMetric;
|
||||
import net.sourceforge.pmd.lang.metrics.MetricKey;
|
||||
|
||||
@ -12,7 +13,8 @@ import net.sourceforge.pmd.lang.metrics.MetricKey;
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public enum ApexOperationMetricKey implements MetricKey<ASTMethod> {
|
||||
CYCLO(new CycloMetric());
|
||||
CYCLO(new CycloMetric()),
|
||||
COGNITIVE(new CognitiveComplexityMetric());
|
||||
|
||||
|
||||
private final ApexOperationMetric calculator;
|
||||
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.metrics.impl;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey;
|
||||
import net.sourceforge.pmd.lang.metrics.MetricOptions;
|
||||
import net.sourceforge.pmd.lang.metrics.ResultOption;
|
||||
|
||||
/**
|
||||
* The sum of the cognitive complexities of all the methods within a class.
|
||||
*
|
||||
* @author Gwilym Kuiper
|
||||
*/
|
||||
public class ClassCognitiveComplexityMetric extends AbstractApexClassMetric {
|
||||
@Override
|
||||
public double computeFor(ASTUserClassOrInterface<?> node, MetricOptions options) {
|
||||
return ApexMetrics.get(ApexOperationMetricKey.COGNITIVE, node, ResultOption.SUM);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.metrics.impl;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.impl.visitors.CognitiveComplexityVisitor;
|
||||
import net.sourceforge.pmd.lang.metrics.MetricOptions;
|
||||
|
||||
/**
|
||||
* Measures the cognitive complexity of a Class / Method in Apex.
|
||||
*
|
||||
* See https://www.sonarsource.com/docs/CognitiveComplexity.pdf for information about the metric
|
||||
*
|
||||
* @author Gwilym Kuiper
|
||||
*/
|
||||
public class CognitiveComplexityMetric extends AbstractApexOperationMetric {
|
||||
@Override
|
||||
public double computeFor(ASTMethod node, MetricOptions options) {
|
||||
CognitiveComplexityVisitor.State resultingState = (CognitiveComplexityVisitor.State) node.jjtAccept(new CognitiveComplexityVisitor(), new CognitiveComplexityVisitor.State());
|
||||
return resultingState.getComplexity();
|
||||
}
|
||||
}
|
@ -0,0 +1,246 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.metrics.impl.visitors;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTBooleanExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTBreakStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTCatchBlockStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTContinueStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTDoLoopStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTIfElseBlockStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTMethodCallExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTPrefixExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTSwitchStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTTernaryExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ApexNode;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter;
|
||||
|
||||
import apex.jorje.data.ast.BooleanOp;
|
||||
import apex.jorje.data.ast.PrefixOp;
|
||||
|
||||
/**
|
||||
* @author Gwilym Kuiper
|
||||
*/
|
||||
public class CognitiveComplexityVisitor extends ApexParserVisitorAdapter {
|
||||
public static class State {
|
||||
private int complexity = 0;
|
||||
private int nestingLevel = 0;
|
||||
|
||||
private BooleanOp currentBooleanOperation = null;
|
||||
private String methodName = null;
|
||||
|
||||
public double getComplexity() {
|
||||
return complexity;
|
||||
}
|
||||
|
||||
void structureComplexity() {
|
||||
complexity += 1;
|
||||
}
|
||||
|
||||
void nestingComplexity() {
|
||||
complexity += nestingLevel;
|
||||
}
|
||||
|
||||
void booleanOperation(BooleanOp op) {
|
||||
if (currentBooleanOperation != op) {
|
||||
if (op != null) {
|
||||
structureComplexity();
|
||||
}
|
||||
|
||||
currentBooleanOperation = op;
|
||||
}
|
||||
}
|
||||
|
||||
void increaseNestingLevel() {
|
||||
structureComplexity();
|
||||
nestingComplexity();
|
||||
nestingLevel++;
|
||||
}
|
||||
|
||||
void decreaseNestingLevel() {
|
||||
nestingLevel--;
|
||||
}
|
||||
|
||||
void methodCall(String methodCalledName) {
|
||||
if (methodCalledName.equals(methodName)) {
|
||||
structureComplexity();
|
||||
}
|
||||
}
|
||||
|
||||
void setMethodName(String name) {
|
||||
methodName = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTIfElseBlockStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
boolean hasElseStatement = node.getNode().hasElseStatement();
|
||||
for (ApexNode<?> child : node.children()) {
|
||||
// If we don't have an else statement, we get an empty block statement which we shouldn't count
|
||||
if (!hasElseStatement && child instanceof ASTBlockStatement) {
|
||||
break;
|
||||
}
|
||||
|
||||
state.increaseNestingLevel();
|
||||
super.visit(child, data);
|
||||
state.decreaseNestingLevel();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTForLoopStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.increaseNestingLevel();
|
||||
super.visit(node, data);
|
||||
state.decreaseNestingLevel();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTForEachStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.increaseNestingLevel();
|
||||
super.visit(node, data);
|
||||
state.decreaseNestingLevel();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTContinueStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.structureComplexity();
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTBreakStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.structureComplexity();
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTWhileLoopStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.increaseNestingLevel();
|
||||
super.visit(node, data);
|
||||
state.decreaseNestingLevel();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTCatchBlockStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.increaseNestingLevel();
|
||||
super.visit(node, data);
|
||||
state.decreaseNestingLevel();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTDoLoopStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.increaseNestingLevel();
|
||||
super.visit(node, data);
|
||||
state.decreaseNestingLevel();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTTernaryExpression node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.increaseNestingLevel();
|
||||
super.visit(node, data);
|
||||
state.decreaseNestingLevel();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTBooleanExpression node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
BooleanOp op = node.getNode().getOp();
|
||||
if (op == BooleanOp.AND || op == BooleanOp.OR) {
|
||||
state.booleanOperation(op);
|
||||
}
|
||||
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTPrefixExpression node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
PrefixOp op = node.getNode().getOp();
|
||||
if (op == PrefixOp.NOT) {
|
||||
state.booleanOperation(null);
|
||||
}
|
||||
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTBlockStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
for (ApexNode<?> child : node.children()) {
|
||||
child.jjtAccept(this, data);
|
||||
|
||||
// This needs to happen because the current 'run' of boolean operations is terminated
|
||||
// once we finish a statement.
|
||||
state.booleanOperation(null);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTMethod node, Object data) {
|
||||
State state = (State) data;
|
||||
state.setMethodName(node.getNode().getMethodInfo().getCanonicalName());
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTMethodCallExpression node, Object data) {
|
||||
State state = (State) data;
|
||||
state.methodCall(node.getNode().getMethodName());
|
||||
return super.visit(node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTSwitchStatement node, Object data) {
|
||||
State state = (State) data;
|
||||
|
||||
state.increaseNestingLevel();
|
||||
super.visit(node, data);
|
||||
state.decreaseNestingLevel();
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
@ -36,6 +36,8 @@ import net.sourceforge.pmd.lang.apex.ast.ASTDmlUndeleteStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTDmlUpdateStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTDmlUpsertStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTDoLoopStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTElseWhenBlock;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTEmptyReferenceExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTExpressionStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTField;
|
||||
@ -44,12 +46,14 @@ import net.sourceforge.pmd.lang.apex.ast.ASTFieldDeclarationStatements;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTFormalComment;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTIdentifierCase;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTIfElseBlockStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTIllegalStoreExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTInstanceOfExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTJavaMethodCallExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTJavaVariableExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTLiteralCase;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTLiteralExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTMapEntryNode;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
|
||||
@ -84,18 +88,21 @@ import net.sourceforge.pmd.lang.apex.ast.ASTStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTStatementExecuted;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTSuperMethodCallExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTSuperVariableExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTSwitchStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTTernaryExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTThisMethodCallExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTThisVariableExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTThrowStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTTriggerVariableExpression;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTTryCatchFinallyBlockStatement;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTTypeWhenBlock;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserClassMethods;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserExceptionMethods;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserTrigger;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTValueWhenBlock;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclarationStatements;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression;
|
||||
@ -600,4 +607,39 @@ public abstract class AbstractApexRule extends AbstractRule
|
||||
public Object visit(ASTFormalComment node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTSwitchStatement node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTElseWhenBlock node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTTypeWhenBlock node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTValueWhenBlock node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTIdentifierCase node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTLiteralCase node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTEmptyReferenceExpression node, Object data) {
|
||||
return visit((ApexNode<?>) node, data);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.rule.design;
|
||||
|
||||
import static net.sourceforge.pmd.properties.constraints.NumericConstraints.positive;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTUserTrigger;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey;
|
||||
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
|
||||
import net.sourceforge.pmd.lang.metrics.MetricsUtil;
|
||||
import net.sourceforge.pmd.lang.metrics.ResultOption;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
|
||||
public class CognitiveComplexityRule extends AbstractApexRule {
|
||||
|
||||
private static final PropertyDescriptor<Integer> CLASS_LEVEL_DESCRIPTOR
|
||||
= PropertyFactory.intProperty("classReportLevel")
|
||||
.desc("Total class cognitive complexity reporting threshold")
|
||||
.require(positive())
|
||||
.defaultValue(50)
|
||||
.build();
|
||||
|
||||
private static final PropertyDescriptor<Integer> METHOD_LEVEL_DESCRIPTOR
|
||||
= PropertyFactory.intProperty("methodReportLevel")
|
||||
.desc("Cognitive complexity reporting threshold")
|
||||
.require(positive())
|
||||
.defaultValue(15)
|
||||
.build();
|
||||
|
||||
private Stack<String> classNames = new Stack<>();
|
||||
private boolean inTrigger;
|
||||
|
||||
|
||||
public CognitiveComplexityRule() {
|
||||
definePropertyDescriptor(CLASS_LEVEL_DESCRIPTOR);
|
||||
definePropertyDescriptor(METHOD_LEVEL_DESCRIPTOR);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTUserTrigger node, Object data) {
|
||||
inTrigger = true;
|
||||
super.visit(node, data);
|
||||
inTrigger = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object visit(ASTUserClass node, Object data) {
|
||||
|
||||
classNames.push(node.getImage());
|
||||
super.visit(node, data);
|
||||
classNames.pop();
|
||||
|
||||
if (ApexClassMetricKey.COGNITIVE.supports(node)) {
|
||||
int classCognitive = (int) MetricsUtil.computeMetric(ApexClassMetricKey.COGNITIVE, node);
|
||||
|
||||
if (classCognitive >= getProperty(CLASS_LEVEL_DESCRIPTOR)) {
|
||||
int classHighest = (int) ApexMetrics.get(ApexOperationMetricKey.COGNITIVE, node, ResultOption.HIGHEST);
|
||||
|
||||
String[] messageParams = {
|
||||
"class",
|
||||
node.getImage(),
|
||||
" total",
|
||||
classCognitive + " (highest " + classHighest + ")",
|
||||
};
|
||||
|
||||
addViolation(data, node, messageParams);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Object visit(ASTMethod node, Object data) {
|
||||
|
||||
if (ApexOperationMetricKey.COGNITIVE.supports(node)) {
|
||||
int cognitive = (int) MetricsUtil.computeMetric(ApexOperationMetricKey.COGNITIVE, node);
|
||||
if (cognitive >= getProperty(METHOD_LEVEL_DESCRIPTOR)) {
|
||||
String opType = inTrigger ? "trigger"
|
||||
: node.getImage().equals(classNames.peek()) ? "constructor"
|
||||
: "method";
|
||||
|
||||
addViolation(data, node, new String[] {
|
||||
opType,
|
||||
node.getQualifiedName().getOperation(),
|
||||
"",
|
||||
"" + cognitive,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
@ -86,6 +86,65 @@ public class Complicated {
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="CognitiveComplexity"
|
||||
message="The {0} ''{1}'' has a{2} cognitive complexity of {3}."
|
||||
since="6.22.0"
|
||||
class="net.sourceforge.pmd.lang.apex.rule.design.CognitiveComplexityRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#cognitivecomplexity">
|
||||
<description>
|
||||
Methods that are highly complex are difficult to read and more costly to maintain. If you include too much decisional
|
||||
logic within a single method, you make its behavior hard to understand and more difficult to modify.
|
||||
|
||||
Cognitive complexity is a measure of how difficult it is for humans to read and understand a method. Code that contains
|
||||
a break in the control flow is more complex, whereas the use of language shorthands doesn't increase the level of
|
||||
complexity. Nested control flows can make a method more difficult to understand, with each additional nesting of the
|
||||
control flow leading to an increase in cognitive complexity.
|
||||
|
||||
Information about Cognitive complexity can be found in the original paper here:
|
||||
https://www.sonarsource.com/docs/CognitiveComplexity.pdf
|
||||
|
||||
By default, this rule reports methods with a complexity of 15 or more. Reported methods should be broken down into less
|
||||
complex components.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo {
|
||||
// Has a cognitive complexity of 0
|
||||
public void createAccount() {
|
||||
Account account = new Account(Name = 'PMD');
|
||||
insert account;
|
||||
}
|
||||
|
||||
// Has a cognitive complexity of 1
|
||||
public Boolean setPhoneNumberIfNotExisting(Account a, String phone) {
|
||||
if (a.Phone == null) { // +1
|
||||
a.Phone = phone;
|
||||
update a;
|
||||
}
|
||||
}
|
||||
|
||||
// Has a cognitive complexity of 5
|
||||
public void updateContacts(List<Contact> contacts) {
|
||||
List<Contact> contactsToUpdate = new List<Contact>();
|
||||
|
||||
for (Contact contact : contacts) { // +1
|
||||
if (contact.Department == 'Finance') { // +2
|
||||
contact.Title = 'Finance Specialist';
|
||||
contactsToUpdate.add(contact);
|
||||
} else if (contact.Department == 'Sales') { // +2
|
||||
contact.Title = 'Sales Specialist';
|
||||
contactsToUpdate.add(contact);
|
||||
}
|
||||
}
|
||||
|
||||
update contactsToUpdate;
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="ExcessiveClassLength"
|
||||
since="5.5.0"
|
||||
message="Avoid really long classes."
|
||||
|
@ -328,4 +328,68 @@ public class MyClass {
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="TestMethodsMustBeInTestClasses"
|
||||
since="6.22.0"
|
||||
message="Test methods must be in test classes"
|
||||
class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#testmethodsmustbeintestclasses">
|
||||
<description>
|
||||
Test methods marked as a testMethod or annotated with @IsTest,
|
||||
but not residing in a test class should be moved to a proper
|
||||
class or have the @IsTest annotation added to the class.
|
||||
|
||||
Support for tests inside functional classes was removed in Spring-13 (API Version 27.0),
|
||||
making classes that violate this rule fail compile-time. This rule is mostly usable when
|
||||
dealing with legacy code.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="version" value="2.0"/>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//UserClass[
|
||||
not(./ModifierNode/Annotation[lower-case(@Image) = 'istest']) and
|
||||
(
|
||||
(./Method/ModifierNode/Annotation[lower-case(@Image) = 'istest']) or
|
||||
(./Method/ModifierNode[@Test = true()])
|
||||
)
|
||||
]
|
||||
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[// Violating
|
||||
private class TestClass {
|
||||
@IsTest static void myTest() {
|
||||
// Code here
|
||||
}
|
||||
}
|
||||
|
||||
private class TestClass {
|
||||
static testMethod void myTest() {
|
||||
// Code here
|
||||
}
|
||||
}
|
||||
|
||||
// Compliant
|
||||
@IsTest
|
||||
private class TestClass {
|
||||
@IsTest static void myTest() {
|
||||
// Code here
|
||||
}
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private class TestClass {
|
||||
static testMethod void myTest() {
|
||||
// Code here
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
</ruleset>
|
||||
|
@ -63,6 +63,7 @@
|
||||
<rule ref="category/apex/design.xml/CyclomaticComplexity">
|
||||
<priority>3</priority>
|
||||
</rule>
|
||||
<!-- <rule ref="category/apex/design.xml/CognitiveComplexity"/> -->
|
||||
|
||||
<!-- PERFORMANCE -->
|
||||
<rule ref="category/apex/performance.xml/AvoidSoqlInLoops" message="Avoid Soql queries inside loops">
|
||||
@ -123,6 +124,7 @@
|
||||
</rule>
|
||||
<!-- <rule ref="category/apex/bestpractices.xml/ApexAssertionsShouldIncludeMessage"/> -->
|
||||
<!-- <rule ref="category/apex/bestpractices.xml/ApexUnitTestMethodShouldHaveIsTestAnnotation"/> -->
|
||||
<!-- <rule ref="category/apex/errorprone.xml/TestMethodsMustBeInTestClasses"/> -->
|
||||
|
||||
<!-- SECURITY -->
|
||||
<rule ref="category/apex/security.xml/ApexSharingViolations" message="Apex classes should declare a sharing model if DML or SOQL is used">
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import apex.jorje.semantic.ast.compilation.Compilation;
|
||||
|
||||
public class ASTSwitchStatementTest extends ApexParserTestBase {
|
||||
|
||||
@Test
|
||||
public void testExamples() {
|
||||
ApexNode<Compilation> node = parseResource("SwitchStatements.cls");
|
||||
List<ASTSwitchStatement> switchStatements = node.findDescendantsOfType(ASTSwitchStatement.class);
|
||||
Assert.assertEquals(4, switchStatements.size());
|
||||
|
||||
Assert.assertTrue(switchStatements.get(0).getChild(0) instanceof ASTVariableExpression);
|
||||
Assert.assertEquals(5, switchStatements.get(0).findChildrenOfType(ASTValueWhenBlock.class).size());
|
||||
Assert.assertEquals(3, switchStatements.get(0).findChildrenOfType(ASTValueWhenBlock.class)
|
||||
.get(1).findChildrenOfType(ASTLiteralCase.class).size());
|
||||
Assert.assertEquals(1, switchStatements.get(0).findChildrenOfType(ASTElseWhenBlock.class).size());
|
||||
|
||||
Assert.assertTrue(switchStatements.get(1).getChild(0) instanceof ASTMethodCallExpression);
|
||||
Assert.assertEquals(2, switchStatements.get(1).findChildrenOfType(ASTValueWhenBlock.class).size());
|
||||
Assert.assertEquals(1, switchStatements.get(1).findChildrenOfType(ASTElseWhenBlock.class).size());
|
||||
|
||||
Assert.assertTrue(switchStatements.get(2).getChild(0) instanceof ASTVariableExpression);
|
||||
Assert.assertEquals(2, switchStatements.get(2).findChildrenOfType(ASTTypeWhenBlock.class).size());
|
||||
Assert.assertEquals("Account", switchStatements.get(2).findChildrenOfType(ASTTypeWhenBlock.class)
|
||||
.get(0).getType());
|
||||
Assert.assertEquals("a", switchStatements.get(2).findChildrenOfType(ASTTypeWhenBlock.class)
|
||||
.get(0).getName());
|
||||
Assert.assertEquals(1, switchStatements.get(2).findChildrenOfType(ASTValueWhenBlock.class).size());
|
||||
Assert.assertEquals(1, switchStatements.get(2).findChildrenOfType(ASTElseWhenBlock.class).size());
|
||||
|
||||
Assert.assertTrue(switchStatements.get(3).getChild(0) instanceof ASTVariableExpression);
|
||||
Assert.assertEquals(2, switchStatements.get(3).findChildrenOfType(ASTValueWhenBlock.class).size());
|
||||
Assert.assertEquals(1, switchStatements.get(3).findChildrenOfType(ASTElseWhenBlock.class).size());
|
||||
}
|
||||
|
||||
}
|
@ -179,7 +179,7 @@ public class ApexParserTest extends ApexParserTestBase {
|
||||
Assert.assertNotNull(rootNode);
|
||||
|
||||
int count = visitPosition(rootNode, 0);
|
||||
Assert.assertEquals(427, count);
|
||||
Assert.assertEquals(487, count);
|
||||
}
|
||||
|
||||
private int visitPosition(Node node, int count) {
|
||||
|
@ -20,6 +20,7 @@ public class AllMetricsTest extends SimpleAggregatorTst {
|
||||
public void setUp() {
|
||||
addRule(RULESET, "CycloTest");
|
||||
addRule(RULESET, "WmcTest");
|
||||
addRule(RULESET, "CognitiveComplexityTest");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.metrics.impl;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey;
|
||||
|
||||
/**
|
||||
* @author Gwilym Kuiper
|
||||
*/
|
||||
public class CognitiveComplexityTestRule extends AbstractApexMetricTestRule {
|
||||
@Override
|
||||
protected ApexClassMetricKey getClassKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ApexOperationMetricKey getOpKey() {
|
||||
return ApexOperationMetricKey.COGNITIVE;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.rule.errorprone;
|
||||
|
||||
import net.sourceforge.pmd.testframework.PmdRuleTst;
|
||||
|
||||
public class TestMethodsMustBeInTestClassesTest extends PmdRuleTst {
|
||||
// no additional unit tests
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
public class ApexSwitchStatements {
|
||||
public void example1() {
|
||||
int i = 3;
|
||||
switch on i {
|
||||
when 2 {
|
||||
System.debug('when block 2');
|
||||
}
|
||||
when 3, 4, 5 {
|
||||
System.debug('when block 3 and 4 and 5');
|
||||
}
|
||||
when 6, 7 {
|
||||
System.debug('when block 6 and 7');
|
||||
}
|
||||
when -3 {
|
||||
System.debug('when block -3');
|
||||
}
|
||||
when null {
|
||||
System.debug('bad integer');
|
||||
}
|
||||
when else {
|
||||
System.debug('default');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void example2() {
|
||||
int i = 1;
|
||||
switch on someInteger(i) {
|
||||
when 2 {
|
||||
System.debug('when block 2');
|
||||
}
|
||||
when 3 {
|
||||
System.debug('when block 3');
|
||||
}
|
||||
when else {
|
||||
System.debug('default');
|
||||
}
|
||||
}
|
||||
}
|
||||
private int someInteger(int i) {
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
public void example3() {
|
||||
switch on sobject {
|
||||
when Account a {
|
||||
System.debug('account ' + a);
|
||||
}
|
||||
when Contact c {
|
||||
System.debug('contact ' + c);
|
||||
}
|
||||
when null {
|
||||
System.debug('null');
|
||||
}
|
||||
when else {
|
||||
System.debug('default');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void example4() {
|
||||
switch on season {
|
||||
when WINTER {
|
||||
System.debug('boots');
|
||||
}
|
||||
when SPRING, SUMMER {
|
||||
System.debug('sandals');
|
||||
}
|
||||
when else {
|
||||
System.debug('none of the above');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user