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 int[]}. 049 * Default value is: 050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 051 * LITERAL_DO</a>, 052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE"> 053 * LITERAL_ELSE</a>, 054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 055 * LITERAL_FOR</a>, 056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 057 * LITERAL_IF</a>, 058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 059 * LITERAL_WHILE</a>. 060 * </li> 061 * </ul> 062 * <p> 063 * To configure the check: 064 * </p> 065 * <pre> 066 * <module name="NeedBraces"/> 067 * </pre> 068 * <p> 069 * To configure the check for {@code if} and {@code else} blocks: 070 * </p> 071 * <pre> 072 * <module name="NeedBraces"> 073 * <property name="tokens" value="LITERAL_IF, LITERAL_ELSE"/> 074 * </module> 075 * </pre> 076 * <p> 077 * To configure the check to allow single-line statements 078 * ({@code if, while, do-while, for}) without braces: 079 * </p> 080 * <pre> 081 * <module name="NeedBraces"> 082 * <property name="allowSingleLineStatement" value="true"/> 083 * </module> 084 * </pre> 085 * <p> 086 * Next statements won't be violated by check: 087 * </p> 088 * <pre> 089 * if (obj.isValid()) return true; // OK 090 * while (obj.isValid()) return true; // OK 091 * do this.notify(); while (o != null); // OK 092 * for (int i = 0; ; ) this.notify(); // OK 093 * </pre> 094 * <p> 095 * To configure the check to allow {@code case, default} single-line statements without braces: 096 * </p> 097 * <pre> 098 * <module name="NeedBraces"> 099 * <property name="tokens" value="LITERAL_CASE, LITERAL_DEFAULT"/> 100 * <property name="allowSingleLineStatement" value="true"/> 101 * </module> 102 * </pre> 103 * <p> 104 * Next statements won't be violated by check: 105 * </p> 106 * <pre> 107 * switch (num) { 108 * case 1: counter++; break; // OK 109 * case 6: counter += 10; break; // OK 110 * default: counter = 100; break; // OK 111 * } 112 * </pre> 113 * <p> 114 * To configure the check to allow loops ({@code while, for}) with empty bodies: 115 * </p> 116 * <pre> 117 * <module name="NeedBraces"> 118 * <property name="allowEmptyLoopBody" value="true"/> 119 * </module> 120 * </pre> 121 * <p> 122 * Next statements won't be violated by check: 123 * </p> 124 * <pre> 125 * while (value.incrementValue() < 5); // OK 126 * for(int i = 0; i < 10; value.incrementValue()); // OK 127 * </pre> 128 * <p> 129 * To configure the check to lambdas: 130 * </p> 131 * <pre> 132 * <module name="NeedBraces"> 133 * <property name="tokens" value="LAMBDA"/> 134 * <property name="allowSingleLineStatement" value="true"/> 135 * </module> 136 * </pre> 137 * <p> 138 * Results in following: 139 * </p> 140 * <pre> 141 * allowedFuture.addCallback(result -> assertEquals("Invalid response", 142 * EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS), result), // violation, lambda spans 2 lines 143 * ex -> fail(ex.getMessage())); // OK 144 * 145 * allowedFuture.addCallback(result -> { 146 * return assertEquals("Invalid response", 147 * EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS), result); 148 * }, // OK 149 * ex -> fail(ex.getMessage())); 150 * </pre> 151 * <p> 152 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 153 * </p> 154 * <p> 155 * Violation Message Keys: 156 * </p> 157 * <ul> 158 * <li> 159 * {@code needBraces} 160 * </li> 161 * </ul> 162 * 163 * @since 3.0 164 */ 165@StatelessCheck 166public class NeedBracesCheck extends AbstractCheck { 167 168 /** 169 * A key is pointing to the warning message text in "messages.properties" 170 * file. 171 */ 172 public static final String MSG_KEY_NEED_BRACES = "needBraces"; 173 174 /** 175 * Allow single-line statements without braces. 176 */ 177 private boolean allowSingleLineStatement; 178 179 /** 180 * Allow loops with empty bodies. 181 */ 182 private boolean allowEmptyLoopBody; 183 184 /** 185 * Setter to allow single-line statements without braces. 186 * 187 * @param allowSingleLineStatement Check's option for skipping single-line statements 188 */ 189 public void setAllowSingleLineStatement(boolean allowSingleLineStatement) { 190 this.allowSingleLineStatement = allowSingleLineStatement; 191 } 192 193 /** 194 * Setter to allow loops with empty bodies. 195 * 196 * @param allowEmptyLoopBody Check's option for allowing loops with empty body. 197 */ 198 public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) { 199 this.allowEmptyLoopBody = allowEmptyLoopBody; 200 } 201 202 @Override 203 public int[] getDefaultTokens() { 204 return new int[] { 205 TokenTypes.LITERAL_DO, 206 TokenTypes.LITERAL_ELSE, 207 TokenTypes.LITERAL_FOR, 208 TokenTypes.LITERAL_IF, 209 TokenTypes.LITERAL_WHILE, 210 }; 211 } 212 213 @Override 214 public int[] getAcceptableTokens() { 215 return new int[] { 216 TokenTypes.LITERAL_DO, 217 TokenTypes.LITERAL_ELSE, 218 TokenTypes.LITERAL_FOR, 219 TokenTypes.LITERAL_IF, 220 TokenTypes.LITERAL_WHILE, 221 TokenTypes.LITERAL_CASE, 222 TokenTypes.LITERAL_DEFAULT, 223 TokenTypes.LAMBDA, 224 }; 225 } 226 227 @Override 228 public int[] getRequiredTokens() { 229 return CommonUtil.EMPTY_INT_ARRAY; 230 } 231 232 @Override 233 public void visitToken(DetailAST ast) { 234 final boolean hasNoSlist = ast.findFirstToken(TokenTypes.SLIST) == null; 235 if (hasNoSlist && !isSkipStatement(ast) && isBracesNeeded(ast)) { 236 log(ast, MSG_KEY_NEED_BRACES, ast.getText()); 237 } 238 } 239 240 /** 241 * Checks if token needs braces. 242 * Some tokens have additional conditions: 243 * <ul> 244 * <li>{@link TokenTypes#LITERAL_FOR}</li> 245 * <li>{@link TokenTypes#LITERAL_WHILE}</li> 246 * <li>{@link TokenTypes#LITERAL_CASE}</li> 247 * <li>{@link TokenTypes#LITERAL_DEFAULT}</li> 248 * <li>{@link TokenTypes#LITERAL_ELSE}</li> 249 * </ul> 250 * For all others default value {@code true} is returned. 251 * 252 * @param ast token to check 253 * @return result of additional checks for specific token types, 254 * {@code true} if there is no additional checks for token 255 */ 256 private boolean isBracesNeeded(DetailAST ast) { 257 final boolean result; 258 switch (ast.getType()) { 259 case TokenTypes.LITERAL_FOR: 260 case TokenTypes.LITERAL_WHILE: 261 result = !isEmptyLoopBodyAllowed(ast); 262 break; 263 case TokenTypes.LITERAL_CASE: 264 case TokenTypes.LITERAL_DEFAULT: 265 result = hasUnbracedStatements(ast); 266 break; 267 case TokenTypes.LITERAL_ELSE: 268 result = ast.findFirstToken(TokenTypes.LITERAL_IF) == null; 269 break; 270 default: 271 result = true; 272 break; 273 } 274 return result; 275 } 276 277 /** 278 * Checks if current loop has empty body and can be skipped by this check. 279 * 280 * @param ast for, while statements. 281 * @return true if current loop can be skipped by check. 282 */ 283 private boolean isEmptyLoopBodyAllowed(DetailAST ast) { 284 return allowEmptyLoopBody && ast.findFirstToken(TokenTypes.EMPTY_STAT) != null; 285 } 286 287 /** 288 * Checks if switch member (case, default statements) has statements without curly braces. 289 * 290 * @param ast case, default statements. 291 * @return true if switch member has unbraced statements, false otherwise. 292 */ 293 private static boolean hasUnbracedStatements(DetailAST ast) { 294 final DetailAST nextSibling = ast.getNextSibling(); 295 return nextSibling != null 296 && nextSibling.getType() == TokenTypes.SLIST 297 && nextSibling.getFirstChild().getType() != TokenTypes.SLIST; 298 } 299 300 /** 301 * Checks if current statement can be skipped by "need braces" warning. 302 * 303 * @param statement if, for, while, do-while, lambda, else, case, default statements. 304 * @return true if current statement can be skipped by Check. 305 */ 306 private boolean isSkipStatement(DetailAST statement) { 307 return allowSingleLineStatement && isSingleLineStatement(statement); 308 } 309 310 /** 311 * Checks if current statement is single-line statement, e.g.: 312 * <p> 313 * {@code 314 * if (obj.isValid()) return true; 315 * } 316 * </p> 317 * <p> 318 * {@code 319 * while (obj.isValid()) return true; 320 * } 321 * </p> 322 * 323 * @param statement if, for, while, do-while, lambda, else, case, default statements. 324 * @return true if current statement is single-line statement. 325 */ 326 private static boolean isSingleLineStatement(DetailAST statement) { 327 final boolean result; 328 329 switch (statement.getType()) { 330 case TokenTypes.LITERAL_IF: 331 result = isSingleLineIf(statement); 332 break; 333 case TokenTypes.LITERAL_FOR: 334 result = isSingleLineFor(statement); 335 break; 336 case TokenTypes.LITERAL_DO: 337 result = isSingleLineDoWhile(statement); 338 break; 339 case TokenTypes.LITERAL_WHILE: 340 result = isSingleLineWhile(statement); 341 break; 342 case TokenTypes.LAMBDA: 343 result = isSingleLineLambda(statement); 344 break; 345 case TokenTypes.LITERAL_CASE: 346 case TokenTypes.LITERAL_DEFAULT: 347 result = isSingleLineSwitchMember(statement); 348 break; 349 default: 350 result = isSingleLineElse(statement); 351 break; 352 } 353 354 return result; 355 } 356 357 /** 358 * Checks if current while statement is single-line statement, e.g.: 359 * <p> 360 * {@code 361 * while (obj.isValid()) return true; 362 * } 363 * </p> 364 * 365 * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}. 366 * @return true if current while statement is single-line statement. 367 */ 368 private static boolean isSingleLineWhile(DetailAST literalWhile) { 369 boolean result = false; 370 if (literalWhile.getParent().getType() == TokenTypes.SLIST) { 371 final DetailAST block = literalWhile.getLastChild().getPreviousSibling(); 372 result = TokenUtil.areOnSameLine(literalWhile, block); 373 } 374 return result; 375 } 376 377 /** 378 * Checks if current do-while statement is single-line statement, e.g.: 379 * <p> 380 * {@code 381 * do this.notify(); while (o != null); 382 * } 383 * </p> 384 * 385 * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}. 386 * @return true if current do-while statement is single-line statement. 387 */ 388 private static boolean isSingleLineDoWhile(DetailAST literalDo) { 389 boolean result = false; 390 if (literalDo.getParent().getType() == TokenTypes.SLIST) { 391 final DetailAST block = literalDo.getFirstChild(); 392 result = TokenUtil.areOnSameLine(block, literalDo); 393 } 394 return result; 395 } 396 397 /** 398 * Checks if current for statement is single-line statement, e.g.: 399 * <p> 400 * {@code 401 * for (int i = 0; ; ) this.notify(); 402 * } 403 * </p> 404 * 405 * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}. 406 * @return true if current for statement is single-line statement. 407 */ 408 private static boolean isSingleLineFor(DetailAST literalFor) { 409 boolean result = false; 410 if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) { 411 result = true; 412 } 413 else if (literalFor.getParent().getType() == TokenTypes.SLIST) { 414 result = TokenUtil.areOnSameLine(literalFor, literalFor.getLastChild()); 415 } 416 return result; 417 } 418 419 /** 420 * Checks if current if statement is single-line statement, e.g.: 421 * <p> 422 * {@code 423 * if (obj.isValid()) return true; 424 * } 425 * </p> 426 * 427 * @param literalIf {@link TokenTypes#LITERAL_IF if statement}. 428 * @return true if current if statement is single-line statement. 429 */ 430 private static boolean isSingleLineIf(DetailAST literalIf) { 431 boolean result = false; 432 if (literalIf.getParent().getType() == TokenTypes.SLIST) { 433 final DetailAST literalIfLastChild = literalIf.getLastChild(); 434 final DetailAST block; 435 if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) { 436 block = literalIfLastChild.getPreviousSibling(); 437 } 438 else { 439 block = literalIfLastChild; 440 } 441 final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR); 442 result = TokenUtil.areOnSameLine(ifCondition, block); 443 } 444 return result; 445 } 446 447 /** 448 * Checks if current lambda statement is single-line statement, e.g.: 449 * <p> 450 * {@code 451 * Runnable r = () -> System.out.println("Hello, world!"); 452 * } 453 * </p> 454 * 455 * @param lambda {@link TokenTypes#LAMBDA lambda statement}. 456 * @return true if current lambda statement is single-line statement. 457 */ 458 private static boolean isSingleLineLambda(DetailAST lambda) { 459 final DetailAST lastLambdaToken = getLastLambdaToken(lambda); 460 return TokenUtil.areOnSameLine(lambda, lastLambdaToken); 461 } 462 463 /** 464 * Looks for the last token in lambda. 465 * 466 * @param lambda token to check. 467 * @return last token in lambda 468 */ 469 private static DetailAST getLastLambdaToken(DetailAST lambda) { 470 DetailAST node = lambda; 471 do { 472 node = node.getLastChild(); 473 } while (node.getLastChild() != null); 474 return node; 475 } 476 477 /** 478 * Checks if switch member (case or default statement) is single-line statement, e.g.: 479 * <p> 480 * {@code 481 * case 1: doSomeStuff(); break; 482 * case 2: doSomeStuff(); break; 483 * case 3: ; 484 * default: doSomeStuff();break; 485 * } 486 * </p> 487 * 488 * @param ast {@link TokenTypes#LITERAL_CASE case statement} or 489 * {@link TokenTypes#LITERAL_DEFAULT default statement}. 490 * @return true if current switch member is single-line statement. 491 */ 492 private static boolean isSingleLineSwitchMember(DetailAST ast) { 493 return Optional.of(ast) 494 .map(DetailAST::getNextSibling) 495 .map(DetailAST::getLastChild) 496 .map(lastToken -> TokenUtil.areOnSameLine(ast, lastToken)) 497 .orElse(true); 498 } 499 500 /** 501 * Checks if current else statement is single-line statement, e.g.: 502 * <p> 503 * {@code 504 * else doSomeStuff(); 505 * } 506 * </p> 507 * 508 * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}. 509 * @return true if current else statement is single-line statement. 510 */ 511 private static boolean isSingleLineElse(DetailAST literalElse) { 512 final DetailAST block = literalElse.getFirstChild(); 513 return TokenUtil.areOnSameLine(literalElse, block); 514 } 515 516}