public static int complexity(CompilationUnitTree cut) { // Only explicitly visit functions, methods and function expressions. // Rest of the compilation unit is based on CognitiveComplexityVisitor computation class CompilationUnitVisitor extends CognitiveComplexityVisitor { private int functionsComplexity = 0; @Override public void visitMethodDeclaration(MethodDeclarationTree tree) { sumComplexity(tree); } @Override public void visitFunctionDeclaration(FunctionDeclarationTree tree) { sumComplexity(tree); } @Override public void visitFunctionExpression(FunctionExpressionTree tree) { sumComplexity(tree); } private void sumComplexity(FunctionTree tree) { functionsComplexity += complexity(tree).getValue(); } private int complexityWithFunctionsAndRestOfSript() { int scriptComplexity = super.complexity.value; return functionsComplexity + scriptComplexity; } } CompilationUnitVisitor compilationUnitVisitor = new CompilationUnitVisitor(); cut.accept(compilationUnitVisitor); return compilationUnitVisitor.complexityWithFunctionsAndRestOfSript(); }
private void flattenLogicalExpression(int i, List<SyntaxToken> operators, ExpressionTree expression) { if (expression.is(CONDITIONAL_AND, CONDITIONAL_OR)) { nestedLogicalExpressions.add(expression); BinaryExpressionTree binaryExpression = (BinaryExpressionTree) expression; operators.add(i, binaryExpression.operator()); ExpressionTree leftChild = removeParenthesis(binaryExpression.leftOperand()); ExpressionTree rightChild = removeParenthesis(binaryExpression.rightOperand()); flattenLogicalExpression(i + 1, operators, rightChild); flattenLogicalExpression(i, operators, leftChild); } }
@Override public void visitForEachStatement(ForEachStatementTree tree) { complexity.addComplexityWithNesting(tree.foreachToken()); visit(tree.expression()); visit(tree.key()); visit(tree.value()); visitWithNesting(tree.statements()); }
private List<TestComplexityComponent> components(String functionBody) throws Exception { FunctionTree tree = parse("function f() { " + functionBody + " }", PHPLexicalGrammar.FUNCTION_DECLARATION); CognitiveComplexity complexity = CognitiveComplexityVisitor.complexity(tree); return complexity.getComplexityComponents().stream().map(TestComplexityComponent::create).collect(Collectors.toList()); }
@Override public void visitFunctionDeclaration(FunctionDeclarationTree tree) { visitWithNesting(()-> super.visitFunctionDeclaration(tree)); }
private static ExpressionTree removeParenthesis(ExpressionTree expressionTree) { if (expressionTree.is(Tree.Kind.PARENTHESISED_EXPRESSION)) { return removeParenthesis(((ParenthesisedExpressionTree) expressionTree).expression()); } return expressionTree; }
@Override public void visitBinaryExpression(BinaryExpressionTree tree) { if (tree.is(CONDITIONAL_AND, CONDITIONAL_OR) && !nestedLogicalExpressions.contains(tree)) { List<SyntaxToken> flatOperators = new ArrayList<>(); flattenLogicalExpression(0, flatOperators, tree); complexity.addComplexityWithoutNesting(flatOperators.get(0)); for (int i = 1; i < flatOperators.size(); i++) { if (!flatOperators.get(i).text().equals(flatOperators.get(i - 1).text())) { complexity.addComplexityWithoutNesting(flatOperators.get(i)); } } } super.visitBinaryExpression(tree); }
private int complexity(String functionBody) throws Exception { FunctionTree tree = parse("function f() { " + functionBody + " }", PHPLexicalGrammar.FUNCTION_DECLARATION); CognitiveComplexity complexity = CognitiveComplexityVisitor.complexity(tree); return complexity.getValue(); }
private void visitWithNesting(@Nullable Tree tree) { if (tree != null) { visitWithNesting(() -> tree.accept(this)); } }
private static ExpressionTree removeParenthesis(ExpressionTree expressionTree) { if (expressionTree.is(Tree.Kind.PARENTHESISED_EXPRESSION)) { return removeParenthesis(((ParenthesisedExpressionTree) expressionTree).expression()); } return expressionTree; }
@Override public void visitBinaryExpression(BinaryExpressionTree tree) { if (tree.is(CONDITIONAL_AND, CONDITIONAL_OR) && !nestedLogicalExpressions.contains(tree)) { List<SyntaxToken> flatOperators = new ArrayList<>(); flattenLogicalExpression(0, flatOperators, tree); complexity.addComplexityWithoutNesting(flatOperators.get(0)); for (int i = 1; i < flatOperators.size(); i++) { if (!flatOperators.get(i).text().equals(flatOperators.get(i - 1).text())) { complexity.addComplexityWithoutNesting(flatOperators.get(i)); } } } super.visitBinaryExpression(tree); }
@Test public void file_complexity_is_sum_of_functions() throws Exception { File file = new File("src/test/resources/metrics/file_cognitive_complexity.php"); ActionParser<Tree> p = PHPParserBuilder.createParser(PHPLexicalGrammar.COMPILATION_UNIT); CompilationUnitTree cut = (CompilationUnitTree) p.parse(file); int complexity = CognitiveComplexityVisitor.complexity(cut); assertThat(complexity).isEqualTo( 1 // foo + 3 // bar (incl. qix) + 2 // gul (incl. function expression) + 1 // dom + 1 // $func function expression + 4 // rest of the script ); }
@Override public void visitForEachStatement(ForEachStatementTree tree) { complexity.addComplexityWithNesting(tree.foreachToken()); visit(tree.expression()); visit(tree.key()); visit(tree.value()); visitWithNesting(tree.statements()); }
private void visitWithNesting(@Nullable Tree tree) { if (tree != null) { visitWithNesting(() -> tree.accept(this)); } }
private void flattenLogicalExpression(int i, List<SyntaxToken> operators, ExpressionTree expression) { if (expression.is(CONDITIONAL_AND, CONDITIONAL_OR)) { nestedLogicalExpressions.add(expression); BinaryExpressionTree binaryExpression = (BinaryExpressionTree) expression; operators.add(i, binaryExpression.operator()); ExpressionTree leftChild = removeParenthesis(binaryExpression.leftOperand()); ExpressionTree rightChild = removeParenthesis(binaryExpression.rightOperand()); flattenLogicalExpression(i + 1, operators, rightChild); flattenLogicalExpression(i, operators, leftChild); } }
public static int complexity(CompilationUnitTree cut) { // Only explicitly visit functions, methods and function expressions. // Rest of the compilation unit is based on CognitiveComplexityVisitor computation class CompilationUnitVisitor extends CognitiveComplexityVisitor { private int functionsComplexity = 0; @Override public void visitMethodDeclaration(MethodDeclarationTree tree) { sumComplexity(tree); } @Override public void visitFunctionDeclaration(FunctionDeclarationTree tree) { sumComplexity(tree); } @Override public void visitFunctionExpression(FunctionExpressionTree tree) { sumComplexity(tree); } private void sumComplexity(FunctionTree tree) { functionsComplexity += complexity(tree).getValue(); } private int complexityWithFunctionsAndRestOfSript() { int scriptComplexity = super.complexity.value; return functionsComplexity + scriptComplexity; } } CompilationUnitVisitor compilationUnitVisitor = new CompilationUnitVisitor(); cut.accept(compilationUnitVisitor); return compilationUnitVisitor.complexityWithFunctionsAndRestOfSript(); }
private void checkFunctionComplexity(FunctionTree functionTree) { CognitiveComplexity complexity = CognitiveComplexityVisitor.complexity(functionTree); if (complexity.getValue() > threshold) { String message = String.format(MESSAGE, complexity.getValue(), threshold); int cost = complexity.getValue() - threshold; PreciseIssue issue = context().newIssue(this, (functionTree).functionToken(), message).cost(cost); complexity.getComplexityComponents().forEach(complexityComponent -> issue.secondary( complexityComponent.tree(), secondaryMessage(complexityComponent.addedComplexity()))); } }
@Override public void visitDoWhileStatement(DoWhileStatementTree tree) { complexity.addComplexityWithNesting(tree.doToken()); visitWithNesting(tree.statement()); visit(tree.condition()); }
@Override public void visitMethodDeclaration(MethodDeclarationTree tree) { visitWithNesting(()-> super.visitMethodDeclaration(tree)); }
private void checkFunctionComplexity(FunctionTree functionTree) { CognitiveComplexity complexity = CognitiveComplexityVisitor.complexity(functionTree); if (complexity.getValue() > threshold) { String message = String.format(MESSAGE, complexity.getValue(), threshold); int cost = complexity.getValue() - threshold; PreciseIssue issue = context().newIssue(this, (functionTree).functionToken(), message).cost(cost); complexity.getComplexityComponents().forEach(complexityComponent -> issue.secondary( complexityComponent.tree(), secondaryMessage(complexityComponent.addedComplexity()))); } }