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