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

org.sonar.python.metrics.CognitiveComplexityVisitor Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Python Plugin
 * Copyright (C) 2011-2024 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * 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 Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonar.python.metrics;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.tree.BinaryExpression;
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.plugins.python.api.tree.ConditionalExpression;
import org.sonar.plugins.python.api.tree.ElseClause;
import org.sonar.plugins.python.api.tree.ExceptClause;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.ForStatement;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.IfStatement;
import org.sonar.plugins.python.api.tree.ReturnStatement;
import org.sonar.plugins.python.api.tree.StatementList;
import org.sonar.plugins.python.api.tree.Statement;
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.WhileStatement;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.Tree.Kind;
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;

public class CognitiveComplexityVisitor extends BaseTreeVisitor {

  private int complexity = 0;
  private Deque nestingLevelStack = new LinkedList<>();
  private Set alreadyConsideredOperators = new HashSet<>();

  @Nullable
  private final SecondaryLocationConsumer secondaryLocationConsumer;

  public interface SecondaryLocationConsumer {
    void consume(Token secondaryLocation, String message);
  }

  CognitiveComplexityVisitor(@Nullable SecondaryLocationConsumer secondaryLocationConsumer) {
    this.secondaryLocationConsumer = secondaryLocationConsumer;
    nestingLevelStack.push(new NestingLevel());
  }

  public static int complexity(Tree tree, @Nullable SecondaryLocationConsumer secondaryLocationConsumer) {
    CognitiveComplexityVisitor visitor = new CognitiveComplexityVisitor(secondaryLocationConsumer);
    visitor.scan(tree);
    return visitor.complexity;
  }

  public int getComplexity() {
    return complexity;
  }

  @Override
  public void visitIfStatement(IfStatement pyIfStatementTree) {
    if (pyIfStatementTree.isElif()) {
      incrementWithoutNesting(pyIfStatementTree.keyword());
    } else {
      incrementWithNesting(pyIfStatementTree.keyword());
    }
    super.visitIfStatement(pyIfStatementTree);
  }

  @Override
  public void visitElseClause(ElseClause pyElseClauseTree) {
    incrementWithoutNesting(pyElseClauseTree.elseKeyword());
    super.visitElseClause(pyElseClauseTree);
  }

  @Override
  public void visitWhileStatement(WhileStatement pyWhileStatementTree) {
    incrementWithNesting(pyWhileStatementTree.whileKeyword());
    super.visitWhileStatement(pyWhileStatementTree);
  }

  @Override
  public void visitForStatement(ForStatement pyForStatementTree) {
    incrementWithNesting(pyForStatementTree.forKeyword());
    super.visitForStatement(pyForStatementTree);
  }

  @Override
  public void visitExceptClause(ExceptClause exceptClause) {
    incrementWithNesting(exceptClause.exceptKeyword());
    super.visitExceptClause(exceptClause);
  }

  @Override
  public void visitBinaryExpression(BinaryExpression pyBinaryExpressionTree) {
    if (pyBinaryExpressionTree.is(Kind.AND) || pyBinaryExpressionTree.is(Kind.OR)) {
      if (alreadyConsideredOperators.contains(pyBinaryExpressionTree.operator())) {
        super.visitBinaryExpression(pyBinaryExpressionTree);
        return;
      }
      List operators = new ArrayList<>();
      flattenOperators(pyBinaryExpressionTree, operators);
      Token previous = null;
      for (Token operator : operators) {
        if (previous == null || !previous.type().equals(operator.type())) {
          incrementWithoutNesting(pyBinaryExpressionTree.operator());
        }
        previous = operator;
        alreadyConsideredOperators.add(operator);
      }
    }
    super.visitBinaryExpression(pyBinaryExpressionTree);
  }

  private static void flattenOperators(BinaryExpression binaryExpression, List operators) {
    Expression left = binaryExpression.leftOperand();
    if (left.is(Kind.AND) || left.is(Kind.OR)) {
      flattenOperators((BinaryExpression) left, operators);
    }

    operators.add(binaryExpression.operator());

    Expression right = binaryExpression.rightOperand();
    if (right.is(Kind.AND) || right.is(Kind.OR)) {
      flattenOperators((BinaryExpression) right, operators);
    }
  }

  @Override
  public void visitFunctionDef(FunctionDef pyFunctionDefTree) {
    nestingLevelStack.push(new NestingLevel(nestingLevelStack.peek(), pyFunctionDefTree));
    super.visitFunctionDef(pyFunctionDefTree);
    nestingLevelStack.pop();
  }

  @Override
  public void visitClassDef(ClassDef pyClassDefTree) {
    nestingLevelStack.push(new NestingLevel(nestingLevelStack.peek(), pyClassDefTree));
    super.visitClassDef(pyClassDefTree);
    nestingLevelStack.pop();
  }

  @Override
  public void visitStatementList(StatementList statementList) {
    if (isStmtListIncrementsNestingLevel(statementList)) {
      nestingLevelStack.peek().increment();
      super.visitStatementList(statementList);
      nestingLevelStack.peek().decrement();
    } else {
      super.visitStatementList(statementList);
    }
  }

  @Override
  public void visitConditionalExpression(ConditionalExpression pyConditionalExpressionTree) {
    incrementWithNesting(pyConditionalExpressionTree.ifKeyword());
    nestingLevelStack.peek().increment();
    super.visitConditionalExpression(pyConditionalExpressionTree);
    nestingLevelStack.peek().decrement();
  }

  private static boolean isStmtListIncrementsNestingLevel(StatementList statementListTree) {
    if (statementListTree.parent().is(Kind.FILE_INPUT)) {
      return false;
    }
    List notIncrementingNestingKinds = Arrays.asList(Kind.TRY_STMT, Kind.FINALLY_CLAUSE, Kind.CLASSDEF, Kind.FUNCDEF, Kind.WITH_STMT);
    return statementListTree.parent() != null && notIncrementingNestingKinds.stream().noneMatch(kind -> statementListTree.parent().is(kind));
  }

  private void incrementWithNesting(Token secondaryLocation) {
    incrementComplexity(secondaryLocation, 1 + nestingLevelStack.peek().level());
  }

  private void incrementWithoutNesting(Token secondaryLocation) {
    incrementComplexity(secondaryLocation, 1);
  }

  private void incrementComplexity(Token secondaryLocation, int currentNodeComplexity) {
    if (secondaryLocationConsumer != null) {
      secondaryLocationConsumer.consume(secondaryLocation, secondaryMessage(currentNodeComplexity));
    }
    complexity += currentNodeComplexity;
  }

  private static String secondaryMessage(int complexity) {
    if (complexity == 1) {
      return "+1";
    } else {
      return String.format("+%s (incl %s for nesting)", complexity, complexity - 1);
    }
  }

  private static class NestingLevel {

    @Nullable
    private Tree tree;
    private int level;

    private NestingLevel() {
      tree = null;
      level = 0;
    }

    private NestingLevel(NestingLevel parent, Tree tree) {
      this.tree = tree;
      if (tree.is(Kind.FUNCDEF)) {
        if (parent.isWrapperFunction((FunctionDef) tree)) {
          level = parent.level;
        } else if (parent.isFunction()) {
          level = parent.level + 1;
        } else {
          level = 0;
        }
      } else {
        // PythonGrammar.CLASSDEF
        level = 0;
      }
    }

    private boolean isFunction() {
      return tree != null && tree.is(Kind.FUNCDEF);
    }

    private boolean isWrapperFunction(FunctionDef childFunction) {
      if(tree != null && tree.is(Kind.FUNCDEF)) {
        return ((FunctionDef) tree).body()
          .statements()
          .stream()
          .filter(statement -> statement != childFunction)
          .allMatch(NestingLevel::isSimpleReturn);
      }
      return false;
    }

    private static boolean isSimpleReturn(Statement statement) {
      if (statement.is(Kind.RETURN_STMT)) {
        ReturnStatement returnStatementTree = (ReturnStatement) statement;
        return returnStatementTree.expressions().size() == 1 && returnStatementTree.expressions().get(0).is(Kind.NAME);
      }
      return false;
    }

    private int level() {
      return level;
    }

    private void increment() {
      level++;
    }

    private void decrement() {
      level--;
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy