Coverage Report - com.puppycrawl.tools.checkstyle.checks.blocks.NeedBracesCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
NeedBracesCheck
100%
107/107
100%
96/96
3.167
 
 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  24
 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  9
         this.allowSingleLineStatement = allowSingleLineStatement;
 161  9
     }
 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  2
         this.allowEmptyLoopBody = allowEmptyLoopBody;
 169  2
     }
 170  
 
 171  
     @Override
 172  
     public int[] getDefaultTokens() {
 173  15
         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  14
         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  32
         return CommonUtils.EMPTY_INT_ARRAY;
 199  
     }
 200  
 
 201  
     @Override
 202  
     public void visitToken(DetailAST ast) {
 203  122
         final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
 204  122
         boolean isElseIf = false;
 205  122
         if (ast.getType() == TokenTypes.LITERAL_ELSE
 206  8
             && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) {
 207  1
             isElseIf = true;
 208  
         }
 209  122
         final boolean isDefaultInAnnotation = isDefaultInAnnotation(ast);
 210  122
         final boolean skipStatement = isSkipStatement(ast);
 211  122
         final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast);
 212  
 
 213  122
         if (slistAST == null && !isElseIf && !isDefaultInAnnotation
 214  
                 && !skipStatement && !skipEmptyLoopBody) {
 215  57
             log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText());
 216  
         }
 217  122
     }
 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  122
         boolean isDefaultInAnnotation = false;
 226  122
         if (ast.getType() == TokenTypes.LITERAL_DEFAULT
 227  7
                 && ast.getParent().getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
 228  2
             isDefaultInAnnotation = true;
 229  
         }
 230  122
         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  122
         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  20
         boolean noBodyLoop = false;
 256  
 
 257  20
         if (ast.getType() == TokenTypes.LITERAL_FOR
 258  12
                 || ast.getType() == TokenTypes.LITERAL_WHILE) {
 259  18
             DetailAST currentToken = ast.getFirstChild();
 260  104
             while (currentToken.getNextSibling() != null) {
 261  86
                 currentToken = currentToken.getNextSibling();
 262  
             }
 263  18
             noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT;
 264  
         }
 265  20
         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  55
         switch (statement.getType()) {
 287  
             case TokenTypes.LITERAL_IF:
 288  11
                 result = isSingleLineIf(statement);
 289  11
                 break;
 290  
             case TokenTypes.LITERAL_FOR:
 291  11
                 result = isSingleLineFor(statement);
 292  11
                 break;
 293  
             case TokenTypes.LITERAL_DO:
 294  4
                 result = isSingleLineDoWhile(statement);
 295  4
                 break;
 296  
             case TokenTypes.LITERAL_WHILE:
 297  4
                 result = isSingleLineWhile(statement);
 298  4
                 break;
 299  
             case TokenTypes.LAMBDA:
 300  4
                 result = isSingleLineLambda(statement);
 301  4
                 break;
 302  
             case TokenTypes.LITERAL_CASE:
 303  11
                 result = isSingleLineCase(statement);
 304  11
                 break;
 305  
             case TokenTypes.LITERAL_DEFAULT:
 306  7
                 result = isSingleLineDefault(statement);
 307  7
                 break;
 308  
             default:
 309  3
                 result = isSingleLineElse(statement);
 310  
                 break;
 311  
         }
 312  
 
 313  55
         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  4
         boolean result = false;
 328  4
         if (literalWhile.getParent().getType() == TokenTypes.SLIST
 329  3
                 && literalWhile.getLastChild().getType() != TokenTypes.SLIST) {
 330  2
             final DetailAST block = literalWhile.getLastChild().getPreviousSibling();
 331  2
             result = literalWhile.getLineNo() == block.getLineNo();
 332  
         }
 333  4
         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  4
         boolean result = false;
 348  4
         if (literalDo.getParent().getType() == TokenTypes.SLIST
 349  3
                 && literalDo.getFirstChild().getType() != TokenTypes.SLIST) {
 350  2
             final DetailAST block = literalDo.getFirstChild();
 351  2
             result = block.getLineNo() == literalDo.getLineNo();
 352  
         }
 353  4
         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  11
         boolean result = false;
 368  11
         if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) {
 369  1
             result = true;
 370  
         }
 371  10
         else if (literalFor.getParent().getType() == TokenTypes.SLIST
 372  9
                 && literalFor.getLastChild().getType() != TokenTypes.SLIST) {
 373  8
             result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo();
 374  
         }
 375  11
         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  11
         boolean result = false;
 390  11
         if (literalIf.getParent().getType() == TokenTypes.SLIST) {
 391  9
             final DetailAST literalIfLastChild = literalIf.getLastChild();
 392  
             final DetailAST block;
 393  9
             if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) {
 394  2
                 block = literalIfLastChild.getPreviousSibling();
 395  
             }
 396  
             else {
 397  7
                 block = literalIfLastChild;
 398  
             }
 399  9
             final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR);
 400  9
             result = ifCondition.getLineNo() == block.getLineNo();
 401  
         }
 402  11
         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  4
         boolean result = false;
 417  4
         final DetailAST block = lambda.getLastChild();
 418  4
         if (block.getType() != TokenTypes.SLIST) {
 419  3
             result = lambda.getLineNo() == block.getLineNo();
 420  
         }
 421  4
         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  11
         boolean result = false;
 438  11
         final DetailAST slist = literalCase.getNextSibling();
 439  11
         if (slist == null) {
 440  1
             result = true;
 441  
         }
 442  
         else {
 443  10
             final DetailAST block = slist.getFirstChild();
 444  10
             if (block.getType() != TokenTypes.SLIST) {
 445  7
                 final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK);
 446  7
                 if (caseBreak != null) {
 447  6
                     final boolean atOneLine = literalCase.getLineNo() == block.getLineNo();
 448  6
                     result = atOneLine && block.getLineNo() == caseBreak.getLineNo();
 449  
                 }
 450  
             }
 451  
         }
 452  11
         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  7
         boolean result = false;
 467  7
         final DetailAST slist = literalDefault.getNextSibling();
 468  7
         if (slist == null) {
 469  2
             result = true;
 470  
         }
 471  
         else {
 472  5
             final DetailAST block = slist.getFirstChild();
 473  5
             if (block != null && block.getType() != TokenTypes.SLIST) {
 474  2
                 result = literalDefault.getLineNo() == block.getLineNo();
 475  
             }
 476  
         }
 477  7
         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  3
         boolean result = false;
 492  3
         final DetailAST block = literalElse.getFirstChild();
 493  3
         if (block.getType() != TokenTypes.SLIST) {
 494  2
             result = literalElse.getLineNo() == block.getLineNo();
 495  
         }
 496  3
         return result;
 497  
     }
 498  
 }