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