1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.ArrayDeque;
23 import java.util.Deque;
24
25 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29
30 /**
31 * <div>
32 * Checks that there is only one statement per line.
33 * </div>
34 *
35 * <p>
36 * Rationale: It's very difficult to read multiple statements on one line.
37 * </p>
38 *
39 * <p>
40 * In the Java programming language, statements are the fundamental unit of
41 * execution. All statements except blocks are terminated by a semicolon.
42 * Blocks are denoted by open and close curly braces.
43 * </p>
44 *
45 * <p>
46 * OneStatementPerLineCheck checks the following types of statements:
47 * variable declaration statements, empty statements, import statements,
48 * assignment statements, expression statements, increment statements,
49 * object creation statements, 'for loop' statements, 'break' statements,
50 * 'continue' statements, 'return' statements, resources statements (optional).
51 * </p>
52 * <ul>
53 * <li>
54 * Property {@code treatTryResourcesAsStatement} - Enable resources processing.
55 * Type is {@code boolean}.
56 * Default value is {@code false}.
57 * </li>
58 * </ul>
59 *
60 * <p>
61 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
62 * </p>
63 *
64 * <p>
65 * Violation Message Keys:
66 * </p>
67 * <ul>
68 * <li>
69 * {@code multiple.statements.line}
70 * </li>
71 * </ul>
72 *
73 * @since 5.3
74 */
75 @FileStatefulCheck
76 public final class OneStatementPerLineCheck extends AbstractCheck {
77
78 /**
79 * A key is pointing to the warning message text in "messages.properties"
80 * file.
81 */
82 public static final String MSG_KEY = "multiple.statements.line";
83
84 /**
85 * Counts number of semicolons in nested lambdas.
86 */
87 private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>();
88
89 /**
90 * Hold the line-number where the last statement ended.
91 */
92 private int lastStatementEnd;
93
94 /**
95 * Hold the line-number where the last 'for-loop' statement ended.
96 */
97 private int forStatementEnd;
98
99 /**
100 * The for-header usually has 3 statements on one line, but THIS IS OK.
101 */
102 private boolean inForHeader;
103
104 /**
105 * Holds if current token is inside lambda.
106 */
107 private boolean isInLambda;
108
109 /**
110 * Hold the line-number where the last lambda statement ended.
111 */
112 private int lambdaStatementEnd;
113
114 /**
115 * Hold the line-number where the last resource variable statement ended.
116 */
117 private int lastVariableResourceStatementEnd;
118
119 /**
120 * Enable resources processing.
121 */
122 private boolean treatTryResourcesAsStatement;
123
124 /**
125 * Setter to enable resources processing.
126 *
127 * @param treatTryResourcesAsStatement user's value of treatTryResourcesAsStatement.
128 * @since 8.23
129 */
130 public void setTreatTryResourcesAsStatement(boolean treatTryResourcesAsStatement) {
131 this.treatTryResourcesAsStatement = treatTryResourcesAsStatement;
132 }
133
134 @Override
135 public int[] getDefaultTokens() {
136 return getRequiredTokens();
137 }
138
139 @Override
140 public int[] getAcceptableTokens() {
141 return getRequiredTokens();
142 }
143
144 @Override
145 public int[] getRequiredTokens() {
146 return new int[] {
147 TokenTypes.SEMI,
148 TokenTypes.FOR_INIT,
149 TokenTypes.FOR_ITERATOR,
150 TokenTypes.LAMBDA,
151 };
152 }
153
154 @Override
155 public void beginTree(DetailAST rootAST) {
156 lastStatementEnd = 0;
157 lastVariableResourceStatementEnd = 0;
158 }
159
160 @Override
161 public void visitToken(DetailAST ast) {
162 switch (ast.getType()) {
163 case TokenTypes.SEMI:
164 checkIfSemicolonIsInDifferentLineThanPrevious(ast);
165 break;
166 case TokenTypes.FOR_ITERATOR:
167 forStatementEnd = ast.getLineNo();
168 break;
169 case TokenTypes.LAMBDA:
170 isInLambda = true;
171 countOfSemiInLambda.push(0);
172 break;
173 default:
174 inForHeader = true;
175 break;
176 }
177 }
178
179 @Override
180 public void leaveToken(DetailAST ast) {
181 switch (ast.getType()) {
182 case TokenTypes.SEMI:
183 lastStatementEnd = ast.getLineNo();
184 forStatementEnd = 0;
185 lambdaStatementEnd = 0;
186 break;
187 case TokenTypes.FOR_ITERATOR:
188 inForHeader = false;
189 break;
190 case TokenTypes.LAMBDA:
191 countOfSemiInLambda.pop();
192 if (countOfSemiInLambda.isEmpty()) {
193 isInLambda = false;
194 }
195 lambdaStatementEnd = ast.getLineNo();
196 break;
197 default:
198 break;
199 }
200 }
201
202 /**
203 * Checks if given semicolon is in different line than previous.
204 *
205 * @param ast semicolon to check
206 */
207 private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) {
208 DetailAST currentStatement = ast;
209 final DetailAST previousSibling = ast.getPreviousSibling();
210 final boolean isUnnecessarySemicolon = previousSibling == null
211 || previousSibling.getType() == TokenTypes.RESOURCES
212 || ast.getParent().getType() == TokenTypes.COMPILATION_UNIT;
213 if (!isUnnecessarySemicolon) {
214 currentStatement = ast.getPreviousSibling();
215 }
216 if (isInLambda) {
217 checkLambda(ast, currentStatement);
218 }
219 else if (isResource(ast.getParent())) {
220 checkResourceVariable(ast);
221 }
222 else if (!inForHeader && isOnTheSameLine(currentStatement, lastStatementEnd,
223 forStatementEnd, lambdaStatementEnd)) {
224 log(ast, MSG_KEY);
225 }
226 }
227
228 /**
229 * Checks semicolon placement in lambda.
230 *
231 * @param ast semicolon to check
232 * @param currentStatement current statement
233 */
234 private void checkLambda(DetailAST ast, DetailAST currentStatement) {
235 int countOfSemiInCurrentLambda = countOfSemiInLambda.pop();
236 countOfSemiInCurrentLambda++;
237 countOfSemiInLambda.push(countOfSemiInCurrentLambda);
238 if (!inForHeader && countOfSemiInCurrentLambda > 1
239 && isOnTheSameLine(currentStatement,
240 lastStatementEnd, forStatementEnd,
241 lambdaStatementEnd)) {
242 log(ast, MSG_KEY);
243 }
244 }
245
246 /**
247 * Checks that given node is a resource.
248 *
249 * @param ast semicolon to check
250 * @return true if node is a resource
251 */
252 private static boolean isResource(DetailAST ast) {
253 return ast.getType() == TokenTypes.RESOURCES
254 || ast.getType() == TokenTypes.RESOURCE_SPECIFICATION;
255 }
256
257 /**
258 * Checks resource variable.
259 *
260 * @param currentStatement current statement
261 */
262 private void checkResourceVariable(DetailAST currentStatement) {
263 if (treatTryResourcesAsStatement) {
264 final DetailAST nextNode = currentStatement.getNextSibling();
265 if (currentStatement.getPreviousSibling().findFirstToken(TokenTypes.ASSIGN) != null) {
266 lastVariableResourceStatementEnd = currentStatement.getLineNo();
267 }
268 if (nextNode.findFirstToken(TokenTypes.ASSIGN) != null
269 && nextNode.getLineNo() == lastVariableResourceStatementEnd) {
270 log(currentStatement, MSG_KEY);
271 }
272 }
273 }
274
275 /**
276 * Checks whether two statements are on the same line.
277 *
278 * @param ast token for the current statement.
279 * @param lastStatementEnd the line-number where the last statement ended.
280 * @param forStatementEnd the line-number where the last 'for-loop'
281 * statement ended.
282 * @param lambdaStatementEnd the line-number where the last lambda
283 * statement ended.
284 * @return true if two statements are on the same line.
285 */
286 private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd,
287 int forStatementEnd, int lambdaStatementEnd) {
288 return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo()
289 && lambdaStatementEnd != ast.getLineNo();
290 }
291
292 }