net.sourceforge.pmd.lang.apex.rule.design.StdCyclomaticComplexityRule Maven / Gradle / Ivy
/**
* 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.NumericConstraints.inRange;
import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty;
import java.util.ArrayDeque;
import java.util.Deque;
import net.sourceforge.pmd.lang.apex.ast.ASTBooleanExpression;
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.ASTIfBlockStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
import net.sourceforge.pmd.lang.apex.ast.ASTTernaryExpression;
import net.sourceforge.pmd.lang.apex.ast.ASTTryCatchFinallyBlockStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum;
import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface;
import net.sourceforge.pmd.lang.apex.ast.ASTUserTrigger;
import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
import net.sourceforge.pmd.properties.PropertyDescriptor;
import net.sourceforge.pmd.properties.PropertyFactory;
import net.sourceforge.pmd.reporting.RuleContext;
/**
* Implements the standard cyclomatic complexity rule
*
* Standard rules: +1 for each decision point, but not including boolean
* operators unlike CyclomaticComplexityRule.
*
* @author ported on Java version of Alan Hohn, based on work by Donald A.
* Leckie
*
* @since June 18, 2014
*/
public class StdCyclomaticComplexityRule extends AbstractApexRule {
public static final PropertyDescriptor REPORT_LEVEL_DESCRIPTOR
= PropertyFactory.intProperty("reportLevel")
.desc("Cyclomatic Complexity reporting threshold")
.require(inRange(1, 30))
.defaultValue(10)
.build();
public static final PropertyDescriptor SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = booleanProperty("showClassesComplexity").desc("Add class average violations to the report").defaultValue(true).build();
public static final PropertyDescriptor SHOW_METHODS_COMPLEXITY_DESCRIPTOR = booleanProperty("showMethodsComplexity").desc("Add method average violations to the report").defaultValue(true).build();
private int reportLevel;
private boolean showClassesComplexity = true;
private boolean showMethodsComplexity = true;
protected static final class Entry {
private int decisionPoints = 1;
public int highestDecisionPoints;
public int methodCount;
private Entry() {
}
public void bumpDecisionPoints() {
decisionPoints++;
}
public void bumpDecisionPoints(int size) {
decisionPoints += size;
}
public int getComplexityAverage() {
return (double) methodCount == 0 ? 1 : (int) Math.rint((double) decisionPoints / (double) methodCount);
}
}
protected Deque entryStack = new ArrayDeque<>();
public StdCyclomaticComplexityRule() {
definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
}
@Override
public void start(RuleContext ctx) {
reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
}
@Override
public Object visit(ASTUserClass node, Object data) {
entryStack.push(new Entry());
super.visit(node, data);
Entry classEntry = entryStack.pop();
if (showClassesComplexity) {
if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) {
asCtx(data).addViolation(node, "class", node.getSimpleName(),
classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')');
}
}
return data;
}
@Override
public Object visit(ASTUserTrigger node, Object data) {
entryStack.push(new Entry());
super.visit(node, data);
Entry classEntry = entryStack.pop();
if (showClassesComplexity) {
if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) {
asCtx(data).addViolation(node, "trigger", node.getSimpleName(),
classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')');
}
}
return data;
}
@Override
public Object visit(ASTUserInterface node, Object data) {
return data;
}
@Override
public Object visit(ASTUserEnum node, Object data) {
return data;
}
@Override
public Object visit(ASTMethod node, Object data) {
entryStack.push(new Entry());
super.visit(node, data);
Entry methodEntry = entryStack.pop();
int methodDecisionPoints = methodEntry.decisionPoints;
Entry classEntry = entryStack.peek();
classEntry.methodCount++;
classEntry.bumpDecisionPoints(methodDecisionPoints);
if (methodDecisionPoints > classEntry.highestDecisionPoints) {
classEntry.highestDecisionPoints = methodDecisionPoints;
}
if (showMethodsComplexity && methodEntry.decisionPoints >= reportLevel) {
String methodType = node.isConstructor() ? "constructor" : "method";
asCtx(data).addViolation(node,
methodType, node.getImage(), String.valueOf(methodEntry.decisionPoints));
}
return data;
}
@Override
public Object visit(ASTIfBlockStatement node, Object data) {
entryStack.peek().bumpDecisionPoints();
super.visit(node, data);
return data;
}
@Override
public Object visit(ASTTryCatchFinallyBlockStatement node, Object data) {
entryStack.peek().bumpDecisionPoints();
super.visit(node, data);
return data;
}
@Override
public Object visit(ASTForLoopStatement node, Object data) {
entryStack.peek().bumpDecisionPoints();
super.visit(node, data);
return data;
}
@Override
public Object visit(ASTForEachStatement node, Object data) {
entryStack.peek().bumpDecisionPoints();
super.visit(node, data);
return data;
}
@Override
public Object visit(ASTWhileLoopStatement node, Object data) {
entryStack.peek().bumpDecisionPoints();
super.visit(node, data);
return data;
}
@Override
public Object visit(ASTDoLoopStatement node, Object data) {
entryStack.peek().bumpDecisionPoints();
super.visit(node, data);
return data;
}
@Override
public Object visit(ASTTernaryExpression node, Object data) {
entryStack.peek().bumpDecisionPoints();
super.visit(node, data);
return data;
}
@Override
public Object visit(ASTBooleanExpression node, Object data) {
return data;
}
}