All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.checks.CognitiveComplexityMethodCheck Maven / Gradle / Ivy

/*
 * SonarQube Java
 * Copyright (C) 2012-2017 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.java.checks;

import com.google.common.collect.ImmutableList;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.BinaryExpressionTree;
import org.sonar.plugins.java.api.tree.BreakStatementTree;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.ConditionalExpressionTree;
import org.sonar.plugins.java.api.tree.ContinueStatementTree;
import org.sonar.plugins.java.api.tree.DoWhileStatementTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.ForEachStatement;
import org.sonar.plugins.java.api.tree.ForStatementTree;
import org.sonar.plugins.java.api.tree.IfStatementTree;
import org.sonar.plugins.java.api.tree.LambdaExpressionTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.SwitchStatementTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TryStatementTree;
import org.sonar.plugins.java.api.tree.WhileStatementTree;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.sonar.plugins.java.api.tree.Tree.Kind.CONDITIONAL_AND;
import static org.sonar.plugins.java.api.tree.Tree.Kind.CONDITIONAL_OR;
import static org.sonar.plugins.java.api.tree.Tree.Kind.CONSTRUCTOR;
import static org.sonar.plugins.java.api.tree.Tree.Kind.IF_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.METHOD;


@Rule(key = "S3776")
public class CognitiveComplexityMethodCheck  extends IssuableSubscriptionVisitor {

  private static final int DEFAULT_MAX = 15;

  @RuleProperty(
          key = "Threshold",
          description = "The maximum authorized complexity.",
          defaultValue = "" + DEFAULT_MAX)
  private int max = DEFAULT_MAX;

  @Override
  public List nodesToVisit() {
    return ImmutableList.of(METHOD, CONSTRUCTOR);
  }

  @Override
  public void visitNode(Tree tree) {
    MethodTree method = (MethodTree) tree;
    if (method.block() == null || ((ClassTree)method.parent()).simpleName() == null || isWithinLocalClass(method)) {
      return;
    }
    CognitiveComplexityVisitor complexityVisitor = new CognitiveComplexityVisitor();
    method.accept(complexityVisitor);
    int total = complexityVisitor.complexity;
    if (total > max) {
      reportIssue(method.simpleName(),
        "Refactor this method to reduce its Cognitive Complexity from " + total + " to the " + max + " allowed.", complexityVisitor.flow, total - max);
    }

  }

  private boolean isWithinLocalClass(MethodTree method) {
    return hasSemantic() && method.symbol().owner().owner().isMethodSymbol();
  }

  private static class CognitiveComplexityVisitor extends BaseTreeVisitor {

    private final List flow;
    private final Set ignored;
    int complexity;
    int nesting;
    boolean ignoreNesting;

    CognitiveComplexityVisitor() {
      complexity = 0;
      nesting = 1;
      ignoreNesting = false;
      flow = new ArrayList<>();
      ignored = new HashSet<>();
    }

    private void increaseComplexityByNesting(Tree tree) {
      increaseComplexity(tree, nesting);
    }

    private void increaseComplexityByOne(Tree tree) {
      increaseComplexity(tree, 1);
    }

    private void increaseComplexity(Tree tree, int increase) {
      complexity += increase;
      if (ignoreNesting) {
        flow.add(new JavaFileScannerContext.Location("+1", tree));
        ignoreNesting = false;
      } else if (!ignored.contains(tree)) {
        String message = "+" + increase;
        if (increase > 1) {
          message += " (incl " + (increase - 1) + " for nesting)";
        }
        flow.add(new JavaFileScannerContext.Location(message, tree));
      }
    }

    @Override
    public void visitIfStatement(IfStatementTree tree) {
      increaseComplexityByNesting(tree.ifKeyword());
      scan(tree.condition());
      nesting++;
      scan(tree.thenStatement());
      nesting--;
      boolean elseStatementNotIF = tree.elseStatement() != null && !tree.elseStatement().is(IF_STATEMENT);
      if(elseStatementNotIF) {
        increaseComplexityByOne(tree.elseKeyword());
        nesting++;
      } else if(tree.elseStatement() != null) {
        // else statement is an if, visiting it will increase complexity by nesting so by one only.
        ignoreNesting = true;
        complexity -= nesting - 1;
      }
      scan(tree.elseStatement());
      if(elseStatementNotIF) {
        nesting--;
      }
    }

    @Override
    public void visitTryStatement(TryStatementTree tree) {
      scan(tree.resources());
      scan(tree.block());
      tree.catches().forEach(c -> increaseComplexityByNesting(c.catchKeyword()));
      nesting++;
      scan(tree.catches());
      nesting--;
      scan(tree.finallyBlock());
    }

    @Override
    public void visitForStatement(ForStatementTree tree) {
      increaseComplexityByNesting(tree.forKeyword());
      nesting++;
      super.visitForStatement(tree);
      nesting--;
    }

    @Override
    public void visitForEachStatement(ForEachStatement tree) {
      increaseComplexityByNesting(tree.forKeyword());
      nesting++;
      super.visitForEachStatement(tree);
      nesting--;
    }

    @Override
    public void visitWhileStatement(WhileStatementTree tree) {
      increaseComplexityByNesting(tree.whileKeyword());
      nesting++;
      super.visitWhileStatement(tree);
      nesting--;
    }

    @Override
    public void visitDoWhileStatement(DoWhileStatementTree tree) {
      increaseComplexityByNesting(tree.doKeyword());
      nesting++;
      super.visitDoWhileStatement(tree);
      nesting--;
    }

    @Override
    public void visitConditionalExpression(ConditionalExpressionTree tree) {
      increaseComplexityByNesting(tree.questionToken());
      nesting++;
      super.visitConditionalExpression(tree);
      nesting--;
    }

    @Override
    public void visitSwitchStatement(SwitchStatementTree tree) {
      increaseComplexityByNesting(tree.switchKeyword());
      nesting++;
      super.visitSwitchStatement(tree);
      nesting--;
    }

    @Override
    public void visitBreakStatement(BreakStatementTree tree) {
      if(tree.label() != null) {
        increaseComplexityByOne(tree.breakKeyword());
      }
      super.visitBreakStatement(tree);
    }

    @Override
    public void visitContinueStatement(ContinueStatementTree tree) {
      if(tree.label() != null) {
        increaseComplexityByOne(tree.continueKeyword());
      }
      super.visitContinueStatement(tree);
    }

    @Override
    public void visitClass(ClassTree tree) {
      nesting++;
      super.visitClass(tree);
      nesting--;
    }

    @Override
    public void visitLambdaExpression(LambdaExpressionTree lambdaExpressionTree) {
      nesting++;
      super.visitLambdaExpression(lambdaExpressionTree);
      nesting--;
    }

    @Override
    public void visitBinaryExpression(BinaryExpressionTree tree) {
      if(tree.is(CONDITIONAL_AND, CONDITIONAL_OR)) {
        increaseComplexityByOne(tree.operatorToken());
        ExpressionTree left = ExpressionUtils.skipParentheses(tree.leftOperand());
        if(left.kind() == tree.kind()) {
          ignored.add(((BinaryExpressionTree) left).operatorToken());
          complexity--;
        }
        ExpressionTree right = ExpressionUtils.skipParentheses(tree.rightOperand());
        if(right.kind() == tree.kind()) {
          ignored.add(((BinaryExpressionTree) right).operatorToken());
          complexity--;
        }
      }
      super.visitBinaryExpression(tree);
    }
  }

  public void setMax(int max) {
    this.max = max;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy