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

org.sonar.python.checks.IgnoredPureOperationsCheck Maven / Gradle / Ivy

There is a newer version: 4.23.0.17664
Show 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;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.SubscriptionContext;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.ExpressionStatement;
import org.sonar.plugins.python.api.tree.InExpression;
import org.sonar.plugins.python.api.tree.SubscriptionExpression;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.tree.TreeUtils;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TriBool;
import org.sonar.python.types.v2.TypeChecker;

@Rule(key = "S2201")
public class IgnoredPureOperationsCheck extends PythonSubscriptionCheck {

  private static final String MESSAGE_FORMAT = "The return value of \"%s\" must be used.";

  private static final Set PURE_FUNCTIONS = new HashSet<>(Arrays.asList(
    "set",
    "dict",
    "frozenset",
    "str",
    "repr",
    "ascii",
    "ord",
    "hex",
    "oct",
    "bin",
    "bool",
    "bytes",
    "memoryview",
    "bytearray",
    "abs",
    "round",
    "min",
    "max",
    "divmod",
    "sum",
    "pow",
    "sorted",
    "filter",
    "enumerate",
    "reversed",
    "range",
    "slice",
    "zip",
    "help",
    "dir",
    "id",
    "object",
    "staticmethod",
    "classmethod",
    "property",
    "type",
    "isinstance",
    "issubclass",
    "callable",
    "format",
    "vars",
    "locals",
    "globals",
    "super",
    "str.capitalize",
    "str.casefold",
    "str.center",
    "str.count",
    "str.endswith",
    "str.expandtabs",
    "str.find",
    "str.format",
    "str.format_map",
    "str.index",
    "str.isalnum",
    "str.isalpha",
    "str.isascii",
    "str.isdecimal",
    "str.isdigit",
    "str.isidentifier",
    "str.islower",
    "str.isnumeric",
    "str.isprintable",
    "str.isspace",
    "str.istitle",
    "str.isupper",
    "str.join",
    "str.ljust",
    "str.lower",
    "str.lstrip",
    "str.maketrans",
    "str.partition",
    "str.replace",
    "str.rfind",
    "str.rindex",
    "str.rjust",
    "str.rpartition",
    "str.rsplit",
    "str.rstrip",
    "str.split",
    "str.splitlines",
    "str.startswith",
    "str.strip",
    "str.swapcase",
    "str.title",
    "str.translate",
    "str.upper",
    "str.zfill",
    "bytes.capitalize",
    "bytes.center",
    "bytes.count",
    "bytes.decode",
    "bytes.endswith",
    "bytes.expandtabs",
    "bytes.find",
    "bytes.fromhex",
    "bytes.hex",
    "bytes.index",
    "bytes.isalnum",
    "bytes.isalpha",
    "bytes.isascii",
    "bytes.isdigit",
    "bytes.islower",
    "bytes.isspace",
    "bytes.istitle",
    "bytes.isupper",
    "bytes.join",
    "bytes.ljust",
    "bytes.lower",
    "bytes.lstrip",
    "bytes.maketrans",
    "bytes.partition",
    "bytes.replace",
    "bytes.rfind",
    "bytes.rindex",
    "bytes.rjust",
    "bytes.rpartition",
    "bytes.rsplit",
    "bytes.rstrip",
    "bytes.split",
    "bytes.splitlines",
    "bytes.startswith",
    "bytes.strip",
    "bytes.swapcase",
    "bytes.title",
    "bytes.translate",
    "bytes.upper",
    "bytes.zfill",
    "bytearray.capitalize",
    "bytearray.center",
    "bytearray.count",
    "bytearray.decode",
    "bytearray.endswith",
    "bytearray.expandtabs",
    "bytearray.find",
    "bytearray.fromhex",
    "bytearray.hex",
    "bytearray.index",
    "bytearray.isalnum",
    "bytearray.isalpha",
    "bytearray.isascii",
    "bytearray.isdigit",
    "bytearray.islower",
    "bytearray.isspace",
    "bytearray.istitle",
    "bytearray.isupper",
    "bytearray.join",
    "bytearray.ljust",
    "bytearray.lower",
    "bytearray.lstrip",
    "bytearray.maketrans",
    "bytearray.partition",
    "bytearray.replace",
    "bytearray.rfind",
    "bytearray.rindex",
    "bytearray.rjust",
    "bytearray.rpartition",
    "bytearray.rsplit",
    "bytearray.rstrip",
    "bytearray.split",
    "bytearray.splitlines",
    "bytearray.startswith",
    "bytearray.strip",
    "bytearray.swapcase",
    "bytearray.title",
    "bytearray.translate",
    "bytearray.upper",
    "bytearray.zfill",
    "memoryview.cast",
    "memoryview.hex",
    "memoryview.tobytes",
    "memoryview.tolist",
    "memoryview.toreadonly",
    "int.as_integer_ratio",
    "int.bit_length",
    "int.conjugate",
    "int.from_bytes",
    "int.to_bytes",
    "float.as_integer_ratio",
    "float.conjugate",
    "float.fromhex",
    "float.hex",
    "float.is_integer",
    "bool.as_integer_ratio",
    "bool.bit_length",
    "bool.conjugate",
    "bool.from_bytes",
    "bool.to_bytes",
    "list.copy",
    "list.count",
    "list.index",
    "tuple.count",
    "tuple.index",
    "range.count",
    "range.index",
    "set.copy",
    "set.difference",
    "set.intersection",
    "set.isdisjoint",
    "set.issubset",
    "set.issuperset",
    "set.symmetric_difference",
    "set.union",
    "frozenset.copy",
    "frozenset.difference",
    "frozenset.intersection",
    "frozenset.isdisjoint",
    "frozenset.issubset",
    "frozenset.issuperset",
    "frozenset.symmetric_difference",
    "frozenset.union",
    "dict.copy",
    "dict.dicfromkeys",
    "dict.get",
    "dict.items",
    "dict.keys",
    "dict.values"
  ));

  private static final Set PURE_GETITEM_TYPES = new HashSet<>(Arrays.asList(
    "str",
    "bytes",
    "bytearray",
    "memoryview",
    "list",
    "tuple",
    "range",
    "dict"
  ));

  private static final Set PURE_CONTAINS_TYPES = new HashSet<>();
  static {
    PURE_CONTAINS_TYPES.addAll(PURE_GETITEM_TYPES);
    PURE_CONTAINS_TYPES.addAll(Arrays.asList(
      "set",
      "frozenset"
    ));
  }

  @Override
  public void initialize(Context context) {
    context.registerSyntaxNodeConsumer(Tree.Kind.EXPRESSION_STMT, ctx -> {
      ExpressionStatement expressionStatement = (ExpressionStatement) ctx.syntaxNode();
      if (TreeUtils.firstAncestor(expressionStatement, IgnoredPureOperationsCheck::isInTryBlock) != null) {
        return;
      }

      expressionStatement.expressions().forEach(expression -> checkExpression(ctx, expression));
    });
  }

  private static void checkExpression(SubscriptionContext ctx, Expression expression) {
    TypeChecker typeChecker = ctx.typeChecker();
    if (expression.is(Tree.Kind.CALL_EXPR)) {
      CallExpression callExpression = (CallExpression) expression;
      PythonType pythonType = callExpression.callee().typeV2();
      PURE_FUNCTIONS.stream()
        .filter(f -> typeChecker.typeCheckBuilder().isTypeWithName(f).check(pythonType).equals(TriBool.TRUE))
        .findFirst()
        .ifPresent(result -> ctx.addIssue(callExpression.callee(), String.format(MESSAGE_FORMAT, result)));
    } else if (expression.is(Tree.Kind.SUBSCRIPTION)) {
      SubscriptionExpression subscriptionExpression = (SubscriptionExpression) expression;
      PythonType pythonType = subscriptionExpression.object().typeV2();
      boolean isPureGetitemType = PURE_GETITEM_TYPES.stream().anyMatch(t -> typeChecker.typeCheckBuilder().isTypeOrInstanceWithName(t).check(pythonType).equals(TriBool.TRUE));
      if (isPureGetitemType) {
        ctx.addIssue(subscriptionExpression, String.format(MESSAGE_FORMAT, "__getitem__"));
      }
    } else if (expression.is(Tree.Kind.IN)) {
      InExpression inExpression = (InExpression) expression;
      PythonType pythonType = inExpression.rightOperand().typeV2();
      boolean isPureContainsType = PURE_CONTAINS_TYPES.stream().anyMatch(t -> typeChecker.typeCheckBuilder().isTypeOrInstanceWithName(t).check(pythonType).equals(TriBool.TRUE));
      if (isPureContainsType) {
        ctx.addIssue(inExpression, String.format(MESSAGE_FORMAT, "__contains__"));
      }
    }
  }

  private static boolean isInTryBlock(Tree tree) {
    // We need a direct STATEMENT_LIST descendant of a TRY_STATEMENT, other clauses are
    // descendants of except or finally clauses.
    return tree.is(Tree.Kind.STATEMENT_LIST) && tree.parent().is(Tree.Kind.TRY_STMT);
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy