1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2025 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 * <div> 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"> 39 * EmptyForIteratorPad</a> to validate empty for iterators and 40 * <a href="https://checkstyle.org/checks/whitespace/emptyforinitializerpad.html"> 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"> 44 * TypecastParenPad</a> to validate them. 45 * </div> 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 * 106 * <p> 107 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 108 * </p> 109 * 110 * <p> 111 * Violation Message Keys: 112 * </p> 113 * <ul> 114 * <li> 115 * {@code ws.followed} 116 * </li> 117 * <li> 118 * {@code ws.notFollowed} 119 * </li> 120 * <li> 121 * {@code ws.notPreceded} 122 * </li> 123 * <li> 124 * {@code ws.preceded} 125 * </li> 126 * </ul> 127 * 128 * @since 3.0 129 */ 130 public class ParenPadCheck extends AbstractParenPadCheck { 131 132 /** 133 * Tokens that this check handles. 134 */ 135 private final BitSet acceptableTokens; 136 137 /** 138 * Initializes acceptableTokens. 139 */ 140 public ParenPadCheck() { 141 acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens()); 142 } 143 144 @Override 145 public int[] getDefaultTokens() { 146 return makeAcceptableTokens(); 147 } 148 149 @Override 150 public int[] getAcceptableTokens() { 151 return makeAcceptableTokens(); 152 } 153 154 @Override 155 public int[] getRequiredTokens() { 156 return CommonUtil.EMPTY_INT_ARRAY; 157 } 158 159 @Override 160 public void visitToken(DetailAST ast) { 161 switch (ast.getType()) { 162 case TokenTypes.METHOD_CALL -> { 163 processLeft(ast); 164 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 165 } 166 167 case TokenTypes.DOT, TokenTypes.EXPR, TokenTypes.QUESTION -> processExpression(ast); 168 169 case TokenTypes.LITERAL_FOR -> visitLiteralFor(ast); 170 171 case TokenTypes.ANNOTATION, 172 TokenTypes.ENUM_CONSTANT_DEF, 173 TokenTypes.LITERAL_NEW, 174 TokenTypes.LITERAL_SYNCHRONIZED, 175 TokenTypes.LAMBDA -> visitTokenWithOptionalParentheses(ast); 176 177 case TokenTypes.RESOURCE_SPECIFICATION -> visitResourceSpecification(ast); 178 179 default -> { 180 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 181 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 182 } 183 } 184 } 185 186 /** 187 * Checks parens in token which may not contain parens, e.g. 188 * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION} 189 * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and 190 * {@link TokenTypes#LAMBDA}. 191 * 192 * @param ast the token to check. 193 */ 194 private void visitTokenWithOptionalParentheses(DetailAST ast) { 195 final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN); 196 if (parenAst != null) { 197 processLeft(parenAst); 198 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 199 } 200 } 201 202 /** 203 * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}. 204 * 205 * @param ast the token to check. 206 */ 207 private void visitResourceSpecification(DetailAST ast) { 208 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 209 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 210 if (!hasPrecedingSemiColon(rparen)) { 211 processRight(rparen); 212 } 213 } 214 215 /** 216 * Checks that a token is preceded by a semicolon. 217 * 218 * @param ast the token to check 219 * @return whether a token is preceded by a semicolon 220 */ 221 private static boolean hasPrecedingSemiColon(DetailAST ast) { 222 return ast.getPreviousSibling().getType() == TokenTypes.SEMI; 223 } 224 225 /** 226 * Checks parens in {@link TokenTypes#LITERAL_FOR}. 227 * 228 * @param ast the token to check. 229 */ 230 private void visitLiteralFor(DetailAST ast) { 231 final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN); 232 if (!isPrecedingEmptyForInit(lparen)) { 233 processLeft(lparen); 234 } 235 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 236 if (!isFollowsEmptyForIterator(rparen)) { 237 processRight(rparen); 238 } 239 } 240 241 /** 242 * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION} 243 * and {@link TokenTypes#METHOD_CALL}. 244 * 245 * @param ast the token to check. 246 */ 247 private void processExpression(DetailAST ast) { 248 DetailAST currentNode = ast.getFirstChild(); 249 while (currentNode != null) { 250 if (currentNode.getType() == TokenTypes.LPAREN) { 251 processLeft(currentNode); 252 } 253 else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) { 254 processRight(currentNode); 255 } 256 else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) { 257 // Traverse all subtree tokens which will never be configured 258 // to be launched in visitToken() 259 currentNode = currentNode.getFirstChild(); 260 continue; 261 } 262 263 // Go up after processing the last child 264 while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) { 265 currentNode = currentNode.getParent(); 266 } 267 currentNode = currentNode.getNextSibling(); 268 } 269 } 270 271 /** 272 * Checks whether AcceptableTokens contains the given ast. 273 * 274 * @param ast the token to check. 275 * @return true if the ast is in AcceptableTokens. 276 */ 277 private boolean isAcceptableToken(DetailAST ast) { 278 return acceptableTokens.get(ast.getType()); 279 } 280 281 /** 282 * Returns array of acceptable tokens. 283 * 284 * @return acceptableTokens. 285 */ 286 private static int[] makeAcceptableTokens() { 287 return new int[] {TokenTypes.ANNOTATION, 288 TokenTypes.ANNOTATION_FIELD_DEF, 289 TokenTypes.CTOR_CALL, 290 TokenTypes.CTOR_DEF, 291 TokenTypes.DOT, 292 TokenTypes.ENUM_CONSTANT_DEF, 293 TokenTypes.EXPR, 294 TokenTypes.LITERAL_CATCH, 295 TokenTypes.LITERAL_DO, 296 TokenTypes.LITERAL_FOR, 297 TokenTypes.LITERAL_IF, 298 TokenTypes.LITERAL_NEW, 299 TokenTypes.LITERAL_SWITCH, 300 TokenTypes.LITERAL_SYNCHRONIZED, 301 TokenTypes.LITERAL_WHILE, 302 TokenTypes.METHOD_CALL, 303 TokenTypes.METHOD_DEF, 304 TokenTypes.QUESTION, 305 TokenTypes.RESOURCE_SPECIFICATION, 306 TokenTypes.SUPER_CTOR_CALL, 307 TokenTypes.LAMBDA, 308 TokenTypes.RECORD_DEF, 309 TokenTypes.RECORD_PATTERN_DEF, 310 }; 311 } 312 313 /** 314 * Checks whether {@link TokenTypes#RPAREN} is a closing paren 315 * of a {@link TokenTypes#TYPECAST}. 316 * 317 * @param ast of a {@link TokenTypes#RPAREN} to check. 318 * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. 319 */ 320 private static boolean isInTypecast(DetailAST ast) { 321 boolean result = false; 322 if (ast.getParent().getType() == TokenTypes.TYPECAST) { 323 final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); 324 if (TokenUtil.areOnSameLine(firstRparen, ast) 325 && firstRparen.getColumnNo() == ast.getColumnNo()) { 326 result = true; 327 } 328 } 329 return result; 330 } 331 332 /** 333 * Checks that a token follows an empty for iterator. 334 * 335 * @param ast the token to check 336 * @return whether a token follows an empty for iterator 337 */ 338 private static boolean isFollowsEmptyForIterator(DetailAST ast) { 339 boolean result = false; 340 final DetailAST parent = ast.getParent(); 341 // Only traditional for statements are examined, not for-each statements 342 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 343 final DetailAST forIterator = 344 parent.findFirstToken(TokenTypes.FOR_ITERATOR); 345 result = !forIterator.hasChildren(); 346 } 347 return result; 348 } 349 350 /** 351 * Checks that a token precedes an empty for initializer. 352 * 353 * @param ast the token to check 354 * @return whether a token precedes an empty for initializer 355 */ 356 private static boolean isPrecedingEmptyForInit(DetailAST ast) { 357 boolean result = false; 358 final DetailAST parent = ast.getParent(); 359 // Only traditional for statements are examined, not for-each statements 360 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 361 final DetailAST forIterator = 362 parent.findFirstToken(TokenTypes.FOR_INIT); 363 result = !forIterator.hasChildren(); 364 } 365 return result; 366 } 367 368 }