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