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   *
47   * @since 3.0
48   */
49  public class ParenPadCheck extends AbstractParenPadCheck {
50  
51      /**
52       * Tokens that this check handles.
53       */
54      private final BitSet acceptableTokens;
55  
56      /**
57       * Initializes acceptableTokens.
58       */
59      public ParenPadCheck() {
60          acceptableTokens = TokenUtil.asBitSet(makeAcceptableTokens());
61      }
62  
63      @Override
64      public int[] getDefaultTokens() {
65          return makeAcceptableTokens();
66      }
67  
68      @Override
69      public int[] getAcceptableTokens() {
70          return makeAcceptableTokens();
71      }
72  
73      @Override
74      public int[] getRequiredTokens() {
75          return CommonUtil.EMPTY_INT_ARRAY;
76      }
77  
78      @Override
79      public void visitToken(DetailAST ast) {
80          switch (ast.getType()) {
81              case TokenTypes.METHOD_CALL -> {
82                  processLeft(ast);
83                  processRight(ast.findFirstToken(TokenTypes.RPAREN));
84              }
85  
86              case TokenTypes.DOT, TokenTypes.EXPR, TokenTypes.QUESTION -> processExpression(ast);
87  
88              case TokenTypes.LITERAL_FOR -> visitLiteralFor(ast);
89  
90              case TokenTypes.ANNOTATION,
91                   TokenTypes.ENUM_CONSTANT_DEF,
92                   TokenTypes.LITERAL_NEW,
93                   TokenTypes.LITERAL_SYNCHRONIZED,
94                   TokenTypes.LAMBDA -> visitTokenWithOptionalParentheses(ast);
95  
96              case TokenTypes.RESOURCE_SPECIFICATION -> visitResourceSpecification(ast);
97  
98              default -> {
99                  processLeft(ast.findFirstToken(TokenTypes.LPAREN));
100                 processRight(ast.findFirstToken(TokenTypes.RPAREN));
101             }
102         }
103     }
104 
105     /**
106      * Checks parens in token which may not contain parens, e.g.
107      * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION}
108      * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and
109      * {@link TokenTypes#LAMBDA}.
110      *
111      * @param ast the token to check.
112      */
113     private void visitTokenWithOptionalParentheses(DetailAST ast) {
114         final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN);
115         if (parenAst != null) {
116             processLeft(parenAst);
117             processRight(ast.findFirstToken(TokenTypes.RPAREN));
118         }
119     }
120 
121     /**
122      * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}.
123      *
124      * @param ast the token to check.
125      */
126     private void visitResourceSpecification(DetailAST ast) {
127         processLeft(ast.findFirstToken(TokenTypes.LPAREN));
128         final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
129         if (!hasPrecedingSemiColon(rparen)) {
130             processRight(rparen);
131         }
132     }
133 
134     /**
135      * Checks that a token is preceded by a semicolon.
136      *
137      * @param ast the token to check
138      * @return whether a token is preceded by a semicolon
139      */
140     private static boolean hasPrecedingSemiColon(DetailAST ast) {
141         return ast.getPreviousSibling().getType() == TokenTypes.SEMI;
142     }
143 
144     /**
145      * Checks parens in {@link TokenTypes#LITERAL_FOR}.
146      *
147      * @param ast the token to check.
148      */
149     private void visitLiteralFor(DetailAST ast) {
150         final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN);
151         if (!isPrecedingEmptyForInit(lparen)) {
152             processLeft(lparen);
153         }
154         final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
155         if (!isFollowsEmptyForIterator(rparen)) {
156             processRight(rparen);
157         }
158     }
159 
160     /**
161      * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION}
162      * and {@link TokenTypes#METHOD_CALL}.
163      *
164      * @param ast the token to check.
165      */
166     private void processExpression(DetailAST ast) {
167         DetailAST currentNode = ast.getFirstChild();
168         while (currentNode != null) {
169             if (currentNode.getType() == TokenTypes.LPAREN) {
170                 processLeft(currentNode);
171             }
172             else if (currentNode.getType() == TokenTypes.RPAREN && !isInTypecast(currentNode)) {
173                 processRight(currentNode);
174             }
175             else if (currentNode.hasChildren() && !isAcceptableToken(currentNode)) {
176                 // Traverse all subtree tokens which will never be configured
177                 // to be launched in visitToken()
178                 currentNode = currentNode.getFirstChild();
179                 continue;
180             }
181 
182             // Go up after processing the last child
183             while (currentNode.getNextSibling() == null && currentNode.getParent() != ast) {
184                 currentNode = currentNode.getParent();
185             }
186             currentNode = currentNode.getNextSibling();
187         }
188     }
189 
190     /**
191      * Checks whether AcceptableTokens contains the given ast.
192      *
193      * @param ast the token to check.
194      * @return true if the ast is in AcceptableTokens.
195      */
196     private boolean isAcceptableToken(DetailAST ast) {
197         return acceptableTokens.get(ast.getType());
198     }
199 
200     /**
201      * Returns array of acceptable tokens.
202      *
203      * @return acceptableTokens.
204      */
205     private static int[] makeAcceptableTokens() {
206         return new int[] {TokenTypes.ANNOTATION,
207             TokenTypes.ANNOTATION_FIELD_DEF,
208             TokenTypes.CTOR_CALL,
209             TokenTypes.CTOR_DEF,
210             TokenTypes.DOT,
211             TokenTypes.ENUM_CONSTANT_DEF,
212             TokenTypes.EXPR,
213             TokenTypes.LITERAL_CATCH,
214             TokenTypes.LITERAL_DO,
215             TokenTypes.LITERAL_FOR,
216             TokenTypes.LITERAL_IF,
217             TokenTypes.LITERAL_NEW,
218             TokenTypes.LITERAL_SWITCH,
219             TokenTypes.LITERAL_SYNCHRONIZED,
220             TokenTypes.LITERAL_WHILE,
221             TokenTypes.METHOD_CALL,
222             TokenTypes.METHOD_DEF,
223             TokenTypes.QUESTION,
224             TokenTypes.RESOURCE_SPECIFICATION,
225             TokenTypes.SUPER_CTOR_CALL,
226             TokenTypes.LAMBDA,
227             TokenTypes.RECORD_DEF,
228             TokenTypes.RECORD_PATTERN_DEF,
229         };
230     }
231 
232     /**
233      * Checks whether {@link TokenTypes#RPAREN} is a closing paren
234      * of a {@link TokenTypes#TYPECAST}.
235      *
236      * @param ast of a {@link TokenTypes#RPAREN} to check.
237      * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}.
238      */
239     private static boolean isInTypecast(DetailAST ast) {
240         boolean result = false;
241         if (ast.getParent().getType() == TokenTypes.TYPECAST) {
242             final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN);
243             if (TokenUtil.areOnSameLine(firstRparen, ast)
244                     && firstRparen.getColumnNo() == ast.getColumnNo()) {
245                 result = true;
246             }
247         }
248         return result;
249     }
250 
251     /**
252      * Checks that a token follows an empty for iterator.
253      *
254      * @param ast the token to check
255      * @return whether a token follows an empty for iterator
256      */
257     private static boolean isFollowsEmptyForIterator(DetailAST ast) {
258         boolean result = false;
259         final DetailAST parent = ast.getParent();
260         // Only traditional for statements are examined, not for-each statements
261         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
262             final DetailAST forIterator =
263                 parent.findFirstToken(TokenTypes.FOR_ITERATOR);
264             result = !forIterator.hasChildren();
265         }
266         return result;
267     }
268 
269     /**
270      * Checks that a token precedes an empty for initializer.
271      *
272      * @param ast the token to check
273      * @return whether a token precedes an empty for initializer
274      */
275     private static boolean isPrecedingEmptyForInit(DetailAST ast) {
276         boolean result = false;
277         final DetailAST parent = ast.getParent();
278         // Only traditional for statements are examined, not for-each statements
279         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
280             final DetailAST forIterator =
281                     parent.findFirstToken(TokenTypes.FOR_INIT);
282             result = !forIterator.hasChildren();
283         }
284         return result;
285     }
286 
287 }