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.api.AbstractCheck;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
026
027/**
028 * <p>
029 * Checks if unnecessary parentheses are used in a statement or expression.
030 * The check will flag the following with warnings:
031 * </p>
032 * <pre>
033 *     return (x);          // parens around identifier
034 *     return (x + 1);      // parens around return value
035 *     int x = (y / 2 + 1); // parens around assignment rhs
036 *     for (int i = (0); i &lt; 10; i++) {  // parens around literal
037 *     t -= (z + 1);        // parens around assignment rhs</pre>
038 * <p>
039 * The check is not "type aware", that is to say, it can't tell if parentheses
040 * are unnecessary based on the types in an expression.  It also doesn't know
041 * about operator precedence and associativity; therefore it won't catch
042 * something like
043 * </p>
044 * <pre>
045 *     int x = (a + b) + c;</pre>
046 * <p>
047 * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are
048 * all {@code int} variables, the parentheses around {@code a + b}
049 * are not needed.
050 * </p>
051 *
052 * @author Eric Roe
053 */
054public class UnnecessaryParenthesesCheck extends AbstractCheck {
055
056    /**
057     * A key is pointing to the warning message text in "messages.properties"
058     * file.
059     */
060    public static final String MSG_IDENT = "unnecessary.paren.ident";
061
062    /**
063     * A key is pointing to the warning message text in "messages.properties"
064     * file.
065     */
066    public static final String MSG_ASSIGN = "unnecessary.paren.assign";
067
068    /**
069     * A key is pointing to the warning message text in "messages.properties"
070     * file.
071     */
072    public static final String MSG_EXPR = "unnecessary.paren.expr";
073
074    /**
075     * A key is pointing to the warning message text in "messages.properties"
076     * file.
077     */
078    public static final String MSG_LITERAL = "unnecessary.paren.literal";
079
080    /**
081     * A key is pointing to the warning message text in "messages.properties"
082     * file.
083     */
084    public static final String MSG_STRING = "unnecessary.paren.string";
085
086    /**
087     * A key is pointing to the warning message text in "messages.properties"
088     * file.
089     */
090    public static final String MSG_RETURN = "unnecessary.paren.return";
091
092    /** The maximum string length before we chop the string. */
093    private static final int MAX_QUOTED_LENGTH = 25;
094
095    /** Token types for literals. */
096    private static final int[] LITERALS = {
097        TokenTypes.NUM_DOUBLE,
098        TokenTypes.NUM_FLOAT,
099        TokenTypes.NUM_INT,
100        TokenTypes.NUM_LONG,
101        TokenTypes.STRING_LITERAL,
102        TokenTypes.LITERAL_NULL,
103        TokenTypes.LITERAL_FALSE,
104        TokenTypes.LITERAL_TRUE,
105    };
106
107    /** Token types for assignment operations. */
108    private static final int[] ASSIGNMENTS = {
109        TokenTypes.ASSIGN,
110        TokenTypes.BAND_ASSIGN,
111        TokenTypes.BOR_ASSIGN,
112        TokenTypes.BSR_ASSIGN,
113        TokenTypes.BXOR_ASSIGN,
114        TokenTypes.DIV_ASSIGN,
115        TokenTypes.MINUS_ASSIGN,
116        TokenTypes.MOD_ASSIGN,
117        TokenTypes.PLUS_ASSIGN,
118        TokenTypes.SL_ASSIGN,
119        TokenTypes.SR_ASSIGN,
120        TokenTypes.STAR_ASSIGN,
121    };
122
123    /**
124     * Used to test if logging a warning in a parent node may be skipped
125     * because a warning was already logged on an immediate child node.
126     */
127    private DetailAST parentToSkip;
128    /** Depth of nested assignments.  Normally this will be 0 or 1. */
129    private int assignDepth;
130
131    @Override
132    public int[] getDefaultTokens() {
133        return new int[] {
134            TokenTypes.EXPR,
135            TokenTypes.IDENT,
136            TokenTypes.NUM_DOUBLE,
137            TokenTypes.NUM_FLOAT,
138            TokenTypes.NUM_INT,
139            TokenTypes.NUM_LONG,
140            TokenTypes.STRING_LITERAL,
141            TokenTypes.LITERAL_NULL,
142            TokenTypes.LITERAL_FALSE,
143            TokenTypes.LITERAL_TRUE,
144            TokenTypes.ASSIGN,
145            TokenTypes.BAND_ASSIGN,
146            TokenTypes.BOR_ASSIGN,
147            TokenTypes.BSR_ASSIGN,
148            TokenTypes.BXOR_ASSIGN,
149            TokenTypes.DIV_ASSIGN,
150            TokenTypes.MINUS_ASSIGN,
151            TokenTypes.MOD_ASSIGN,
152            TokenTypes.PLUS_ASSIGN,
153            TokenTypes.SL_ASSIGN,
154            TokenTypes.SR_ASSIGN,
155            TokenTypes.STAR_ASSIGN,
156        };
157    }
158
159    @Override
160    public int[] getAcceptableTokens() {
161        return new int[] {
162            TokenTypes.EXPR,
163            TokenTypes.IDENT,
164            TokenTypes.NUM_DOUBLE,
165            TokenTypes.NUM_FLOAT,
166            TokenTypes.NUM_INT,
167            TokenTypes.NUM_LONG,
168            TokenTypes.STRING_LITERAL,
169            TokenTypes.LITERAL_NULL,
170            TokenTypes.LITERAL_FALSE,
171            TokenTypes.LITERAL_TRUE,
172            TokenTypes.ASSIGN,
173            TokenTypes.BAND_ASSIGN,
174            TokenTypes.BOR_ASSIGN,
175            TokenTypes.BSR_ASSIGN,
176            TokenTypes.BXOR_ASSIGN,
177            TokenTypes.DIV_ASSIGN,
178            TokenTypes.MINUS_ASSIGN,
179            TokenTypes.MOD_ASSIGN,
180            TokenTypes.PLUS_ASSIGN,
181            TokenTypes.SL_ASSIGN,
182            TokenTypes.SR_ASSIGN,
183            TokenTypes.STAR_ASSIGN,
184        };
185    }
186
187    @Override
188    public int[] getRequiredTokens() {
189        // Check can work with any of acceptable tokens
190        return CommonUtils.EMPTY_INT_ARRAY;
191    }
192
193    @Override
194    public void visitToken(DetailAST ast) {
195        final int type = ast.getType();
196        final DetailAST parent = ast.getParent();
197
198        if (type != TokenTypes.ASSIGN
199            || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
200
201            final boolean surrounded = isSurrounded(ast);
202            // An identifier surrounded by parentheses.
203            if (surrounded && type == TokenTypes.IDENT) {
204                parentToSkip = ast.getParent();
205                log(ast, MSG_IDENT, ast.getText());
206            }
207            // A literal (numeric or string) surrounded by parentheses.
208            else if (surrounded && isInTokenList(type, LITERALS)) {
209                parentToSkip = ast.getParent();
210                if (type == TokenTypes.STRING_LITERAL) {
211                    log(ast, MSG_STRING,
212                        chopString(ast.getText()));
213                }
214                else {
215                    log(ast, MSG_LITERAL, ast.getText());
216                }
217            }
218            // The rhs of an assignment surrounded by parentheses.
219            else if (isInTokenList(type, ASSIGNMENTS)) {
220                assignDepth++;
221                final DetailAST last = ast.getLastChild();
222                if (last.getType() == TokenTypes.RPAREN) {
223                    log(ast, MSG_ASSIGN);
224                }
225            }
226        }
227    }
228
229    @Override
230    public void leaveToken(DetailAST ast) {
231        final int type = ast.getType();
232        final DetailAST parent = ast.getParent();
233
234        // shouldn't process assign in annotation pairs
235        if (type != TokenTypes.ASSIGN
236            || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
237            // An expression is surrounded by parentheses.
238            if (type == TokenTypes.EXPR) {
239
240                // If 'parentToSkip' == 'ast', then we've already logged a
241                // warning about an immediate child node in visitToken, so we don't
242                // need to log another one here.
243
244                if (parentToSkip != ast && isExprSurrounded(ast)) {
245                    if (assignDepth >= 1) {
246                        log(ast, MSG_ASSIGN);
247                    }
248                    else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) {
249                        log(ast, MSG_RETURN);
250                    }
251                    else {
252                        log(ast, MSG_EXPR);
253                    }
254                }
255
256                parentToSkip = null;
257            }
258            else if (isInTokenList(type, ASSIGNMENTS)) {
259                assignDepth--;
260            }
261
262            super.leaveToken(ast);
263        }
264    }
265
266    /**
267     * Tests if the given {@code DetailAST} is surrounded by parentheses.
268     * In short, does {@code ast} have a previous sibling whose type is
269     * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code
270     * TokenTypes.RPAREN}.
271     * @param ast the {@code DetailAST} to check if it is surrounded by
272     *        parentheses.
273     * @return {@code true} if {@code ast} is surrounded by
274     *         parentheses.
275     */
276    private static boolean isSurrounded(DetailAST ast) {
277        // if previous sibling is left parenthesis,
278        // next sibling can't be other than right parenthesis
279        final DetailAST prev = ast.getPreviousSibling();
280        return prev != null && prev.getType() == TokenTypes.LPAREN;
281    }
282
283    /**
284     * Tests if the given expression node is surrounded by parentheses.
285     * @param ast a {@code DetailAST} whose type is
286     *        {@code TokenTypes.EXPR}.
287     * @return {@code true} if the expression is surrounded by
288     *         parentheses.
289     */
290    private static boolean isExprSurrounded(DetailAST ast) {
291        return ast.getFirstChild().getType() == TokenTypes.LPAREN;
292    }
293
294    /**
295     * Check if the given token type can be found in an array of token types.
296     * @param type the token type.
297     * @param tokens an array of token types to search.
298     * @return {@code true} if {@code type} was found in {@code
299     *         tokens}.
300     */
301    private static boolean isInTokenList(int type, int... tokens) {
302        // NOTE: Given the small size of the two arrays searched, I'm not sure
303        //       it's worth bothering with doing a binary search or using a
304        //       HashMap to do the searches.
305
306        boolean found = false;
307        for (int i = 0; i < tokens.length && !found; i++) {
308            found = tokens[i] == type;
309        }
310        return found;
311    }
312
313    /**
314     * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH}
315     * plus an ellipsis (...) if the length of the string exceeds {@code
316     * MAX_QUOTED_LENGTH}.
317     * @param value the string to potentially chop.
318     * @return the chopped string if {@code string} is longer than
319     *         {@code MAX_QUOTED_LENGTH}; otherwise {@code string}.
320     */
321    private static String chopString(String value) {
322        if (value.length() > MAX_QUOTED_LENGTH) {
323            return value.substring(0, MAX_QUOTED_LENGTH) + "...\"";
324        }
325        return value;
326    }
327}