View Javadoc
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   * &lt;module name="NeedBraces"/&gt;
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   * &lt;module name="NeedBraces"&gt;
50   *     &lt;property name="tokens" value="LITERAL_IF, LITERAL_ELSE"/&gt;
51   * &lt;/module&gt;
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   * &lt;module name=&quot;NeedBraces&quot;&gt;
94   *     &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_CASE, LITERAL_DEFAULT&quot;/&gt;
95   *     &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
96   * &lt;/module&gt;
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  * &lt;module name=&quot;NeedBraces&quot;&gt;
118  *     &lt;property name=&quot;allowEmptyLoopBody&quot; value=&quot;true&quot;/&gt;
119  * &lt;/module&gt;
120  * </pre>
121  *
122  * <p>
123  * Such statements would be allowed:
124  * </p>
125  *
126  * <pre>
127  * {@code
128  * while (value.incrementValue() &lt; 5); // OK
129  * for(int i = 0; i &lt; 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 }