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