001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.ArrayDeque; 023import java.util.Deque; 024 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028 029/** 030 * Restricts the number of statements per line to one. 031 * <p> 032 * Rationale: It's very difficult to read multiple statements on one line. 033 * </p> 034 * <p> 035 * In the Java programming language, statements are the fundamental unit of 036 * execution. All statements except blocks are terminated by a semicolon. 037 * Blocks are denoted by open and close curly braces. 038 * </p> 039 * <p> 040 * OneStatementPerLineCheck checks the following types of statements: 041 * variable declaration statements, empty statements, assignment statements, 042 * expression statements, increment statements, object creation statements, 043 * 'for loop' statements, 'break' statements, 'continue' statements, 044 * 'return' statements, import statements. 045 * </p> 046 * <p> 047 * The following examples will be flagged as a violation: 048 * </p> 049 * <pre> 050 * //Each line causes violation: 051 * int var1; int var2; 052 * var1 = 1; var2 = 2; 053 * int var1 = 1; int var2 = 2; 054 * var1++; var2++; 055 * Object obj1 = new Object(); Object obj2 = new Object(); 056 * import java.io.EOFException; import java.io.BufferedReader; 057 * ;; //two empty statements on the same line. 058 * 059 * //Multi-line statements: 060 * int var1 = 1 061 * ; var2 = 2; //violation here 062 * int o = 1, p = 2, 063 * r = 5; int t; //violation here 064 * </pre> 065 * 066 * @author Alexander Jesse 067 * @author Oliver Burn 068 * @author Andrei Selkin 069 */ 070public final class OneStatementPerLineCheck extends AbstractCheck { 071 072 /** 073 * A key is pointing to the warning message text in "messages.properties" 074 * file. 075 */ 076 public static final String MSG_KEY = "multiple.statements.line"; 077 078 /** 079 * Counts number of semicolons in nested lambdas. 080 */ 081 private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>(); 082 083 /** 084 * Hold the line-number where the last statement ended. 085 */ 086 private int lastStatementEnd = -1; 087 088 /** 089 * Hold the line-number where the last 'for-loop' statement ended. 090 */ 091 private int forStatementEnd = -1; 092 093 /** 094 * The for-header usually has 3 statements on one line, but THIS IS OK. 095 */ 096 private boolean inForHeader; 097 098 /** 099 * Holds if current token is inside lambda. 100 */ 101 private boolean isInLambda; 102 103 /** 104 * Hold the line-number where the last lambda statement ended. 105 */ 106 private int lambdaStatementEnd = -1; 107 108 @Override 109 public int[] getDefaultTokens() { 110 return getAcceptableTokens(); 111 } 112 113 @Override 114 public int[] getAcceptableTokens() { 115 return new int[] { 116 TokenTypes.SEMI, 117 TokenTypes.FOR_INIT, 118 TokenTypes.FOR_ITERATOR, 119 TokenTypes.LAMBDA, 120 }; 121 } 122 123 @Override 124 public int[] getRequiredTokens() { 125 return getAcceptableTokens(); 126 } 127 128 @Override 129 public void beginTree(DetailAST rootAST) { 130 inForHeader = false; 131 lastStatementEnd = -1; 132 forStatementEnd = -1; 133 isInLambda = false; 134 } 135 136 @Override 137 public void visitToken(DetailAST ast) { 138 switch (ast.getType()) { 139 case TokenTypes.SEMI: 140 checkIfSemicolonIsInDifferentLineThanPrevious(ast); 141 break; 142 case TokenTypes.FOR_ITERATOR: 143 forStatementEnd = ast.getLineNo(); 144 break; 145 case TokenTypes.LAMBDA: 146 isInLambda = true; 147 countOfSemiInLambda.push(0); 148 break; 149 default: 150 inForHeader = true; 151 break; 152 } 153 } 154 155 @Override 156 public void leaveToken(DetailAST ast) { 157 switch (ast.getType()) { 158 case TokenTypes.SEMI: 159 lastStatementEnd = ast.getLineNo(); 160 forStatementEnd = -1; 161 lambdaStatementEnd = -1; 162 break; 163 case TokenTypes.FOR_ITERATOR: 164 inForHeader = false; 165 break; 166 case TokenTypes.LAMBDA: 167 countOfSemiInLambda.pop(); 168 if (countOfSemiInLambda.isEmpty()) { 169 isInLambda = false; 170 } 171 lambdaStatementEnd = ast.getLineNo(); 172 break; 173 default: 174 break; 175 } 176 } 177 178 /** 179 * Checks if given semicolon is in different line than previous. 180 * @param ast semicolon to check 181 */ 182 private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) { 183 DetailAST currentStatement = ast; 184 final boolean hasResourcesPrevSibling = 185 currentStatement.getPreviousSibling() != null 186 && currentStatement.getPreviousSibling().getType() == TokenTypes.RESOURCES; 187 if (!hasResourcesPrevSibling && isMultilineStatement(currentStatement)) { 188 currentStatement = ast.getPreviousSibling(); 189 } 190 if (isInLambda) { 191 int countOfSemiInCurrentLambda = countOfSemiInLambda.pop(); 192 countOfSemiInCurrentLambda++; 193 countOfSemiInLambda.push(countOfSemiInCurrentLambda); 194 if (!inForHeader && countOfSemiInCurrentLambda > 1 195 && isOnTheSameLine(currentStatement, 196 lastStatementEnd, forStatementEnd, 197 lambdaStatementEnd)) { 198 log(ast, MSG_KEY); 199 } 200 } 201 else if (!inForHeader && isOnTheSameLine(currentStatement, lastStatementEnd, 202 forStatementEnd, lambdaStatementEnd)) { 203 log(ast, MSG_KEY); 204 } 205 } 206 207 /** 208 * Checks whether two statements are on the same line. 209 * @param ast token for the current statement. 210 * @param lastStatementEnd the line-number where the last statement ended. 211 * @param forStatementEnd the line-number where the last 'for-loop' 212 * statement ended. 213 * @param lambdaStatementEnd the line-number where the last lambda 214 * statement ended. 215 * @return true if two statements are on the same line. 216 */ 217 private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd, 218 int forStatementEnd, int lambdaStatementEnd) { 219 return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo() 220 && lambdaStatementEnd != ast.getLineNo(); 221 } 222 223 /** 224 * Checks whether statement is multiline. 225 * @param ast token for the current statement. 226 * @return true if one statement is distributed over two or more lines. 227 */ 228 private static boolean isMultilineStatement(DetailAST ast) { 229 final boolean multiline; 230 if (ast.getPreviousSibling() == null) { 231 multiline = false; 232 } 233 else { 234 final DetailAST prevSibling = ast.getPreviousSibling(); 235 multiline = prevSibling.getLineNo() != ast.getLineNo() 236 && ast.getParent() != null; 237 } 238 return multiline; 239 } 240}