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