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