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