Merge branch '7.0.x' into java-grammar
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,72 @@ 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.
|
||||
|
||||
### 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
|
||||
* 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 +164,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 +175,8 @@ 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)
|
||||
|
||||
{% 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,234 @@
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
}
|
@ -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."
|
||||
|
@ -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">
|
||||
|
@ -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,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
@ -19,4 +19,9 @@
|
||||
class="net.sourceforge.pmd.lang.apex.metrics.impl.WmcTestRule">
|
||||
</rule>
|
||||
|
||||
<rule name="CognitiveComplexityTest"
|
||||
message = "''{0}'' has value {1}."
|
||||
class="net.sourceforge.pmd.lang.apex.metrics.impl.CognitiveComplexityTestRule">
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
||||
|
@ -4,6 +4,12 @@
|
||||
|
||||
package net.sourceforge.pmd.cpd.token.internal;
|
||||
|
||||
import static net.sourceforge.pmd.internal.util.IteratorUtil.AbstractIterator;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import net.sourceforge.pmd.cpd.token.TokenFilter;
|
||||
import net.sourceforge.pmd.lang.TokenManager;
|
||||
import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
@ -15,7 +21,10 @@ import net.sourceforge.pmd.lang.ast.GenericToken;
|
||||
public abstract class BaseTokenFilter<T extends GenericToken> implements TokenFilter {
|
||||
|
||||
private final TokenManager tokenManager;
|
||||
private final LinkedList<T> unprocessedTokens; // NOPMD - used both as Queue and List
|
||||
private final Iterable<T> remainingTokens;
|
||||
private boolean discardingSuppressing;
|
||||
private T currentToken;
|
||||
|
||||
/**
|
||||
* Creates a new BaseTokenFilter
|
||||
@ -23,13 +32,21 @@ public abstract class BaseTokenFilter<T extends GenericToken> implements TokenFi
|
||||
*/
|
||||
public BaseTokenFilter(final TokenManager tokenManager) {
|
||||
this.tokenManager = tokenManager;
|
||||
this.unprocessedTokens = new LinkedList<>();
|
||||
this.remainingTokens = new RemainingTokens();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T getNextToken() {
|
||||
T currentToken = (T) tokenManager.getNextToken();
|
||||
currentToken = null;
|
||||
if (!unprocessedTokens.isEmpty()) {
|
||||
currentToken = unprocessedTokens.poll();
|
||||
return currentToken;
|
||||
}
|
||||
currentToken = (T) tokenManager.getNextToken();
|
||||
while (!shouldStopProcessing(currentToken)) {
|
||||
analyzeToken(currentToken);
|
||||
analyzeTokens(currentToken, remainingTokens);
|
||||
processCPDSuppression(currentToken);
|
||||
|
||||
if (!isDiscarding()) {
|
||||
@ -73,6 +90,18 @@ public abstract class BaseTokenFilter<T extends GenericToken> implements TokenFi
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to analyze all tokens (before filtering)
|
||||
* and update internal status to decide on custom discard rules.
|
||||
*
|
||||
* @param currentToken The token to be analyzed
|
||||
* @param remainingTokens All upcoming tokens
|
||||
* @see #isLanguageSpecificDiscarding()
|
||||
*/
|
||||
protected void analyzeTokens(final T currentToken, final Iterable<T> remainingTokens) {
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to indicate tokens are to be filtered.
|
||||
*
|
||||
@ -90,4 +119,43 @@ public abstract class BaseTokenFilter<T extends GenericToken> implements TokenFi
|
||||
*/
|
||||
protected abstract boolean shouldStopProcessing(T currentToken);
|
||||
|
||||
private class RemainingTokens implements Iterable<T> {
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new RemainingTokensIterator(currentToken);
|
||||
}
|
||||
|
||||
private class RemainingTokensIterator extends AbstractIterator<T> implements Iterator<T> {
|
||||
|
||||
int index = 0; // index of next element
|
||||
T startToken;
|
||||
|
||||
RemainingTokensIterator(final T startToken) {
|
||||
this.startToken = startToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeNext() {
|
||||
assert index >= 0;
|
||||
if (startToken != currentToken) { // NOPMD - intentional check for reference equality
|
||||
throw new ConcurrentModificationException("Using iterator after next token has been requested.");
|
||||
}
|
||||
if (index < unprocessedTokens.size()) {
|
||||
setNext(unprocessedTokens.get(index++));
|
||||
} else {
|
||||
final T nextToken = (T) tokenManager.getNextToken();
|
||||
if (shouldStopProcessing(nextToken)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
unprocessedTokens.add(nextToken);
|
||||
setNext(nextToken);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user