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