1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2019 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.blocks;
21
22 import com.puppycrawl.tools.checkstyle.StatelessCheck;
23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24 import com.puppycrawl.tools.checkstyle.api.DetailAST;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
27
28 /**
29 * <p>
30 * Checks for braces around code blocks.
31 * </p>
32 * <ul>
33 * <li>
34 * Property {@code allowSingleLineStatement} - allow single-line statements without braces.
35 * Default value is {@code false}.
36 * </li>
37 * <li>
38 * Property {@code allowEmptyLoopBody} - allow loops with empty bodies.
39 * Default value is {@code false}.
40 * </li>
41 * <li>
42 * Property {@code tokens} - tokens to check
43 * Default value is:
44 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
45 * LITERAL_DO</a>,
46 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
47 * LITERAL_ELSE</a>,
48 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
49 * LITERAL_FOR</a>,
50 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
51 * LITERAL_IF</a>,
52 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
53 * LITERAL_WHILE</a>.
54 * </li>
55 * </ul>
56 * <p>
57 * To configure the check:
58 * </p>
59 * <pre>
60 * <module name="NeedBraces"/>
61 * </pre>
62 * <p>
63 * To configure the check for {@code if} and {@code else} blocks:
64 * </p>
65 * <pre>
66 * <module name="NeedBraces">
67 * <property name="tokens" value="LITERAL_IF, LITERAL_ELSE"/>
68 * </module>
69 * </pre>
70 * <p>
71 * To configure the check to allow single-line statements
72 * ({@code if, while, do-while, for}) without braces:
73 * </p>
74 * <pre>
75 * <module name="NeedBraces">
76 * <property name="allowSingleLineStatement" value="true"/>
77 * </module>
78 * </pre>
79 * <p>
80 * Next statements won't be violated by check:
81 * </p>
82 * <pre>
83 * if (obj.isValid()) return true; // OK
84 * while (obj.isValid()) return true; // OK
85 * do this.notify(); while (o != null); // OK
86 * for (int i = 0; ; ) this.notify(); // OK
87 * </pre>
88 * <p>
89 * To configure the check to allow {@code case, default} single-line statements without braces:
90 * </p>
91 * <pre>
92 * <module name="NeedBraces">
93 * <property name="tokens" value="LITERAL_CASE, LITERAL_DEFAULT"/>
94 * <property name="allowSingleLineStatement" value="true"/>
95 * </module>
96 * </pre>
97 * <p>
98 * Next statements won't be violated by check:
99 * </p>
100 * <pre>
101 * switch (num) {
102 * case 1: counter++; break; // OK
103 * case 6: counter += 10; break; // OK
104 * default: counter = 100; break; // OK
105 * }
106 * </pre>
107 * <p>
108 * To configure the check to allow loops ({@code while, for}) with empty bodies:
109 * </p>
110 * <pre>
111 * <module name="NeedBraces">
112 * <property name="allowEmptyLoopBody" value="true"/>
113 * </module>
114 * </pre>
115 * <p>
116 * Next statements won't be violated by check:
117 * </p>
118 * <pre>
119 * while (value.incrementValue() < 5); // OK
120 * for(int i = 0; i < 10; value.incrementValue()); // OK
121 * </pre>
122 *
123 * @since 3.0
124 */
125 @StatelessCheck
126 public class NeedBracesCheck extends AbstractCheck {
127
128 /**
129 * A key is pointing to the warning message text in "messages.properties"
130 * file.
131 */
132 public static final String MSG_KEY_NEED_BRACES = "needBraces";
133
134 /**
135 * Allow single-line statements without braces.
136 */
137 private boolean allowSingleLineStatement;
138
139 /**
140 * Allow loops with empty bodies.
141 */
142 private boolean allowEmptyLoopBody;
143
144 /**
145 * Setter to allow single-line statements without braces.
146 * @param allowSingleLineStatement Check's option for skipping single-line statements
147 */
148 public void setAllowSingleLineStatement(boolean allowSingleLineStatement) {
149 this.allowSingleLineStatement = allowSingleLineStatement;
150 }
151
152 /**
153 * Setter to allow loops with empty bodies.
154 * @param allowEmptyLoopBody Check's option for allowing loops with empty body.
155 */
156 public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) {
157 this.allowEmptyLoopBody = allowEmptyLoopBody;
158 }
159
160 @Override
161 public int[] getDefaultTokens() {
162 return new int[] {
163 TokenTypes.LITERAL_DO,
164 TokenTypes.LITERAL_ELSE,
165 TokenTypes.LITERAL_FOR,
166 TokenTypes.LITERAL_IF,
167 TokenTypes.LITERAL_WHILE,
168 };
169 }
170
171 @Override
172 public int[] getAcceptableTokens() {
173 return new int[] {
174 TokenTypes.LITERAL_DO,
175 TokenTypes.LITERAL_ELSE,
176 TokenTypes.LITERAL_FOR,
177 TokenTypes.LITERAL_IF,
178 TokenTypes.LITERAL_WHILE,
179 TokenTypes.LITERAL_CASE,
180 TokenTypes.LITERAL_DEFAULT,
181 TokenTypes.LAMBDA,
182 };
183 }
184
185 @Override
186 public int[] getRequiredTokens() {
187 return CommonUtil.EMPTY_INT_ARRAY;
188 }
189
190 @Override
191 public void visitToken(DetailAST ast) {
192 final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
193 boolean isElseIf = false;
194 if (ast.getType() == TokenTypes.LITERAL_ELSE
195 && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) {
196 isElseIf = true;
197 }
198 final boolean isInAnnotationField = isInAnnotationField(ast);
199 final boolean skipStatement = isSkipStatement(ast);
200 final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast);
201
202 if (slistAST == null && !isElseIf && !isInAnnotationField
203 && !skipStatement && !skipEmptyLoopBody) {
204 log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText());
205 }
206 }
207
208 /**
209 * Checks if ast is in an annotation field.
210 * @param ast ast to test.
211 * @return true if current ast is part of an annotation field.
212 */
213 private static boolean isInAnnotationField(DetailAST ast) {
214 boolean isDefaultInAnnotation = false;
215 if (ast.getParent().getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
216 isDefaultInAnnotation = true;
217 }
218 return isDefaultInAnnotation;
219 }
220
221 /**
222 * Checks if current statement can be skipped by "need braces" warning.
223 * @param statement if, for, while, do-while, lambda, else, case, default statements.
224 * @return true if current statement can be skipped by Check.
225 */
226 private boolean isSkipStatement(DetailAST statement) {
227 return allowSingleLineStatement && isSingleLineStatement(statement);
228 }
229
230 /**
231 * Checks if current loop statement does not have body, e.g.:
232 * <p>
233 * {@code
234 * while (value.incrementValue() < 5);
235 * ...
236 * for(int i = 0; i < 10; value.incrementValue());
237 * }
238 * </p>
239 * @param ast ast token.
240 * @return true if current loop statement does not have body.
241 */
242 private static boolean isEmptyLoopBody(DetailAST ast) {
243 boolean noBodyLoop = false;
244
245 if (ast.getType() == TokenTypes.LITERAL_FOR
246 || ast.getType() == TokenTypes.LITERAL_WHILE) {
247 DetailAST currentToken = ast.getFirstChild();
248 while (currentToken.getNextSibling() != null) {
249 currentToken = currentToken.getNextSibling();
250 }
251 noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT;
252 }
253 return noBodyLoop;
254 }
255
256 /**
257 * Checks if current statement is single-line statement, e.g.:
258 * <p>
259 * {@code
260 * if (obj.isValid()) return true;
261 * }
262 * </p>
263 * <p>
264 * {@code
265 * while (obj.isValid()) return true;
266 * }
267 * </p>
268 * @param statement if, for, while, do-while, lambda, else, case, default statements.
269 * @return true if current statement is single-line statement.
270 */
271 private static boolean isSingleLineStatement(DetailAST statement) {
272 final boolean result;
273
274 switch (statement.getType()) {
275 case TokenTypes.LITERAL_IF:
276 result = isSingleLineIf(statement);
277 break;
278 case TokenTypes.LITERAL_FOR:
279 result = isSingleLineFor(statement);
280 break;
281 case TokenTypes.LITERAL_DO:
282 result = isSingleLineDoWhile(statement);
283 break;
284 case TokenTypes.LITERAL_WHILE:
285 result = isSingleLineWhile(statement);
286 break;
287 case TokenTypes.LAMBDA:
288 result = isSingleLineLambda(statement);
289 break;
290 case TokenTypes.LITERAL_CASE:
291 result = isSingleLineCase(statement);
292 break;
293 case TokenTypes.LITERAL_DEFAULT:
294 result = isSingleLineDefault(statement);
295 break;
296 default:
297 result = isSingleLineElse(statement);
298 break;
299 }
300
301 return result;
302 }
303
304 /**
305 * Checks if current while statement is single-line statement, e.g.:
306 * <p>
307 * {@code
308 * while (obj.isValid()) return true;
309 * }
310 * </p>
311 * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}.
312 * @return true if current while statement is single-line statement.
313 */
314 private static boolean isSingleLineWhile(DetailAST literalWhile) {
315 boolean result = false;
316 if (literalWhile.getParent().getType() == TokenTypes.SLIST) {
317 final DetailAST block = literalWhile.getLastChild().getPreviousSibling();
318 result = literalWhile.getLineNo() == block.getLineNo();
319 }
320 return result;
321 }
322
323 /**
324 * Checks if current do-while statement is single-line statement, e.g.:
325 * <p>
326 * {@code
327 * do this.notify(); while (o != null);
328 * }
329 * </p>
330 * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}.
331 * @return true if current do-while statement is single-line statement.
332 */
333 private static boolean isSingleLineDoWhile(DetailAST literalDo) {
334 boolean result = false;
335 if (literalDo.getParent().getType() == TokenTypes.SLIST) {
336 final DetailAST block = literalDo.getFirstChild();
337 result = block.getLineNo() == literalDo.getLineNo();
338 }
339 return result;
340 }
341
342 /**
343 * Checks if current for statement is single-line statement, e.g.:
344 * <p>
345 * {@code
346 * for (int i = 0; ; ) this.notify();
347 * }
348 * </p>
349 * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}.
350 * @return true if current for statement is single-line statement.
351 */
352 private static boolean isSingleLineFor(DetailAST literalFor) {
353 boolean result = false;
354 if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) {
355 result = true;
356 }
357 else if (literalFor.getParent().getType() == TokenTypes.SLIST) {
358 result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo();
359 }
360 return result;
361 }
362
363 /**
364 * Checks if current if statement is single-line statement, e.g.:
365 * <p>
366 * {@code
367 * if (obj.isValid()) return true;
368 * }
369 * </p>
370 * @param literalIf {@link TokenTypes#LITERAL_IF if statement}.
371 * @return true if current if statement is single-line statement.
372 */
373 private static boolean isSingleLineIf(DetailAST literalIf) {
374 boolean result = false;
375 if (literalIf.getParent().getType() == TokenTypes.SLIST) {
376 final DetailAST literalIfLastChild = literalIf.getLastChild();
377 final DetailAST block;
378 if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) {
379 block = literalIfLastChild.getPreviousSibling();
380 }
381 else {
382 block = literalIfLastChild;
383 }
384 final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR);
385 result = ifCondition.getLineNo() == block.getLineNo();
386 }
387 return result;
388 }
389
390 /**
391 * Checks if current lambda statement is single-line statement, e.g.:
392 * <p>
393 * {@code
394 * Runnable r = () -> System.out.println("Hello, world!");
395 * }
396 * </p>
397 * @param lambda {@link TokenTypes#LAMBDA lambda statement}.
398 * @return true if current lambda statement is single-line statement.
399 */
400 private static boolean isSingleLineLambda(DetailAST lambda) {
401 final DetailAST block = lambda.getLastChild();
402 return lambda.getLineNo() == block.getLineNo();
403 }
404
405 /**
406 * Checks if current case statement is single-line statement, e.g.:
407 * <p>
408 * {@code
409 * case 1: doSomeStuff(); break;
410 * case 2: doSomeStuff(); break;
411 * case 3: ;
412 * }
413 * </p>
414 * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}.
415 * @return true if current case statement is single-line statement.
416 */
417 private static boolean isSingleLineCase(DetailAST literalCase) {
418 boolean result = false;
419 final DetailAST slist = literalCase.getNextSibling();
420 if (slist == null) {
421 result = true;
422 }
423 else {
424 final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK);
425 if (caseBreak != null) {
426 final DetailAST block = slist.getFirstChild();
427 final boolean atOneLine = literalCase.getLineNo() == block.getLineNo();
428 result = atOneLine && block.getLineNo() == caseBreak.getLineNo();
429 }
430 }
431 return result;
432 }
433
434 /**
435 * Checks if current default statement is single-line statement, e.g.:
436 * <p>
437 * {@code
438 * default: doSomeStuff();
439 * }
440 * </p>
441 * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}.
442 * @return true if current default statement is single-line statement.
443 */
444 private static boolean isSingleLineDefault(DetailAST literalDefault) {
445 boolean result = false;
446 final DetailAST slist = literalDefault.getNextSibling();
447 if (slist == null) {
448 result = true;
449 }
450 else {
451 final DetailAST block = slist.getFirstChild();
452 if (block != null && block.getType() != TokenTypes.SLIST) {
453 result = literalDefault.getLineNo() == block.getLineNo();
454 }
455 }
456 return result;
457 }
458
459 /**
460 * Checks if current else statement is single-line statement, e.g.:
461 * <p>
462 * {@code
463 * else doSomeStuff();
464 * }
465 * </p>
466 * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}.
467 * @return true if current else statement is single-line statement.
468 */
469 private static boolean isSingleLineElse(DetailAST literalElse) {
470 final DetailAST block = literalElse.getFirstChild();
471 return literalElse.getLineNo() == block.getLineNo();
472 }
473
474 }