001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.whitespace;
021
022import java.util.BitSet;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
027import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
028
029/**
030 * <div>
031 * Checks the policy on the padding of parentheses; that is whether a space is required
032 * after a left parenthesis and before a right parenthesis, or such spaces are
033 * forbidden. No check occurs at the right parenthesis after an empty for
034 * iterator, at the left parenthesis before an empty for initialization, or at
035 * the right parenthesis of a try-with-resources resource specification where
036 * the last resource variable has a trailing semicolon.
037 * Use Check
038 * <a href="https://checkstyle.org/checks/whitespace/emptyforiteratorpad.html#EmptyForIteratorPad">
039 * EmptyForIteratorPad</a> to validate empty for iterators and
040 * <a href="https://checkstyle.org/checks/whitespace/emptyforinitializerpad.html#EmptyForInitializerPad">
041 * EmptyForInitializerPad</a> to validate empty for initializers.
042 * Typecasts are also not checked, as there is
043 * <a href="https://checkstyle.org/checks/whitespace/typecastparenpad.html#TypecastParenPad">
044 * TypecastParenPad</a> to validate them.
045 * </div>
046 * <ul>
047 * <li>
048 * Property {@code option} - Specify policy on how to pad parentheses.
049 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption}.
050 * Default value is {@code nospace}.
051 * </li>
052 * <li>
053 * Property {@code tokens} - tokens to check
054 * Type is {@code java.lang.String[]}.
055 * Validation type is {@code tokenSet}.
056 * Default value is:
057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION">
058 * ANNOTATION</a>,
059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
060 * ANNOTATION_FIELD_DEF</a>,
061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_CALL">
062 * CTOR_CALL</a>,
063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
064 * CTOR_DEF</a>,
065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT">
066 * DOT</a>,
067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
068 * ENUM_CONSTANT_DEF</a>,
069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
070 * EXPR</a>,
071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
072 * LITERAL_CATCH</a>,
073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
074 * LITERAL_DO</a>,
075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
076 * LITERAL_FOR</a>,
077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
078 * LITERAL_IF</a>,
079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
080 * LITERAL_NEW</a>,
081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
082 * LITERAL_SWITCH</a>,
083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
084 * LITERAL_SYNCHRONIZED</a>,
085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
086 * LITERAL_WHILE</a>,
087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
088 * METHOD_CALL</a>,
089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
090 * METHOD_DEF</a>,
091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION">
092 * QUESTION</a>,
093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RESOURCE_SPECIFICATION">
094 * RESOURCE_SPECIFICATION</a>,
095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SUPER_CTOR_CALL">
096 * SUPER_CTOR_CALL</a>,
097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
098 * LAMBDA</a>,
099 * <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 */
130public 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}