001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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 if (nextToken != null && nextToken.getType() == TokenTypes.DO_WHILE) { 332 final DetailAST doWhileSemi = nextToken.getParent(); 333 nextToken = Details.getNextToken(doWhileSemi); 334 } 335 336 return TokenUtil.areOnSameLine(details.lcurly, details.rcurly) 337 && (nextToken == null || !TokenUtil.areOnSameLine(details.rcurly, nextToken) 338 || isRightcurlyFollowedBySemicolon(details)); 339 } 340 341 /** 342 * Checks whether the right curly is followed by a semicolon. 343 * 344 * @param details details for validation. 345 * @return true if the right curly is followed by a semicolon. 346 */ 347 private static boolean isRightcurlyFollowedBySemicolon(Details details) { 348 return details.nextToken.getType() == TokenTypes.SEMI; 349 } 350 351 /** 352 * Checks if right curly has line break before. 353 * 354 * @param rightCurly right curly token. 355 * @return true, if right curly has line break before. 356 */ 357 private static boolean hasLineBreakBefore(DetailAST rightCurly) { 358 DetailAST previousToken = rightCurly.getPreviousSibling(); 359 if (previousToken == null) { 360 previousToken = rightCurly.getParent(); 361 } 362 return !TokenUtil.areOnSameLine(rightCurly, previousToken); 363 } 364 365 /** 366 * Structure that contains all details for validation. 367 */ 368 private static final class Details { 369 370 /** 371 * Token types that identify tokens that will never have SLIST in their AST. 372 */ 373 private static final int[] TOKENS_WITH_NO_CHILD_SLIST = { 374 TokenTypes.CLASS_DEF, 375 TokenTypes.ENUM_DEF, 376 TokenTypes.ANNOTATION_DEF, 377 TokenTypes.INTERFACE_DEF, 378 TokenTypes.RECORD_DEF, 379 }; 380 381 /** Right curly. */ 382 private final DetailAST rcurly; 383 /** Left curly. */ 384 private final DetailAST lcurly; 385 /** Next token. */ 386 private final DetailAST nextToken; 387 /** Should check last right curly. */ 388 private final boolean shouldCheckLastRcurly; 389 390 /** 391 * Constructor. 392 * 393 * @param lcurly the lcurly of the token whose details are being collected 394 * @param rcurly the rcurly of the token whose details are being collected 395 * @param nextToken the token after the token whose details are being collected 396 * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly 397 */ 398 private Details(DetailAST lcurly, DetailAST rcurly, 399 DetailAST nextToken, boolean shouldCheckLastRcurly) { 400 this.lcurly = lcurly; 401 this.rcurly = rcurly; 402 this.nextToken = nextToken; 403 this.shouldCheckLastRcurly = shouldCheckLastRcurly; 404 } 405 406 /** 407 * Collects validation Details. 408 * 409 * @param ast a {@code DetailAST} value 410 * @return object containing all details to make a validation 411 */ 412 private static Details getDetails(DetailAST ast) { 413 final Details details; 414 switch (ast.getType()) { 415 case TokenTypes.LITERAL_TRY: 416 case TokenTypes.LITERAL_CATCH: 417 details = getDetailsForTryCatch(ast); 418 break; 419 case TokenTypes.LITERAL_IF: 420 details = getDetailsForIf(ast); 421 break; 422 case TokenTypes.LITERAL_DO: 423 details = getDetailsForDoLoops(ast); 424 break; 425 case TokenTypes.LITERAL_SWITCH: 426 details = getDetailsForSwitch(ast); 427 break; 428 default: 429 details = getDetailsForOthers(ast); 430 break; 431 } 432 return details; 433 } 434 435 /** 436 * Collects details about switch statements and expressions. 437 * 438 * @param switchNode switch statement or expression to gather details about 439 * @return new Details about given switch statement or expression 440 */ 441 private static Details getDetailsForSwitch(DetailAST switchNode) { 442 final DetailAST lcurly = switchNode.findFirstToken(TokenTypes.LCURLY); 443 final DetailAST rcurly; 444 DetailAST nextToken = null; 445 // skipping switch expression as check only handles statements 446 if (isSwitchExpression(switchNode)) { 447 rcurly = null; 448 } 449 else { 450 rcurly = switchNode.getLastChild(); 451 nextToken = getNextToken(switchNode); 452 } 453 return new Details(lcurly, rcurly, nextToken, true); 454 } 455 456 /** 457 * Check whether switch is expression or not. 458 * 459 * @param switchNode switch statement or expression to provide detail 460 * @return true if it is a switch expression 461 */ 462 private static boolean isSwitchExpression(DetailAST switchNode) { 463 DetailAST currentNode = switchNode; 464 boolean ans = false; 465 466 while (currentNode != null) { 467 if (currentNode.getType() == TokenTypes.EXPR) { 468 ans = true; 469 } 470 currentNode = currentNode.getParent(); 471 } 472 return ans; 473 } 474 475 /** 476 * Collects validation details for LITERAL_TRY, and LITERAL_CATCH. 477 * 478 * @param ast a {@code DetailAST} value 479 * @return object containing all details to make a validation 480 */ 481 private static Details getDetailsForTryCatch(DetailAST ast) { 482 final DetailAST lcurly; 483 DetailAST nextToken; 484 final int tokenType = ast.getType(); 485 if (tokenType == TokenTypes.LITERAL_TRY) { 486 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 487 lcurly = ast.getFirstChild().getNextSibling(); 488 } 489 else { 490 lcurly = ast.getFirstChild(); 491 } 492 nextToken = lcurly.getNextSibling(); 493 } 494 else { 495 nextToken = ast.getNextSibling(); 496 lcurly = ast.getLastChild(); 497 } 498 499 final boolean shouldCheckLastRcurly; 500 if (nextToken == null) { 501 shouldCheckLastRcurly = true; 502 nextToken = getNextToken(ast); 503 } 504 else { 505 shouldCheckLastRcurly = false; 506 } 507 508 final DetailAST rcurly = lcurly.getLastChild(); 509 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 510 } 511 512 /** 513 * Collects validation details for LITERAL_IF. 514 * 515 * @param ast a {@code DetailAST} value 516 * @return object containing all details to make a validation 517 */ 518 private static Details getDetailsForIf(DetailAST ast) { 519 final boolean shouldCheckLastRcurly; 520 final DetailAST lcurly; 521 DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE); 522 523 if (nextToken == null) { 524 shouldCheckLastRcurly = true; 525 nextToken = getNextToken(ast); 526 lcurly = ast.getLastChild(); 527 } 528 else { 529 shouldCheckLastRcurly = false; 530 lcurly = nextToken.getPreviousSibling(); 531 } 532 533 DetailAST rcurly = null; 534 if (lcurly.getType() == TokenTypes.SLIST) { 535 rcurly = lcurly.getLastChild(); 536 } 537 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 538 } 539 540 /** 541 * Collects validation details for CLASS_DEF, RECORD_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, 542 * INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, and COMPACT_CTOR_DEF. 543 * 544 * @param ast a {@code DetailAST} value 545 * @return an object containing all details to make a validation 546 */ 547 private static Details getDetailsForOthers(DetailAST ast) { 548 DetailAST rcurly = null; 549 final DetailAST lcurly; 550 final int tokenType = ast.getType(); 551 if (isTokenWithNoChildSlist(tokenType)) { 552 final DetailAST child = ast.getLastChild(); 553 lcurly = child; 554 rcurly = child.getLastChild(); 555 } 556 else { 557 lcurly = ast.findFirstToken(TokenTypes.SLIST); 558 if (lcurly != null) { 559 // SLIST could be absent if method is abstract 560 rcurly = lcurly.getLastChild(); 561 } 562 } 563 return new Details(lcurly, rcurly, getNextToken(ast), true); 564 } 565 566 /** 567 * Tests whether the provided tokenType will never have a SLIST as child in its AST. 568 * Like CLASS_DEF, ANNOTATION_DEF etc. 569 * 570 * @param tokenType the tokenType to test against. 571 * @return weather provided tokenType is definition token. 572 */ 573 private static boolean isTokenWithNoChildSlist(int tokenType) { 574 return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType); 575 } 576 577 /** 578 * Collects validation details for LITERAL_DO loops' tokens. 579 * 580 * @param ast a {@code DetailAST} value 581 * @return an object containing all details to make a validation 582 */ 583 private static Details getDetailsForDoLoops(DetailAST ast) { 584 final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST); 585 final DetailAST nextToken = ast.findFirstToken(TokenTypes.DO_WHILE); 586 DetailAST rcurly = null; 587 if (lcurly != null) { 588 rcurly = lcurly.getLastChild(); 589 } 590 return new Details(lcurly, rcurly, nextToken, false); 591 } 592 593 /** 594 * Finds next token after the given one. 595 * 596 * @param ast the given node. 597 * @return the token which represents next lexical item. 598 */ 599 private static DetailAST getNextToken(DetailAST ast) { 600 DetailAST next = null; 601 DetailAST parent = ast; 602 while (next == null && parent != null) { 603 next = parent.getNextSibling(); 604 parent = parent.getParent(); 605 } 606 return next; 607 } 608 } 609}