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.Locale; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 030 031/** 032 * <p> 033 * Checks for the placement of left curly braces (<code>'{'</code>) for code blocks. 034 * </p> 035 * <ul> 036 * <li> 037 * Property {@code option} - Specify the policy on placement of a left curly brace 038 * (<code>'{'</code>). 039 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyOption}. 040 * Default value is {@code eol}. 041 * </li> 042 * <li> 043 * Property {@code ignoreEnums} - Allow to ignore enums when left curly brace policy is EOL. 044 * Type is {@code boolean}. 045 * Default value is {@code true}. 046 * </li> 047 * <li> 048 * Property {@code tokens} - tokens to check 049 * Type is {@code java.lang.String[]}. 050 * Validation type is {@code tokenSet}. 051 * Default value is: 052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 053 * ANNOTATION_DEF</a>, 054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 055 * CLASS_DEF</a>, 056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 057 * CTOR_DEF</a>, 058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 059 * ENUM_CONSTANT_DEF</a>, 060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 061 * ENUM_DEF</a>, 062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 063 * INTERFACE_DEF</a>, 064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 065 * LAMBDA</a>, 066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE"> 067 * LITERAL_CASE</a>, 068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH"> 069 * LITERAL_CATCH</a>, 070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DEFAULT"> 071 * LITERAL_DEFAULT</a>, 072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 073 * LITERAL_DO</a>, 074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE"> 075 * LITERAL_ELSE</a>, 076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY"> 077 * LITERAL_FINALLY</a>, 078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 079 * LITERAL_FOR</a>, 080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 081 * LITERAL_IF</a>, 082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 083 * LITERAL_SWITCH</a>, 084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED"> 085 * LITERAL_SYNCHRONIZED</a>, 086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY"> 087 * LITERAL_TRY</a>, 088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 089 * LITERAL_WHILE</a>, 090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 091 * METHOD_DEF</a>, 092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#OBJBLOCK"> 093 * OBJBLOCK</a>, 094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT"> 095 * STATIC_INIT</a>, 096 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 097 * RECORD_DEF</a>, 098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 099 * COMPACT_CTOR_DEF</a>. 100 * </li> 101 * </ul> 102 * <p> 103 * To configure the check: 104 * </p> 105 * <pre> 106 * <module name="LeftCurly"/> 107 * </pre> 108 * <pre> 109 * class Test 110 * { // Violation - '{' should be on the previous line 111 * private interface TestInterface 112 * { // Violation - '{' should be on the previous line 113 * } 114 * 115 * private 116 * class 117 * MyClass { // OK 118 * } 119 * 120 * enum Colors {RED, // OK 121 * BLUE, 122 * GREEN; 123 * } 124 * } 125 * </pre> 126 * <p> 127 * To configure the check to apply the {@code nl} policy to type blocks: 128 * </p> 129 * <pre> 130 * <module name="LeftCurly"> 131 * <property name="option" value="nl"/> 132 * <property name="tokens" value="CLASS_DEF,INTERFACE_DEF"/> 133 * </module> 134 * </pre> 135 * <pre> 136 * class Test 137 * { // OK 138 * private interface TestInterface 139 * { // OK 140 * } 141 * 142 * private 143 * class 144 * MyClass { // Violation - '{' should be on a new line 145 * } 146 * 147 * enum Colors {RED, // OK 148 * BLUE, 149 * GREEN; 150 * } 151 * } 152 * </pre> 153 * <p> 154 * To configure the check to apply the {@code nlow} policy to type blocks: 155 * </p> 156 * <pre> 157 * <module name="LeftCurly"> 158 * <property name="option" value="nlow"/> 159 * <property name="tokens" value="CLASS_DEF,INTERFACE_DEF"/> 160 * </module> 161 * </pre> 162 * <pre> 163 * class Test 164 * { // Violation - '{' should be on the previous line 165 * private interface TestInterface { // OK 166 * } 167 * 168 * private 169 * class 170 * MyClass { // Violation - '{' should be on a new line 171 * } 172 * 173 * enum Colors {RED, // OK 174 * BLUE, 175 * GREEN; 176 * } 177 * } 178 * </pre> 179 * <p> 180 * An example of how to configure the check to validate enum definitions: 181 * </p> 182 * <pre> 183 * <module name="LeftCurly"> 184 * <property name="ignoreEnums" value="false"/> 185 * </module> 186 * </pre> 187 * <pre> 188 * class Test 189 * { // Violation - '{' should be on the previous line 190 * private interface TestInterface 191 * { // Violation - '{' should be on the previous line 192 * } 193 * 194 * private 195 * class 196 * MyClass { // OK 197 * } 198 * 199 * enum Colors {RED, // Violation - '{' should have line break after 200 * BLUE, 201 * GREEN; 202 * } 203 * } 204 * </pre> 205 * <p> 206 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 207 * </p> 208 * <p> 209 * Violation Message Keys: 210 * </p> 211 * <ul> 212 * <li> 213 * {@code line.break.after} 214 * </li> 215 * <li> 216 * {@code line.new} 217 * </li> 218 * <li> 219 * {@code line.previous} 220 * </li> 221 * </ul> 222 * 223 * @since 3.0 224 */ 225@StatelessCheck 226public class LeftCurlyCheck 227 extends AbstractCheck { 228 229 /** 230 * A key is pointing to the warning message text in "messages.properties" 231 * file. 232 */ 233 public static final String MSG_KEY_LINE_NEW = "line.new"; 234 235 /** 236 * A key is pointing to the warning message text in "messages.properties" 237 * file. 238 */ 239 public static final String MSG_KEY_LINE_PREVIOUS = "line.previous"; 240 241 /** 242 * A key is pointing to the warning message text in "messages.properties" 243 * file. 244 */ 245 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after"; 246 247 /** Open curly brace literal. */ 248 private static final String OPEN_CURLY_BRACE = "{"; 249 250 /** Allow to ignore enums when left curly brace policy is EOL. */ 251 private boolean ignoreEnums = true; 252 253 /** 254 * Specify the policy on placement of a left curly brace (<code>'{'</code>). 255 * */ 256 private LeftCurlyOption option = LeftCurlyOption.EOL; 257 258 /** 259 * Setter to specify the policy on placement of a left curly brace (<code>'{'</code>). 260 * 261 * @param optionStr string to decode option from 262 * @throws IllegalArgumentException if unable to decode 263 */ 264 public void setOption(String optionStr) { 265 option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 266 } 267 268 /** 269 * Setter to allow to ignore enums when left curly brace policy is EOL. 270 * 271 * @param ignoreEnums check's option for ignoring enums. 272 */ 273 public void setIgnoreEnums(boolean ignoreEnums) { 274 this.ignoreEnums = ignoreEnums; 275 } 276 277 @Override 278 public int[] getDefaultTokens() { 279 return getAcceptableTokens(); 280 } 281 282 @Override 283 public int[] getAcceptableTokens() { 284 return new int[] { 285 TokenTypes.ANNOTATION_DEF, 286 TokenTypes.CLASS_DEF, 287 TokenTypes.CTOR_DEF, 288 TokenTypes.ENUM_CONSTANT_DEF, 289 TokenTypes.ENUM_DEF, 290 TokenTypes.INTERFACE_DEF, 291 TokenTypes.LAMBDA, 292 TokenTypes.LITERAL_CASE, 293 TokenTypes.LITERAL_CATCH, 294 TokenTypes.LITERAL_DEFAULT, 295 TokenTypes.LITERAL_DO, 296 TokenTypes.LITERAL_ELSE, 297 TokenTypes.LITERAL_FINALLY, 298 TokenTypes.LITERAL_FOR, 299 TokenTypes.LITERAL_IF, 300 TokenTypes.LITERAL_SWITCH, 301 TokenTypes.LITERAL_SYNCHRONIZED, 302 TokenTypes.LITERAL_TRY, 303 TokenTypes.LITERAL_WHILE, 304 TokenTypes.METHOD_DEF, 305 TokenTypes.OBJBLOCK, 306 TokenTypes.STATIC_INIT, 307 TokenTypes.RECORD_DEF, 308 TokenTypes.COMPACT_CTOR_DEF, 309 }; 310 } 311 312 @Override 313 public int[] getRequiredTokens() { 314 return CommonUtil.EMPTY_INT_ARRAY; 315 } 316 317 /** 318 * Visits token. 319 * 320 * @param ast the token to process 321 * @noinspection SwitchStatementWithTooManyBranches 322 * @noinspectionreason SwitchStatementWithTooManyBranches - we cannot reduce 323 * the number of branches in this switch statement, since many tokens 324 * require specific methods to find the first left curly 325 */ 326 @Override 327 public void visitToken(DetailAST ast) { 328 final DetailAST startToken; 329 final DetailAST brace; 330 331 switch (ast.getType()) { 332 case TokenTypes.CTOR_DEF: 333 case TokenTypes.METHOD_DEF: 334 case TokenTypes.COMPACT_CTOR_DEF: 335 startToken = skipModifierAnnotations(ast); 336 brace = ast.findFirstToken(TokenTypes.SLIST); 337 break; 338 case TokenTypes.INTERFACE_DEF: 339 case TokenTypes.CLASS_DEF: 340 case TokenTypes.ANNOTATION_DEF: 341 case TokenTypes.ENUM_DEF: 342 case TokenTypes.ENUM_CONSTANT_DEF: 343 case TokenTypes.RECORD_DEF: 344 startToken = skipModifierAnnotations(ast); 345 brace = ast.findFirstToken(TokenTypes.OBJBLOCK); 346 break; 347 case TokenTypes.LITERAL_WHILE: 348 case TokenTypes.LITERAL_CATCH: 349 case TokenTypes.LITERAL_SYNCHRONIZED: 350 case TokenTypes.LITERAL_FOR: 351 case TokenTypes.LITERAL_TRY: 352 case TokenTypes.LITERAL_FINALLY: 353 case TokenTypes.LITERAL_DO: 354 case TokenTypes.LITERAL_IF: 355 case TokenTypes.STATIC_INIT: 356 case TokenTypes.LAMBDA: 357 startToken = ast; 358 brace = ast.findFirstToken(TokenTypes.SLIST); 359 break; 360 case TokenTypes.LITERAL_ELSE: 361 startToken = ast; 362 brace = getBraceAsFirstChild(ast); 363 break; 364 case TokenTypes.LITERAL_CASE: 365 case TokenTypes.LITERAL_DEFAULT: 366 startToken = ast; 367 brace = getBraceFromSwitchMember(ast); 368 break; 369 default: 370 // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF, 371 // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only. 372 // It has been done to improve coverage to 100%. I couldn't replace it with 373 // if-else-if block because code was ugly and didn't pass pmd check. 374 375 startToken = ast; 376 brace = ast.findFirstToken(TokenTypes.LCURLY); 377 break; 378 } 379 380 if (brace != null) { 381 verifyBrace(brace, startToken); 382 } 383 } 384 385 /** 386 * Gets the brace of a switch statement/ expression member. 387 * 388 * @param ast {@code DetailAST}. 389 * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST}, 390 * {@code null} otherwise. 391 */ 392 private static DetailAST getBraceFromSwitchMember(DetailAST ast) { 393 final DetailAST brace; 394 final DetailAST parent = ast.getParent(); 395 if (parent.getType() == TokenTypes.SWITCH_RULE) { 396 brace = parent.findFirstToken(TokenTypes.SLIST); 397 } 398 else { 399 brace = getBraceAsFirstChild(ast.getNextSibling()); 400 } 401 return brace; 402 } 403 404 /** 405 * Gets a SLIST if it is the first child of the AST. 406 * 407 * @param ast {@code DetailAST}. 408 * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST}, 409 * {@code null} otherwise. 410 */ 411 private static DetailAST getBraceAsFirstChild(DetailAST ast) { 412 DetailAST brace = null; 413 if (ast != null) { 414 final DetailAST candidate = ast.getFirstChild(); 415 if (candidate != null && candidate.getType() == TokenTypes.SLIST) { 416 brace = candidate; 417 } 418 } 419 return brace; 420 } 421 422 /** 423 * Skip all {@code TokenTypes.ANNOTATION}s to the first non-annotation. 424 * 425 * @param ast {@code DetailAST}. 426 * @return {@code DetailAST}. 427 */ 428 private static DetailAST skipModifierAnnotations(DetailAST ast) { 429 DetailAST resultNode = ast; 430 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 431 432 if (modifiers != null) { 433 final DetailAST lastAnnotation = findLastAnnotation(modifiers); 434 435 if (lastAnnotation != null) { 436 if (lastAnnotation.getNextSibling() == null) { 437 resultNode = modifiers.getNextSibling(); 438 } 439 else { 440 resultNode = lastAnnotation.getNextSibling(); 441 } 442 } 443 } 444 return resultNode; 445 } 446 447 /** 448 * Find the last token of type {@code TokenTypes.ANNOTATION} 449 * under the given set of modifiers. 450 * 451 * @param modifiers {@code DetailAST}. 452 * @return {@code DetailAST} or null if there are no annotations. 453 */ 454 private static DetailAST findLastAnnotation(DetailAST modifiers) { 455 DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION); 456 while (annotation != null && annotation.getNextSibling() != null 457 && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) { 458 annotation = annotation.getNextSibling(); 459 } 460 return annotation; 461 } 462 463 /** 464 * Verifies that a specified left curly brace is placed correctly 465 * according to policy. 466 * 467 * @param brace token for left curly brace 468 * @param startToken token for start of expression 469 */ 470 private void verifyBrace(final DetailAST brace, 471 final DetailAST startToken) { 472 final String braceLine = getLine(brace.getLineNo() - 1); 473 474 // Check for being told to ignore, or have '{}' which is a special case 475 if (braceLine.length() <= brace.getColumnNo() + 1 476 || braceLine.charAt(brace.getColumnNo() + 1) != '}') { 477 if (option == LeftCurlyOption.NL) { 478 if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 479 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 480 } 481 } 482 else if (option == LeftCurlyOption.EOL) { 483 validateEol(brace, braceLine); 484 } 485 else if (!TokenUtil.areOnSameLine(startToken, brace)) { 486 validateNewLinePosition(brace, startToken, braceLine); 487 } 488 } 489 } 490 491 /** 492 * Validate EOL case. 493 * 494 * @param brace brace AST 495 * @param braceLine line content 496 */ 497 private void validateEol(DetailAST brace, String braceLine) { 498 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 499 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 500 } 501 if (!hasLineBreakAfter(brace)) { 502 log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 503 } 504 } 505 506 /** 507 * Validate token on new Line position. 508 * 509 * @param brace brace AST 510 * @param startToken start Token 511 * @param braceLine content of line with Brace 512 */ 513 private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) { 514 // not on the same line 515 if (startToken.getLineNo() + 1 == brace.getLineNo()) { 516 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 517 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 518 } 519 else { 520 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 521 } 522 } 523 else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 524 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 525 } 526 } 527 528 /** 529 * Checks if left curly has line break after. 530 * 531 * @param leftCurly 532 * Left curly token. 533 * @return 534 * True, left curly has line break after. 535 */ 536 private boolean hasLineBreakAfter(DetailAST leftCurly) { 537 DetailAST nextToken = null; 538 if (leftCurly.getType() == TokenTypes.SLIST) { 539 nextToken = leftCurly.getFirstChild(); 540 } 541 else { 542 if (!ignoreEnums 543 && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) { 544 nextToken = leftCurly.getNextSibling(); 545 } 546 } 547 return nextToken == null 548 || nextToken.getType() == TokenTypes.RCURLY 549 || !TokenUtil.areOnSameLine(leftCurly, nextToken); 550 } 551 552}