1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 * A key is pointing to the warning message text in "messages.properties"
53 * file.
54 */
55 public static final String MSG_WS_FOLLOWED = "ws.followed";
56
57 /**
58 * A key is pointing to the warning message text in "messages.properties"
59 * file.
60 */
61 public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed";
62
63 /**
64 * A key is pointing to the warning message text in "messages.properties"
65 * file.
66 */
67 public static final String MSG_WS_PRECEDED = "ws.preceded";
68
69 /**
70 * A key is pointing to the warning message text in "messages.properties"
71 * file.
72 */
73 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
74
75 /**
76 * Tokens that this check handles.
77 */
78 private final BitSet acceptableTokens;
79
80 /**
81 * Initializes acceptableTokens and message keys.
82 */
83 public ParenPadCheck() {
84 super(MSG_WS_FOLLOWED, MSG_WS_NOT_FOLLOWED, MSG_WS_PRECEDED, MSG_WS_NOT_PRECEDED);
85 acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens());
86 }
87
88 @Override
89 public int[] getDefaultTokens() {
90 return makeAcceptableTokens();
91 }
92
93 @Override
94 public int[] getAcceptableTokens() {
95 return makeAcceptableTokens();
96 }
97
98 @Override
99 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 }