001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * <div> 032 * Checks that there is only one statement per line. 033 * </div> 034 * 035 * <p> 036 * Rationale: It's very difficult to read multiple statements on one line. 037 * </p> 038 * 039 * <p> 040 * In the Java programming language, statements are the fundamental unit of 041 * execution. All statements except blocks are terminated by a semicolon. 042 * Blocks are denoted by open and close curly braces. 043 * </p> 044 * 045 * <p> 046 * OneStatementPerLineCheck checks the following types of statements: 047 * variable declaration statements, empty statements, import statements, 048 * assignment statements, expression statements, increment statements, 049 * object creation statements, 'for loop' statements, 'break' statements, 050 * 'continue' statements, 'return' statements, resources statements (optional). 051 * </p> 052 * 053 * @since 5.3 054 */ 055@FileStatefulCheck 056public final class OneStatementPerLineCheck extends AbstractCheck { 057 058 /** 059 * A key is pointing to the warning message text in "messages.properties" 060 * file. 061 */ 062 public static final String MSG_KEY = "multiple.statements.line"; 063 064 /** 065 * Counts number of semicolons in nested lambdas. 066 */ 067 private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>(); 068 069 /** 070 * Hold the line-number where the last statement ended. 071 */ 072 private int lastStatementEnd; 073 074 /** 075 * Hold the line-number where the last 'for-loop' statement ended. 076 */ 077 private int forStatementEnd; 078 079 /** 080 * The for-header usually has 3 statements on one line, but THIS IS OK. 081 */ 082 private boolean inForHeader; 083 084 /** 085 * Holds if current token is inside lambda. 086 */ 087 private boolean isInLambda; 088 089 /** 090 * Hold the line-number where the last lambda statement ended. 091 */ 092 private int lambdaStatementEnd; 093 094 /** 095 * Hold the line-number where the last resource variable statement ended. 096 */ 097 private int lastVariableResourceStatementEnd; 098 099 /** 100 * Enable resources processing. 101 */ 102 private boolean treatTryResourcesAsStatement; 103 104 /** 105 * Setter to enable resources processing. 106 * 107 * @param treatTryResourcesAsStatement user's value of treatTryResourcesAsStatement. 108 * @since 8.23 109 */ 110 public void setTreatTryResourcesAsStatement(boolean treatTryResourcesAsStatement) { 111 this.treatTryResourcesAsStatement = treatTryResourcesAsStatement; 112 } 113 114 @Override 115 public int[] getDefaultTokens() { 116 return getRequiredTokens(); 117 } 118 119 @Override 120 public int[] getAcceptableTokens() { 121 return getRequiredTokens(); 122 } 123 124 @Override 125 public int[] getRequiredTokens() { 126 return new int[] { 127 TokenTypes.SEMI, 128 TokenTypes.FOR_INIT, 129 TokenTypes.FOR_ITERATOR, 130 TokenTypes.LAMBDA, 131 }; 132 } 133 134 @Override 135 public void beginTree(DetailAST rootAST) { 136 lastStatementEnd = 0; 137 lastVariableResourceStatementEnd = 0; 138 } 139 140 @Override 141 public void visitToken(DetailAST ast) { 142 switch (ast.getType()) { 143 case TokenTypes.SEMI -> checkIfSemicolonIsInDifferentLineThanPrevious(ast); 144 case TokenTypes.FOR_ITERATOR -> forStatementEnd = ast.getLineNo(); 145 case TokenTypes.LAMBDA -> { 146 isInLambda = true; 147 countOfSemiInLambda.push(0); 148 } 149 default -> inForHeader = true; 150 } 151 } 152 153 @Override 154 public void leaveToken(DetailAST ast) { 155 switch (ast.getType()) { 156 case TokenTypes.SEMI -> { 157 lastStatementEnd = ast.getLineNo(); 158 forStatementEnd = 0; 159 lambdaStatementEnd = 0; 160 } 161 case TokenTypes.FOR_ITERATOR -> inForHeader = false; 162 case TokenTypes.LAMBDA -> { 163 countOfSemiInLambda.pop(); 164 if (countOfSemiInLambda.isEmpty()) { 165 isInLambda = false; 166 } 167 lambdaStatementEnd = ast.getLineNo(); 168 } 169 default -> { 170 // do nothing 171 } 172 } 173 } 174 175 /** 176 * Checks if given semicolon is in different line than previous. 177 * 178 * @param ast semicolon to check 179 */ 180 private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) { 181 DetailAST currentStatement = ast; 182 final DetailAST previousSibling = ast.getPreviousSibling(); 183 final boolean isUnnecessarySemicolon = previousSibling == null 184 || previousSibling.getType() == TokenTypes.RESOURCES 185 || ast.getParent().getType() == TokenTypes.COMPILATION_UNIT; 186 if (!isUnnecessarySemicolon) { 187 currentStatement = ast.getPreviousSibling(); 188 } 189 if (isInLambda) { 190 checkLambda(ast, currentStatement); 191 } 192 else if (isResource(ast.getParent())) { 193 checkResourceVariable(ast); 194 } 195 else if (!inForHeader && isOnTheSameLine(currentStatement, lastStatementEnd, 196 forStatementEnd, lambdaStatementEnd)) { 197 log(ast, MSG_KEY); 198 } 199 } 200 201 /** 202 * Checks semicolon placement in lambda. 203 * 204 * @param ast semicolon to check 205 * @param currentStatement current statement 206 */ 207 private void checkLambda(DetailAST ast, DetailAST currentStatement) { 208 int countOfSemiInCurrentLambda = countOfSemiInLambda.pop(); 209 countOfSemiInCurrentLambda++; 210 countOfSemiInLambda.push(countOfSemiInCurrentLambda); 211 if (!inForHeader && countOfSemiInCurrentLambda > 1 212 && isOnTheSameLine(currentStatement, 213 lastStatementEnd, forStatementEnd, 214 lambdaStatementEnd)) { 215 log(ast, MSG_KEY); 216 } 217 } 218 219 /** 220 * Checks that given node is a resource. 221 * 222 * @param ast semicolon to check 223 * @return true if node is a resource 224 */ 225 private static boolean isResource(DetailAST ast) { 226 return ast.getType() == TokenTypes.RESOURCES 227 || ast.getType() == TokenTypes.RESOURCE_SPECIFICATION; 228 } 229 230 /** 231 * Checks resource variable. 232 * 233 * @param currentStatement current statement 234 */ 235 private void checkResourceVariable(DetailAST currentStatement) { 236 if (treatTryResourcesAsStatement) { 237 final DetailAST nextNode = currentStatement.getNextSibling(); 238 if (currentStatement.getPreviousSibling().findFirstToken(TokenTypes.ASSIGN) != null) { 239 lastVariableResourceStatementEnd = currentStatement.getLineNo(); 240 } 241 if (nextNode.findFirstToken(TokenTypes.ASSIGN) != null 242 && nextNode.getLineNo() == lastVariableResourceStatementEnd) { 243 log(currentStatement, MSG_KEY); 244 } 245 } 246 } 247 248 /** 249 * Checks whether two statements are on the same line. 250 * 251 * @param ast token for the current statement. 252 * @param lastStatementEnd the line-number where the last statement ended. 253 * @param forStatementEnd the line-number where the last 'for-loop' 254 * statement ended. 255 * @param lambdaStatementEnd the line-number where the last lambda 256 * statement ended. 257 * @return true if two statements are on the same line. 258 */ 259 private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd, 260 int forStatementEnd, int lambdaStatementEnd) { 261 return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo() 262 && lambdaStatementEnd != ast.getLineNo(); 263 } 264 265}