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