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

org.sonar.php.checks.MultilineBlocksCurlyBracesCheck 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 org.sonar.check.Rule;
import org.sonar.php.checks.utils.AbstractStatementsCheck;
import org.sonar.php.tree.impl.PHPTree;
import org.sonar.plugins.php.api.tree.Tree;
import org.sonar.plugins.php.api.tree.Tree.Kind;
import org.sonar.plugins.php.api.tree.statement.ElseClauseTree;
import org.sonar.plugins.php.api.tree.statement.ElseifClauseTree;
import org.sonar.plugins.php.api.tree.statement.ForEachStatementTree;
import org.sonar.plugins.php.api.tree.statement.ForStatementTree;
import org.sonar.plugins.php.api.tree.statement.IfStatementTree;
import org.sonar.plugins.php.api.tree.statement.StatementTree;
import org.sonar.plugins.php.api.tree.statement.WhileStatementTree;
import org.sonar.plugins.php.api.visitors.PreciseIssue;

@Rule(key = MultilineBlocksCurlyBracesCheck.KEY)
public class MultilineBlocksCurlyBracesCheck extends AbstractStatementsCheck {

  public static final String KEY = "S2681";

  private static final String MESSAGE_LOOP = "This statement will not be executed in a loop; only the first statement of this %s-statement block will be."
    + " The rest will execute only once.";

  private static final String MESSAGE_IF = "This statement will not be executed conditionally; only the first statement of this %s-statement block will be."
    + " The rest will execute unconditionally.";

  @Override
  public void visitNode(Tree tree) {
    List statements = getStatements(tree);

    for (int i = 0; i < statements.size() - 1; i++) {
      StatementTree currentStatement = statements.get(i);
      if (currentStatement.is(Kind.IF_STATEMENT)) {
        IfStatementTree ifStatement = (IfStatementTree) currentStatement;
        checkStatement(ifStatement.ifToken(), ifStatement.condition(), getLastStatementOfIf(ifStatement), i, statements);
      } else if (currentStatement.is(Kind.FOR_STATEMENT)) {
        ForStatementTree forStatement = (ForStatementTree) currentStatement;
        checkStatement(forStatement.forToken(), forStatement.closeParenthesisToken(), forStatement.statements().get(0), i, statements);
      } else if (currentStatement.is(Kind.FOREACH_STATEMENT)) {
        ForEachStatementTree forStatement = (ForEachStatementTree) currentStatement;
        checkStatement(forStatement.foreachToken(), forStatement.closeParenthesisToken(), forStatement.statements().get(0), i, statements);
      } else if (currentStatement.is(Kind.WHILE_STATEMENT)) {
        WhileStatementTree whileStatement = (WhileStatementTree) currentStatement;
        checkStatement(whileStatement.whileToken(), whileStatement.condition(), whileStatement.statements().get(0), i, statements);
      }
    }
  }

  private static StatementTree getLastStatementOfIf(StatementTree statement) {
    if (!statement.is(Kind.IF_STATEMENT)) {
      return statement;
    }
    List childStatements;
    IfStatementTree ifStatement = (IfStatementTree) statement;
    ElseClauseTree elseClause = ifStatement.elseClause();
    List elseifClause = ifStatement.elseifClauses();
    if (elseClause != null) {
      childStatements = elseClause.statements();
    } else if (!elseifClause.isEmpty()) {
      childStatements = elseifClause.get(elseifClause.size() - 1).statements();
    } else {
      childStatements = ifStatement.statements();
    }
    return getLastStatementOfIf(childStatements.get(childStatements.size() - 1));
  }

  private void checkStatement(Tree parentStart, Tree parentEnd, StatementTree firstInnerStatement, int nestingStatementNum, List statements) {
    if (firstInnerStatement.is(Kind.BLOCK)) {
      return;
    }
    int parentLine = lastTokenLine(parentEnd);
    int parentColumn = column(parentStart);
    StatementBlock statementBlock = findBlock(parentLine, parentColumn, firstInnerStatement, nestingStatementNum, statements);
    if (!statementBlock.otherStatement.isEmpty()) {
      boolean isIfStatement = statements.get(nestingStatementNum).is(Kind.IF_STATEMENT);
      String message = String.format(isIfStatement ? MESSAGE_IF : MESSAGE_LOOP, statementBlock.size());
      PreciseIssue issue = context().newIssue(this, statementBlock.otherStatement.get(0), message);
      issue.secondary(firstInnerStatement, isIfStatement ? "Executed conditionally" : "Executed in a loop");
      String secondaryMessage = isIfStatement ? "Always executed" : "Executed once";
      statementBlock.otherStatement.stream().skip(1).forEach(statement -> issue.secondary(statement, secondaryMessage));
    }
  }

  private static StatementBlock findBlock(int parentLine, int parentColumn, StatementTree firstInnerStatement, int nestingStatementNum,
    List statements) {
    StatementBlock statementBlock = new StatementBlock(firstInnerStatement, parentColumn, parentLine);
    for (int i = nestingStatementNum + 1; i < statements.size(); i++) {
      StatementTree statement = statements.get(i);
      if (!statementBlock.add(statement)) {
        break;
      }
    }
    return statementBlock;
  }

  private static class StatementBlock {

    static final int MAX_STATEMENT_DISTANCE_IN_THE_SAME_BLOCK = 4;

    final StatementTree firstStatement;

    final List otherStatement = new ArrayList<>();

    final int parentMarginColumn;

    int lastStatementLine;

    int lastStatementColumn;

    boolean firstStatementOneLiner;

    StatementBlock(StatementTree firstStatement, int parentMarginColumn, int parentLastLine) {
      this.firstStatement = firstStatement;
      lastStatementLine = line(firstStatement);
      this.firstStatementOneLiner = lastStatementLine == parentLastLine;
      lastStatementColumn = column(firstStatement);
      this.parentMarginColumn = parentMarginColumn;
    }

    int size() {
      return 1 + otherStatement.size();
    }

    /**
     * @return false if the statement is not part of the group
     */
    boolean add(StatementTree statement) {
      if (statement.is(Kind.INLINE_HTML)) {
        // ignore and continue
        return true;
      }
      int line = line(statement);
      int column = column(statement);
      if (column < parentMarginColumn || !isLineCloseToPreviousStatement(line)) {
        return false;
      } else if (column == parentMarginColumn &&
        (firstStatementOneLiner || column != lastStatementColumn || !isOneLineAfterPreviousStatement(line))) {
          return false;
        } else {
          otherStatement.add(statement);
          lastStatementLine = line;
          lastStatementColumn = column;
          return true;
        }
    }

    private boolean isLineCloseToPreviousStatement(int line) {
      return line < (lastStatementLine + MAX_STATEMENT_DISTANCE_IN_THE_SAME_BLOCK);
    }

    private boolean isOneLineAfterPreviousStatement(int line) {
      return line == (lastStatementLine + 1);
    }

    private static int line(Tree tree) {
      return ((PHPTree) tree).getFirstToken().line();
    }

  }

  private static int column(Tree tree) {
    return ((PHPTree) tree).getFirstToken().column();
  }

  private static int lastTokenLine(Tree tree) {
    return ((PHPTree) tree).getLastToken().line();
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy