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

org.sonar.php.checks.OverridingMethodSimplyCallParentCheck Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube PHP Plugin
 * Copyright (C) 2010-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 GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * 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 GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.php.checks;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.php.symbols.ClassSymbol;
import org.sonar.php.symbols.MethodSymbol;
import org.sonar.php.symbols.Parameter;
import org.sonar.php.symbols.Symbol;
import org.sonar.php.tree.symbols.HasClassSymbol;
import org.sonar.php.tree.symbols.HasMethodSymbol;
import org.sonar.plugins.php.api.tree.Tree.Kind;
import org.sonar.plugins.php.api.tree.declaration.CallArgumentTree;
import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ClassMemberTree;
import org.sonar.plugins.php.api.tree.declaration.ClassTree;
import org.sonar.plugins.php.api.tree.declaration.MethodDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ParameterTree;
import org.sonar.plugins.php.api.tree.expression.AnonymousClassTree;
import org.sonar.plugins.php.api.tree.expression.ExpressionTree;
import org.sonar.plugins.php.api.tree.expression.FunctionCallTree;
import org.sonar.plugins.php.api.tree.expression.MemberAccessTree;
import org.sonar.plugins.php.api.tree.expression.VariableIdentifierTree;
import org.sonar.plugins.php.api.tree.statement.BlockTree;
import org.sonar.plugins.php.api.tree.statement.ExpressionStatementTree;
import org.sonar.plugins.php.api.tree.statement.ReturnStatementTree;
import org.sonar.plugins.php.api.tree.statement.StatementTree;
import org.sonar.plugins.php.api.visitors.PHPVisitorCheck;

@Rule(key = "S1185")
public class OverridingMethodSimplyCallParentCheck extends PHPVisitorCheck {

  private static final String MESSAGE = "Remove this method \"%s\" to simply inherit it.";

  @Override
  public void visitClassDeclaration(ClassDeclarationTree tree) {
    super.visitClassDeclaration(tree);
    visitClass(tree);
  }

  @Override
  public void visitAnonymousClass(AnonymousClassTree tree) {
    super.visitAnonymousClass(tree);
    visitClass(tree);
  }

  // existence of superclass is still checked
  @SuppressWarnings("java:S3655")
  private void visitClass(ClassTree tree) {
    if (tree.superClass() != null) {
      ClassSymbol superClassSymbol = ((HasClassSymbol) tree).symbol().superClass().get();
      for (ClassMemberTree member : tree.members()) {
        if (member.is(Kind.METHOD_DECLARATION)) {
          checkMethod((MethodDeclarationTree) member, superClassSymbol);
        }
      }
    }
  }

  private void checkMethod(MethodDeclarationTree method, ClassSymbol superClass) {
    if (method.body().is(Kind.BLOCK)) {
      BlockTree blockTree = (BlockTree) method.body();

      if (blockTree.statements().size() == 1) {
        StatementTree statementTree = blockTree.statements().get(0);

        ExpressionTree expressionTree = null;
        if (statementTree.is(Kind.EXPRESSION_STATEMENT)) {
          expressionTree = ((ExpressionStatementTree) statementTree).expression();

        } else if (statementTree.is(Kind.RETURN_STATEMENT)) {
          expressionTree = ((ReturnStatementTree) statementTree).expression();
        }

        checkExpression(expressionTree, method, superClass);
      }
    }
  }

  private void checkExpression(@Nullable ExpressionTree expressionTree, MethodDeclarationTree method, ClassSymbol superClass) {
    if (expressionTree != null && expressionTree.is(Kind.FUNCTION_CALL)) {
      FunctionCallTree functionCallTree = (FunctionCallTree) expressionTree;

      if (functionCallTree.callee().is(Kind.CLASS_MEMBER_ACCESS)) {
        MemberAccessTree memberAccessTree = (MemberAccessTree) functionCallTree.callee();
        String methodName = method.name().text();

        boolean isCallingSuperclassMethodWithSameNameAndArguments = isSuperClassReference(memberAccessTree.object(), superClass.qualifiedName().toString()) &&
          memberAccessTree.member().toString().equals(methodName) &&
          isFunctionCalledWithSameArgumentsAsDeclared(functionCallTree, method);
        if (isCallingSuperclassMethodWithSameNameAndArguments) {
          boolean duplicatesDeclarationFromSuper = Stream.iterate(superClass, Objects::nonNull, c -> c.superClass().orElse(null))
            .flatMap(c -> c.declaredMethods().stream())
            .anyMatch(ms -> ms.name().equalsIgnoreCase(methodName) &&
              hasSameVisibilityAs(method, ms) &&
              hasSameParameterList(method, ms));
          boolean isInheritanceChainUnresolvable = Stream.iterate(superClass, Objects::nonNull, c -> c.superClass().orElse(null))
            .anyMatch(Symbol::isUnknownSymbol);

          if (isInheritanceChainUnresolvable || duplicatesDeclarationFromSuper) {
            String message = String.format(MESSAGE, methodName);
            context().newIssue(this, method.name(), message);
          }
        }
      }
    }
  }

  private static boolean hasSameParameterList(MethodDeclarationTree method, MethodSymbol other) {
    MethodSymbol methodSymbol = ((HasMethodSymbol) method).symbol();

    List parameters = methodSymbol.parameters();
    List otherParameters = other.parameters();
    if (parameters.size() != otherParameters.size()) {
      return false;
    }
    for (int i = 0; i < parameters.size(); ++i) {
      if (!parameters.get(i).equals(otherParameters.get(i))) {
        return false;
      }
    }
    return true;
  }

  private static boolean hasSameVisibilityAs(MethodDeclarationTree method, MethodSymbol other) {
    return ((HasMethodSymbol) method).symbol().visibility().equals(other.visibility());
  }

  private static boolean isFunctionCalledWithSameArgumentsAsDeclared(FunctionCallTree functionCallTree, MethodDeclarationTree method) {
    List argumentNames = new ArrayList<>();
    for (CallArgumentTree argument : functionCallTree.callArguments()) {
      if (!argument.value().is(Kind.VARIABLE_IDENTIFIER) || argument.name() != null) {
        return false;
      }
      argumentNames.add(((VariableIdentifierTree) argument.value()).variableExpression().text());
    }

    List parameterNames = new ArrayList<>();
    for (ParameterTree parameter : method.parameters().parameters()) {
      if (parameter.initValue() != null) {
        return false;
      }
      parameterNames.add(parameter.variableIdentifier().variableExpression().text());
    }

    return argumentNames.equals(parameterNames);
  }

  private static boolean isSuperClassReference(ExpressionTree tree, String superClass) {
    String str = tree.toString();
    return superClass.equalsIgnoreCase(str) || "parent".equalsIgnoreCase(str);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy