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 * <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#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 * </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 break; 166 case TokenTypes.DOT: 167 case TokenTypes.EXPR: 168 case TokenTypes.QUESTION: 169 processExpression(ast); 170 break; 171 case TokenTypes.LITERAL_FOR: 172 visitLiteralFor(ast); 173 break; 174 case TokenTypes.ANNOTATION: 175 case TokenTypes.ENUM_CONSTANT_DEF: 176 case TokenTypes.LITERAL_NEW: 177 case TokenTypes.LITERAL_SYNCHRONIZED: 178 case TokenTypes.LAMBDA: 179 visitTokenWithOptionalParentheses(ast); 180 break; 181 case TokenTypes.RESOURCE_SPECIFICATION: 182 visitResourceSpecification(ast); 183 break; 184 default: 185 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 186 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 187 } 188 } 189 190 /** 191 * Checks parens in token which may not contain parens, e.g. 192 * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION} 193 * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and 194 * {@link TokenTypes#LAMBDA}. 195 * 196 * @param ast the token to check. 197 */ 198 private void visitTokenWithOptionalParentheses(DetailAST ast) { 199 final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN); 200 if (parenAst != null) { 201 processLeft(parenAst); 202 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 203 } 204 } 205 206 /** 207 * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}. 208 * 209 * @param ast the token to check. 210 */ 211 private void visitResourceSpecification(DetailAST ast) { 212 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 213 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 214 if (!hasPrecedingSemiColon(rparen)) { 215 processRight(rparen); 216 } 217 } 218 219 /** 220 * Checks that a token is preceded by a semicolon. 221 * 222 * @param ast the token to check 223 * @return whether a token is preceded by a semicolon 224 */ 225 private static boolean hasPrecedingSemiColon(DetailAST ast) { 226 return ast.getPreviousSibling().getType() == TokenTypes.SEMI; 227 } 228 229 /** 230 * Checks parens in {@link TokenTypes#LITERAL_FOR}. 231 * 232 * @param ast the token to check. 233 */ 234 private void visitLiteralFor(DetailAST ast) { 235 final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN); 236 if (!isPrecedingEmptyForInit(lparen)) { 237 processLeft(lparen); 238 } 239 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 240 if (!isFollowsEmptyForIterator(rparen)) { 241 processRight(rparen); 242 } 243 } 244 245 /** 246 * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION} 247 * and {@link TokenTypes#METHOD_CALL}. 248 * 249 * @param ast the token to check. 250 */ 251 private void processExpression(DetailAST ast) { 252 DetailAST currentNode = ast.getFirstChild(); 253 while (currentNode != null) { 254 if (currentNode.getType() == TokenTypes.LPAREN) { 255 processLeft(currentNode); 256 } 257 else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) { 258 processRight(currentNode); 259 } 260 else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) { 261 // Traverse all subtree tokens which will never be configured 262 // to be launched in visitToken() 263 currentNode = currentNode.getFirstChild(); 264 continue; 265 } 266 267 // Go up after processing the last child 268 while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) { 269 currentNode = currentNode.getParent(); 270 } 271 currentNode = currentNode.getNextSibling(); 272 } 273 } 274 275 /** 276 * Checks whether AcceptableTokens contains the given ast. 277 * 278 * @param ast the token to check. 279 * @return true if the ast is in AcceptableTokens. 280 */ 281 private boolean isAcceptableToken(DetailAST ast) { 282 return acceptableTokens.get(ast.getType()); 283 } 284 285 /** 286 * Returns array of acceptable tokens. 287 * 288 * @return acceptableTokens. 289 */ 290 private static int[] makeAcceptableTokens() { 291 return new int[] {TokenTypes.ANNOTATION, 292 TokenTypes.ANNOTATION_FIELD_DEF, 293 TokenTypes.CTOR_CALL, 294 TokenTypes.CTOR_DEF, 295 TokenTypes.DOT, 296 TokenTypes.ENUM_CONSTANT_DEF, 297 TokenTypes.EXPR, 298 TokenTypes.LITERAL_CATCH, 299 TokenTypes.LITERAL_DO, 300 TokenTypes.LITERAL_FOR, 301 TokenTypes.LITERAL_IF, 302 TokenTypes.LITERAL_NEW, 303 TokenTypes.LITERAL_SWITCH, 304 TokenTypes.LITERAL_SYNCHRONIZED, 305 TokenTypes.LITERAL_WHILE, 306 TokenTypes.METHOD_CALL, 307 TokenTypes.METHOD_DEF, 308 TokenTypes.QUESTION, 309 TokenTypes.RESOURCE_SPECIFICATION, 310 TokenTypes.SUPER_CTOR_CALL, 311 TokenTypes.LAMBDA, 312 TokenTypes.RECORD_DEF, 313 TokenTypes.RECORD_PATTERN_DEF, 314 }; 315 } 316 317 /** 318 * Checks whether {@link TokenTypes#RPAREN} is a closing paren 319 * of a {@link TokenTypes#TYPECAST}. 320 * 321 * @param ast of a {@link TokenTypes#RPAREN} to check. 322 * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. 323 */ 324 private static boolean isInTypecast(DetailAST ast) { 325 boolean result = false; 326 if (ast.getParent().getType() == TokenTypes.TYPECAST) { 327 final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); 328 if (TokenUtil.areOnSameLine(firstRparen, ast) 329 && firstRparen.getColumnNo() == ast.getColumnNo()) { 330 result = true; 331 } 332 } 333 return result; 334 } 335 336 /** 337 * Checks that a token follows an empty for iterator. 338 * 339 * @param ast the token to check 340 * @return whether a token follows an empty for iterator 341 */ 342 private static boolean isFollowsEmptyForIterator(DetailAST ast) { 343 boolean result = false; 344 final DetailAST parent = ast.getParent(); 345 // Only traditional for statements are examined, not for-each statements 346 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 347 final DetailAST forIterator = 348 parent.findFirstToken(TokenTypes.FOR_ITERATOR); 349 result = !forIterator.hasChildren(); 350 } 351 return result; 352 } 353 354 /** 355 * Checks that a token precedes an empty for initializer. 356 * 357 * @param ast the token to check 358 * @return whether a token precedes an empty for initializer 359 */ 360 private static boolean isPrecedingEmptyForInit(DetailAST ast) { 361 boolean result = false; 362 final DetailAST parent = ast.getParent(); 363 // Only traditional for statements are examined, not for-each statements 364 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 365 final DetailAST forIterator = 366 parent.findFirstToken(TokenTypes.FOR_INIT); 367 result = !forIterator.hasChildren(); 368 } 369 return result; 370 } 371 372 }