001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * <p> 032 * Checks that there is only one statement per line. 033 * </p> 034 * <p> 035 * Rationale: It's very difficult to read multiple statements on one line. 036 * </p> 037 * <p> 038 * In the Java programming language, statements are the fundamental unit of 039 * execution. All statements except blocks are terminated by a semicolon. 040 * Blocks are denoted by open and close curly braces. 041 * </p> 042 * <p> 043 * OneStatementPerLineCheck checks the following types of statements: 044 * variable declaration statements, empty statements, import statements, 045 * assignment statements, expression statements, increment statements, 046 * object creation statements, 'for loop' statements, 'break' statements, 047 * 'continue' statements, 'return' statements, resources statements (optional). 048 * </p> 049 * <ul> 050 * <li> 051 * Property {@code treatTryResourcesAsStatement} - Enable resources processing. 052 * Type is {@code boolean}. 053 * Default value is {@code false}. 054 * </li> 055 * </ul> 056 * <p> 057 * To configure the check: 058 * </p> 059 * <pre> 060 * <module name="OneStatementPerLine"/> 061 * </pre> 062 * <p> 063 * The following examples will be flagged as a violation: 064 * </p> 065 * <pre> 066 * //Each line causes violation: 067 * int var1; int var2; 068 * var1 = 1; var2 = 2; 069 * int var1 = 1; int var2 = 2; 070 * var1++; var2++; 071 * Object obj1 = new Object(); Object obj2 = new Object(); 072 * import java.io.EOFException; import java.io.BufferedReader; 073 * ;; //two empty statements on the same line. 074 * 075 * //Multi-line statements: 076 * int var1 = 1 077 * ; var2 = 2; //violation here 078 * int o = 1, p = 2, 079 * r = 5; int t; //violation here 080 * </pre> 081 * <p> 082 * An example of how to configure the check to treat resources 083 * in a try statement as statements to require them on their own line: 084 * </p> 085 * <pre> 086 * <module name="OneStatementPerLine"> 087 * <property name="treatTryResourcesAsStatement" value="true"/> 088 * </module> 089 * </pre> 090 * <p> 091 * Note: resource declarations can contain variable definitions 092 * and variable references (from java9). 093 * When property "treatTryResourcesAsStatement" is enabled, 094 * this check is only applied to variable definitions. 095 * If there are one or more variable references 096 * and one variable definition on the same line in resources declaration, 097 * there is no violation. 098 * The following examples will illustrate difference: 099 * </p> 100 * <pre> 101 * OutputStream s1 = new PipedOutputStream(); 102 * OutputStream s2 = new PipedOutputStream(); 103 * // only one statement(variable definition) with two variable references 104 * try (s1; s2; OutputStream s3 = new PipedOutputStream();) // OK 105 * {} 106 * // two statements with variable definitions 107 * try (Reader r = new PipedReader(); s2; Reader s3 = new PipedReader() // violation 108 * ) {} 109 * </pre> 110 * <p> 111 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 112 * </p> 113 * <p> 114 * Violation Message Keys: 115 * </p> 116 * <ul> 117 * <li> 118 * {@code multiple.statements.line} 119 * </li> 120 * </ul> 121 * 122 * @since 5.3 123 */ 124@FileStatefulCheck 125public final class OneStatementPerLineCheck extends AbstractCheck { 126 127 /** 128 * A key is pointing to the warning message text in "messages.properties" 129 * file. 130 */ 131 public static final String MSG_KEY = "multiple.statements.line"; 132 133 /** 134 * Counts number of semicolons in nested lambdas. 135 */ 136 private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>(); 137 138 /** 139 * Hold the line-number where the last statement ended. 140 */ 141 private int lastStatementEnd = -1; 142 143 /** 144 * Hold the line-number where the last 'for-loop' statement ended. 145 */ 146 private int forStatementEnd = -1; 147 148 /** 149 * The for-header usually has 3 statements on one line, but THIS IS OK. 150 */ 151 private boolean inForHeader; 152 153 /** 154 * Holds if current token is inside lambda. 155 */ 156 private boolean isInLambda; 157 158 /** 159 * Hold the line-number where the last lambda statement ended. 160 */ 161 private int lambdaStatementEnd = -1; 162 163 /** 164 * Hold the line-number where the last resource variable statement ended. 165 */ 166 private int lastVariableResourceStatementEnd = -1; 167 168 /** 169 * Enable resources processing. 170 */ 171 private boolean treatTryResourcesAsStatement; 172 173 /** 174 * Setter to enable resources processing. 175 * 176 * @param treatTryResourcesAsStatement user's value of treatTryResourcesAsStatement. 177 */ 178 public void setTreatTryResourcesAsStatement(boolean treatTryResourcesAsStatement) { 179 this.treatTryResourcesAsStatement = treatTryResourcesAsStatement; 180 } 181 182 @Override 183 public int[] getDefaultTokens() { 184 return getRequiredTokens(); 185 } 186 187 @Override 188 public int[] getAcceptableTokens() { 189 return getRequiredTokens(); 190 } 191 192 @Override 193 public int[] getRequiredTokens() { 194 return new int[] { 195 TokenTypes.SEMI, 196 TokenTypes.FOR_INIT, 197 TokenTypes.FOR_ITERATOR, 198 TokenTypes.LAMBDA, 199 }; 200 } 201 202 @Override 203 public void beginTree(DetailAST rootAST) { 204 inForHeader = false; 205 lastStatementEnd = -1; 206 forStatementEnd = -1; 207 isInLambda = false; 208 lastVariableResourceStatementEnd = -1; 209 } 210 211 @Override 212 public void visitToken(DetailAST ast) { 213 switch (ast.getType()) { 214 case TokenTypes.SEMI: 215 checkIfSemicolonIsInDifferentLineThanPrevious(ast); 216 break; 217 case TokenTypes.FOR_ITERATOR: 218 forStatementEnd = ast.getLineNo(); 219 break; 220 case TokenTypes.LAMBDA: 221 isInLambda = true; 222 countOfSemiInLambda.push(0); 223 break; 224 default: 225 inForHeader = true; 226 break; 227 } 228 } 229 230 @Override 231 public void leaveToken(DetailAST ast) { 232 switch (ast.getType()) { 233 case TokenTypes.SEMI: 234 lastStatementEnd = ast.getLineNo(); 235 forStatementEnd = -1; 236 lambdaStatementEnd = -1; 237 break; 238 case TokenTypes.FOR_ITERATOR: 239 inForHeader = false; 240 break; 241 case TokenTypes.LAMBDA: 242 countOfSemiInLambda.pop(); 243 if (countOfSemiInLambda.isEmpty()) { 244 isInLambda = false; 245 } 246 lambdaStatementEnd = ast.getLineNo(); 247 break; 248 default: 249 break; 250 } 251 } 252 253 /** 254 * Checks if given semicolon is in different line than previous. 255 * 256 * @param ast semicolon to check 257 */ 258 private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) { 259 DetailAST currentStatement = ast; 260 final DetailAST previousSibling = ast.getPreviousSibling(); 261 final boolean isUnnecessarySemicolon = previousSibling == null 262 || previousSibling.getType() == TokenTypes.RESOURCES 263 || ast.getParent().getType() == TokenTypes.COMPILATION_UNIT; 264 if (!isUnnecessarySemicolon) { 265 currentStatement = ast.getPreviousSibling(); 266 } 267 if (isInLambda) { 268 checkLambda(ast, currentStatement); 269 } 270 else if (isResource(ast.getParent())) { 271 checkResourceVariable(ast); 272 } 273 else if (!inForHeader && isOnTheSameLine(currentStatement, lastStatementEnd, 274 forStatementEnd, lambdaStatementEnd)) { 275 log(ast, MSG_KEY); 276 } 277 } 278 279 /** 280 * Checks semicolon placement in lambda. 281 * 282 * @param ast semicolon to check 283 * @param currentStatement current statement 284 */ 285 private void checkLambda(DetailAST ast, DetailAST currentStatement) { 286 int countOfSemiInCurrentLambda = countOfSemiInLambda.pop(); 287 countOfSemiInCurrentLambda++; 288 countOfSemiInLambda.push(countOfSemiInCurrentLambda); 289 if (!inForHeader && countOfSemiInCurrentLambda > 1 290 && isOnTheSameLine(currentStatement, 291 lastStatementEnd, forStatementEnd, 292 lambdaStatementEnd)) { 293 log(ast, MSG_KEY); 294 } 295 } 296 297 /** 298 * Checks that given node is a resource. 299 * 300 * @param ast semicolon to check 301 * @return true if node is a resource 302 */ 303 private static boolean isResource(DetailAST ast) { 304 return ast.getType() == TokenTypes.RESOURCES 305 || ast.getType() == TokenTypes.RESOURCE_SPECIFICATION; 306 } 307 308 /** 309 * Checks resource variable. 310 * 311 * @param currentStatement current statement 312 */ 313 private void checkResourceVariable(DetailAST currentStatement) { 314 if (treatTryResourcesAsStatement) { 315 final DetailAST nextNode = currentStatement.getNextSibling(); 316 if (currentStatement.getPreviousSibling().findFirstToken(TokenTypes.ASSIGN) != null) { 317 lastVariableResourceStatementEnd = currentStatement.getLineNo(); 318 } 319 if (nextNode.findFirstToken(TokenTypes.ASSIGN) != null 320 && nextNode.getLineNo() == lastVariableResourceStatementEnd) { 321 log(currentStatement, MSG_KEY); 322 } 323 } 324 } 325 326 /** 327 * Checks whether two statements are on the same line. 328 * 329 * @param ast token for the current statement. 330 * @param lastStatementEnd the line-number where the last statement ended. 331 * @param forStatementEnd the line-number where the last 'for-loop' 332 * statement ended. 333 * @param lambdaStatementEnd the line-number where the last lambda 334 * statement ended. 335 * @return true if two statements are on the same line. 336 */ 337 private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd, 338 int forStatementEnd, int lambdaStatementEnd) { 339 return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo() 340 && lambdaStatementEnd != ast.getLineNo(); 341 } 342 343}