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  * </li>
102  * </ul>
103  * <p>
104  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
105  * </p>
106  * <p>
107  * Violation Message Keys:
108  * </p>
109  * <ul>
110  * <li>
111  * {@code ws.followed}
112  * </li>
113  * <li>
114  * {@code ws.notFollowed}
115  * </li>
116  * <li>
117  * {@code ws.notPreceded}
118  * </li>
119  * <li>
120  * {@code ws.preceded}
121  * </li>
122  * </ul>
123  *
124  * @since 3.0
125  */
126 public class ParenPadCheck extends AbstractParenPadCheck {
127 
128     /**
129      * Tokens that this check handles.
130      */
131     private final BitSet acceptableTokens;
132 
133     /**
134      * Initializes acceptableTokens.
135      */
136     public ParenPadCheck() {
137         acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens());
138     }
139 
140     @Override
141     public int[] getDefaultTokens() {
142         return makeAcceptableTokens();
143     }
144 
145     @Override
146     public int[] getAcceptableTokens() {
147         return makeAcceptableTokens();
148     }
149 
150     @Override
151     public int[] getRequiredTokens() {
152         return CommonUtil.EMPTY_INT_ARRAY;
153     }
154 
155     @Override
156     public void visitToken(DetailAST ast) {
157         switch (ast.getType()) {
158             case TokenTypes.METHOD_CALL:
159                 processLeft(ast);
160                 processRight(ast.findFirstToken(TokenTypes.RPAREN));
161                 break;
162             case TokenTypes.DOT:
163             case TokenTypes.EXPR:
164             case TokenTypes.QUESTION:
165                 processExpression(ast);
166                 break;
167             case TokenTypes.LITERAL_FOR:
168                 visitLiteralFor(ast);
169                 break;
170             case TokenTypes.ANNOTATION:
171             case TokenTypes.ENUM_CONSTANT_DEF:
172             case TokenTypes.LITERAL_NEW:
173             case TokenTypes.LITERAL_SYNCHRONIZED:
174             case TokenTypes.LAMBDA:
175                 visitTokenWithOptionalParentheses(ast);
176                 break;
177             case TokenTypes.RESOURCE_SPECIFICATION:
178                 visitResourceSpecification(ast);
179                 break;
180             default:
181                 processLeft(ast.findFirstToken(TokenTypes.LPAREN));
182                 processRight(ast.findFirstToken(TokenTypes.RPAREN));
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         };
310     }
311 
312     /**
313      * Checks whether {@link TokenTypes#RPAREN} is a closing paren
314      * of a {@link TokenTypes#TYPECAST}.
315      *
316      * @param ast of a {@link TokenTypes#RPAREN} to check.
317      * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}.
318      */
319     private static boolean isInTypecast(DetailAST ast) {
320         boolean result = false;
321         if (ast.getParent().getType() == TokenTypes.TYPECAST) {
322             final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN);
323             if (TokenUtil.areOnSameLine(firstRparen, ast)
324                     && firstRparen.getColumnNo() == ast.getColumnNo()) {
325                 result = true;
326             }
327         }
328         return result;
329     }
330 
331     /**
332      * Checks that a token follows an empty for iterator.
333      *
334      * @param ast the token to check
335      * @return whether a token follows an empty for iterator
336      */
337     private static boolean isFollowsEmptyForIterator(DetailAST ast) {
338         boolean result = false;
339         final DetailAST parent = ast.getParent();
340         // Only traditional for statements are examined, not for-each statements
341         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
342             final DetailAST forIterator =
343                 parent.findFirstToken(TokenTypes.FOR_ITERATOR);
344             result = !forIterator.hasChildren();
345         }
346         return result;
347     }
348 
349     /**
350      * Checks that a token precedes an empty for initializer.
351      *
352      * @param ast the token to check
353      * @return whether a token precedes an empty for initializer
354      */
355     private static boolean isPrecedingEmptyForInit(DetailAST ast) {
356         boolean result = false;
357         final DetailAST parent = ast.getParent();
358         // Only traditional for statements are examined, not for-each statements
359         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
360             final DetailAST forIterator =
361                     parent.findFirstToken(TokenTypes.FOR_INIT);
362             result = !forIterator.hasChildren();
363         }
364         return result;
365     }
366 
367 }