001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.whitespace; 021 022import java.util.BitSet; 023 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 027import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 028 029/** 030 * <p> 031 * Checks the policy on the padding of parentheses; that is whether a space is required 032 * after a left parenthesis and before a right parenthesis, or such spaces are 033 * forbidden. No check occurs at the right parenthesis after an empty for 034 * iterator, at the left parenthesis before an empty for initialization, or at 035 * the right parenthesis of a try-with-resources resource specification where 036 * the last resource variable has a trailing semicolon. 037 * Use Check 038 * <a href="https://checkstyle.org/checks/whitespace/emptyforiteratorpad.html#EmptyForIteratorPad"> 039 * EmptyForIteratorPad</a> to validate empty for iterators and 040 * <a href="https://checkstyle.org/checks/whitespace/emptyforinitializerpad.html#EmptyForInitializerPad"> 041 * EmptyForInitializerPad</a> to validate empty for initializers. 042 * Typecasts are also not checked, as there is 043 * <a href="https://checkstyle.org/checks/whitespace/typecastparenpad.html#TypecastParenPad"> 044 * TypecastParenPad</a> to validate them. 045 * </p> 046 * <ul> 047 * <li> 048 * Property {@code option} - Specify policy on how to pad parentheses. 049 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption}. 050 * Default value is {@code nospace}. 051 * </li> 052 * <li> 053 * Property {@code tokens} - tokens to check 054 * Type is {@code java.lang.String[]}. 055 * Validation type is {@code tokenSet}. 056 * Default value is: 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION"> 058 * ANNOTATION</a>, 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 060 * ANNOTATION_FIELD_DEF</a>, 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_CALL"> 062 * CTOR_CALL</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 064 * CTOR_DEF</a>, 065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT"> 066 * DOT</a>, 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 068 * ENUM_CONSTANT_DEF</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR"> 070 * EXPR</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH"> 072 * LITERAL_CATCH</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 074 * LITERAL_DO</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 076 * LITERAL_FOR</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 078 * LITERAL_IF</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW"> 080 * LITERAL_NEW</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 082 * LITERAL_SWITCH</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED"> 084 * LITERAL_SYNCHRONIZED</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 086 * LITERAL_WHILE</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL"> 088 * METHOD_CALL</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 090 * METHOD_DEF</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION"> 092 * QUESTION</a>, 093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE_SPECIFICATION"> 094 * RESOURCE_SPECIFICATION</a>, 095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SUPER_CTOR_CALL"> 096 * SUPER_CTOR_CALL</a>, 097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 098 * LAMBDA</a>, 099 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 100 * RECORD_DEF</a>. 101 * </li> 102 * </ul> 103 * <p> 104 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 105 * </p> 106 * <p> 107 * Violation Message Keys: 108 * </p> 109 * <ul> 110 * <li> 111 * {@code ws.followed} 112 * </li> 113 * <li> 114 * {@code ws.notFollowed} 115 * </li> 116 * <li> 117 * {@code ws.notPreceded} 118 * </li> 119 * <li> 120 * {@code ws.preceded} 121 * </li> 122 * </ul> 123 * 124 * @since 3.0 125 */ 126public class ParenPadCheck extends AbstractParenPadCheck { 127 128 /** 129 * Tokens that this check handles. 130 */ 131 private final BitSet acceptableTokens; 132 133 /** 134 * Initializes acceptableTokens. 135 */ 136 public ParenPadCheck() { 137 acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens()); 138 } 139 140 @Override 141 public int[] getDefaultTokens() { 142 return makeAcceptableTokens(); 143 } 144 145 @Override 146 public int[] getAcceptableTokens() { 147 return makeAcceptableTokens(); 148 } 149 150 @Override 151 public int[] getRequiredTokens() { 152 return CommonUtil.EMPTY_INT_ARRAY; 153 } 154 155 @Override 156 public void visitToken(DetailAST ast) { 157 switch (ast.getType()) { 158 case TokenTypes.METHOD_CALL: 159 processLeft(ast); 160 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 161 break; 162 case TokenTypes.DOT: 163 case TokenTypes.EXPR: 164 case TokenTypes.QUESTION: 165 processExpression(ast); 166 break; 167 case TokenTypes.LITERAL_FOR: 168 visitLiteralFor(ast); 169 break; 170 case TokenTypes.ANNOTATION: 171 case TokenTypes.ENUM_CONSTANT_DEF: 172 case TokenTypes.LITERAL_NEW: 173 case TokenTypes.LITERAL_SYNCHRONIZED: 174 case TokenTypes.LAMBDA: 175 visitTokenWithOptionalParentheses(ast); 176 break; 177 case TokenTypes.RESOURCE_SPECIFICATION: 178 visitResourceSpecification(ast); 179 break; 180 default: 181 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 182 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 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 }; 310 } 311 312 /** 313 * Checks whether {@link TokenTypes#RPAREN} is a closing paren 314 * of a {@link TokenTypes#TYPECAST}. 315 * 316 * @param ast of a {@link TokenTypes#RPAREN} to check. 317 * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. 318 */ 319 private static boolean isInTypecast(DetailAST ast) { 320 boolean result = false; 321 if (ast.getParent().getType() == TokenTypes.TYPECAST) { 322 final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); 323 if (TokenUtil.areOnSameLine(firstRparen, ast) 324 && firstRparen.getColumnNo() == ast.getColumnNo()) { 325 result = true; 326 } 327 } 328 return result; 329 } 330 331 /** 332 * Checks that a token follows an empty for iterator. 333 * 334 * @param ast the token to check 335 * @return whether a token follows an empty for iterator 336 */ 337 private static boolean isFollowsEmptyForIterator(DetailAST ast) { 338 boolean result = false; 339 final DetailAST parent = ast.getParent(); 340 // Only traditional for statements are examined, not for-each statements 341 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 342 final DetailAST forIterator = 343 parent.findFirstToken(TokenTypes.FOR_ITERATOR); 344 result = !forIterator.hasChildren(); 345 } 346 return result; 347 } 348 349 /** 350 * Checks that a token precedes an empty for initializer. 351 * 352 * @param ast the token to check 353 * @return whether a token precedes an empty for initializer 354 */ 355 private static boolean isPrecedingEmptyForInit(DetailAST ast) { 356 boolean result = false; 357 final DetailAST parent = ast.getParent(); 358 // Only traditional for statements are examined, not for-each statements 359 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 360 final DetailAST forIterator = 361 parent.findFirstToken(TokenTypes.FOR_INIT); 362 result = !forIterator.hasChildren(); 363 } 364 return result; 365 } 366 367}