001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.coding;
021
022import java.util.regex.Pattern;
023
024import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029
030/**
031 * <p>
032 * Checks if unnecessary parentheses are used in a statement or expression.
033 * The check will flag the following with warnings:
034 * </p>
035 * <pre>
036 *     return (x);          // parens around identifier
037 *     return (x + 1);      // parens around return value
038 *     int x = (y / 2 + 1); // parens around assignment rhs
039 *     for (int i = (0); i &lt; 10; i++) {  // parens around literal
040 *     t -= (z + 1);        // parens around assignment rhs</pre>
041 * <p>
042 * The check is not "type aware", that is to say, it can't tell if parentheses
043 * are unnecessary based on the types in an expression.  It also doesn't know
044 * about operator precedence and associativity; therefore it won't catch
045 * something like
046 * </p>
047 * <pre>
048 *     int x = (a + b) + c;</pre>
049 * <p>
050 * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are
051 * all {@code int} variables, the parentheses around {@code a + b}
052 * are not needed.
053 * </p>
054 * <ul>
055 * <li>
056 * Property {@code tokens} - tokens to check
057 * Type is {@code java.lang.String[]}.
058 * Validation type is {@code tokenSet}.
059 * Default value is:
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
061 * EXPR</a>,
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#IDENT">
063 * IDENT</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE">
065 * NUM_DOUBLE</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT">
067 * NUM_FLOAT</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT">
069 * NUM_INT</a>,
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG">
071 * NUM_LONG</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STRING_LITERAL">
073 * STRING_LITERAL</a>,
074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NULL">
075 * LITERAL_NULL</a>,
076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FALSE">
077 * LITERAL_FALSE</a>,
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRUE">
079 * LITERAL_TRUE</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN">
081 * ASSIGN</a>,
082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND_ASSIGN">
083 * BAND_ASSIGN</a>,
084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR_ASSIGN">
085 * BOR_ASSIGN</a>,
086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR_ASSIGN">
087 * BSR_ASSIGN</a>,
088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR_ASSIGN">
089 * BXOR_ASSIGN</a>,
090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV_ASSIGN">
091 * DIV_ASSIGN</a>,
092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS_ASSIGN">
093 * MINUS_ASSIGN</a>,
094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD_ASSIGN">
095 * MOD_ASSIGN</a>,
096 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS_ASSIGN">
097 * PLUS_ASSIGN</a>,
098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL_ASSIGN">
099 * SL_ASSIGN</a>,
100 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR_ASSIGN">
101 * SR_ASSIGN</a>,
102 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR_ASSIGN">
103 * STAR_ASSIGN</a>,
104 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
105 * LAMBDA</a>,
106 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TEXT_BLOCK_LITERAL_BEGIN">
107 * TEXT_BLOCK_LITERAL_BEGIN</a>.
108 * </li>
109 * </ul>
110 * <p>
111 * To configure the check:
112 * </p>
113 * <pre>
114 * &lt;module name=&quot;UnnecessaryParentheses&quot;/&gt;
115 * </pre>
116 * <p>
117 * Which results in the following violations:
118 * </p>
119 * <pre>
120 * public int square(int a, int b){
121 *   int square = (a * b); //violation
122 *   return (square); //violation
123 * }
124 * int sumOfSquares = 0;
125 * for(int i=(0); i&lt;10; i++){ //violation
126 *   int x = (i + 1); //violation
127 *   sumOfSquares += (square(x * x)); //violation
128 * }
129 * double num = (10.0); //violation
130 * List&lt;String&gt; list = Arrays.asList(&quot;a1&quot;, &quot;b1&quot;, &quot;c1&quot;);
131 * myList.stream()
132 *   .filter((s) -&gt; s.startsWith(&quot;c&quot;)) //violation
133 *   .forEach(System.out::println);
134 * </pre>
135 * <p>
136 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
137 * </p>
138 * <p>
139 * Violation Message Keys:
140 * </p>
141 * <ul>
142 * <li>
143 * {@code unnecessary.paren.assign}
144 * </li>
145 * <li>
146 * {@code unnecessary.paren.expr}
147 * </li>
148 * <li>
149 * {@code unnecessary.paren.ident}
150 * </li>
151 * <li>
152 * {@code unnecessary.paren.lambda}
153 * </li>
154 * <li>
155 * {@code unnecessary.paren.literal}
156 * </li>
157 * <li>
158 * {@code unnecessary.paren.return}
159 * </li>
160 * <li>
161 * {@code unnecessary.paren.string}
162 * </li>
163 * </ul>
164 *
165 * @since 3.4
166 */
167@FileStatefulCheck
168public class UnnecessaryParenthesesCheck extends AbstractCheck {
169
170    /**
171     * A key is pointing to the warning message text in "messages.properties"
172     * file.
173     */
174    public static final String MSG_IDENT = "unnecessary.paren.ident";
175
176    /**
177     * A key is pointing to the warning message text in "messages.properties"
178     * file.
179     */
180    public static final String MSG_ASSIGN = "unnecessary.paren.assign";
181
182    /**
183     * A key is pointing to the warning message text in "messages.properties"
184     * file.
185     */
186    public static final String MSG_EXPR = "unnecessary.paren.expr";
187
188    /**
189     * A key is pointing to the warning message text in "messages.properties"
190     * file.
191     */
192    public static final String MSG_LITERAL = "unnecessary.paren.literal";
193
194    /**
195     * A key is pointing to the warning message text in "messages.properties"
196     * file.
197     */
198    public static final String MSG_STRING = "unnecessary.paren.string";
199
200    /**
201     * A key is pointing to the warning message text in "messages.properties"
202     * file.
203     */
204    public static final String MSG_RETURN = "unnecessary.paren.return";
205
206    /**
207     * A key is pointing to the warning message text in "messages.properties"
208     * file.
209     */
210    public static final String MSG_LAMBDA = "unnecessary.paren.lambda";
211
212    /**
213     * Compiled pattern used to match newline control characters, for replacement.
214     */
215    private static final Pattern NEWLINE = Pattern.compile("\\R");
216
217    /**
218     * String used to amend TEXT_BLOCK_CONTENT so that it matches STRING_LITERAL.
219     */
220    private static final String QUOTE = "\"";
221
222    /** The maximum string length before we chop the string. */
223    private static final int MAX_QUOTED_LENGTH = 25;
224
225    /** Token types for literals. */
226    private static final int[] LITERALS = {
227        TokenTypes.NUM_DOUBLE,
228        TokenTypes.NUM_FLOAT,
229        TokenTypes.NUM_INT,
230        TokenTypes.NUM_LONG,
231        TokenTypes.STRING_LITERAL,
232        TokenTypes.LITERAL_NULL,
233        TokenTypes.LITERAL_FALSE,
234        TokenTypes.LITERAL_TRUE,
235        TokenTypes.TEXT_BLOCK_LITERAL_BEGIN,
236    };
237
238    /** Token types for assignment operations. */
239    private static final int[] ASSIGNMENTS = {
240        TokenTypes.ASSIGN,
241        TokenTypes.BAND_ASSIGN,
242        TokenTypes.BOR_ASSIGN,
243        TokenTypes.BSR_ASSIGN,
244        TokenTypes.BXOR_ASSIGN,
245        TokenTypes.DIV_ASSIGN,
246        TokenTypes.MINUS_ASSIGN,
247        TokenTypes.MOD_ASSIGN,
248        TokenTypes.PLUS_ASSIGN,
249        TokenTypes.SL_ASSIGN,
250        TokenTypes.SR_ASSIGN,
251        TokenTypes.STAR_ASSIGN,
252    };
253
254    /**
255     * Used to test if logging a warning in a parent node may be skipped
256     * because a warning was already logged on an immediate child node.
257     */
258    private DetailAST parentToSkip;
259    /** Depth of nested assignments.  Normally this will be 0 or 1. */
260    private int assignDepth;
261
262    @Override
263    public int[] getDefaultTokens() {
264        return new int[] {
265            TokenTypes.EXPR,
266            TokenTypes.IDENT,
267            TokenTypes.NUM_DOUBLE,
268            TokenTypes.NUM_FLOAT,
269            TokenTypes.NUM_INT,
270            TokenTypes.NUM_LONG,
271            TokenTypes.STRING_LITERAL,
272            TokenTypes.LITERAL_NULL,
273            TokenTypes.LITERAL_FALSE,
274            TokenTypes.LITERAL_TRUE,
275            TokenTypes.ASSIGN,
276            TokenTypes.BAND_ASSIGN,
277            TokenTypes.BOR_ASSIGN,
278            TokenTypes.BSR_ASSIGN,
279            TokenTypes.BXOR_ASSIGN,
280            TokenTypes.DIV_ASSIGN,
281            TokenTypes.MINUS_ASSIGN,
282            TokenTypes.MOD_ASSIGN,
283            TokenTypes.PLUS_ASSIGN,
284            TokenTypes.SL_ASSIGN,
285            TokenTypes.SR_ASSIGN,
286            TokenTypes.STAR_ASSIGN,
287            TokenTypes.LAMBDA,
288            TokenTypes.TEXT_BLOCK_LITERAL_BEGIN,
289        };
290    }
291
292    @Override
293    public int[] getAcceptableTokens() {
294        return new int[] {
295            TokenTypes.EXPR,
296            TokenTypes.IDENT,
297            TokenTypes.NUM_DOUBLE,
298            TokenTypes.NUM_FLOAT,
299            TokenTypes.NUM_INT,
300            TokenTypes.NUM_LONG,
301            TokenTypes.STRING_LITERAL,
302            TokenTypes.LITERAL_NULL,
303            TokenTypes.LITERAL_FALSE,
304            TokenTypes.LITERAL_TRUE,
305            TokenTypes.ASSIGN,
306            TokenTypes.BAND_ASSIGN,
307            TokenTypes.BOR_ASSIGN,
308            TokenTypes.BSR_ASSIGN,
309            TokenTypes.BXOR_ASSIGN,
310            TokenTypes.DIV_ASSIGN,
311            TokenTypes.MINUS_ASSIGN,
312            TokenTypes.MOD_ASSIGN,
313            TokenTypes.PLUS_ASSIGN,
314            TokenTypes.SL_ASSIGN,
315            TokenTypes.SR_ASSIGN,
316            TokenTypes.STAR_ASSIGN,
317            TokenTypes.LAMBDA,
318            TokenTypes.TEXT_BLOCK_LITERAL_BEGIN,
319        };
320    }
321
322    @Override
323    public int[] getRequiredTokens() {
324        // Check can work with any of acceptable tokens
325        return CommonUtil.EMPTY_INT_ARRAY;
326    }
327
328    // -@cs[CyclomaticComplexity] All logs should be in visit token.
329    @Override
330    public void visitToken(DetailAST ast) {
331        final int type = ast.getType();
332        final DetailAST parent = ast.getParent();
333
334        if (type == TokenTypes.LAMBDA && isLambdaSingleParameterSurrounded(ast)) {
335            log(ast, MSG_LAMBDA, ast.getText());
336        }
337        else if (type != TokenTypes.ASSIGN
338            || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
339            final boolean surrounded = isSurrounded(ast);
340            // An identifier surrounded by parentheses.
341            if (surrounded && type == TokenTypes.IDENT) {
342                parentToSkip = ast.getParent();
343                log(ast, MSG_IDENT, ast.getText());
344            }
345            // A literal (numeric or string) surrounded by parentheses.
346            else if (surrounded && isInTokenList(type, LITERALS)) {
347                parentToSkip = ast.getParent();
348                if (type == TokenTypes.STRING_LITERAL) {
349                    log(ast, MSG_STRING,
350                        chopString(ast.getText()));
351                }
352                else if (type == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) {
353                    // Strip newline control characters to keep message as single line, add
354                    // quotes to make string consistent with STRING_LITERAL
355                    final String logString = QUOTE
356                        + NEWLINE.matcher(
357                            ast.getFirstChild().getText()).replaceAll("\\\\n")
358                        + QUOTE;
359                    log(ast, MSG_STRING, chopString(logString));
360                }
361                else {
362                    log(ast, MSG_LITERAL, ast.getText());
363                }
364            }
365            // The rhs of an assignment surrounded by parentheses.
366            else if (isInTokenList(type, ASSIGNMENTS)) {
367                assignDepth++;
368                final DetailAST last = ast.getLastChild();
369                if (last.getType() == TokenTypes.RPAREN) {
370                    log(ast, MSG_ASSIGN);
371                }
372            }
373        }
374    }
375
376    @Override
377    public void leaveToken(DetailAST ast) {
378        final int type = ast.getType();
379        final DetailAST parent = ast.getParent();
380
381        // shouldn't process assign in annotation pairs
382        if (type != TokenTypes.ASSIGN
383            || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
384            // An expression is surrounded by parentheses.
385            if (type == TokenTypes.EXPR) {
386                // If 'parentToSkip' == 'ast', then we've already logged a
387                // warning about an immediate child node in visitToken, so we don't
388                // need to log another one here.
389
390                if (parentToSkip != ast && isExprSurrounded(ast)) {
391                    if (assignDepth >= 1) {
392                        log(ast, MSG_ASSIGN);
393                    }
394                    else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) {
395                        log(ast, MSG_RETURN);
396                    }
397                    else {
398                        log(ast, MSG_EXPR);
399                    }
400                }
401
402                parentToSkip = null;
403            }
404            else if (isInTokenList(type, ASSIGNMENTS)) {
405                assignDepth--;
406            }
407        }
408    }
409
410    /**
411     * Tests if the given {@code DetailAST} is surrounded by parentheses.
412     * In short, does {@code ast} have a previous sibling whose type is
413     * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code
414     * TokenTypes.RPAREN}.
415     *
416     * @param ast the {@code DetailAST} to check if it is surrounded by
417     *        parentheses.
418     * @return {@code true} if {@code ast} is surrounded by
419     *         parentheses.
420     */
421    private static boolean isSurrounded(DetailAST ast) {
422        // if previous sibling is left parenthesis,
423        // next sibling can't be other than right parenthesis
424        final DetailAST prev = ast.getPreviousSibling();
425        return prev != null && prev.getType() == TokenTypes.LPAREN;
426    }
427
428    /**
429     * Tests if the given expression node is surrounded by parentheses.
430     *
431     * @param ast a {@code DetailAST} whose type is
432     *        {@code TokenTypes.EXPR}.
433     * @return {@code true} if the expression is surrounded by
434     *         parentheses.
435     */
436    private static boolean isExprSurrounded(DetailAST ast) {
437        return ast.getFirstChild().getType() == TokenTypes.LPAREN;
438    }
439
440    /**
441     * Tests if the given lambda node has a single parameter, no defined type, and is surrounded
442     * by parentheses.
443     *
444     * @param ast a {@code DetailAST} whose type is
445     *        {@code TokenTypes.LAMBDA}.
446     * @return {@code true} if the lambda has a single parameter, no defined type, and is
447     *         surrounded by parentheses.
448     */
449    private static boolean isLambdaSingleParameterSurrounded(DetailAST ast) {
450        final DetailAST firstChild = ast.getFirstChild();
451        boolean result = false;
452        if (firstChild != null && firstChild.getType() == TokenTypes.LPAREN) {
453            final DetailAST parameters = firstChild.getNextSibling();
454            if (parameters.getChildCount(TokenTypes.PARAMETER_DEF) == 1
455                    && !parameters.getFirstChild().findFirstToken(TokenTypes.TYPE).hasChildren()) {
456                result = true;
457            }
458        }
459        return result;
460    }
461
462    /**
463     * Check if the given token type can be found in an array of token types.
464     *
465     * @param type the token type.
466     * @param tokens an array of token types to search.
467     * @return {@code true} if {@code type} was found in {@code
468     *         tokens}.
469     */
470    private static boolean isInTokenList(int type, int... tokens) {
471        // NOTE: Given the small size of the two arrays searched, I'm not sure
472        //       it's worth bothering with doing a binary search or using a
473        //       HashMap to do the searches.
474
475        boolean found = false;
476        for (int i = 0; i < tokens.length && !found; i++) {
477            found = tokens[i] == type;
478        }
479        return found;
480    }
481
482    /**
483     * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH}
484     * plus an ellipsis (...) if the length of the string exceeds {@code
485     * MAX_QUOTED_LENGTH}.
486     *
487     * @param value the string to potentially chop.
488     * @return the chopped string if {@code string} is longer than
489     *         {@code MAX_QUOTED_LENGTH}; otherwise {@code string}.
490     */
491    private static String chopString(String value) {
492        String result = value;
493        if (value.length() > MAX_QUOTED_LENGTH) {
494            result = value.substring(0, MAX_QUOTED_LENGTH) + "...\"";
495        }
496        return result;
497    }
498
499}