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