001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.blocks; 021 022import java.util.Optional; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 030 031/** 032 * <p> 033 * Checks for braces around code blocks. 034 * </p> 035 * <ul> 036 * <li> 037 * Property {@code allowSingleLineStatement} - allow single-line statements without braces. 038 * Type is {@code boolean}. 039 * Default value is {@code false}. 040 * </li> 041 * <li> 042 * Property {@code allowEmptyLoopBody} - allow loops with empty bodies. 043 * Type is {@code boolean}. 044 * Default value is {@code false}. 045 * </li> 046 * <li> 047 * Property {@code tokens} - tokens to check 048 * Type is {@code java.lang.String[]}. 049 * Validation type is {@code tokenSet}. 050 * Default value is: 051 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 052 * LITERAL_DO</a>, 053 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE"> 054 * LITERAL_ELSE</a>, 055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 056 * LITERAL_FOR</a>, 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 058 * LITERAL_IF</a>, 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 060 * LITERAL_WHILE</a>. 061 * </li> 062 * </ul> 063 * <p> 064 * To configure the check: 065 * </p> 066 * <pre> 067 * <module name="NeedBraces"/> 068 * </pre> 069 * <p> 070 * To configure the check for {@code if} and {@code else} blocks: 071 * </p> 072 * <pre> 073 * <module name="NeedBraces"> 074 * <property name="tokens" value="LITERAL_IF, LITERAL_ELSE"/> 075 * </module> 076 * </pre> 077 * <p> 078 * To configure the check to allow single-line statements 079 * ({@code if, while, do-while, for}) without braces: 080 * </p> 081 * <pre> 082 * <module name="NeedBraces"> 083 * <property name="allowSingleLineStatement" value="true"/> 084 * </module> 085 * </pre> 086 * <p> 087 * Next statements won't be violated by check: 088 * </p> 089 * <pre> 090 * if (obj.isValid()) return true; // OK 091 * while (obj.isValid()) return true; // OK 092 * do this.notify(); while (o != null); // OK 093 * for (int i = 0; ; ) this.notify(); // OK 094 * </pre> 095 * <p> 096 * To configure the check to allow {@code case, default} single-line statements without braces: 097 * </p> 098 * <pre> 099 * <module name="NeedBraces"> 100 * <property name="tokens" value="LITERAL_CASE, LITERAL_DEFAULT"/> 101 * <property name="allowSingleLineStatement" value="true"/> 102 * </module> 103 * </pre> 104 * <p> 105 * Next statements won't be violated by check: 106 * </p> 107 * <pre> 108 * switch (num) { 109 * case 1: counter++; break; // OK 110 * case 6: counter += 10; break; // OK 111 * default: counter = 100; break; // OK 112 * } 113 * </pre> 114 * <p> 115 * To configure the check to allow loops ({@code while, for}) with empty bodies: 116 * </p> 117 * <pre> 118 * <module name="NeedBraces"> 119 * <property name="allowEmptyLoopBody" value="true"/> 120 * </module> 121 * </pre> 122 * <p> 123 * Next statements won't be violated by check: 124 * </p> 125 * <pre> 126 * while (value.incrementValue() < 5); // OK 127 * for(int i = 0; i < 10; value.incrementValue()); // OK 128 * </pre> 129 * <p> 130 * To configure the check to lambdas: 131 * </p> 132 * <pre> 133 * <module name="NeedBraces"> 134 * <property name="tokens" value="LAMBDA"/> 135 * <property name="allowSingleLineStatement" value="true"/> 136 * </module> 137 * </pre> 138 * <p> 139 * Results in following: 140 * </p> 141 * <pre> 142 * allowedFuture.addCallback(result -> assertEquals("Invalid response", 143 * EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS), result), // violation, lambda spans 2 lines 144 * ex -> fail(ex.getMessage())); // OK 145 * 146 * allowedFuture.addCallback(result -> { 147 * return assertEquals("Invalid response", 148 * EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS), result); 149 * }, // OK 150 * ex -> fail(ex.getMessage())); 151 * </pre> 152 * <p> 153 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 154 * </p> 155 * <p> 156 * Violation Message Keys: 157 * </p> 158 * <ul> 159 * <li> 160 * {@code needBraces} 161 * </li> 162 * </ul> 163 * 164 * @since 3.0 165 */ 166@StatelessCheck 167public class NeedBracesCheck extends AbstractCheck { 168 169 /** 170 * A key is pointing to the warning message text in "messages.properties" 171 * file. 172 */ 173 public static final String MSG_KEY_NEED_BRACES = "needBraces"; 174 175 /** 176 * Allow single-line statements without braces. 177 */ 178 private boolean allowSingleLineStatement; 179 180 /** 181 * Allow loops with empty bodies. 182 */ 183 private boolean allowEmptyLoopBody; 184 185 /** 186 * Setter to allow single-line statements without braces. 187 * 188 * @param allowSingleLineStatement Check's option for skipping single-line statements 189 */ 190 public void setAllowSingleLineStatement(boolean allowSingleLineStatement) { 191 this.allowSingleLineStatement = allowSingleLineStatement; 192 } 193 194 /** 195 * Setter to allow loops with empty bodies. 196 * 197 * @param allowEmptyLoopBody Check's option for allowing loops with empty body. 198 */ 199 public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) { 200 this.allowEmptyLoopBody = allowEmptyLoopBody; 201 } 202 203 @Override 204 public int[] getDefaultTokens() { 205 return new int[] { 206 TokenTypes.LITERAL_DO, 207 TokenTypes.LITERAL_ELSE, 208 TokenTypes.LITERAL_FOR, 209 TokenTypes.LITERAL_IF, 210 TokenTypes.LITERAL_WHILE, 211 }; 212 } 213 214 @Override 215 public int[] getAcceptableTokens() { 216 return new int[] { 217 TokenTypes.LITERAL_DO, 218 TokenTypes.LITERAL_ELSE, 219 TokenTypes.LITERAL_FOR, 220 TokenTypes.LITERAL_IF, 221 TokenTypes.LITERAL_WHILE, 222 TokenTypes.LITERAL_CASE, 223 TokenTypes.LITERAL_DEFAULT, 224 TokenTypes.LAMBDA, 225 }; 226 } 227 228 @Override 229 public int[] getRequiredTokens() { 230 return CommonUtil.EMPTY_INT_ARRAY; 231 } 232 233 @Override 234 public void visitToken(DetailAST ast) { 235 final boolean hasNoSlist = ast.findFirstToken(TokenTypes.SLIST) == null; 236 if (hasNoSlist && !isSkipStatement(ast) && isBracesNeeded(ast)) { 237 log(ast, MSG_KEY_NEED_BRACES, ast.getText()); 238 } 239 } 240 241 /** 242 * Checks if token needs braces. 243 * Some tokens have additional conditions: 244 * <ul> 245 * <li>{@link TokenTypes#LITERAL_FOR}</li> 246 * <li>{@link TokenTypes#LITERAL_WHILE}</li> 247 * <li>{@link TokenTypes#LITERAL_CASE}</li> 248 * <li>{@link TokenTypes#LITERAL_DEFAULT}</li> 249 * <li>{@link TokenTypes#LITERAL_ELSE}</li> 250 * <li>{@link TokenTypes#LAMBDA}</li> 251 * </ul> 252 * For all others default value {@code true} is returned. 253 * 254 * @param ast token to check 255 * @return result of additional checks for specific token types, 256 * {@code true} if there is no additional checks for token 257 */ 258 private boolean isBracesNeeded(DetailAST ast) { 259 final boolean result; 260 switch (ast.getType()) { 261 case TokenTypes.LITERAL_FOR: 262 case TokenTypes.LITERAL_WHILE: 263 result = !isEmptyLoopBodyAllowed(ast); 264 break; 265 case TokenTypes.LITERAL_CASE: 266 case TokenTypes.LITERAL_DEFAULT: 267 result = hasUnbracedStatements(ast) 268 && !isSwitchLabeledExpression(ast); 269 break; 270 case TokenTypes.LITERAL_ELSE: 271 result = ast.findFirstToken(TokenTypes.LITERAL_IF) == null; 272 break; 273 case TokenTypes.LAMBDA: 274 result = !isInSwitchRule(ast); 275 break; 276 default: 277 result = true; 278 break; 279 } 280 return result; 281 } 282 283 /** 284 * Checks if current loop has empty body and can be skipped by this check. 285 * 286 * @param ast for, while statements. 287 * @return true if current loop can be skipped by check. 288 */ 289 private boolean isEmptyLoopBodyAllowed(DetailAST ast) { 290 return allowEmptyLoopBody && ast.findFirstToken(TokenTypes.EMPTY_STAT) != null; 291 } 292 293 /** 294 * Checks if switch member (case, default statements) has statements without curly braces. 295 * 296 * @param ast case, default statements. 297 * @return true if switch member has unbraced statements, false otherwise. 298 */ 299 private static boolean hasUnbracedStatements(DetailAST ast) { 300 final DetailAST nextSibling = ast.getNextSibling(); 301 boolean result = false; 302 303 if (isInSwitchRule(ast)) { 304 final DetailAST parent = ast.getParent(); 305 result = parent.getLastChild().getType() != TokenTypes.SLIST; 306 } 307 else if (nextSibling != null 308 && nextSibling.getType() == TokenTypes.SLIST 309 && nextSibling.getFirstChild().getType() != TokenTypes.SLIST) { 310 result = true; 311 } 312 return result; 313 } 314 315 /** 316 * Checks if current statement can be skipped by "need braces" warning. 317 * 318 * @param statement if, for, while, do-while, lambda, else, case, default statements. 319 * @return true if current statement can be skipped by Check. 320 */ 321 private boolean isSkipStatement(DetailAST statement) { 322 return allowSingleLineStatement && isSingleLineStatement(statement); 323 } 324 325 /** 326 * Checks if current statement is single-line statement, e.g.: 327 * <p> 328 * {@code 329 * if (obj.isValid()) return true; 330 * } 331 * </p> 332 * <p> 333 * {@code 334 * while (obj.isValid()) return true; 335 * } 336 * </p> 337 * 338 * @param statement if, for, while, do-while, lambda, else, case, default statements. 339 * @return true if current statement is single-line statement. 340 */ 341 private static boolean isSingleLineStatement(DetailAST statement) { 342 final boolean result; 343 344 switch (statement.getType()) { 345 case TokenTypes.LITERAL_IF: 346 result = isSingleLineIf(statement); 347 break; 348 case TokenTypes.LITERAL_FOR: 349 result = isSingleLineFor(statement); 350 break; 351 case TokenTypes.LITERAL_DO: 352 result = isSingleLineDoWhile(statement); 353 break; 354 case TokenTypes.LITERAL_WHILE: 355 result = isSingleLineWhile(statement); 356 break; 357 case TokenTypes.LAMBDA: 358 result = !isInSwitchRule(statement) 359 && isSingleLineLambda(statement); 360 break; 361 case TokenTypes.LITERAL_CASE: 362 case TokenTypes.LITERAL_DEFAULT: 363 result = isSingleLineSwitchMember(statement); 364 break; 365 default: 366 result = isSingleLineElse(statement); 367 break; 368 } 369 370 return result; 371 } 372 373 /** 374 * Checks if current while statement is single-line statement, e.g.: 375 * <p> 376 * {@code 377 * while (obj.isValid()) return true; 378 * } 379 * </p> 380 * 381 * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}. 382 * @return true if current while statement is single-line statement. 383 */ 384 private static boolean isSingleLineWhile(DetailAST literalWhile) { 385 boolean result = false; 386 if (literalWhile.getParent().getType() == TokenTypes.SLIST) { 387 final DetailAST block = literalWhile.getLastChild().getPreviousSibling(); 388 result = TokenUtil.areOnSameLine(literalWhile, block); 389 } 390 return result; 391 } 392 393 /** 394 * Checks if current do-while statement is single-line statement, e.g.: 395 * <p> 396 * {@code 397 * do this.notify(); while (o != null); 398 * } 399 * </p> 400 * 401 * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}. 402 * @return true if current do-while statement is single-line statement. 403 */ 404 private static boolean isSingleLineDoWhile(DetailAST literalDo) { 405 boolean result = false; 406 if (literalDo.getParent().getType() == TokenTypes.SLIST) { 407 final DetailAST block = literalDo.getFirstChild(); 408 result = TokenUtil.areOnSameLine(block, literalDo); 409 } 410 return result; 411 } 412 413 /** 414 * Checks if current for statement is single-line statement, e.g.: 415 * <p> 416 * {@code 417 * for (int i = 0; ; ) this.notify(); 418 * } 419 * </p> 420 * 421 * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}. 422 * @return true if current for statement is single-line statement. 423 */ 424 private static boolean isSingleLineFor(DetailAST literalFor) { 425 boolean result = false; 426 if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) { 427 result = true; 428 } 429 else if (literalFor.getParent().getType() == TokenTypes.SLIST) { 430 result = TokenUtil.areOnSameLine(literalFor, literalFor.getLastChild()); 431 } 432 return result; 433 } 434 435 /** 436 * Checks if current if statement is single-line statement, e.g.: 437 * <p> 438 * {@code 439 * if (obj.isValid()) return true; 440 * } 441 * </p> 442 * 443 * @param literalIf {@link TokenTypes#LITERAL_IF if statement}. 444 * @return true if current if statement is single-line statement. 445 */ 446 private static boolean isSingleLineIf(DetailAST literalIf) { 447 boolean result = false; 448 if (literalIf.getParent().getType() == TokenTypes.SLIST) { 449 final DetailAST literalIfLastChild = literalIf.getLastChild(); 450 final DetailAST block; 451 if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) { 452 block = literalIfLastChild.getPreviousSibling(); 453 } 454 else { 455 block = literalIfLastChild; 456 } 457 final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR); 458 result = TokenUtil.areOnSameLine(ifCondition, block); 459 } 460 return result; 461 } 462 463 /** 464 * Checks if current lambda statement is single-line statement, e.g.: 465 * <p> 466 * {@code 467 * Runnable r = () -> System.out.println("Hello, world!"); 468 * } 469 * </p> 470 * 471 * @param lambda {@link TokenTypes#LAMBDA lambda statement}. 472 * @return true if current lambda statement is single-line statement. 473 */ 474 private static boolean isSingleLineLambda(DetailAST lambda) { 475 final DetailAST lastLambdaToken = getLastLambdaToken(lambda); 476 return TokenUtil.areOnSameLine(lambda, lastLambdaToken); 477 } 478 479 /** 480 * Looks for the last token in lambda. 481 * 482 * @param lambda token to check. 483 * @return last token in lambda 484 */ 485 private static DetailAST getLastLambdaToken(DetailAST lambda) { 486 DetailAST node = lambda; 487 do { 488 node = node.getLastChild(); 489 } while (node.getLastChild() != null); 490 return node; 491 } 492 493 /** 494 * Checks if current ast's parent is a switch rule, e.g.: 495 * <p> 496 * {@code 497 * case 1 -> monthString = "January"; 498 * } 499 * </p> 500 * 501 * @param ast the ast to check. 502 * @return true if current ast belongs to a switch rule. 503 */ 504 private static boolean isInSwitchRule(DetailAST ast) { 505 return ast.getParent().getType() == TokenTypes.SWITCH_RULE; 506 } 507 508 /** 509 * Checks if current expression is a switch labeled expression. If so, 510 * braces are not allowed e.g.: 511 * <p> 512 * {@code 513 * case 1 -> 4; 514 * } 515 * </p> 516 * 517 * @param ast the ast to check 518 * @return true if current expression is a switch labeled expression. 519 */ 520 private static boolean isSwitchLabeledExpression(DetailAST ast) { 521 final DetailAST parent = ast.getParent(); 522 return switchRuleHasSingleExpression(parent); 523 } 524 525 /** 526 * Checks if current switch labeled expression contains only a single expression. 527 * 528 * @param switchRule {@link TokenTypes#SWITCH_RULE}. 529 * @return true if current switch rule has a single expression. 530 */ 531 private static boolean switchRuleHasSingleExpression(DetailAST switchRule) { 532 final DetailAST possibleExpression = switchRule.findFirstToken(TokenTypes.EXPR); 533 return possibleExpression != null 534 && possibleExpression.getFirstChild().getFirstChild() == null; 535 } 536 537 /** 538 * Checks if switch member (case or default statement) in a switch rule or 539 * case group is on a single line. 540 * 541 * @param statement {@link TokenTypes#LITERAL_CASE case statement} or 542 * {@link TokenTypes#LITERAL_DEFAULT default statement}. 543 * @return true if current switch member is single-line statement. 544 */ 545 private static boolean isSingleLineSwitchMember(DetailAST statement) { 546 final boolean result; 547 if (isInSwitchRule(statement)) { 548 result = isSingleLineSwitchRule(statement); 549 } 550 else { 551 result = isSingleLineCaseGroup(statement); 552 } 553 return result; 554 } 555 556 /** 557 * Checks if switch member in case group (case or default statement) 558 * is single-line statement, e.g.: 559 * <p> 560 * {@code 561 * case 1: System.out.println("case one"); break; 562 * case 2: System.out.println("case two"); break; 563 * case 3: ; 564 * default: System.out.println("default"); break; 565 * } 566 * </p> 567 * 568 * 569 * @param ast {@link TokenTypes#LITERAL_CASE case statement} or 570 * {@link TokenTypes#LITERAL_DEFAULT default statement}. 571 * @return true if current switch member is single-line statement. 572 */ 573 private static boolean isSingleLineCaseGroup(DetailAST ast) { 574 return Optional.of(ast) 575 .map(DetailAST::getNextSibling) 576 .map(DetailAST::getLastChild) 577 .map(lastToken -> TokenUtil.areOnSameLine(ast, lastToken)) 578 .orElse(true); 579 } 580 581 /** 582 * Checks if switch member in switch rule (case or default statement) is 583 * single-line statement, e.g.: 584 * <p> 585 * {@code 586 * case 1 -> System.out.println("case one"); 587 * case 2 -> System.out.println("case two"); 588 * default -> System.out.println("default"); 589 * } 590 * </p> 591 * 592 * @param ast {@link TokenTypes#LITERAL_CASE case statement} or 593 * {@link TokenTypes#LITERAL_DEFAULT default statement}. 594 * @return true if current switch label is single-line statement. 595 */ 596 private static boolean isSingleLineSwitchRule(DetailAST ast) { 597 final DetailAST lastSibling = ast.getParent().getLastChild(); 598 return TokenUtil.areOnSameLine(ast, lastSibling); 599 } 600 601 /** 602 * Checks if current else statement is single-line statement, e.g.: 603 * <p> 604 * {@code 605 * else doSomeStuff(); 606 * } 607 * </p> 608 * 609 * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}. 610 * @return true if current else statement is single-line statement. 611 */ 612 private static boolean isSingleLineElse(DetailAST literalElse) { 613 final DetailAST block = literalElse.getFirstChild(); 614 return TokenUtil.areOnSameLine(literalElse, block); 615 } 616 617}