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

org.sonar.python.checks.DuplicatedMethodFieldNamesCheck 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.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
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.symbols.ClassSymbol;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.tree.TreeUtils;

@Rule(key = "S1845")
public class DuplicatedMethodFieldNamesCheck extends PythonSubscriptionCheck {

  private static final String MESSAGE = "Rename %s \"%s\" to prevent any misunderstanding/clash with %s \"%s\" defined on line %s";

  @Override
  public void initialize(Context context) {
    context.registerSyntaxNodeConsumer(Tree.Kind.CLASSDEF, ctx -> {
      ClassDef classDef = (ClassDef) ctx.syntaxNode();
      ClassSymbol classSymbol = TreeUtils.getClassSymbolFromDef(classDef);
      if (classSymbol == null) {
        return;
      }
      MethodVisitor methodVisitor = new MethodVisitor();
      classDef.body().accept(methodVisitor);
      List fieldNames = classSymbol.declaredMembers().stream()
        .filter(s -> s.kind() == Symbol.Kind.OTHER)
        .map(s -> s.usages().stream().findFirst())
        .filter(Optional::isPresent)
        .map(usage -> usage.get().tree())
        .toList();
      lookForDuplications(ctx, fieldNames, methodVisitor.methodNames);
    });
  }

  private static class MethodVisitor extends BaseTreeVisitor {
    private List methodNames = new ArrayList<>();

    @Override
    public void visitFunctionDef(FunctionDef pyFunctionDefTree) {
      methodNames.add(pyFunctionDefTree.name());
    }

    @Override
    public void visitClassDef(ClassDef pyClassDefTree) {
      // skip nested class definition
    }
  }

  private static class TokenWithTypeInfo {
    private final Tree tree;
    private final String type;

    TokenWithTypeInfo(Tree tree, String type) {
      this.tree = tree;
      this.type = type;
    }

    String getValue() {
      return tree.firstToken().value();
    }

    int getLine() {
      return tree.firstToken().line();
    }

    String getType() {
      return type;
    }
  }

  private static void lookForDuplications(SubscriptionContext ctx, List fieldNames, List methodNames) {
    List allTokensWithInfo = mergeLists(fieldNames, methodNames);
    allTokensWithInfo.sort(Comparator.comparingInt(TokenWithTypeInfo::getLine));
    for (int i = 1; i < allTokensWithInfo.size(); i++) {
      for (int j = i - 1; j >= 0; j--) {
        TokenWithTypeInfo token1 = allTokensWithInfo.get(j);
        TokenWithTypeInfo token2 = allTokensWithInfo.get(i);
        if (differOnlyByCapitalization(token1.getValue(), token2.getValue())) {
          ctx.addIssue(token2.tree, getMessage(token1, token2))
            .secondary(token1.tree, "Original");
          break;
        }
      }
    }
  }

  private static boolean differOnlyByCapitalization(String name1, String name2) {
    return name1.equalsIgnoreCase(name2) && !name1.equals(name2);
  }

  private static List mergeLists(List fieldNames, List methodNames) {
    List allTokensWithInfo = new LinkedList<>();
    for (Tree tree : fieldNames) {
      allTokensWithInfo.add(new TokenWithTypeInfo(tree, "field"));
    }
    for (Tree tree : methodNames) {
      allTokensWithInfo.add(new TokenWithTypeInfo(tree, "method"));
    }
    return allTokensWithInfo;
  }

  private static String getMessage(TokenWithTypeInfo token1, TokenWithTypeInfo token2) {
    return String.format(MESSAGE, token2.getType(), token2.getValue(), token1.getType(), token1.getValue(), token1.getLine());
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy