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