View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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      * A key is pointing to the warning message text in "messages.properties"
141      * file.
142      */
143     public static final String MSG_KEY_NEED_BRACES = "needBraces";
144 
145     /**
146      * Check's option for skipping single-line statements.
147      */
148     private boolean allowSingleLineStatement;
149 
150     /**
151      * Check's option for allowing loops with empty body.
152      */
153     private boolean allowEmptyLoopBody;
154 
155     /**
156      * Setter.
157      * @param allowSingleLineStatement Check's option for skipping single-line statements
158      */
159     public void setAllowSingleLineStatement(boolean allowSingleLineStatement) {
160         this.allowSingleLineStatement = allowSingleLineStatement;
161     }
162 
163     /**
164      * Sets whether to allow empty loop body.
165      * @param allowEmptyLoopBody Check's option for allowing loops with empty body.
166      */
167     public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) {
168         this.allowEmptyLoopBody = allowEmptyLoopBody;
169     }
170 
171     @Override
172     public int[] getDefaultTokens() {
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         };
180     }
181 
182     @Override
183     public int[] getAcceptableTokens() {
184         return new int[] {
185             TokenTypes.LITERAL_DO,
186             TokenTypes.LITERAL_ELSE,
187             TokenTypes.LITERAL_FOR,
188             TokenTypes.LITERAL_IF,
189             TokenTypes.LITERAL_WHILE,
190             TokenTypes.LITERAL_CASE,
191             TokenTypes.LITERAL_DEFAULT,
192             TokenTypes.LAMBDA,
193         };
194     }
195 
196     @Override
197     public int[] getRequiredTokens() {
198         return CommonUtils.EMPTY_INT_ARRAY;
199     }
200 
201     @Override
202     public void visitToken(DetailAST ast) {
203         final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
204         boolean isElseIf = false;
205         if (ast.getType() == TokenTypes.LITERAL_ELSE
206             && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) {
207             isElseIf = true;
208         }
209         final boolean isDefaultInAnnotation = isDefaultInAnnotation(ast);
210         final boolean skipStatement = isSkipStatement(ast);
211         final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast);
212 
213         if (slistAST == null && !isElseIf && !isDefaultInAnnotation
214                 && !skipStatement && !skipEmptyLoopBody) {
215             log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText());
216         }
217     }
218 
219     /**
220      * Checks if ast is the default token of an annotation field.
221      * @param ast ast to test.
222      * @return true if current ast is default and it is part of annotation.
223      */
224     private static boolean isDefaultInAnnotation(DetailAST ast) {
225         boolean isDefaultInAnnotation = false;
226         if (ast.getType() == TokenTypes.LITERAL_DEFAULT
227                 && ast.getParent().getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
228             isDefaultInAnnotation = true;
229         }
230         return isDefaultInAnnotation;
231     }
232 
233     /**
234      * Checks if current statement can be skipped by "need braces" warning.
235      * @param statement if, for, while, do-while, lambda, else, case, default statements.
236      * @return true if current statement can be skipped by Check.
237      */
238     private boolean isSkipStatement(DetailAST statement) {
239         return allowSingleLineStatement && isSingleLineStatement(statement);
240     }
241 
242     /**
243      * Checks if current loop statement does not have body, e.g.:
244      * <p>
245      * {@code
246      *   while (value.incrementValue() < 5);
247      *   ...
248      *   for(int i = 0; i < 10; value.incrementValue());
249      * }
250      * </p>
251      * @param ast ast token.
252      * @return true if current loop statement does not have body.
253      */
254     private static boolean isEmptyLoopBody(DetailAST ast) {
255         boolean noBodyLoop = false;
256 
257         if (ast.getType() == TokenTypes.LITERAL_FOR
258                 || ast.getType() == TokenTypes.LITERAL_WHILE) {
259             DetailAST currentToken = ast.getFirstChild();
260             while (currentToken.getNextSibling() != null) {
261                 currentToken = currentToken.getNextSibling();
262             }
263             noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT;
264         }
265         return noBodyLoop;
266     }
267 
268     /**
269      * Checks if current statement is single-line statement, e.g.:
270      * <p>
271      * {@code
272      * if (obj.isValid()) return true;
273      * }
274      * </p>
275      * <p>
276      * {@code
277      * while (obj.isValid()) return true;
278      * }
279      * </p>
280      * @param statement if, for, while, do-while, lambda, else, case, default statements.
281      * @return true if current statement is single-line statement.
282      */
283     private static boolean isSingleLineStatement(DetailAST statement) {
284         final boolean result;
285 
286         switch (statement.getType()) {
287             case TokenTypes.LITERAL_IF:
288                 result = isSingleLineIf(statement);
289                 break;
290             case TokenTypes.LITERAL_FOR:
291                 result = isSingleLineFor(statement);
292                 break;
293             case TokenTypes.LITERAL_DO:
294                 result = isSingleLineDoWhile(statement);
295                 break;
296             case TokenTypes.LITERAL_WHILE:
297                 result = isSingleLineWhile(statement);
298                 break;
299             case TokenTypes.LAMBDA:
300                 result = isSingleLineLambda(statement);
301                 break;
302             case TokenTypes.LITERAL_CASE:
303                 result = isSingleLineCase(statement);
304                 break;
305             case TokenTypes.LITERAL_DEFAULT:
306                 result = isSingleLineDefault(statement);
307                 break;
308             default:
309                 result = isSingleLineElse(statement);
310                 break;
311         }
312 
313         return result;
314     }
315 
316     /**
317      * Checks if current while statement is single-line statement, e.g.:
318      * <p>
319      * {@code
320      * while (obj.isValid()) return true;
321      * }
322      * </p>
323      * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}.
324      * @return true if current while statement is single-line statement.
325      */
326     private static boolean isSingleLineWhile(DetailAST literalWhile) {
327         boolean result = false;
328         if (literalWhile.getParent().getType() == TokenTypes.SLIST
329                 && literalWhile.getLastChild().getType() != TokenTypes.SLIST) {
330             final DetailAST block = literalWhile.getLastChild().getPreviousSibling();
331             result = literalWhile.getLineNo() == block.getLineNo();
332         }
333         return result;
334     }
335 
336     /**
337      * Checks if current do-while statement is single-line statement, e.g.:
338      * <p>
339      * {@code
340      * do this.notify(); while (o != null);
341      * }
342      * </p>
343      * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}.
344      * @return true if current do-while statement is single-line statement.
345      */
346     private static boolean isSingleLineDoWhile(DetailAST literalDo) {
347         boolean result = false;
348         if (literalDo.getParent().getType() == TokenTypes.SLIST
349                 && literalDo.getFirstChild().getType() != TokenTypes.SLIST) {
350             final DetailAST block = literalDo.getFirstChild();
351             result = block.getLineNo() == literalDo.getLineNo();
352         }
353         return result;
354     }
355 
356     /**
357      * Checks if current for statement is single-line statement, e.g.:
358      * <p>
359      * {@code
360      * for (int i = 0; ; ) this.notify();
361      * }
362      * </p>
363      * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}.
364      * @return true if current for statement is single-line statement.
365      */
366     private static boolean isSingleLineFor(DetailAST literalFor) {
367         boolean result = false;
368         if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) {
369             result = true;
370         }
371         else if (literalFor.getParent().getType() == TokenTypes.SLIST
372                 && literalFor.getLastChild().getType() != TokenTypes.SLIST) {
373             result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo();
374         }
375         return result;
376     }
377 
378     /**
379      * Checks if current if statement is single-line statement, e.g.:
380      * <p>
381      * {@code
382      * if (obj.isValid()) return true;
383      * }
384      * </p>
385      * @param literalIf {@link TokenTypes#LITERAL_IF if statement}.
386      * @return true if current if statement is single-line statement.
387      */
388     private static boolean isSingleLineIf(DetailAST literalIf) {
389         boolean result = false;
390         if (literalIf.getParent().getType() == TokenTypes.SLIST) {
391             final DetailAST literalIfLastChild = literalIf.getLastChild();
392             final DetailAST block;
393             if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) {
394                 block = literalIfLastChild.getPreviousSibling();
395             }
396             else {
397                 block = literalIfLastChild;
398             }
399             final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR);
400             result = ifCondition.getLineNo() == block.getLineNo();
401         }
402         return result;
403     }
404 
405     /**
406      * Checks if current lambda statement is single-line statement, e.g.:
407      * <p>
408      * {@code
409      * Runnable r = () -> System.out.println("Hello, world!");
410      * }
411      * </p>
412      * @param lambda {@link TokenTypes#LAMBDA lambda statement}.
413      * @return true if current lambda statement is single-line statement.
414      */
415     private static boolean isSingleLineLambda(DetailAST lambda) {
416         boolean result = false;
417         final DetailAST block = lambda.getLastChild();
418         if (block.getType() != TokenTypes.SLIST) {
419             result = lambda.getLineNo() == block.getLineNo();
420         }
421         return result;
422     }
423 
424     /**
425      * Checks if current case statement is single-line statement, e.g.:
426      * <p>
427      * {@code
428      * case 1: doSomeStuff(); break;
429      * case 2: doSomeStuff(); break;
430      * case 3: ;
431      * }
432      * </p>
433      * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}.
434      * @return true if current case statement is single-line statement.
435      */
436     private static boolean isSingleLineCase(DetailAST literalCase) {
437         boolean result = false;
438         final DetailAST slist = literalCase.getNextSibling();
439         if (slist == null) {
440             result = true;
441         }
442         else {
443             final DetailAST block = slist.getFirstChild();
444             if (block.getType() != TokenTypes.SLIST) {
445                 final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK);
446                 if (caseBreak != null) {
447                     final boolean atOneLine = literalCase.getLineNo() == block.getLineNo();
448                     result = atOneLine && block.getLineNo() == caseBreak.getLineNo();
449                 }
450             }
451         }
452         return result;
453     }
454 
455     /**
456      * Checks if current default statement is single-line statement, e.g.:
457      * <p>
458      * {@code
459      * default: doSomeStuff();
460      * }
461      * </p>
462      * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}.
463      * @return true if current default statement is single-line statement.
464      */
465     private static boolean isSingleLineDefault(DetailAST literalDefault) {
466         boolean result = false;
467         final DetailAST slist = literalDefault.getNextSibling();
468         if (slist == null) {
469             result = true;
470         }
471         else {
472             final DetailAST block = slist.getFirstChild();
473             if (block != null && block.getType() != TokenTypes.SLIST) {
474                 result = literalDefault.getLineNo() == block.getLineNo();
475             }
476         }
477         return result;
478     }
479 
480     /**
481      * Checks if current else statement is single-line statement, e.g.:
482      * <p>
483      * {@code
484      * else doSomeStuff();
485      * }
486      * </p>
487      * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}.
488      * @return true if current else statement is single-line statement.
489      */
490     private static boolean isSingleLineElse(DetailAST literalElse) {
491         boolean result = false;
492         final DetailAST block = literalElse.getFirstChild();
493         if (block.getType() != TokenTypes.SLIST) {
494             result = literalElse.getLineNo() == block.getLineNo();
495         }
496         return result;
497     }
498 }