View Javadoc
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   * <ul>
47   * <li>
48   * Property {@code option} - Specify policy on how to pad parentheses.
49   * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption}.
50   * Default value is {@code nospace}.
51   * </li>
52   * <li>
53   * Property {@code tokens} - tokens to check
54   * Type is {@code java.lang.String[]}.
55   * Validation type is {@code tokenSet}.
56   * Default value is:
57   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION">
58   * ANNOTATION</a>,
59   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
60   * ANNOTATION_FIELD_DEF</a>,
61   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_CALL">
62   * CTOR_CALL</a>,
63   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
64   * CTOR_DEF</a>,
65   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT">
66   * DOT</a>,
67   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
68   * ENUM_CONSTANT_DEF</a>,
69   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
70   * EXPR</a>,
71   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
72   * LITERAL_CATCH</a>,
73   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
74   * LITERAL_DO</a>,
75   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
76   * LITERAL_FOR</a>,
77   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
78   * LITERAL_IF</a>,
79   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
80   * LITERAL_NEW</a>,
81   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
82   * LITERAL_SWITCH</a>,
83   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
84   * LITERAL_SYNCHRONIZED</a>,
85   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
86   * LITERAL_WHILE</a>,
87   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
88   * METHOD_CALL</a>,
89   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
90   * METHOD_DEF</a>,
91   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION">
92   * QUESTION</a>,
93   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE_SPECIFICATION">
94   * RESOURCE_SPECIFICATION</a>,
95   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SUPER_CTOR_CALL">
96   * SUPER_CTOR_CALL</a>,
97   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
98   * LAMBDA</a>,
99   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
100  * RECORD_DEF</a>,
101  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_PATTERN_DEF">
102  * RECORD_PATTERN_DEF</a>.
103  * </li>
104  * </ul>
105  *
106  * <p>
107  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
108  * </p>
109  *
110  * <p>
111  * Violation Message Keys:
112  * </p>
113  * <ul>
114  * <li>
115  * {@code ws.followed}
116  * </li>
117  * <li>
118  * {@code ws.notFollowed}
119  * </li>
120  * <li>
121  * {@code ws.notPreceded}
122  * </li>
123  * <li>
124  * {@code ws.preceded}
125  * </li>
126  * </ul>
127  *
128  * @since 3.0
129  */
130 public class ParenPadCheck extends AbstractParenPadCheck {
131 
132     /**
133      * Tokens that this check handles.
134      */
135     private final BitSet acceptableTokens;
136 
137     /**
138      * Initializes acceptableTokens.
139      */
140     public ParenPadCheck() {
141         acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens());
142     }
143 
144     @Override
145     public int[] getDefaultTokens() {
146         return makeAcceptableTokens();
147     }
148 
149     @Override
150     public int[] getAcceptableTokens() {
151         return makeAcceptableTokens();
152     }
153 
154     @Override
155     public int[] getRequiredTokens() {
156         return CommonUtil.EMPTY_INT_ARRAY;
157     }
158 
159     @Override
160     public void visitToken(DetailAST ast) {
161         switch (ast.getType()) {
162             case TokenTypes.METHOD_CALL -> {
163                 processLeft(ast);
164                 processRight(ast.findFirstToken(TokenTypes.RPAREN));
165             }
166 
167             case TokenTypes.DOT, TokenTypes.EXPR, TokenTypes.QUESTION -> processExpression(ast);
168 
169             case TokenTypes.LITERAL_FOR -> visitLiteralFor(ast);
170 
171             case TokenTypes.ANNOTATION,
172                  TokenTypes.ENUM_CONSTANT_DEF,
173                  TokenTypes.LITERAL_NEW,
174                  TokenTypes.LITERAL_SYNCHRONIZED,
175                  TokenTypes.LAMBDA -> visitTokenWithOptionalParentheses(ast);
176 
177             case TokenTypes.RESOURCE_SPECIFICATION -> visitResourceSpecification(ast);
178 
179             default -> {
180                 processLeft(ast.findFirstToken(TokenTypes.LPAREN));
181                 processRight(ast.findFirstToken(TokenTypes.RPAREN));
182             }
183         }
184     }
185 
186     /**
187      * Checks parens in token which may not contain parens, e.g.
188      * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION}
189      * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and
190      * {@link TokenTypes#LAMBDA}.
191      *
192      * @param ast the token to check.
193      */
194     private void visitTokenWithOptionalParentheses(DetailAST ast) {
195         final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN);
196         if (parenAst != null) {
197             processLeft(parenAst);
198             processRight(ast.findFirstToken(TokenTypes.RPAREN));
199         }
200     }
201 
202     /**
203      * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}.
204      *
205      * @param ast the token to check.
206      */
207     private void visitResourceSpecification(DetailAST ast) {
208         processLeft(ast.findFirstToken(TokenTypes.LPAREN));
209         final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
210         if (!hasPrecedingSemiColon(rparen)) {
211             processRight(rparen);
212         }
213     }
214 
215     /**
216      * Checks that a token is preceded by a semicolon.
217      *
218      * @param ast the token to check
219      * @return whether a token is preceded by a semicolon
220      */
221     private static boolean hasPrecedingSemiColon(DetailAST ast) {
222         return ast.getPreviousSibling().getType() == TokenTypes.SEMI;
223     }
224 
225     /**
226      * Checks parens in {@link TokenTypes#LITERAL_FOR}.
227      *
228      * @param ast the token to check.
229      */
230     private void visitLiteralFor(DetailAST ast) {
231         final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN);
232         if (!isPrecedingEmptyForInit(lparen)) {
233             processLeft(lparen);
234         }
235         final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
236         if (!isFollowsEmptyForIterator(rparen)) {
237             processRight(rparen);
238         }
239     }
240 
241     /**
242      * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION}
243      * and {@link TokenTypes#METHOD_CALL}.
244      *
245      * @param ast the token to check.
246      */
247     private void processExpression(DetailAST ast) {
248         DetailAST currentNode = ast.getFirstChild();
249         while (currentNode != null) {
250             if (currentNode.getType() == TokenTypes.LPAREN) {
251                 processLeft(currentNode);
252             }
253             else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) {
254                 processRight(currentNode);
255             }
256             else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) {
257                 // Traverse all subtree tokens which will never be configured
258                 // to be launched in visitToken()
259                 currentNode = currentNode.getFirstChild();
260                 continue;
261             }
262 
263             // Go up after processing the last child
264             while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) {
265                 currentNode = currentNode.getParent();
266             }
267             currentNode = currentNode.getNextSibling();
268         }
269     }
270 
271     /**
272      * Checks whether AcceptableTokens contains the given ast.
273      *
274      * @param ast the token to check.
275      * @return true if the ast is in AcceptableTokens.
276      */
277     private boolean isAcceptableToken(DetailAST ast) {
278         return acceptableTokens.get(ast.getType());
279     }
280 
281     /**
282      * Returns array of acceptable tokens.
283      *
284      * @return acceptableTokens.
285      */
286     private static int[] makeAcceptableTokens() {
287         return new int[] {TokenTypes.ANNOTATION,
288             TokenTypes.ANNOTATION_FIELD_DEF,
289             TokenTypes.CTOR_CALL,
290             TokenTypes.CTOR_DEF,
291             TokenTypes.DOT,
292             TokenTypes.ENUM_CONSTANT_DEF,
293             TokenTypes.EXPR,
294             TokenTypes.LITERAL_CATCH,
295             TokenTypes.LITERAL_DO,
296             TokenTypes.LITERAL_FOR,
297             TokenTypes.LITERAL_IF,
298             TokenTypes.LITERAL_NEW,
299             TokenTypes.LITERAL_SWITCH,
300             TokenTypes.LITERAL_SYNCHRONIZED,
301             TokenTypes.LITERAL_WHILE,
302             TokenTypes.METHOD_CALL,
303             TokenTypes.METHOD_DEF,
304             TokenTypes.QUESTION,
305             TokenTypes.RESOURCE_SPECIFICATION,
306             TokenTypes.SUPER_CTOR_CALL,
307             TokenTypes.LAMBDA,
308             TokenTypes.RECORD_DEF,
309             TokenTypes.RECORD_PATTERN_DEF,
310         };
311     }
312 
313     /**
314      * Checks whether {@link TokenTypes#RPAREN} is a closing paren
315      * of a {@link TokenTypes#TYPECAST}.
316      *
317      * @param ast of a {@link TokenTypes#RPAREN} to check.
318      * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}.
319      */
320     private static boolean isInTypecast(DetailAST ast) {
321         boolean result = false;
322         if (ast.getParent().getType() == TokenTypes.TYPECAST) {
323             final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN);
324             if (TokenUtil.areOnSameLine(firstRparen, ast)
325                     && firstRparen.getColumnNo() == ast.getColumnNo()) {
326                 result = true;
327             }
328         }
329         return result;
330     }
331 
332     /**
333      * Checks that a token follows an empty for iterator.
334      *
335      * @param ast the token to check
336      * @return whether a token follows an empty for iterator
337      */
338     private static boolean isFollowsEmptyForIterator(DetailAST ast) {
339         boolean result = false;
340         final DetailAST parent = ast.getParent();
341         // Only traditional for statements are examined, not for-each statements
342         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
343             final DetailAST forIterator =
344                 parent.findFirstToken(TokenTypes.FOR_ITERATOR);
345             result = !forIterator.hasChildren();
346         }
347         return result;
348     }
349 
350     /**
351      * Checks that a token precedes an empty for initializer.
352      *
353      * @param ast the token to check
354      * @return whether a token precedes an empty for initializer
355      */
356     private static boolean isPrecedingEmptyForInit(DetailAST ast) {
357         boolean result = false;
358         final DetailAST parent = ast.getParent();
359         // Only traditional for statements are examined, not for-each statements
360         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
361             final DetailAST forIterator =
362                     parent.findFirstToken(TokenTypes.FOR_INIT);
363             result = !forIterator.hasChildren();
364         }
365         return result;
366     }
367 
368 }