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