1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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">
39 * EmptyForIteratorPad</a> to validate empty for iterators and
40 * <a href="https://checkstyle.org/checks/whitespace/emptyforinitializerpad.html">
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">
44 * TypecastParenPad</a> to validate them.
45 * </div>
46 *
47 * @since 3.0
48 */
49 public class ParenPadCheck extends AbstractParenPadCheck {
50
51 /**
52 * Tokens that this check handles.
53 */
54 private final BitSet acceptableTokens;
55
56 /**
57 * Initializes acceptableTokens.
58 */
59 public ParenPadCheck() {
60 acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens());
61 }
62
63 @Override
64 public int[] getDefaultTokens() {
65 return makeAcceptableTokens();
66 }
67
68 @Override
69 public int[] getAcceptableTokens() {
70 return makeAcceptableTokens();
71 }
72
73 @Override
74 public int[] getRequiredTokens() {
75 return CommonUtil.EMPTY_INT_ARRAY;
76 }
77
78 @Override
79 public void visitToken(DetailAST ast) {
80 switch (ast.getType()) {
81 case TokenTypes.METHOD_CALL -> {
82 processLeft(ast);
83 processRight(ast.findFirstToken(TokenTypes.RPAREN));
84 }
85
86 case TokenTypes.DOT, TokenTypes.EXPR, TokenTypes.QUESTION -> processExpression(ast);
87
88 case TokenTypes.LITERAL_FOR -> visitLiteralFor(ast);
89
90 case TokenTypes.ANNOTATION,
91 TokenTypes.ENUM_CONSTANT_DEF,
92 TokenTypes.LITERAL_NEW,
93 TokenTypes.LITERAL_SYNCHRONIZED,
94 TokenTypes.LAMBDA -> visitTokenWithOptionalParentheses(ast);
95
96 case TokenTypes.RESOURCE_SPECIFICATION -> visitResourceSpecification(ast);
97
98 default -> {
99 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 }