001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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.Arrays; 023import java.util.Locale; 024import java.util.Optional; 025 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 031import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 032 033/** 034 * <div> 035 * Checks the placement of right curly braces (<code>'}'</code>) for code blocks. This check 036 * supports if-else, try-catch-finally blocks, switch statements, switch cases, while-loops, 037 * for-loops, method definitions, class definitions, constructor definitions, 038 * instance, static initialization blocks, annotation definitions and enum definitions. 039 * For right curly brace of expression blocks of arrays, lambdas and class instances 040 * please follow issue 041 * <a href="https://github.com/checkstyle/checkstyle/issues/5945">#5945</a>. 042 * For right curly brace of enum constant please follow issue 043 * <a href="https://github.com/checkstyle/checkstyle/issues/7519">#7519</a>. 044 * </div> 045 * 046 * 047 * @since 3.0 048 */ 049@StatelessCheck 050public class RightCurlyCheck extends AbstractCheck { 051 052 /** 053 * A key is pointing to the warning message text in "messages.properties" 054 * file. 055 */ 056 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before"; 057 058 /** 059 * A key is pointing to the warning message text in "messages.properties" 060 * file. 061 */ 062 public static final String MSG_KEY_LINE_ALONE = "line.alone"; 063 064 /** 065 * A key is pointing to the warning message text in "messages.properties" 066 * file. 067 */ 068 public static final String MSG_KEY_LINE_SAME = "line.same"; 069 070 /** 071 * Specify the policy on placement of a right curly brace (<code>'}'</code>). 072 */ 073 private RightCurlyOption option = RightCurlyOption.SAME; 074 075 /** 076 * Setter to specify the policy on placement of a right curly brace (<code>'}'</code>). 077 * 078 * @param optionStr string to decode option from 079 * @throws IllegalArgumentException if unable to decode 080 * @since 3.0 081 */ 082 public void setOption(String optionStr) { 083 option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 084 } 085 086 @Override 087 public int[] getDefaultTokens() { 088 return new int[] { 089 TokenTypes.LITERAL_TRY, 090 TokenTypes.LITERAL_CATCH, 091 TokenTypes.LITERAL_FINALLY, 092 TokenTypes.LITERAL_IF, 093 TokenTypes.LITERAL_ELSE, 094 }; 095 } 096 097 @Override 098 public int[] getAcceptableTokens() { 099 return new int[] { 100 TokenTypes.LITERAL_TRY, 101 TokenTypes.LITERAL_CATCH, 102 TokenTypes.LITERAL_FINALLY, 103 TokenTypes.LITERAL_IF, 104 TokenTypes.LITERAL_ELSE, 105 TokenTypes.CLASS_DEF, 106 TokenTypes.METHOD_DEF, 107 TokenTypes.CTOR_DEF, 108 TokenTypes.LITERAL_FOR, 109 TokenTypes.LITERAL_WHILE, 110 TokenTypes.LITERAL_DO, 111 TokenTypes.STATIC_INIT, 112 TokenTypes.INSTANCE_INIT, 113 TokenTypes.ANNOTATION_DEF, 114 TokenTypes.ENUM_DEF, 115 TokenTypes.INTERFACE_DEF, 116 TokenTypes.RECORD_DEF, 117 TokenTypes.COMPACT_CTOR_DEF, 118 TokenTypes.LITERAL_SWITCH, 119 TokenTypes.LITERAL_CASE, 120 }; 121 } 122 123 @Override 124 public int[] getRequiredTokens() { 125 return CommonUtil.EMPTY_INT_ARRAY; 126 } 127 128 @Override 129 public void visitToken(DetailAST ast) { 130 final Details details = Details.getDetails(ast); 131 final DetailAST rcurly = details.rcurly; 132 133 if (rcurly != null) { 134 final String violation = validate(details); 135 if (!violation.isEmpty()) { 136 log(rcurly, violation, "}", rcurly.getColumnNo() + 1); 137 } 138 } 139 } 140 141 /** 142 * Does general validation. 143 * 144 * @param details for validation. 145 * @return violation message or empty string 146 * if there was no violation during validation. 147 */ 148 private String validate(Details details) { 149 String violation = ""; 150 if (shouldHaveLineBreakBefore(option, details)) { 151 violation = MSG_KEY_LINE_BREAK_BEFORE; 152 } 153 else if (shouldBeOnSameLine(option, details)) { 154 violation = MSG_KEY_LINE_SAME; 155 } 156 else if (shouldBeAloneOnLine(option, details, getLine(details.rcurly.getLineNo() - 1))) { 157 violation = MSG_KEY_LINE_ALONE; 158 } 159 return violation; 160 } 161 162 /** 163 * Checks whether a right curly should have a line break before. 164 * 165 * @param bracePolicy option for placing the right curly brace. 166 * @param details details for validation. 167 * @return true if a right curly should have a line break before. 168 */ 169 private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy, 170 Details details) { 171 return bracePolicy == RightCurlyOption.SAME 172 && !hasLineBreakBefore(details.rcurly) 173 && !TokenUtil.areOnSameLine(details.lcurly, details.rcurly); 174 } 175 176 /** 177 * Checks that a right curly should be on the same line as the next statement. 178 * 179 * @param bracePolicy option for placing the right curly brace 180 * @param details Details for validation 181 * @return true if a right curly should be alone on a line. 182 */ 183 private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) { 184 return bracePolicy == RightCurlyOption.SAME 185 && !details.shouldCheckLastRcurly 186 && !TokenUtil.areOnSameLine(details.rcurly, details.nextToken); 187 } 188 189 /** 190 * Checks that a right curly should be alone on a line. 191 * 192 * @param bracePolicy option for placing the right curly brace 193 * @param details Details for validation 194 * @param targetSrcLine A string with contents of rcurly's line 195 * @return true if a right curly should be alone on a line. 196 */ 197 private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy, 198 Details details, 199 String targetSrcLine) { 200 return bracePolicy == RightCurlyOption.ALONE 201 && shouldBeAloneOnLineWithAloneOption(details, targetSrcLine) 202 || (bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE 203 || details.shouldCheckLastRcurly) 204 && shouldBeAloneOnLineWithNotAloneOption(details, targetSrcLine); 205 } 206 207 /** 208 * Whether right curly should be alone on line when ALONE option is used. 209 * 210 * @param details details for validation. 211 * @param targetSrcLine A string with contents of rcurly's line 212 * @return true, if right curly should be alone on line when ALONE option is used. 213 */ 214 private static boolean shouldBeAloneOnLineWithAloneOption(Details details, 215 String targetSrcLine) { 216 return !isAloneOnLine(details, targetSrcLine); 217 } 218 219 /** 220 * Whether right curly should be alone on line when ALONE_OR_SINGLELINE or SAME option is used. 221 * 222 * @param details details for validation. 223 * @param targetSrcLine A string with contents of rcurly's line 224 * @return true, if right curly should be alone on line 225 * when ALONE_OR_SINGLELINE or SAME option is used. 226 */ 227 private static boolean shouldBeAloneOnLineWithNotAloneOption(Details details, 228 String targetSrcLine) { 229 return shouldBeAloneOnLineWithAloneOption(details, targetSrcLine) 230 && !isBlockAloneOnSingleLine(details); 231 } 232 233 /** 234 * Checks whether right curly is alone on a line. 235 * 236 * @param details for validation. 237 * @param targetSrcLine A string with contents of rcurly's line 238 * @return true if right curly is alone on a line. 239 */ 240 private static boolean isAloneOnLine(Details details, String targetSrcLine) { 241 final DetailAST rcurly = details.rcurly; 242 final DetailAST nextToken = details.nextToken; 243 return (nextToken == null || !TokenUtil.areOnSameLine(rcurly, nextToken) 244 || skipDoubleBraceInstInit(details)) 245 && CommonUtil.hasWhitespaceBefore(details.rcurly.getColumnNo(), 246 targetSrcLine); 247 } 248 249 /** 250 * This method determines if the double brace initialization should be skipped over by the 251 * check. Double brace initializations are treated differently. The corresponding inner 252 * rcurly is treated as if it was alone on line even when it may be followed by another 253 * rcurly and a semi, raising no violations. 254 * <i>Please do note though that the line should not contain anything other than the following 255 * right curly and the semi following it or else violations will be raised.</i> 256 * Only the kind of double brace initializations shown in the following example code will be 257 * skipped over:<br> 258 * <pre> 259 * {@code Map<String, String> map = new LinkedHashMap<>() {{ 260 * put("alpha", "man"); 261 * }}; // no violation} 262 * </pre> 263 * 264 * @param details {@link Details} object containing the details relevant to the rcurly 265 * @return if the double brace initialization rcurly should be skipped over by the check 266 */ 267 private static boolean skipDoubleBraceInstInit(Details details) { 268 boolean skipDoubleBraceInstInit = false; 269 final DetailAST tokenAfterNextToken = Details.getNextToken(details.nextToken); 270 if (tokenAfterNextToken != null) { 271 final DetailAST rcurly = details.rcurly; 272 skipDoubleBraceInstInit = rcurly.getParent().getParent() 273 .getType() == TokenTypes.INSTANCE_INIT 274 && details.nextToken.getType() == TokenTypes.RCURLY 275 && !TokenUtil.areOnSameLine(rcurly, Details.getNextToken(tokenAfterNextToken)); 276 } 277 return skipDoubleBraceInstInit; 278 } 279 280 /** 281 * Checks whether block has a single-line format and is alone on a line. 282 * 283 * @param details for validation. 284 * @return true if block has single-line format and is alone on a line. 285 */ 286 private static boolean isBlockAloneOnSingleLine(Details details) { 287 DetailAST nextToken = details.nextToken; 288 289 while (nextToken != null && nextToken.getType() == TokenTypes.LITERAL_ELSE) { 290 nextToken = Details.getNextToken(nextToken); 291 } 292 293 // sibling tokens should be allowed on a single line 294 final int[] tokensWithBlockSibling = { 295 TokenTypes.DO_WHILE, 296 TokenTypes.LITERAL_FINALLY, 297 TokenTypes.LITERAL_CATCH, 298 }; 299 300 if (TokenUtil.isOfType(nextToken, tokensWithBlockSibling)) { 301 final DetailAST parent = nextToken.getParent(); 302 nextToken = Details.getNextToken(parent); 303 } 304 305 return TokenUtil.areOnSameLine(details.lcurly, details.rcurly) 306 && (nextToken == null || !TokenUtil.areOnSameLine(details.rcurly, nextToken) 307 || isRightcurlyFollowedBySemicolon(details)); 308 } 309 310 /** 311 * Checks whether the right curly is followed by a semicolon. 312 * 313 * @param details details for validation. 314 * @return true if the right curly is followed by a semicolon. 315 */ 316 private static boolean isRightcurlyFollowedBySemicolon(Details details) { 317 return details.nextToken.getType() == TokenTypes.SEMI; 318 } 319 320 /** 321 * Checks if right curly has line break before. 322 * 323 * @param rightCurly right curly token. 324 * @return true, if right curly has line break before. 325 */ 326 private static boolean hasLineBreakBefore(DetailAST rightCurly) { 327 DetailAST previousToken = rightCurly.getPreviousSibling(); 328 if (previousToken == null) { 329 previousToken = rightCurly.getParent(); 330 } 331 return !TokenUtil.areOnSameLine(rightCurly, previousToken); 332 } 333 334 /** 335 * Structure that contains all details for validation. 336 */ 337 private static final class Details { 338 339 /** 340 * Token types that identify tokens that will never have SLIST in their AST. 341 */ 342 private static final int[] TOKENS_WITH_NO_CHILD_SLIST = { 343 TokenTypes.CLASS_DEF, 344 TokenTypes.ENUM_DEF, 345 TokenTypes.ANNOTATION_DEF, 346 TokenTypes.INTERFACE_DEF, 347 TokenTypes.RECORD_DEF, 348 }; 349 350 /** Right curly. */ 351 private final DetailAST rcurly; 352 /** Left curly. */ 353 private final DetailAST lcurly; 354 /** Next token. */ 355 private final DetailAST nextToken; 356 /** Should check last right curly. */ 357 private final boolean shouldCheckLastRcurly; 358 359 /** 360 * Constructor. 361 * 362 * @param lcurly the lcurly of the token whose details are being collected 363 * @param rcurly the rcurly of the token whose details are being collected 364 * @param nextToken the token after the token whose details are being collected 365 * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly 366 */ 367 private Details(DetailAST lcurly, DetailAST rcurly, 368 DetailAST nextToken, boolean shouldCheckLastRcurly) { 369 this.lcurly = lcurly; 370 this.rcurly = rcurly; 371 this.nextToken = nextToken; 372 this.shouldCheckLastRcurly = shouldCheckLastRcurly; 373 } 374 375 /** 376 * Collects validation Details. 377 * 378 * @param ast a {@code DetailAST} value 379 * @return object containing all details to make a validation 380 */ 381 private static Details getDetails(DetailAST ast) { 382 return switch (ast.getType()) { 383 case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_CATCH -> getDetailsForTryCatch(ast); 384 case TokenTypes.LITERAL_IF -> getDetailsForIf(ast); 385 case TokenTypes.LITERAL_DO -> getDetailsForDoLoops(ast); 386 case TokenTypes.LITERAL_SWITCH -> getDetailsForSwitch(ast); 387 case TokenTypes.LITERAL_CASE -> getDetailsForCase(ast); 388 default -> getDetailsForOthers(ast); 389 }; 390 } 391 392 /** 393 * Collects details about switch statements and expressions. 394 * 395 * @param switchNode switch statement or expression to gather details about 396 * @return new Details about given switch statement or expression 397 */ 398 private static Details getDetailsForSwitch(DetailAST switchNode) { 399 final DetailAST lcurly = switchNode.findFirstToken(TokenTypes.LCURLY); 400 final DetailAST rcurly; 401 DetailAST nextToken = null; 402 // skipping switch expression as check only handles statements 403 if (isSwitchExpression(switchNode)) { 404 rcurly = null; 405 } 406 else { 407 rcurly = switchNode.getLastChild(); 408 nextToken = getNextToken(switchNode); 409 } 410 return new Details(lcurly, rcurly, nextToken, true); 411 } 412 413 /** 414 * Collects details about case statements. 415 * 416 * @param caseNode case statement to gather details about 417 * @return new Details about given case statement 418 */ 419 private static Details getDetailsForCase(DetailAST caseNode) { 420 final DetailAST caseParent = caseNode.getParent(); 421 final int parentType = caseParent.getType(); 422 final Optional<DetailAST> lcurly; 423 final DetailAST statementList; 424 425 if (parentType == TokenTypes.SWITCH_RULE) { 426 statementList = caseParent.findFirstToken(TokenTypes.SLIST); 427 lcurly = Optional.ofNullable(statementList); 428 } 429 else { 430 statementList = caseNode.getNextSibling(); 431 lcurly = Optional.ofNullable(statementList) 432 .map(DetailAST::getFirstChild) 433 .filter(node -> node.getType() == TokenTypes.SLIST); 434 } 435 final DetailAST rcurly = lcurly.map(DetailAST::getLastChild) 436 .filter(child -> !isSwitchExpression(caseParent)) 437 .orElse(null); 438 final Optional<DetailAST> nextToken = 439 Optional.ofNullable(lcurly.map(DetailAST::getNextSibling) 440 .orElseGet(() -> getNextToken(caseParent))); 441 442 return new Details(lcurly.orElse(null), rcurly, nextToken.orElse(null), true); 443 } 444 445 /** 446 * Check whether switch is expression or not. 447 * 448 * @param switchNode switch statement or expression to provide detail 449 * @return true if it is a switch expression 450 */ 451 private static boolean isSwitchExpression(DetailAST switchNode) { 452 DetailAST currentNode = switchNode; 453 boolean ans = false; 454 455 while (currentNode != null) { 456 if (currentNode.getType() == TokenTypes.EXPR) { 457 ans = true; 458 } 459 currentNode = currentNode.getParent(); 460 } 461 return ans; 462 } 463 464 /** 465 * Collects validation details for LITERAL_TRY, and LITERAL_CATCH. 466 * 467 * @param ast a {@code DetailAST} value 468 * @return object containing all details to make a validation 469 */ 470 private static Details getDetailsForTryCatch(DetailAST ast) { 471 final DetailAST lcurly; 472 DetailAST nextToken; 473 final int tokenType = ast.getType(); 474 if (tokenType == TokenTypes.LITERAL_TRY) { 475 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 476 lcurly = ast.getFirstChild().getNextSibling(); 477 } 478 else { 479 lcurly = ast.getFirstChild(); 480 } 481 nextToken = lcurly.getNextSibling(); 482 } 483 else { 484 nextToken = ast.getNextSibling(); 485 lcurly = ast.getLastChild(); 486 } 487 488 final boolean shouldCheckLastRcurly; 489 if (nextToken == null) { 490 shouldCheckLastRcurly = true; 491 nextToken = getNextToken(ast); 492 } 493 else { 494 shouldCheckLastRcurly = false; 495 } 496 497 final DetailAST rcurly = lcurly.getLastChild(); 498 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 499 } 500 501 /** 502 * Collects validation details for LITERAL_IF. 503 * 504 * @param ast a {@code DetailAST} value 505 * @return object containing all details to make a validation 506 */ 507 private static Details getDetailsForIf(DetailAST ast) { 508 final boolean shouldCheckLastRcurly; 509 final DetailAST lcurly; 510 DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE); 511 512 if (nextToken == null) { 513 shouldCheckLastRcurly = true; 514 nextToken = getNextToken(ast); 515 lcurly = ast.getLastChild(); 516 } 517 else { 518 shouldCheckLastRcurly = false; 519 lcurly = nextToken.getPreviousSibling(); 520 } 521 522 DetailAST rcurly = null; 523 if (lcurly.getType() == TokenTypes.SLIST) { 524 rcurly = lcurly.getLastChild(); 525 } 526 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 527 } 528 529 /** 530 * Collects validation details for CLASS_DEF, RECORD_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, 531 * INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, and COMPACT_CTOR_DEF. 532 * 533 * @param ast a {@code DetailAST} value 534 * @return an object containing all details to make a validation 535 */ 536 private static Details getDetailsForOthers(DetailAST ast) { 537 DetailAST rcurly = null; 538 final DetailAST lcurly; 539 final int tokenType = ast.getType(); 540 if (isTokenWithNoChildSlist(tokenType)) { 541 final DetailAST child = ast.getLastChild(); 542 lcurly = child; 543 rcurly = child.getLastChild(); 544 } 545 else { 546 lcurly = ast.findFirstToken(TokenTypes.SLIST); 547 if (lcurly != null) { 548 // SLIST could be absent if method is abstract 549 rcurly = lcurly.getLastChild(); 550 } 551 } 552 return new Details(lcurly, rcurly, getNextToken(ast), true); 553 } 554 555 /** 556 * Tests whether the provided tokenType will never have a SLIST as child in its AST. 557 * Like CLASS_DEF, ANNOTATION_DEF etc. 558 * 559 * @param tokenType the tokenType to test against. 560 * @return weather provided tokenType is definition token. 561 */ 562 private static boolean isTokenWithNoChildSlist(int tokenType) { 563 return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType); 564 } 565 566 /** 567 * Collects validation details for LITERAL_DO loops' tokens. 568 * 569 * @param ast a {@code DetailAST} value 570 * @return an object containing all details to make a validation 571 */ 572 private static Details getDetailsForDoLoops(DetailAST ast) { 573 final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST); 574 final DetailAST nextToken = ast.findFirstToken(TokenTypes.DO_WHILE); 575 DetailAST rcurly = null; 576 if (lcurly != null) { 577 rcurly = lcurly.getLastChild(); 578 } 579 return new Details(lcurly, rcurly, nextToken, false); 580 } 581 582 /** 583 * Finds next token after the given one. 584 * 585 * @param ast the given node. 586 * @return the token which represents next lexical item. 587 */ 588 private static DetailAST getNextToken(DetailAST ast) { 589 DetailAST next = null; 590 DetailAST parent = ast; 591 while (next == null && parent != null) { 592 next = parent.getNextSibling(); 593 parent = parent.getParent(); 594 } 595 return next; 596 } 597 } 598}