001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.CommonUtils;
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 *
053 * @author Eric Roe
054 */
055@FileStatefulCheck
056public class UnnecessaryParenthesesCheck extends AbstractCheck {
057
058    /**
059     * A key is pointing to the warning message text in "messages.properties"
060     * file.
061     */
062    public static final String MSG_IDENT = "unnecessary.paren.ident";
063
064    /**
065     * A key is pointing to the warning message text in "messages.properties"
066     * file.
067     */
068    public static final String MSG_ASSIGN = "unnecessary.paren.assign";
069
070    /**
071     * A key is pointing to the warning message text in "messages.properties"
072     * file.
073     */
074    public static final String MSG_EXPR = "unnecessary.paren.expr";
075
076    /**
077     * A key is pointing to the warning message text in "messages.properties"
078     * file.
079     */
080    public static final String MSG_LITERAL = "unnecessary.paren.literal";
081
082    /**
083     * A key is pointing to the warning message text in "messages.properties"
084     * file.
085     */
086    public static final String MSG_STRING = "unnecessary.paren.string";
087
088    /**
089     * A key is pointing to the warning message text in "messages.properties"
090     * file.
091     */
092    public static final String MSG_RETURN = "unnecessary.paren.return";
093
094    /**
095     * A key is pointing to the warning message text in "messages.properties"
096     * file.
097     */
098    public static final String MSG_LAMBDA = "unnecessary.paren.lambda";
099
100    /** The maximum string length before we chop the string. */
101    private static final int MAX_QUOTED_LENGTH = 25;
102
103    /** Token types for literals. */
104    private static final int[] LITERALS = {
105        TokenTypes.NUM_DOUBLE,
106        TokenTypes.NUM_FLOAT,
107        TokenTypes.NUM_INT,
108        TokenTypes.NUM_LONG,
109        TokenTypes.STRING_LITERAL,
110        TokenTypes.LITERAL_NULL,
111        TokenTypes.LITERAL_FALSE,
112        TokenTypes.LITERAL_TRUE,
113    };
114
115    /** Token types for assignment operations. */
116    private static final int[] ASSIGNMENTS = {
117        TokenTypes.ASSIGN,
118        TokenTypes.BAND_ASSIGN,
119        TokenTypes.BOR_ASSIGN,
120        TokenTypes.BSR_ASSIGN,
121        TokenTypes.BXOR_ASSIGN,
122        TokenTypes.DIV_ASSIGN,
123        TokenTypes.MINUS_ASSIGN,
124        TokenTypes.MOD_ASSIGN,
125        TokenTypes.PLUS_ASSIGN,
126        TokenTypes.SL_ASSIGN,
127        TokenTypes.SR_ASSIGN,
128        TokenTypes.STAR_ASSIGN,
129    };
130
131    /**
132     * Used to test if logging a warning in a parent node may be skipped
133     * because a warning was already logged on an immediate child node.
134     */
135    private DetailAST parentToSkip;
136    /** Depth of nested assignments.  Normally this will be 0 or 1. */
137    private int assignDepth;
138
139    @Override
140    public int[] getDefaultTokens() {
141        return new int[] {
142            TokenTypes.EXPR,
143            TokenTypes.IDENT,
144            TokenTypes.NUM_DOUBLE,
145            TokenTypes.NUM_FLOAT,
146            TokenTypes.NUM_INT,
147            TokenTypes.NUM_LONG,
148            TokenTypes.STRING_LITERAL,
149            TokenTypes.LITERAL_NULL,
150            TokenTypes.LITERAL_FALSE,
151            TokenTypes.LITERAL_TRUE,
152            TokenTypes.ASSIGN,
153            TokenTypes.BAND_ASSIGN,
154            TokenTypes.BOR_ASSIGN,
155            TokenTypes.BSR_ASSIGN,
156            TokenTypes.BXOR_ASSIGN,
157            TokenTypes.DIV_ASSIGN,
158            TokenTypes.MINUS_ASSIGN,
159            TokenTypes.MOD_ASSIGN,
160            TokenTypes.PLUS_ASSIGN,
161            TokenTypes.SL_ASSIGN,
162            TokenTypes.SR_ASSIGN,
163            TokenTypes.STAR_ASSIGN,
164            TokenTypes.LAMBDA,
165        };
166    }
167
168    @Override
169    public int[] getAcceptableTokens() {
170        return new int[] {
171            TokenTypes.EXPR,
172            TokenTypes.IDENT,
173            TokenTypes.NUM_DOUBLE,
174            TokenTypes.NUM_FLOAT,
175            TokenTypes.NUM_INT,
176            TokenTypes.NUM_LONG,
177            TokenTypes.STRING_LITERAL,
178            TokenTypes.LITERAL_NULL,
179            TokenTypes.LITERAL_FALSE,
180            TokenTypes.LITERAL_TRUE,
181            TokenTypes.ASSIGN,
182            TokenTypes.BAND_ASSIGN,
183            TokenTypes.BOR_ASSIGN,
184            TokenTypes.BSR_ASSIGN,
185            TokenTypes.BXOR_ASSIGN,
186            TokenTypes.DIV_ASSIGN,
187            TokenTypes.MINUS_ASSIGN,
188            TokenTypes.MOD_ASSIGN,
189            TokenTypes.PLUS_ASSIGN,
190            TokenTypes.SL_ASSIGN,
191            TokenTypes.SR_ASSIGN,
192            TokenTypes.STAR_ASSIGN,
193            TokenTypes.LAMBDA,
194        };
195    }
196
197    @Override
198    public int[] getRequiredTokens() {
199        // Check can work with any of acceptable tokens
200        return CommonUtils.EMPTY_INT_ARRAY;
201    }
202
203    // -@cs[CyclomaticComplexity] All logs should be in visit token.
204    @Override
205    public void visitToken(DetailAST ast) {
206        final int type = ast.getType();
207        final DetailAST parent = ast.getParent();
208
209        if (type == TokenTypes.LAMBDA && isLambdaSingleParameterSurrounded(ast)) {
210            log(ast, MSG_LAMBDA, ast.getText());
211        }
212        else if (type != TokenTypes.ASSIGN
213            || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
214
215            final boolean surrounded = isSurrounded(ast);
216            // An identifier surrounded by parentheses.
217            if (surrounded && type == TokenTypes.IDENT) {
218                parentToSkip = ast.getParent();
219                log(ast, MSG_IDENT, ast.getText());
220            }
221            // A literal (numeric or string) surrounded by parentheses.
222            else if (surrounded && isInTokenList(type, LITERALS)) {
223                parentToSkip = ast.getParent();
224                if (type == TokenTypes.STRING_LITERAL) {
225                    log(ast, MSG_STRING,
226                        chopString(ast.getText()));
227                }
228                else {
229                    log(ast, MSG_LITERAL, ast.getText());
230                }
231            }
232            // The rhs of an assignment surrounded by parentheses.
233            else if (isInTokenList(type, ASSIGNMENTS)) {
234                assignDepth++;
235                final DetailAST last = ast.getLastChild();
236                if (last.getType() == TokenTypes.RPAREN) {
237                    log(ast, MSG_ASSIGN);
238                }
239            }
240        }
241    }
242
243    @Override
244    public void leaveToken(DetailAST ast) {
245        final int type = ast.getType();
246        final DetailAST parent = ast.getParent();
247
248        // shouldn't process assign in annotation pairs
249        if (type != TokenTypes.ASSIGN
250            || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
251            // An expression is surrounded by parentheses.
252            if (type == TokenTypes.EXPR) {
253
254                // If 'parentToSkip' == 'ast', then we've already logged a
255                // warning about an immediate child node in visitToken, so we don't
256                // need to log another one here.
257
258                if (parentToSkip != ast && isExprSurrounded(ast)) {
259                    if (assignDepth >= 1) {
260                        log(ast, MSG_ASSIGN);
261                    }
262                    else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) {
263                        log(ast, MSG_RETURN);
264                    }
265                    else {
266                        log(ast, MSG_EXPR);
267                    }
268                }
269
270                parentToSkip = null;
271            }
272            else if (isInTokenList(type, ASSIGNMENTS)) {
273                assignDepth--;
274            }
275        }
276    }
277
278    /**
279     * Tests if the given {@code DetailAST} is surrounded by parentheses.
280     * In short, does {@code ast} have a previous sibling whose type is
281     * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code
282     * TokenTypes.RPAREN}.
283     * @param ast the {@code DetailAST} to check if it is surrounded by
284     *        parentheses.
285     * @return {@code true} if {@code ast} is surrounded by
286     *         parentheses.
287     */
288    private static boolean isSurrounded(DetailAST ast) {
289        // if previous sibling is left parenthesis,
290        // next sibling can't be other than right parenthesis
291        final DetailAST prev = ast.getPreviousSibling();
292        return prev != null && prev.getType() == TokenTypes.LPAREN;
293    }
294
295    /**
296     * Tests if the given expression node is surrounded by parentheses.
297     * @param ast a {@code DetailAST} whose type is
298     *        {@code TokenTypes.EXPR}.
299     * @return {@code true} if the expression is surrounded by
300     *         parentheses.
301     */
302    private static boolean isExprSurrounded(DetailAST ast) {
303        return ast.getFirstChild().getType() == TokenTypes.LPAREN;
304    }
305
306    /**
307     * Tests if the given lambda node has a single parameter, no defined type, and is surrounded
308     * by parentheses.
309     * @param ast a {@code DetailAST} whose type is
310     *        {@code TokenTypes.LAMBDA}.
311     * @return {@code true} if the lambda has a single parameter, no defined type, and is
312     *         surrounded by parentheses.
313     */
314    private static boolean isLambdaSingleParameterSurrounded(DetailAST ast) {
315        final DetailAST firstChild = ast.getFirstChild();
316        return firstChild.getType() == TokenTypes.LPAREN
317                && firstChild.getNextSibling().getChildCount(TokenTypes.PARAMETER_DEF) == 1
318                && firstChild.getNextSibling().getFirstChild().findFirstToken(TokenTypes.TYPE)
319                        .getChildCount() == 0;
320    }
321
322    /**
323     * Check if the given token type can be found in an array of token types.
324     * @param type the token type.
325     * @param tokens an array of token types to search.
326     * @return {@code true} if {@code type} was found in {@code
327     *         tokens}.
328     */
329    private static boolean isInTokenList(int type, int... tokens) {
330        // NOTE: Given the small size of the two arrays searched, I'm not sure
331        //       it's worth bothering with doing a binary search or using a
332        //       HashMap to do the searches.
333
334        boolean found = false;
335        for (int i = 0; i < tokens.length && !found; i++) {
336            found = tokens[i] == type;
337        }
338        return found;
339    }
340
341    /**
342     * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH}
343     * plus an ellipsis (...) if the length of the string exceeds {@code
344     * MAX_QUOTED_LENGTH}.
345     * @param value the string to potentially chop.
346     * @return the chopped string if {@code string} is longer than
347     *         {@code MAX_QUOTED_LENGTH}; otherwise {@code string}.
348     */
349    private static String chopString(String value) {
350        String result = value;
351        if (value.length() > MAX_QUOTED_LENGTH) {
352            result = value.substring(0, MAX_QUOTED_LENGTH) + "...\"";
353        }
354        return result;
355    }
356}