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