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.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 final Details details; 426 switch (ast.getType()) { 427 case TokenTypes.LITERAL_TRY: 428 case TokenTypes.LITERAL_CATCH: 429 details = getDetailsForTryCatch(ast); 430 break; 431 case TokenTypes.LITERAL_IF: 432 details = getDetailsForIf(ast); 433 break; 434 case TokenTypes.LITERAL_DO: 435 details = getDetailsForDoLoops(ast); 436 break; 437 case TokenTypes.LITERAL_SWITCH: 438 details = getDetailsForSwitch(ast); 439 break; 440 case TokenTypes.LITERAL_CASE: 441 details = getDetailsForCase(ast); 442 break; 443 default: 444 details = getDetailsForOthers(ast); 445 break; 446 } 447 return details; 448 } 449 450 /** 451 * Collects details about switch statements and expressions. 452 * 453 * @param switchNode switch statement or expression to gather details about 454 * @return new Details about given switch statement or expression 455 */ 456 private static Details getDetailsForSwitch(DetailAST switchNode) { 457 final DetailAST lcurly = switchNode.findFirstToken(TokenTypes.LCURLY); 458 final DetailAST rcurly; 459 DetailAST nextToken = null; 460 // skipping switch expression as check only handles statements 461 if (isSwitchExpression(switchNode)) { 462 rcurly = null; 463 } 464 else { 465 rcurly = switchNode.getLastChild(); 466 nextToken = getNextToken(switchNode); 467 } 468 return new Details(lcurly, rcurly, nextToken, true); 469 } 470 471 /** 472 * Collects details about case statements. 473 * 474 * @param caseNode case statement to gather details about 475 * @return new Details about given case statement 476 */ 477 private static Details getDetailsForCase(DetailAST caseNode) { 478 final DetailAST caseParent = caseNode.getParent(); 479 final int parentType = caseParent.getType(); 480 final Optional<DetailAST> lcurly; 481 final DetailAST statementList; 482 483 if (parentType == TokenTypes.SWITCH_RULE) { 484 statementList = caseParent.findFirstToken(TokenTypes.SLIST); 485 lcurly = Optional.ofNullable(statementList); 486 } 487 else { 488 statementList = caseNode.getNextSibling(); 489 lcurly = Optional.ofNullable(statementList) 490 .map(DetailAST::getFirstChild) 491 .filter(node -> node.getType() == TokenTypes.SLIST); 492 } 493 final DetailAST rcurly = lcurly.map(DetailAST::getLastChild) 494 .filter(child -> !isSwitchExpression(caseParent)) 495 .orElse(null); 496 final Optional<DetailAST> nextToken = 497 Optional.ofNullable(lcurly.map(DetailAST::getNextSibling) 498 .orElseGet(() -> getNextToken(caseParent))); 499 500 return new Details(lcurly.orElse(null), rcurly, nextToken.orElse(null), true); 501 } 502 503 /** 504 * Check whether switch is expression or not. 505 * 506 * @param switchNode switch statement or expression to provide detail 507 * @return true if it is a switch expression 508 */ 509 private static boolean isSwitchExpression(DetailAST switchNode) { 510 DetailAST currentNode = switchNode; 511 boolean ans = false; 512 513 while (currentNode != null) { 514 if (currentNode.getType() == TokenTypes.EXPR) { 515 ans = true; 516 } 517 currentNode = currentNode.getParent(); 518 } 519 return ans; 520 } 521 522 /** 523 * Collects validation details for LITERAL_TRY, and LITERAL_CATCH. 524 * 525 * @param ast a {@code DetailAST} value 526 * @return object containing all details to make a validation 527 */ 528 private static Details getDetailsForTryCatch(DetailAST ast) { 529 final DetailAST lcurly; 530 DetailAST nextToken; 531 final int tokenType = ast.getType(); 532 if (tokenType == TokenTypes.LITERAL_TRY) { 533 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 534 lcurly = ast.getFirstChild().getNextSibling(); 535 } 536 else { 537 lcurly = ast.getFirstChild(); 538 } 539 nextToken = lcurly.getNextSibling(); 540 } 541 else { 542 nextToken = ast.getNextSibling(); 543 lcurly = ast.getLastChild(); 544 } 545 546 final boolean shouldCheckLastRcurly; 547 if (nextToken == null) { 548 shouldCheckLastRcurly = true; 549 nextToken = getNextToken(ast); 550 } 551 else { 552 shouldCheckLastRcurly = false; 553 } 554 555 final DetailAST rcurly = lcurly.getLastChild(); 556 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 557 } 558 559 /** 560 * Collects validation details for LITERAL_IF. 561 * 562 * @param ast a {@code DetailAST} value 563 * @return object containing all details to make a validation 564 */ 565 private static Details getDetailsForIf(DetailAST ast) { 566 final boolean shouldCheckLastRcurly; 567 final DetailAST lcurly; 568 DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE); 569 570 if (nextToken == null) { 571 shouldCheckLastRcurly = true; 572 nextToken = getNextToken(ast); 573 lcurly = ast.getLastChild(); 574 } 575 else { 576 shouldCheckLastRcurly = false; 577 lcurly = nextToken.getPreviousSibling(); 578 } 579 580 DetailAST rcurly = null; 581 if (lcurly.getType() == TokenTypes.SLIST) { 582 rcurly = lcurly.getLastChild(); 583 } 584 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 585 } 586 587 /** 588 * Collects validation details for CLASS_DEF, RECORD_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, 589 * INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, and COMPACT_CTOR_DEF. 590 * 591 * @param ast a {@code DetailAST} value 592 * @return an object containing all details to make a validation 593 */ 594 private static Details getDetailsForOthers(DetailAST ast) { 595 DetailAST rcurly = null; 596 final DetailAST lcurly; 597 final int tokenType = ast.getType(); 598 if (isTokenWithNoChildSlist(tokenType)) { 599 final DetailAST child = ast.getLastChild(); 600 lcurly = child; 601 rcurly = child.getLastChild(); 602 } 603 else { 604 lcurly = ast.findFirstToken(TokenTypes.SLIST); 605 if (lcurly != null) { 606 // SLIST could be absent if method is abstract 607 rcurly = lcurly.getLastChild(); 608 } 609 } 610 return new Details(lcurly, rcurly, getNextToken(ast), true); 611 } 612 613 /** 614 * Tests whether the provided tokenType will never have a SLIST as child in its AST. 615 * Like CLASS_DEF, ANNOTATION_DEF etc. 616 * 617 * @param tokenType the tokenType to test against. 618 * @return weather provided tokenType is definition token. 619 */ 620 private static boolean isTokenWithNoChildSlist(int tokenType) { 621 return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType); 622 } 623 624 /** 625 * Collects validation details for LITERAL_DO loops' tokens. 626 * 627 * @param ast a {@code DetailAST} value 628 * @return an object containing all details to make a validation 629 */ 630 private static Details getDetailsForDoLoops(DetailAST ast) { 631 final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST); 632 final DetailAST nextToken = ast.findFirstToken(TokenTypes.DO_WHILE); 633 DetailAST rcurly = null; 634 if (lcurly != null) { 635 rcurly = lcurly.getLastChild(); 636 } 637 return new Details(lcurly, rcurly, nextToken, false); 638 } 639 640 /** 641 * Finds next token after the given one. 642 * 643 * @param ast the given node. 644 * @return the token which represents next lexical item. 645 */ 646 private static DetailAST getNextToken(DetailAST ast) { 647 DetailAST next = null; 648 DetailAST parent = ast; 649 while (next == null && parent != null) { 650 next = parent.getNextSibling(); 651 parent = parent.getParent(); 652 } 653 return next; 654 } 655 } 656}