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