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

org.sonar.python.checks.AbstractDuplicateKeyCheck 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.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.NumericLiteral;
import org.sonar.plugins.python.api.tree.StringElement;
import org.sonar.plugins.python.api.tree.StringLiteral;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.Tuple;
import org.sonar.python.checks.utils.CheckUtils;

public abstract class AbstractDuplicateKeyCheck extends PythonSubscriptionCheck {

  // Avoid performance issues for big dictionary/set literals
  static final int SIZE_THRESHOLD = 100;

  boolean isSameKey(Expression key, Expression comparedKey) {
    if (key.is(Tree.Kind.TUPLE) && comparedKey.is(Tree.Kind.TUPLE)) {
      return areEquivalentTuples((Tuple) key, (Tuple) comparedKey);
    }
    if (key.is(Tree.Kind.STRING_LITERAL) && comparedKey.is(Tree.Kind.STRING_LITERAL)) {
      return areEquivalentStringLiterals((StringLiteral) key, (StringLiteral) comparedKey);
    }
    if (isANumber(key) && isANumber(comparedKey)) {
      return areEquivalentNumbers(key, comparedKey);
    }
    return !key.is(Tree.Kind.CALL_EXPR) && CheckUtils.areEquivalent(key, comparedKey);
  }

  private boolean areEquivalentTuples(Tuple key, Tuple comparedKey) {
    List first = key.elements();
    List second = comparedKey.elements();
    if (first.size() != second.size()) {
      return false;
    }
    for (int i = 0; i < first.size(); i++) {
      if (!isSameKey(first.get(i), second.get(i))) {
        return false;
      }
    }
    return true;
  }

  private boolean areEquivalentNumbers(Tree key, Tree comparedKey) {
    return toNumber(key).isEquivalentNumber((toNumber(comparedKey)));
  }

  private static Number toNumber(Tree numberTree) {
    if (numberTree.is(Tree.Kind.NUMERIC_LITERAL)) {
      return Number.fromString(((NumericLiteral) numberTree).valueAsString());
    }
    return "True".equals(((Name) numberTree).name()) ? new Number(BigDecimal.ONE) : new Number(BigDecimal.ZERO);
  }

  private static boolean areEquivalentStringLiterals(StringLiteral key, StringLiteral comparedKey) {
    if (key.stringElements().stream().anyMatch(StringElement::isInterpolated) || comparedKey.stringElements().stream().anyMatch(StringElement::isInterpolated)) {
      return false;
    }
    if (key.trimmedQuotesValue().equals(comparedKey.trimmedQuotesValue())) {
      String keyWithPrefixes = key.stringElements().stream()
        .map(s -> s.prefix().toLowerCase(Locale.ENGLISH) + s.trimmedQuotesValue()).collect(Collectors.joining());
      String comparedKeyWithPrefixes = comparedKey.stringElements().stream()
        .map(s -> s.prefix().toLowerCase(Locale.ENGLISH) + s.trimmedQuotesValue()).collect(Collectors.joining());
      return keyWithPrefixes.equals(comparedKeyWithPrefixes);
    }
    return false;
  }

  private static boolean isANumber(Tree tree) {
    return tree.is(Tree.Kind.NUMERIC_LITERAL)
      || (tree.is(Tree.Kind.NAME) && ("True".equals(((Name) tree).name()) || "False".equals(((Name) tree).name())));
  }

  static class Number {
    private final boolean isComplex;
    private final BigDecimal value;

    public Number(BigDecimal value, boolean isComplex) {
      this.value = value;
      this.isComplex = isComplex;
    }

    public Number(BigDecimal value) {
      this.value = value;
      this.isComplex = false;
    }

    public static Number fromString(String str) {
      String numberValue = str.replace("_", "");
      if (numberValue.endsWith("L") || numberValue.endsWith("l")) {
        numberValue = numberValue.substring(0, numberValue.length() - 1);
      }
      if (numberValue.startsWith("0b") || numberValue.startsWith("0B")) {
        return new Number(new BigDecimal(new BigInteger(numberValue.substring(2), 2)));
      }
      if (numberValue.startsWith("0o") || numberValue.startsWith("0O")) {
        return new Number(new BigDecimal(new BigInteger(numberValue.substring(2), 8)));
      }
      if (numberValue.startsWith("0x") || numberValue.startsWith("0X")) {
        return new Number(new BigDecimal(new BigInteger(numberValue.substring(2), 16)));
      }
      if (numberValue.endsWith("j") || numberValue.endsWith("J")) {
        return new Number(new BigDecimal(new BigInteger(numberValue.substring(0, numberValue.length() - 1))), true);
      }
      return new Number(new BigDecimal(numberValue));
    }

    public boolean isEquivalentNumber(Number other) {
      // BigDecimal#compareTo is required as equals() returns true only with identical scales
      if (other.value.compareTo(BigDecimal.ZERO) == 0 && value.compareTo(BigDecimal.ZERO) == 0) {
        return true;
      }
      if (other.isComplex != isComplex) {
        return false;
      }
      return other.value.compareTo(value) == 0;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy