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