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

org.sonar.python.checks.MethodShouldBeStaticCheck 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.List;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.tree.AnyParameter;
import org.sonar.plugins.python.api.tree.ExpressionStatement;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.ParameterList;
import org.sonar.plugins.python.api.tree.Parameter;
import org.sonar.plugins.python.api.tree.RaiseStatement;
import org.sonar.plugins.python.api.tree.Statement;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
import org.sonar.python.tree.TreeUtils;

import static org.sonar.python.checks.utils.CheckUtils.classHasInheritance;
import static org.sonar.python.checks.utils.CheckUtils.getParentClassDef;

@Rule(key = "S2325")
public class MethodShouldBeStaticCheck extends PythonSubscriptionCheck {

  private static final String MESSAGE = "Make this method static.";

  @Override
  public void initialize(Context context) {
    context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, ctx -> {
      FunctionDef funcDef = (FunctionDef) ctx.syntaxNode();
      if (funcDef.isMethodDefinition()
        && !classHasInheritance(getParentClassDef(funcDef))
        && !isBuiltInMethod(funcDef)
        && !isStatic(funcDef)
        && hasValuableCode(funcDef)
        && !mayRaiseNotImplementedError(funcDef)
        && !isUsingSelfArg(funcDef)
        && funcDef.decorators().isEmpty()
      ) {
        ctx.addIssue(funcDef.name(), MESSAGE);
      }
    });
  }

  private static boolean mayRaiseNotImplementedError(FunctionDef funcDef) {
    RaiseStatementVisitor visitor = new RaiseStatementVisitor();
    funcDef.accept(visitor);
    return visitor.hasNotImplementedError;

  }

  private static boolean hasValuableCode(FunctionDef funcDef) {
    List statements = funcDef.body().statements();
    return !statements.stream().allMatch(st -> isStringLiteral(st) || st.is(Tree.Kind.PASS_STMT) || isEllipsis(st));
  }

  private static boolean isStringLiteral(Statement st) {
    return st.is(Tree.Kind.EXPRESSION_STMT) && ((ExpressionStatement) st).expressions().stream().allMatch(e -> e.is(Tree.Kind.STRING_LITERAL));
  }

  private static boolean isEllipsis(Statement st) {
    return st.is(Tree.Kind.EXPRESSION_STMT) && ((ExpressionStatement) st).expressions().stream().allMatch(expr -> expr.is(Tree.Kind.ELLIPSIS));
  }

  private static boolean isUsingSelfArg(FunctionDef funcDef) {
    ParameterList parameters = funcDef.parameters();
    if (parameters == null) {
      // if a method has no parameters then it can't be a instance method.
      return true;
    }
    List params = parameters.all();
    if (params.isEmpty()) {
      return false;
    }
    if (params.get(0).is(Tree.Kind.TUPLE_PARAMETER)) {
      return false;
    }
    Parameter first = (Parameter) params.get(0);
    Name paramName = first.name();
    if (paramName == null) {
      // star argument should not raise issue
      return true;
    }
    SelfVisitor visitor = new SelfVisitor(paramName.name());
    funcDef.body().accept(visitor);
    return visitor.isUsingSelfArg;
  }

  private static boolean isStatic(FunctionDef funcDef) {
    return funcDef.decorators().stream()
      .map(d -> TreeUtils.decoratorNameFromExpression(d.expression()))
      .anyMatch(n -> "staticmethod".equals(n) || "classmethod".equals(n));
  }

  private static boolean isBuiltInMethod(FunctionDef funcDef) {
    String name = funcDef.name().name();
    String doubleUnderscore = "__";
    return name.startsWith(doubleUnderscore) && name.endsWith(doubleUnderscore);
  }

  private static class RaiseStatementVisitor extends BaseTreeVisitor {
    private int withinRaise = 0;
    boolean hasNotImplementedError = false;

    @Override
    public void visitRaiseStatement(RaiseStatement pyRaiseStatementTree) {
      withinRaise++;
      scan(pyRaiseStatementTree.expressions());
      withinRaise--;
    }

    @Override
    public void visitName(Name pyNameTree) {
      if (withinRaise > 0) {
        hasNotImplementedError |= pyNameTree.name().equals("NotImplementedError");
      }
    }
  }

  private static class SelfVisitor extends BaseTreeVisitor {
    private final String selfName;
    boolean isUsingSelfArg = false;

    SelfVisitor(String selfName) {
      this.selfName = selfName;
    }

    @Override
    public void visitName(Name pyNameTree) {
      isUsingSelfArg |= selfName.equals(pyNameTree.name());
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy