1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.whitespace; 21 22 import java.util.BitSet; 23 24 import com.puppycrawl.tools.checkstyle.api.DetailAST; 25 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 26 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 27 import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 28 29 /** 30 * <p> 31 * Checks the policy on the padding of parentheses; that is whether a space is required 32 * after a left parenthesis and before a right parenthesis, or such spaces are 33 * forbidden. No check occurs at the right parenthesis after an empty for 34 * iterator, at the left parenthesis before an empty for initialization, or at 35 * the right parenthesis of a try-with-resources resource specification where 36 * the last resource variable has a trailing semicolon. 37 * Use Check 38 * <a href="https://checkstyle.org/checks/whitespace/emptyforiteratorpad.html#EmptyForIteratorPad"> 39 * EmptyForIteratorPad</a> to validate empty for iterators and 40 * <a href="https://checkstyle.org/checks/whitespace/emptyforinitializerpad.html#EmptyForInitializerPad"> 41 * EmptyForInitializerPad</a> to validate empty for initializers. 42 * Typecasts are also not checked, as there is 43 * <a href="https://checkstyle.org/checks/whitespace/typecastparenpad.html#TypecastParenPad"> 44 * TypecastParenPad</a> to validate them. 45 * </p> 46 * <ul> 47 * <li> 48 * Property {@code option} - Specify policy on how to pad parentheses. 49 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption}. 50 * Default value is {@code nospace}. 51 * </li> 52 * <li> 53 * Property {@code tokens} - tokens to check 54 * Type is {@code java.lang.String[]}. 55 * Validation type is {@code tokenSet}. 56 * Default value is: 57 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION"> 58 * ANNOTATION</a>, 59 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 60 * ANNOTATION_FIELD_DEF</a>, 61 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_CALL"> 62 * CTOR_CALL</a>, 63 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 64 * CTOR_DEF</a>, 65 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT"> 66 * DOT</a>, 67 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 68 * ENUM_CONSTANT_DEF</a>, 69 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR"> 70 * EXPR</a>, 71 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH"> 72 * LITERAL_CATCH</a>, 73 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 74 * LITERAL_DO</a>, 75 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 76 * LITERAL_FOR</a>, 77 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 78 * LITERAL_IF</a>, 79 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW"> 80 * LITERAL_NEW</a>, 81 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 82 * LITERAL_SWITCH</a>, 83 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED"> 84 * LITERAL_SYNCHRONIZED</a>, 85 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 86 * LITERAL_WHILE</a>, 87 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL"> 88 * METHOD_CALL</a>, 89 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 90 * METHOD_DEF</a>, 91 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 92 * QUESTION</a>, 93 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE_SPECIFICATION"> 94 * RESOURCE_SPECIFICATION</a>, 95 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SUPER_CTOR_CALL"> 96 * SUPER_CTOR_CALL</a>, 97 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 98 * LAMBDA</a>, 99 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 100 * RECORD_DEF</a>, 101 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_PATTERN_DEF"> 102 * RECORD_PATTERN_DEF</a>. 103 * </li> 104 * </ul> 105 * <p> 106 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 107 * </p> 108 * <p> 109 * Violation Message Keys: 110 * </p> 111 * <ul> 112 * <li> 113 * {@code ws.followed} 114 * </li> 115 * <li> 116 * {@code ws.notFollowed} 117 * </li> 118 * <li> 119 * {@code ws.notPreceded} 120 * </li> 121 * <li> 122 * {@code ws.preceded} 123 * </li> 124 * </ul> 125 * 126 * @since 3.0 127 */ 128 public class ParenPadCheck extends AbstractParenPadCheck { 129 130 /** 131 * Tokens that this check handles. 132 */ 133 private final BitSet acceptableTokens; 134 135 /** 136 * Initializes acceptableTokens. 137 */ 138 public ParenPadCheck() { 139 acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens()); 140 } 141 142 @Override 143 public int[] getDefaultTokens() { 144 return makeAcceptableTokens(); 145 } 146 147 @Override 148 public int[] getAcceptableTokens() { 149 return makeAcceptableTokens(); 150 } 151 152 @Override 153 public int[] getRequiredTokens() { 154 return CommonUtil.EMPTY_INT_ARRAY; 155 } 156 157 @Override 158 public void visitToken(DetailAST ast) { 159 switch (ast.getType()) { 160 case TokenTypes.METHOD_CALL: 161 processLeft(ast); 162 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 163 break; 164 case TokenTypes.DOT: 165 case TokenTypes.EXPR: 166 case TokenTypes.QUESTION: 167 processExpression(ast); 168 break; 169 case TokenTypes.LITERAL_FOR: 170 visitLiteralFor(ast); 171 break; 172 case TokenTypes.ANNOTATION: 173 case TokenTypes.ENUM_CONSTANT_DEF: 174 case TokenTypes.LITERAL_NEW: 175 case TokenTypes.LITERAL_SYNCHRONIZED: 176 case TokenTypes.LAMBDA: 177 visitTokenWithOptionalParentheses(ast); 178 break; 179 case TokenTypes.RESOURCE_SPECIFICATION: 180 visitResourceSpecification(ast); 181 break; 182 default: 183 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 184 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 185 } 186 } 187 188 /** 189 * Checks parens in token which may not contain parens, e.g. 190 * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION} 191 * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and 192 * {@link TokenTypes#LAMBDA}. 193 * 194 * @param ast the token to check. 195 */ 196 private void visitTokenWithOptionalParentheses(DetailAST ast) { 197 final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN); 198 if (parenAst != null) { 199 processLeft(parenAst); 200 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 201 } 202 } 203 204 /** 205 * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}. 206 * 207 * @param ast the token to check. 208 */ 209 private void visitResourceSpecification(DetailAST ast) { 210 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 211 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 212 if (!hasPrecedingSemiColon(rparen)) { 213 processRight(rparen); 214 } 215 } 216 217 /** 218 * Checks that a token is preceded by a semicolon. 219 * 220 * @param ast the token to check 221 * @return whether a token is preceded by a semicolon 222 */ 223 private static boolean hasPrecedingSemiColon(DetailAST ast) { 224 return ast.getPreviousSibling().getType() == TokenTypes.SEMI; 225 } 226 227 /** 228 * Checks parens in {@link TokenTypes#LITERAL_FOR}. 229 * 230 * @param ast the token to check. 231 */ 232 private void visitLiteralFor(DetailAST ast) { 233 final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN); 234 if (!isPrecedingEmptyForInit(lparen)) { 235 processLeft(lparen); 236 } 237 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 238 if (!isFollowsEmptyForIterator(rparen)) { 239 processRight(rparen); 240 } 241 } 242 243 /** 244 * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION} 245 * and {@link TokenTypes#METHOD_CALL}. 246 * 247 * @param ast the token to check. 248 */ 249 private void processExpression(DetailAST ast) { 250 DetailAST currentNode = ast.getFirstChild(); 251 while (currentNode != null) { 252 if (currentNode.getType() == TokenTypes.LPAREN) { 253 processLeft(currentNode); 254 } 255 else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) { 256 processRight(currentNode); 257 } 258 else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) { 259 // Traverse all subtree tokens which will never be configured 260 // to be launched in visitToken() 261 currentNode = currentNode.getFirstChild(); 262 continue; 263 } 264 265 // Go up after processing the last child 266 while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) { 267 currentNode = currentNode.getParent(); 268 } 269 currentNode = currentNode.getNextSibling(); 270 } 271 } 272 273 /** 274 * Checks whether AcceptableTokens contains the given ast. 275 * 276 * @param ast the token to check. 277 * @return true if the ast is in AcceptableTokens. 278 */ 279 private boolean isAcceptableToken(DetailAST ast) { 280 return acceptableTokens.get(ast.getType()); 281 } 282 283 /** 284 * Returns array of acceptable tokens. 285 * 286 * @return acceptableTokens. 287 */ 288 private static int[] makeAcceptableTokens() { 289 return new int[] {TokenTypes.ANNOTATION, 290 TokenTypes.ANNOTATION_FIELD_DEF, 291 TokenTypes.CTOR_CALL, 292 TokenTypes.CTOR_DEF, 293 TokenTypes.DOT, 294 TokenTypes.ENUM_CONSTANT_DEF, 295 TokenTypes.EXPR, 296 TokenTypes.LITERAL_CATCH, 297 TokenTypes.LITERAL_DO, 298 TokenTypes.LITERAL_FOR, 299 TokenTypes.LITERAL_IF, 300 TokenTypes.LITERAL_NEW, 301 TokenTypes.LITERAL_SWITCH, 302 TokenTypes.LITERAL_SYNCHRONIZED, 303 TokenTypes.LITERAL_WHILE, 304 TokenTypes.METHOD_CALL, 305 TokenTypes.METHOD_DEF, 306 TokenTypes.QUESTION, 307 TokenTypes.RESOURCE_SPECIFICATION, 308 TokenTypes.SUPER_CTOR_CALL, 309 TokenTypes.LAMBDA, 310 TokenTypes.RECORD_DEF, 311 TokenTypes.RECORD_PATTERN_DEF, 312 }; 313 } 314 315 /** 316 * Checks whether {@link TokenTypes#RPAREN} is a closing paren 317 * of a {@link TokenTypes#TYPECAST}. 318 * 319 * @param ast of a {@link TokenTypes#RPAREN} to check. 320 * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. 321 */ 322 private static boolean isInTypecast(DetailAST ast) { 323 boolean result = false; 324 if (ast.getParent().getType() == TokenTypes.TYPECAST) { 325 final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); 326 if (TokenUtil.areOnSameLine(firstRparen, ast) 327 && firstRparen.getColumnNo() == ast.getColumnNo()) { 328 result = true; 329 } 330 } 331 return result; 332 } 333 334 /** 335 * Checks that a token follows an empty for iterator. 336 * 337 * @param ast the token to check 338 * @return whether a token follows an empty for iterator 339 */ 340 private static boolean isFollowsEmptyForIterator(DetailAST ast) { 341 boolean result = false; 342 final DetailAST parent = ast.getParent(); 343 // Only traditional for statements are examined, not for-each statements 344 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 345 final DetailAST forIterator = 346 parent.findFirstToken(TokenTypes.FOR_ITERATOR); 347 result = !forIterator.hasChildren(); 348 } 349 return result; 350 } 351 352 /** 353 * Checks that a token precedes an empty for initializer. 354 * 355 * @param ast the token to check 356 * @return whether a token precedes an empty for initializer 357 */ 358 private static boolean isPrecedingEmptyForInit(DetailAST ast) { 359 boolean result = false; 360 final DetailAST parent = ast.getParent(); 361 // Only traditional for statements are examined, not for-each statements 362 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 363 final DetailAST forIterator = 364 parent.findFirstToken(TokenTypes.FOR_INIT); 365 result = !forIterator.hasChildren(); 366 } 367 return result; 368 } 369 370 }