View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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#EmptyForIteratorPad">
39   * EmptyForIteratorPad</a> to validate empty for iterators and
40   * <a href="https://checkstyle.org/checks/whitespace/emptyforinitializerpad.html#EmptyForInitializerPad">
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#TypecastParenPad">
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                 break;
166             case TokenTypes.DOT:
167             case TokenTypes.EXPR:
168             case TokenTypes.QUESTION:
169                 processExpression(ast);
170                 break;
171             case TokenTypes.LITERAL_FOR:
172                 visitLiteralFor(ast);
173                 break;
174             case TokenTypes.ANNOTATION:
175             case TokenTypes.ENUM_CONSTANT_DEF:
176             case TokenTypes.LITERAL_NEW:
177             case TokenTypes.LITERAL_SYNCHRONIZED:
178             case TokenTypes.LAMBDA:
179                 visitTokenWithOptionalParentheses(ast);
180                 break;
181             case TokenTypes.RESOURCE_SPECIFICATION:
182                 visitResourceSpecification(ast);
183                 break;
184             default:
185                 processLeft(ast.findFirstToken(TokenTypes.LPAREN));
186                 processRight(ast.findFirstToken(TokenTypes.RPAREN));
187         }
188     }
189 
190     /**
191      * Checks parens in token which may not contain parens, e.g.
192      * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION}
193      * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and
194      * {@link TokenTypes#LAMBDA}.
195      *
196      * @param ast the token to check.
197      */
198     private void visitTokenWithOptionalParentheses(DetailAST ast) {
199         final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN);
200         if (parenAst != null) {
201             processLeft(parenAst);
202             processRight(ast.findFirstToken(TokenTypes.RPAREN));
203         }
204     }
205 
206     /**
207      * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}.
208      *
209      * @param ast the token to check.
210      */
211     private void visitResourceSpecification(DetailAST ast) {
212         processLeft(ast.findFirstToken(TokenTypes.LPAREN));
213         final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
214         if (!hasPrecedingSemiColon(rparen)) {
215             processRight(rparen);
216         }
217     }
218 
219     /**
220      * Checks that a token is preceded by a semicolon.
221      *
222      * @param ast the token to check
223      * @return whether a token is preceded by a semicolon
224      */
225     private static boolean hasPrecedingSemiColon(DetailAST ast) {
226         return ast.getPreviousSibling().getType() == TokenTypes.SEMI;
227     }
228 
229     /**
230      * Checks parens in {@link TokenTypes#LITERAL_FOR}.
231      *
232      * @param ast the token to check.
233      */
234     private void visitLiteralFor(DetailAST ast) {
235         final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN);
236         if (!isPrecedingEmptyForInit(lparen)) {
237             processLeft(lparen);
238         }
239         final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
240         if (!isFollowsEmptyForIterator(rparen)) {
241             processRight(rparen);
242         }
243     }
244 
245     /**
246      * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION}
247      * and {@link TokenTypes#METHOD_CALL}.
248      *
249      * @param ast the token to check.
250      */
251     private void processExpression(DetailAST ast) {
252         DetailAST currentNode = ast.getFirstChild();
253         while (currentNode != null) {
254             if (currentNode.getType() == TokenTypes.LPAREN) {
255                 processLeft(currentNode);
256             }
257             else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) {
258                 processRight(currentNode);
259             }
260             else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) {
261                 // Traverse all subtree tokens which will never be configured
262                 // to be launched in visitToken()
263                 currentNode = currentNode.getFirstChild();
264                 continue;
265             }
266 
267             // Go up after processing the last child
268             while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) {
269                 currentNode = currentNode.getParent();
270             }
271             currentNode = currentNode.getNextSibling();
272         }
273     }
274 
275     /**
276      * Checks whether AcceptableTokens contains the given ast.
277      *
278      * @param ast the token to check.
279      * @return true if the ast is in AcceptableTokens.
280      */
281     private boolean isAcceptableToken(DetailAST ast) {
282         return acceptableTokens.get(ast.getType());
283     }
284 
285     /**
286      * Returns array of acceptable tokens.
287      *
288      * @return acceptableTokens.
289      */
290     private static int[] makeAcceptableTokens() {
291         return new int[] {TokenTypes.ANNOTATION,
292             TokenTypes.ANNOTATION_FIELD_DEF,
293             TokenTypes.CTOR_CALL,
294             TokenTypes.CTOR_DEF,
295             TokenTypes.DOT,
296             TokenTypes.ENUM_CONSTANT_DEF,
297             TokenTypes.EXPR,
298             TokenTypes.LITERAL_CATCH,
299             TokenTypes.LITERAL_DO,
300             TokenTypes.LITERAL_FOR,
301             TokenTypes.LITERAL_IF,
302             TokenTypes.LITERAL_NEW,
303             TokenTypes.LITERAL_SWITCH,
304             TokenTypes.LITERAL_SYNCHRONIZED,
305             TokenTypes.LITERAL_WHILE,
306             TokenTypes.METHOD_CALL,
307             TokenTypes.METHOD_DEF,
308             TokenTypes.QUESTION,
309             TokenTypes.RESOURCE_SPECIFICATION,
310             TokenTypes.SUPER_CTOR_CALL,
311             TokenTypes.LAMBDA,
312             TokenTypes.RECORD_DEF,
313             TokenTypes.RECORD_PATTERN_DEF,
314         };
315     }
316 
317     /**
318      * Checks whether {@link TokenTypes#RPAREN} is a closing paren
319      * of a {@link TokenTypes#TYPECAST}.
320      *
321      * @param ast of a {@link TokenTypes#RPAREN} to check.
322      * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}.
323      */
324     private static boolean isInTypecast(DetailAST ast) {
325         boolean result = false;
326         if (ast.getParent().getType() == TokenTypes.TYPECAST) {
327             final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN);
328             if (TokenUtil.areOnSameLine(firstRparen, ast)
329                     && firstRparen.getColumnNo() == ast.getColumnNo()) {
330                 result = true;
331             }
332         }
333         return result;
334     }
335 
336     /**
337      * Checks that a token follows an empty for iterator.
338      *
339      * @param ast the token to check
340      * @return whether a token follows an empty for iterator
341      */
342     private static boolean isFollowsEmptyForIterator(DetailAST ast) {
343         boolean result = false;
344         final DetailAST parent = ast.getParent();
345         // Only traditional for statements are examined, not for-each statements
346         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
347             final DetailAST forIterator =
348                 parent.findFirstToken(TokenTypes.FOR_ITERATOR);
349             result = !forIterator.hasChildren();
350         }
351         return result;
352     }
353 
354     /**
355      * Checks that a token precedes an empty for initializer.
356      *
357      * @param ast the token to check
358      * @return whether a token precedes an empty for initializer
359      */
360     private static boolean isPrecedingEmptyForInit(DetailAST ast) {
361         boolean result = false;
362         final DetailAST parent = ast.getParent();
363         // Only traditional for statements are examined, not for-each statements
364         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
365             final DetailAST forIterator =
366                     parent.findFirstToken(TokenTypes.FOR_INIT);
367             result = !forIterator.hasChildren();
368         }
369         return result;
370     }
371 
372 }