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