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