001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * An example of how to configure the check to validate enum definitions: 155 * </p> 156 * <pre> 157 * <module name="LeftCurly"> 158 * <property name="ignoreEnums" value="false"/> 159 * </module> 160 * </pre> 161 * <pre> 162 * class Test 163 * { // Violation - '{' should be on the previous line 164 * private interface TestInterface 165 * { // Violation - '{' should be on the previous line 166 * } 167 * 168 * private 169 * class 170 * MyClass { // OK 171 * } 172 * 173 * enum Colors {RED, // Violation - '{' should have line break after 174 * BLUE, 175 * GREEN; 176 * } 177 * } 178 * </pre> 179 * <p> 180 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 181 * </p> 182 * <p> 183 * Violation Message Keys: 184 * </p> 185 * <ul> 186 * <li> 187 * {@code line.break.after} 188 * </li> 189 * <li> 190 * {@code line.new} 191 * </li> 192 * <li> 193 * {@code line.previous} 194 * </li> 195 * </ul> 196 * 197 * @since 3.0 198 */ 199@StatelessCheck 200public class LeftCurlyCheck 201 extends AbstractCheck { 202 203 /** 204 * A key is pointing to the warning message text in "messages.properties" 205 * file. 206 */ 207 public static final String MSG_KEY_LINE_NEW = "line.new"; 208 209 /** 210 * A key is pointing to the warning message text in "messages.properties" 211 * file. 212 */ 213 public static final String MSG_KEY_LINE_PREVIOUS = "line.previous"; 214 215 /** 216 * A key is pointing to the warning message text in "messages.properties" 217 * file. 218 */ 219 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after"; 220 221 /** Open curly brace literal. */ 222 private static final String OPEN_CURLY_BRACE = "{"; 223 224 /** Allow to ignore enums when left curly brace policy is EOL. */ 225 private boolean ignoreEnums = true; 226 227 /** 228 * Specify the policy on placement of a left curly brace (<code>'{'</code>). 229 * */ 230 private LeftCurlyOption option = LeftCurlyOption.EOL; 231 232 /** 233 * Setter to specify the policy on placement of a left curly brace (<code>'{'</code>). 234 * 235 * @param optionStr string to decode option from 236 * @throws IllegalArgumentException if unable to decode 237 */ 238 public void setOption(String optionStr) { 239 option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 240 } 241 242 /** 243 * Setter to allow to ignore enums when left curly brace policy is EOL. 244 * 245 * @param ignoreEnums check's option for ignoring enums. 246 */ 247 public void setIgnoreEnums(boolean ignoreEnums) { 248 this.ignoreEnums = ignoreEnums; 249 } 250 251 @Override 252 public int[] getDefaultTokens() { 253 return getAcceptableTokens(); 254 } 255 256 @Override 257 public int[] getAcceptableTokens() { 258 return new int[] { 259 TokenTypes.ANNOTATION_DEF, 260 TokenTypes.CLASS_DEF, 261 TokenTypes.CTOR_DEF, 262 TokenTypes.ENUM_CONSTANT_DEF, 263 TokenTypes.ENUM_DEF, 264 TokenTypes.INTERFACE_DEF, 265 TokenTypes.LAMBDA, 266 TokenTypes.LITERAL_CASE, 267 TokenTypes.LITERAL_CATCH, 268 TokenTypes.LITERAL_DEFAULT, 269 TokenTypes.LITERAL_DO, 270 TokenTypes.LITERAL_ELSE, 271 TokenTypes.LITERAL_FINALLY, 272 TokenTypes.LITERAL_FOR, 273 TokenTypes.LITERAL_IF, 274 TokenTypes.LITERAL_SWITCH, 275 TokenTypes.LITERAL_SYNCHRONIZED, 276 TokenTypes.LITERAL_TRY, 277 TokenTypes.LITERAL_WHILE, 278 TokenTypes.METHOD_DEF, 279 TokenTypes.OBJBLOCK, 280 TokenTypes.STATIC_INIT, 281 TokenTypes.RECORD_DEF, 282 TokenTypes.COMPACT_CTOR_DEF, 283 }; 284 } 285 286 @Override 287 public int[] getRequiredTokens() { 288 return CommonUtil.EMPTY_INT_ARRAY; 289 } 290 291 /** 292 * We cannot reduce the number of branches in this switch statement, 293 * since many tokens require specific methods to find the first left 294 * curly. 295 * 296 * @param ast the token to process 297 * @noinspection SwitchStatementWithTooManyBranches 298 */ 299 @Override 300 public void visitToken(DetailAST ast) { 301 final DetailAST startToken; 302 DetailAST brace; 303 304 switch (ast.getType()) { 305 case TokenTypes.CTOR_DEF: 306 case TokenTypes.METHOD_DEF: 307 case TokenTypes.COMPACT_CTOR_DEF: 308 startToken = skipModifierAnnotations(ast); 309 brace = ast.findFirstToken(TokenTypes.SLIST); 310 break; 311 case TokenTypes.INTERFACE_DEF: 312 case TokenTypes.CLASS_DEF: 313 case TokenTypes.ANNOTATION_DEF: 314 case TokenTypes.ENUM_DEF: 315 case TokenTypes.ENUM_CONSTANT_DEF: 316 case TokenTypes.RECORD_DEF: 317 startToken = skipModifierAnnotations(ast); 318 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 319 brace = objBlock; 320 321 if (objBlock != null) { 322 brace = objBlock.getFirstChild(); 323 } 324 break; 325 case TokenTypes.LITERAL_WHILE: 326 case TokenTypes.LITERAL_CATCH: 327 case TokenTypes.LITERAL_SYNCHRONIZED: 328 case TokenTypes.LITERAL_FOR: 329 case TokenTypes.LITERAL_TRY: 330 case TokenTypes.LITERAL_FINALLY: 331 case TokenTypes.LITERAL_DO: 332 case TokenTypes.LITERAL_IF: 333 case TokenTypes.STATIC_INIT: 334 case TokenTypes.LAMBDA: 335 startToken = ast; 336 brace = ast.findFirstToken(TokenTypes.SLIST); 337 break; 338 case TokenTypes.LITERAL_ELSE: 339 startToken = ast; 340 brace = getBraceAsFirstChild(ast); 341 break; 342 case TokenTypes.LITERAL_CASE: 343 case TokenTypes.LITERAL_DEFAULT: 344 startToken = ast; 345 brace = getBraceFromSwitchMember(ast); 346 break; 347 default: 348 // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF, 349 // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only. 350 // It has been done to improve coverage to 100%. I couldn't replace it with 351 // if-else-if block because code was ugly and didn't pass pmd check. 352 353 startToken = ast; 354 brace = ast.findFirstToken(TokenTypes.LCURLY); 355 break; 356 } 357 358 if (brace != null) { 359 verifyBrace(brace, startToken); 360 } 361 } 362 363 /** 364 * Gets the brace of a switch statement/ expression member. 365 * 366 * @param ast {@code DetailAST}. 367 * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST}, 368 * {@code null} otherwise. 369 */ 370 private static DetailAST getBraceFromSwitchMember(DetailAST ast) { 371 final DetailAST brace; 372 final DetailAST parent = ast.getParent(); 373 if (parent.getType() == TokenTypes.SWITCH_RULE) { 374 brace = parent.findFirstToken(TokenTypes.SLIST); 375 } 376 else { 377 brace = getBraceAsFirstChild(ast.getNextSibling()); 378 } 379 return brace; 380 } 381 382 /** 383 * Gets a SLIST if it is the first child of the AST. 384 * 385 * @param ast {@code DetailAST}. 386 * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST}, 387 * {@code null} otherwise. 388 */ 389 private static DetailAST getBraceAsFirstChild(DetailAST ast) { 390 DetailAST brace = null; 391 if (ast != null) { 392 final DetailAST candidate = ast.getFirstChild(); 393 if (candidate != null && candidate.getType() == TokenTypes.SLIST) { 394 brace = candidate; 395 } 396 } 397 return brace; 398 } 399 400 /** 401 * Skip all {@code TokenTypes.ANNOTATION}s to the first non-annotation. 402 * 403 * @param ast {@code DetailAST}. 404 * @return {@code DetailAST}. 405 */ 406 private static DetailAST skipModifierAnnotations(DetailAST ast) { 407 DetailAST resultNode = ast; 408 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 409 410 if (modifiers != null) { 411 final DetailAST lastAnnotation = findLastAnnotation(modifiers); 412 413 if (lastAnnotation != null) { 414 if (lastAnnotation.getNextSibling() == null) { 415 resultNode = modifiers.getNextSibling(); 416 } 417 else { 418 resultNode = lastAnnotation.getNextSibling(); 419 } 420 } 421 } 422 return resultNode; 423 } 424 425 /** 426 * Find the last token of type {@code TokenTypes.ANNOTATION} 427 * under the given set of modifiers. 428 * 429 * @param modifiers {@code DetailAST}. 430 * @return {@code DetailAST} or null if there are no annotations. 431 */ 432 private static DetailAST findLastAnnotation(DetailAST modifiers) { 433 DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION); 434 while (annotation != null && annotation.getNextSibling() != null 435 && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) { 436 annotation = annotation.getNextSibling(); 437 } 438 return annotation; 439 } 440 441 /** 442 * Verifies that a specified left curly brace is placed correctly 443 * according to policy. 444 * 445 * @param brace token for left curly brace 446 * @param startToken token for start of expression 447 */ 448 private void verifyBrace(final DetailAST brace, 449 final DetailAST startToken) { 450 final String braceLine = getLine(brace.getLineNo() - 1); 451 452 // Check for being told to ignore, or have '{}' which is a special case 453 if (braceLine.length() <= brace.getColumnNo() + 1 454 || braceLine.charAt(brace.getColumnNo() + 1) != '}') { 455 if (option == LeftCurlyOption.NL) { 456 if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 457 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 458 } 459 } 460 else if (option == LeftCurlyOption.EOL) { 461 validateEol(brace, braceLine); 462 } 463 else if (!TokenUtil.areOnSameLine(startToken, brace)) { 464 validateNewLinePosition(brace, startToken, braceLine); 465 } 466 } 467 } 468 469 /** 470 * Validate EOL case. 471 * 472 * @param brace brace AST 473 * @param braceLine line content 474 */ 475 private void validateEol(DetailAST brace, String braceLine) { 476 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 477 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 478 } 479 if (!hasLineBreakAfter(brace)) { 480 log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 481 } 482 } 483 484 /** 485 * Validate token on new Line position. 486 * 487 * @param brace brace AST 488 * @param startToken start Token 489 * @param braceLine content of line with Brace 490 */ 491 private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) { 492 // not on the same line 493 if (startToken.getLineNo() + 1 == brace.getLineNo()) { 494 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 495 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 496 } 497 else { 498 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 499 } 500 } 501 else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) { 502 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1); 503 } 504 } 505 506 /** 507 * Checks if left curly has line break after. 508 * 509 * @param leftCurly 510 * Left curly token. 511 * @return 512 * True, left curly has line break after. 513 */ 514 private boolean hasLineBreakAfter(DetailAST leftCurly) { 515 DetailAST nextToken = null; 516 if (leftCurly.getType() == TokenTypes.SLIST) { 517 nextToken = leftCurly.getFirstChild(); 518 } 519 else { 520 if (!ignoreEnums 521 && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) { 522 nextToken = leftCurly.getNextSibling(); 523 } 524 } 525 return nextToken == null 526 || nextToken.getType() == TokenTypes.RCURLY 527 || !TokenUtil.areOnSameLine(leftCurly, nextToken); 528 } 529 530}