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   * &lt;module name="NeedBraces"/&gt;
61   * </pre>
62   * <p>
63   * To configure the check for {@code if} and {@code else} blocks:
64   * </p>
65   * <pre>
66   * &lt;module name=&quot;NeedBraces&quot;&gt;
67   *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_IF, LITERAL_ELSE&quot;/&gt;
68   * &lt;/module&gt;
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   * &lt;module name=&quot;NeedBraces&quot;&gt;
76   *   &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
77   * &lt;/module&gt;
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   * &lt;module name=&quot;NeedBraces&quot;&gt;
93   *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_CASE, LITERAL_DEFAULT&quot;/&gt;
94   *   &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
95   * &lt;/module&gt;
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  * &lt;module name=&quot;NeedBraces&quot;&gt;
112  *   &lt;property name=&quot;allowEmptyLoopBody&quot; value=&quot;true&quot;/&gt;
113  * &lt;/module&gt;
114  * </pre>
115  * <p>
116  * Next statements won't be violated by check:
117  * </p>
118  * <pre>
119  * while (value.incrementValue() &lt; 5); // OK
120  * for(int i = 0; i &lt; 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 }