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