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

org.sonar.python.checks.utils.CheckUtils 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.checks.utils;

import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.ArgList;
import org.sonar.plugins.python.api.tree.Argument;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.plugins.python.api.tree.DictionaryLiteral;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.ListLiteral;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.Parameter;
import org.sonar.plugins.python.api.tree.ParameterList;
import org.sonar.plugins.python.api.tree.SetLiteral;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.Tuple;
import org.sonar.plugins.python.api.types.BuiltinTypes;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.python.api.PythonTokenType;
import org.sonar.python.tree.TreeUtils;

import static org.sonar.plugins.python.api.tree.Tree.Kind.GENERATOR_EXPR;
import static org.sonar.plugins.python.api.tree.Tree.Kind.LAMBDA;
import static org.sonar.plugins.python.api.tree.Tree.Kind.NAME;
import static org.sonar.plugins.python.api.tree.Tree.Kind.NONE;
import static org.sonar.plugins.python.api.tree.Tree.Kind.NUMERIC_LITERAL;
import static org.sonar.plugins.python.api.tree.Tree.Kind.STRING_LITERAL;
import static org.sonar.plugins.python.api.tree.Tree.Kind.UNPACKING_EXPR;

public class CheckUtils {

  private CheckUtils() {

  }

  public static boolean areEquivalent(@Nullable Tree leftTree, @Nullable Tree rightTree) {
    if (leftTree == rightTree) {
      return true;
    }
    if (leftTree == null || rightTree == null) {
      return false;
    }
    if (leftTree.getKind() != rightTree.getKind() || leftTree.children().size() != rightTree.children().size()) {
      return false;
    }
    if (leftTree.children().isEmpty() && rightTree.children().isEmpty()) {
      return areLeavesEquivalent(leftTree, rightTree);
    }

    List children1 = leftTree.children();
    List children2 = rightTree.children();
    for (int i = 0; i < children1.size(); i++) {
      if (!areEquivalent(children1.get(i), children2.get(i))) {
        return false;
      }
    }
    return true;
  }

  private static boolean areLeavesEquivalent(Tree leftLeaf, Tree rightLeaf) {
    if (leftLeaf.firstToken() == null && rightLeaf.firstToken() == null) {
      return true;
    }
    return leftLeaf.firstToken().type().equals(PythonTokenType.INDENT) || leftLeaf.firstToken().type().equals(PythonTokenType.DEDENT) ||
      leftLeaf.firstToken().value().equals(rightLeaf.firstToken().value());
  }

  @CheckForNull
  public static ClassDef getParentClassDef(Tree tree) {
    Tree current = tree.parent();
    while (current != null) {
      if (current.is(Tree.Kind.CLASSDEF)) {
        return (ClassDef) current;
      } else if (current.is(Tree.Kind.FUNCDEF, Tree.Kind.LAMBDA)) {
        return null;
      }
      current = current.parent();
    }
    return null;
  }

  public static boolean classHasInheritance(ClassDef classDef) {
    ArgList argList = classDef.args();
    if (argList == null) {
      return false;
    }
    List arguments = argList.arguments();
    if (arguments.isEmpty()) {
      return false;
    }
    return arguments.size() != 1 || !"object".equals(arguments.get(0).firstToken().value());
  }

  public static boolean containsCallToLocalsFunction(Tree tree) {
    return TreeUtils.hasDescendant(tree, t -> t.is(Tree.Kind.CALL_EXPR) && calleeHasNameLocals(((CallExpression) t)));
  }

  private static boolean calleeHasNameLocals(CallExpression callExpression) {
    Expression callee = callExpression.callee();
    return callee.is(Tree.Kind.NAME) && "locals".equals(((Name) callee).name());
  }

  public static boolean isConstant(Expression condition) {
    return isImmutableConstant(condition) || isConstantCollectionLiteral(condition);
  }

  public static boolean isImmutableConstant(Expression condition) {
    return TreeUtils.isBooleanLiteral(condition) ||
      condition.is(NUMERIC_LITERAL, STRING_LITERAL, NONE, LAMBDA, GENERATOR_EXPR);
  }

  public static boolean isConstantCollectionLiteral(Expression condition) {
    switch (condition.getKind()) {
      case LIST_LITERAL:
        return doesNotContainUnpackingExpression(((ListLiteral) condition).elements().expressions());
      case DICTIONARY_LITERAL:
        return doesNotContainUnpackingExpression(((DictionaryLiteral) condition).elements());
      case SET_LITERAL:
        return doesNotContainUnpackingExpression(((SetLiteral) condition).elements());
      case TUPLE:
        return doesNotContainUnpackingExpression(((Tuple) condition).elements());
      default:
        return false;
    }
  }

  private static boolean doesNotContainUnpackingExpression(List elements) {
    if (elements.isEmpty()) {
      return true;
    }
    return elements.stream().anyMatch(element -> !element.is(UNPACKING_EXPR));
  }

  public static boolean isNone(InferredType type) {
    return type.canOnlyBe(BuiltinTypes.NONE_TYPE);
  }

  private static final List PROTOCOL_LIKE_BASE_TYPES = List.of("typing.Protocol", "zope.interface.Interface");

  /**
   * Determines whether the given class must be a Protocol
   * or a similar Protocol-like definition (e.g. {@code zope.interface.Interface}).
   */
  public static boolean mustBeAProtocolLike(ClassDef classDef) {
    var classSymbol = TreeUtils.getClassSymbolFromDef(classDef);
    if (classSymbol != null) {
      return PROTOCOL_LIKE_BASE_TYPES.stream().anyMatch(classSymbol::isOrExtends);
    }

    return false;
  }

  private static final List ABC_ABSTRACTMETHOD_DECORATORS = List.of("abstractmethod", "abc.abstractmethod");

  public static boolean isAbstract(FunctionDef funDef) {
    return funDef
      .decorators()
      .stream()
      .map(decorator -> TreeUtils.decoratorNameFromExpression(decorator.expression()))
      .anyMatch(foundDeco -> ABC_ABSTRACTMETHOD_DECORATORS.stream().anyMatch(abcDeco -> abcDeco.equals(foundDeco)));
  }

  /**
   * Simple check whether the given expression is the "self" name expression.
   *
   * Carefully check the context when relying on this method!
   * This implementation does not ensure that the name is actually referring to a method parameter or whether the surrounding method might
   * be static, etc.
   */
  public static boolean isSelf(Expression expression) {
    // TODO: Instead of performing a manual string comparison, maybe check the symbol instead for being a SelfSymbolImpl symbol
    // (This might require exposing some more information about the symbol kind being a "Self"-symbol)
    return expression.is(NAME) && "self".equals(((Name) expression).name());
  }

  @CheckForNull
  public static Symbol findFirstParameterSymbol(FunctionDef functionDef) {
    ParameterList parameters = functionDef.parameters();
    if (parameters == null) {
      return null;
    }
    List params = parameters.nonTuple();
    if (params.isEmpty()) {
      return null;
    }
    Name firstParameterName = params.get(0).name();
    if (firstParameterName == null) {
      return null;
    }

    return firstParameterName.symbol();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy