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