Merge branch '7.0.x' into java-grammar

This commit is contained in:
Clément Fournier
2020-03-02 21:05:21 +01:00
94 changed files with 4916 additions and 961 deletions

View File

@ -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)

View File

@ -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=""/>

View File

@ -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 %}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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."

View File

@ -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">

View File

@ -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());
}
}

View File

@ -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) {

View File

@ -20,6 +20,7 @@ public class AllMetricsTest extends SimpleAggregatorTst {
public void setUp() {
addRule(RULESET, "CycloTest");
addRule(RULESET, "WmcTest");
addRule(RULESET, "CognitiveComplexityTest");
}
}

View File

@ -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;
}
}

View File

@ -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');
}
}
}
}

View File

@ -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>

View File

@ -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