org.sonar.php.checks.formatting.IndentationCheck.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.php.checks.formatting.IndentationCheck.java

Source

/*
 * SonarQube PHP Plugin
 * Copyright (C) 2010-2016 SonarSource SA
 * mailto:contact 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.formatting;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.sonar.php.api.PHPPunctuator;
import org.sonar.php.checks.FormattingStandardCheck;
import org.sonar.php.tree.impl.PHPTree;
import org.sonar.plugins.php.api.tree.ScriptTree;
import org.sonar.plugins.php.api.tree.SeparatedList;
import org.sonar.plugins.php.api.tree.Tree;
import org.sonar.plugins.php.api.tree.Tree.Kind;
import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.MethodDeclarationTree;
import org.sonar.plugins.php.api.tree.expression.FunctionCallTree;
import org.sonar.plugins.php.api.tree.expression.FunctionExpressionTree;
import org.sonar.plugins.php.api.tree.lexical.SyntaxToken;
import org.sonar.plugins.php.api.visitors.PHPVisitorCheck;

import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class IndentationCheck extends PHPVisitorCheck implements FormattingCheck {

    private static final String ARGUMENT_LINE_SPLIT_MESSAGE = "Either split this list into multiple lines, aligned at column \"%s\" or put all arguments on line \"%s\".";
    private static final String ARGUMENT_INDENTATION_MESSAGE = "Align all arguments in this list at column \"%s\".";
    private static final int PSR2_INDENTATION = 4;
    private static final String FUNCTION_CALL_PARENTHESIS_MESSAGE = "Move the closing parenthesis on the next line.";
    private static final String FUNCTION_DEC_PARENTHESIS_MESSAGE = "Move the closing parenthesis with the opening brace on the next line.";
    private static final String INTERFACE_SPLIT_MESSAGE = "Either split this list into multiple lines or move it on the same line \"%s\".";
    private static final String INTERFACE_INDENTATION = "Align all interfaces in this list at column \"%s\".";

    private FormattingStandardCheck check;
    private Map<Integer, Integer> startColumnByLine = new HashMap<>();

    @Override
    public void checkFormat(FormattingStandardCheck formattingCheck, ScriptTree scriptTree) {
        this.check = formattingCheck;
        this.startColumnByLine.clear();

        super.visitScript(scriptTree);
    }

    @Override
    public void visitToken(SyntaxToken token) {
        if (startColumnByLine.get(token.line()) == null) {
            startColumnByLine.put(token.line(), token.column());
        }

        super.visitToken(token);
    }

    @Override
    public void visitFunctionCall(FunctionCallTree tree) {
        super.visitFunctionCall(tree);

        if (check.isFunctionCallsArgumentsIndentation && !check.isInternalFunction(tree.callee())) {
            SyntaxToken calleeLastToken = ((PHPTree) tree.callee()).getLastToken();

            checkArgumentsIndentation(tree.arguments(), calleeLastToken, startColumnForLine(calleeLastToken.line()),
                    tree.closeParenthesisToken(), true);
        }
    }

    @Override
    public void visitFunctionDeclaration(FunctionDeclarationTree tree) {
        super.visitFunctionDeclaration(tree);

        if (check.isMethodArgumentsIndentation) {
            checkArgumentsIndentation(tree.parameters().parameters(), tree.name().token(),
                    startColumnForLine(tree.functionToken().line()), tree.parameters().closeParenthesisToken(),
                    false);
        }
    }

    @Override
    public void visitMethodDeclaration(MethodDeclarationTree tree) {
        super.visitMethodDeclaration(tree);

        if (check.isMethodArgumentsIndentation) {
            checkArgumentsIndentation(tree.parameters().parameters(), tree.name().token(),
                    startColumnForLine(tree.functionToken().line()), tree.parameters().closeParenthesisToken(),
                    false);
        }
    }

    @Override
    public void visitFunctionExpression(FunctionExpressionTree tree) {
        super.visitFunctionExpression(tree);

        if (check.isMethodArgumentsIndentation) {
            checkArgumentsIndentation(tree.parameters().parameters(), tree.functionToken(),
                    startColumnForLine(tree.functionToken().line()), tree.parameters().closeParenthesisToken(),
                    false);
        }
    }

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

    private void checkImplementListIndentation(ClassDeclarationTree classTree) {
        if (check.isInterfacesIndentation && classTree.is(Tree.Kind.CLASS_DECLARATION)
                && !classTree.superInterfaces().isEmpty()) {

            List<Tree> interfaceList = ImmutableList.<Tree>copyOf(classTree.superInterfaces());
            SyntaxToken classToken = classTree.classEntryTypeToken();
            SyntaxToken lastInterfaceToken = ((PHPTree) Iterables.getLast(classTree.superInterfaces()))
                    .getFirstToken();
            int expectedColumn = classToken.column() + PSR2_INDENTATION;

            if (!TokenUtils.isOnSameLine(classToken, lastInterfaceToken)) {

                if (!isCorrectlySplitOnLines(classToken.line(), interfaceList)) {
                    check.reportIssue(String.format(INTERFACE_SPLIT_MESSAGE, classToken.line()),
                            classTree.superInterfaces().get(0));

                } else if (!isCorrectlyIndented(expectedColumn, interfaceList)) {
                    check.reportIssue(String.format(INTERFACE_INDENTATION, expectedColumn),
                            classTree.superInterfaces().get(0));
                }
            }
        }
    }

    private void checkArgumentsIndentation(SeparatedList arguments, SyntaxToken functionName, int baseColumn,
            @Nullable SyntaxToken closeParenthesis, boolean isFunctionCall) {
        if (arguments.size() > 1) {
            Tree firstArg = (Tree) arguments.get(0);

            int expectedIndentationColumn = baseColumn + PSR2_INDENTATION;
            int callingLine = functionName.line();

            if (!allArgumentsOnSameLine(functionName, arguments)) {

                if (!isCorrectlySplitOnLines(callingLine, arguments)) {
                    check.reportIssue(
                            String.format(ARGUMENT_LINE_SPLIT_MESSAGE, expectedIndentationColumn, callingLine),
                            firstArg);

                } else if (!isCorrectlyIndented(expectedIndentationColumn, arguments)) {
                    check.reportIssue(String.format(ARGUMENT_INDENTATION_MESSAGE, expectedIndentationColumn),
                            firstArg);
                }

                // Checking parenthesis
                if (closeParenthesis != null) {
                    checkClosingParenthesisLocation((Tree) Iterables.getLast(arguments), closeParenthesis,
                            isFunctionCall);
                }
            }
        }
    }

    // http://www.php-fig.org/psr/psr-2/meta/#3-1-multi-line-arguments-09-08-2013
    private static boolean allArgumentsOnSameLine(SyntaxToken functionName, List<Tree> arguments) {
        int expectedLine = getLastLine(functionName);
        for (Tree argument : arguments) {
            if (getStartLine(argument) != expectedLine) {
                return false;
            }

            if (argument.is(Kind.ARRAY_INITIALIZER_BRACKET, Kind.FUNCTION_EXPRESSION)) {
                expectedLine = getLastLine(argument);
            }
        }

        return getLastLine(arguments.get(arguments.size() - 1)) == expectedLine;
    }

    private void checkClosingParenthesisLocation(Tree lastArgument, SyntaxToken closeParenthesis,
            boolean isFunctionCall) {
        if (!((PHPTree) lastArgument).getLastToken().text().equals(PHPPunctuator.RPARENTHESIS.getValue())
                && TokenUtils.isOnSameLine(((PHPTree) lastArgument).getLastToken(), closeParenthesis)) {
            check.reportIssue(isFunctionCall ? FUNCTION_CALL_PARENTHESIS_MESSAGE : FUNCTION_DEC_PARENTHESIS_MESSAGE,
                    closeParenthesis);
        }
    }

    private static boolean isCorrectlyIndented(int expectedColumn, List<Tree> items) {
        for (Tree item : items) {
            if (((PHPTree) item).getFirstToken().column() != expectedColumn) {
                return false;
            }
        }
        return true;
    }

    private static boolean isCorrectlySplitOnLines(int referenceLine, List<Tree> items) {
        int expectedLine = referenceLine + 1;

        for (Tree item : items) {
            if (getStartLine(item) < expectedLine) {
                return false;
            }

            if (item.is(Kind.ARRAY_INITIALIZER_BRACKET, Kind.FUNCTION_EXPRESSION)) {
                expectedLine = getLastLine(item) + 1;
            } else {
                expectedLine++;
            }
        }

        return true;
    }

    private static int getStartLine(Tree tree) {
        return ((PHPTree) tree).getLine();
    }

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

    private int startColumnForLine(int line) {
        return startColumnByLine.get(line);
    }

}