////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 the original author or authors.
//
// This library 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 2.1 of the License, or (at your option) any later version.
//
// This library 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 library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.coding;
import java.util.ArrayDeque;
import java.util.Deque;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
* Restricts the number of statements per line to one.
* <p>
* Rationale: It's very difficult to read multiple statements on one line.
* </p>
* <p>
* In the Java programming language, statements are the fundamental unit of
* execution. All statements except blocks are terminated by a semicolon.
* Blocks are denoted by open and close curly braces.
* </p>
* <p>
* OneStatementPerLineCheck checks the following types of statements:
* variable declaration statements, empty statements, assignment statements,
* expression statements, increment statements, object creation statements,
* 'for loop' statements, 'break' statements, 'continue' statements,
* 'return' statements, import statements.
* </p>
* <p>
* The following examples will be flagged as a violation:
* </p>
* <pre>
* //Each line causes violation:
* int var1; int var2;
* var1 = 1; var2 = 2;
* int var1 = 1; int var2 = 2;
* var1++; var2++;
* Object obj1 = new Object(); Object obj2 = new Object();
* import java.io.EOFException; import java.io.BufferedReader;
* ;; //two empty statements on the same line.
*
* //Multi-line statements:
* int var1 = 1
* ; var2 = 2; //violation here
* int o = 1, p = 2,
* r = 5; int t; //violation here
* </pre>
*
* @author Alexander Jesse
* @author Oliver Burn
* @author Andrei Selkin
*/
public final class OneStatementPerLineCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY = "multiple.statements.line";
/**
* Counts number of semicolons in nested lambdas.
*/
private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>();
/**
* Hold the line-number where the last statement ended.
*/
private int lastStatementEnd = -1;
/**
* Hold the line-number where the last 'for-loop' statement ended.
*/
private int forStatementEnd = -1;
/**
* The for-header usually has 3 statements on one line, but THIS IS OK.
*/
private boolean inForHeader;
/**
* Holds if current token is inside lambda.
*/
private boolean isInLambda;
/**
* Hold the line-number where the last lambda statement ended.
*/
private int lambdaStatementEnd = -1;
@Override
public int[] getDefaultTokens() {
return getAcceptableTokens();
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.SEMI,
TokenTypes.FOR_INIT,
TokenTypes.FOR_ITERATOR,
TokenTypes.LAMBDA,
};
}
@Override
public int[] getRequiredTokens() {
return getAcceptableTokens();
}
@Override
public void beginTree(DetailAST rootAST) {
inForHeader = false;
lastStatementEnd = -1;
forStatementEnd = -1;
isInLambda = false;
}
@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.SEMI:
checkIfSemicolonIsInDifferentLineThanPrevious(ast);
break;
case TokenTypes.FOR_ITERATOR:
forStatementEnd = ast.getLineNo();
break;
case TokenTypes.LAMBDA:
isInLambda = true;
countOfSemiInLambda.push(0);
break;
default:
inForHeader = true;
break;
}
}
@Override
public void leaveToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.SEMI:
lastStatementEnd = ast.getLineNo();
forStatementEnd = -1;
lambdaStatementEnd = -1;
break;
case TokenTypes.FOR_ITERATOR:
inForHeader = false;
break;
case TokenTypes.LAMBDA:
countOfSemiInLambda.pop();
if (countOfSemiInLambda.isEmpty()) {
isInLambda = false;
}
lambdaStatementEnd = ast.getLineNo();
break;
default:
break;
}
}
/**
* Checks if given semicolon is in different line than previous.
* @param ast semicolon to check
*/
private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) {
DetailAST currentStatement = ast;
final boolean hasResourcesPrevSibling =
currentStatement.getPreviousSibling() != null
&& currentStatement.getPreviousSibling().getType() == TokenTypes.RESOURCES;
if (!hasResourcesPrevSibling && isMultilineStatement(currentStatement)) {
currentStatement = ast.getPreviousSibling();
}
if (isInLambda) {
int countOfSemiInCurrentLambda = countOfSemiInLambda.pop();
countOfSemiInCurrentLambda++;
countOfSemiInLambda.push(countOfSemiInCurrentLambda);
if (!inForHeader && countOfSemiInCurrentLambda > 1
&& isOnTheSameLine(currentStatement,
lastStatementEnd, forStatementEnd,
lambdaStatementEnd)) {
log(ast, MSG_KEY);
}
}
else if (!inForHeader && isOnTheSameLine(currentStatement, lastStatementEnd,
forStatementEnd, lambdaStatementEnd)) {
log(ast, MSG_KEY);
}
}
/**
* Checks whether two statements are on the same line.
* @param ast token for the current statement.
* @param lastStatementEnd the line-number where the last statement ended.
* @param forStatementEnd the line-number where the last 'for-loop'
* statement ended.
* @param lambdaStatementEnd the line-number where the last lambda
* statement ended.
* @return true if two statements are on the same line.
*/
private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd,
int forStatementEnd, int lambdaStatementEnd) {
return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo()
&& lambdaStatementEnd != ast.getLineNo();
}
/**
* Checks whether statement is multiline.
* @param ast token for the current statement.
* @return true if one statement is distributed over two or more lines.
*/
private static boolean isMultilineStatement(DetailAST ast) {
final boolean multiline;
if (ast.getPreviousSibling() == null) {
multiline = false;
}
else {
final DetailAST prevSibling = ast.getPreviousSibling();
multiline = prevSibling.getLineNo() != ast.getLineNo()
&& ast.getParent() != null;
}
return multiline;
}
}