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