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