001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * <div> 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"> 039 * EmptyForIteratorPad</a> to validate empty for iterators and 040 * <a href="https://checkstyle.org/checks/whitespace/emptyforinitializerpad.html"> 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"> 044 * TypecastParenPad</a> to validate them. 045 * </div> 046 * 047 * @since 3.0 048 */ 049public class ParenPadCheck extends AbstractParenPadCheck { 050 051 /** 052 * Tokens that this check handles. 053 */ 054 private final BitSet acceptableTokens; 055 056 /** 057 * Initializes acceptableTokens. 058 */ 059 public ParenPadCheck() { 060 acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens()); 061 } 062 063 @Override 064 public int[] getDefaultTokens() { 065 return makeAcceptableTokens(); 066 } 067 068 @Override 069 public int[] getAcceptableTokens() { 070 return makeAcceptableTokens(); 071 } 072 073 @Override 074 public int[] getRequiredTokens() { 075 return CommonUtil.EMPTY_INT_ARRAY; 076 } 077 078 @Override 079 public void visitToken(DetailAST ast) { 080 switch (ast.getType()) { 081 case TokenTypes.METHOD_CALL -> { 082 processLeft(ast); 083 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 084 } 085 086 case TokenTypes.DOT, TokenTypes.EXPR, TokenTypes.QUESTION -> processExpression(ast); 087 088 case TokenTypes.LITERAL_FOR -> visitLiteralFor(ast); 089 090 case TokenTypes.ANNOTATION, 091 TokenTypes.ENUM_CONSTANT_DEF, 092 TokenTypes.LITERAL_NEW, 093 TokenTypes.LITERAL_SYNCHRONIZED, 094 TokenTypes.LAMBDA -> visitTokenWithOptionalParentheses(ast); 095 096 case TokenTypes.RESOURCE_SPECIFICATION -> visitResourceSpecification(ast); 097 098 default -> { 099 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 100 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 101 } 102 } 103 } 104 105 /** 106 * Checks parens in token which may not contain parens, e.g. 107 * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION} 108 * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and 109 * {@link TokenTypes#LAMBDA}. 110 * 111 * @param ast the token to check. 112 */ 113 private void visitTokenWithOptionalParentheses(DetailAST ast) { 114 final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN); 115 if (parenAst != null) { 116 processLeft(parenAst); 117 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 118 } 119 } 120 121 /** 122 * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}. 123 * 124 * @param ast the token to check. 125 */ 126 private void visitResourceSpecification(DetailAST ast) { 127 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 128 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 129 if (!hasPrecedingSemiColon(rparen)) { 130 processRight(rparen); 131 } 132 } 133 134 /** 135 * Checks that a token is preceded by a semicolon. 136 * 137 * @param ast the token to check 138 * @return whether a token is preceded by a semicolon 139 */ 140 private static boolean hasPrecedingSemiColon(DetailAST ast) { 141 return ast.getPreviousSibling().getType() == TokenTypes.SEMI; 142 } 143 144 /** 145 * Checks parens in {@link TokenTypes#LITERAL_FOR}. 146 * 147 * @param ast the token to check. 148 */ 149 private void visitLiteralFor(DetailAST ast) { 150 final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN); 151 if (!isPrecedingEmptyForInit(lparen)) { 152 processLeft(lparen); 153 } 154 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 155 if (!isFollowsEmptyForIterator(rparen)) { 156 processRight(rparen); 157 } 158 } 159 160 /** 161 * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION} 162 * and {@link TokenTypes#METHOD_CALL}. 163 * 164 * @param ast the token to check. 165 */ 166 private void processExpression(DetailAST ast) { 167 DetailAST currentNode = ast.getFirstChild(); 168 while (currentNode != null) { 169 if (currentNode.getType() == TokenTypes.LPAREN) { 170 processLeft(currentNode); 171 } 172 else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) { 173 processRight(currentNode); 174 } 175 else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) { 176 // Traverse all subtree tokens which will never be configured 177 // to be launched in visitToken() 178 currentNode = currentNode.getFirstChild(); 179 continue; 180 } 181 182 // Go up after processing the last child 183 while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) { 184 currentNode = currentNode.getParent(); 185 } 186 currentNode = currentNode.getNextSibling(); 187 } 188 } 189 190 /** 191 * Checks whether AcceptableTokens contains the given ast. 192 * 193 * @param ast the token to check. 194 * @return true if the ast is in AcceptableTokens. 195 */ 196 private boolean isAcceptableToken(DetailAST ast) { 197 return acceptableTokens.get(ast.getType()); 198 } 199 200 /** 201 * Returns array of acceptable tokens. 202 * 203 * @return acceptableTokens. 204 */ 205 private static int[] makeAcceptableTokens() { 206 return new int[] {TokenTypes.ANNOTATION, 207 TokenTypes.ANNOTATION_FIELD_DEF, 208 TokenTypes.CTOR_CALL, 209 TokenTypes.CTOR_DEF, 210 TokenTypes.DOT, 211 TokenTypes.ENUM_CONSTANT_DEF, 212 TokenTypes.EXPR, 213 TokenTypes.LITERAL_CATCH, 214 TokenTypes.LITERAL_DO, 215 TokenTypes.LITERAL_FOR, 216 TokenTypes.LITERAL_IF, 217 TokenTypes.LITERAL_NEW, 218 TokenTypes.LITERAL_SWITCH, 219 TokenTypes.LITERAL_SYNCHRONIZED, 220 TokenTypes.LITERAL_WHILE, 221 TokenTypes.METHOD_CALL, 222 TokenTypes.METHOD_DEF, 223 TokenTypes.QUESTION, 224 TokenTypes.RESOURCE_SPECIFICATION, 225 TokenTypes.SUPER_CTOR_CALL, 226 TokenTypes.LAMBDA, 227 TokenTypes.RECORD_DEF, 228 TokenTypes.RECORD_PATTERN_DEF, 229 }; 230 } 231 232 /** 233 * Checks whether {@link TokenTypes#RPAREN} is a closing paren 234 * of a {@link TokenTypes#TYPECAST}. 235 * 236 * @param ast of a {@link TokenTypes#RPAREN} to check. 237 * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. 238 */ 239 private static boolean isInTypecast(DetailAST ast) { 240 boolean result = false; 241 if (ast.getParent().getType() == TokenTypes.TYPECAST) { 242 final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); 243 if (TokenUtil.areOnSameLine(firstRparen, ast) 244 && firstRparen.getColumnNo() == ast.getColumnNo()) { 245 result = true; 246 } 247 } 248 return result; 249 } 250 251 /** 252 * Checks that a token follows an empty for iterator. 253 * 254 * @param ast the token to check 255 * @return whether a token follows an empty for iterator 256 */ 257 private static boolean isFollowsEmptyForIterator(DetailAST ast) { 258 boolean result = false; 259 final DetailAST parent = ast.getParent(); 260 // Only traditional for statements are examined, not for-each statements 261 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 262 final DetailAST forIterator = 263 parent.findFirstToken(TokenTypes.FOR_ITERATOR); 264 result = !forIterator.hasChildren(); 265 } 266 return result; 267 } 268 269 /** 270 * Checks that a token precedes an empty for initializer. 271 * 272 * @param ast the token to check 273 * @return whether a token precedes an empty for initializer 274 */ 275 private static boolean isPrecedingEmptyForInit(DetailAST ast) { 276 boolean result = false; 277 final DetailAST parent = ast.getParent(); 278 // Only traditional for statements are examined, not for-each statements 279 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 280 final DetailAST forIterator = 281 parent.findFirstToken(TokenTypes.FOR_INIT); 282 result = !forIterator.hasChildren(); 283 } 284 return result; 285 } 286 287}