001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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 antlr.collections.AST;
023import com.puppycrawl.tools.checkstyle.api.Check;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
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 associatvity; 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</code> variables, the parentheses around <code>a + b</code>
049 * are not needed.
050 * </p>
051 *
052 * @author Eric Roe
053 */
054public class UnnecessaryParenthesesCheck extends Check
055{
056    /** The minimum number of child nodes to consider for a match. */
057    private static final int MIN_CHILDREN_FOR_MATCH = 3;
058    /** The maximum string length before we chop the string. */
059    private static final int MAX_QUOTED_LENGTH = 25;
060
061    /** Token types for literals. */
062    private static final int[] LITERALS = {
063        TokenTypes.NUM_DOUBLE,
064        TokenTypes.NUM_FLOAT,
065        TokenTypes.NUM_INT,
066        TokenTypes.NUM_LONG,
067        TokenTypes.STRING_LITERAL,
068        TokenTypes.LITERAL_NULL,
069        TokenTypes.LITERAL_FALSE,
070        TokenTypes.LITERAL_TRUE,
071    };
072
073    /** Token types for assignment operations. */
074    private static final int[] ASSIGNMENTS = {
075        TokenTypes.ASSIGN,
076        TokenTypes.BAND_ASSIGN,
077        TokenTypes.BOR_ASSIGN,
078        TokenTypes.BSR_ASSIGN,
079        TokenTypes.BXOR_ASSIGN,
080        TokenTypes.DIV_ASSIGN,
081        TokenTypes.MINUS_ASSIGN,
082        TokenTypes.MOD_ASSIGN,
083        TokenTypes.PLUS_ASSIGN,
084        TokenTypes.SL_ASSIGN,
085        TokenTypes.SR_ASSIGN,
086        TokenTypes.STAR_ASSIGN,
087    };
088
089    /**
090     * Used to test if logging a warning in a parent node may be skipped
091     * because a warning was already logged on an immediate child node.
092     */
093    private DetailAST parentToSkip;
094    /** Depth of nested assignments.  Normally this will be 0 or 1. */
095    private int assignDepth;
096
097    @Override
098    public int[] getDefaultTokens()
099    {
100        return new int[] {
101            TokenTypes.EXPR,
102            TokenTypes.IDENT,
103            TokenTypes.NUM_DOUBLE,
104            TokenTypes.NUM_FLOAT,
105            TokenTypes.NUM_INT,
106            TokenTypes.NUM_LONG,
107            TokenTypes.STRING_LITERAL,
108            TokenTypes.LITERAL_NULL,
109            TokenTypes.LITERAL_FALSE,
110            TokenTypes.LITERAL_TRUE,
111            TokenTypes.ASSIGN,
112            TokenTypes.BAND_ASSIGN,
113            TokenTypes.BOR_ASSIGN,
114            TokenTypes.BSR_ASSIGN,
115            TokenTypes.BXOR_ASSIGN,
116            TokenTypes.DIV_ASSIGN,
117            TokenTypes.MINUS_ASSIGN,
118            TokenTypes.MOD_ASSIGN,
119            TokenTypes.PLUS_ASSIGN,
120            TokenTypes.SL_ASSIGN,
121            TokenTypes.SR_ASSIGN,
122            TokenTypes.STAR_ASSIGN,
123        };
124    }
125
126    @Override
127    public void visitToken(DetailAST ast)
128    {
129        final int type = ast.getType();
130        final boolean surrounded = isSurrounded(ast);
131        final DetailAST parent = ast.getParent();
132
133        if ((type == TokenTypes.ASSIGN)
134            && (parent.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR))
135        {
136            // shouldn't process assign in annotation pairs
137            return;
138        }
139
140        // An identifier surrounded by parentheses.
141        if (surrounded && (type == TokenTypes.IDENT)) {
142            parentToSkip = ast.getParent();
143            log(ast, "unnecessary.paren.ident", ast.getText());
144            return;
145        }
146
147        // A literal (numeric or string) surrounded by parentheses.
148        if (surrounded && inTokenList(type, LITERALS)) {
149            parentToSkip = ast.getParent();
150            if (type == TokenTypes.STRING_LITERAL) {
151                log(ast, "unnecessary.paren.string",
152                    chopString(ast.getText()));
153            }
154            else {
155                log(ast, "unnecessary.paren.literal", ast.getText());
156            }
157            return;
158        }
159
160        // The rhs of an assignment surrounded by parentheses.
161        if (inTokenList(type, ASSIGNMENTS)) {
162            assignDepth++;
163            final DetailAST last = ast.getLastChild();
164            if (last.getType() == TokenTypes.RPAREN) {
165                log(ast, "unnecessary.paren.assign");
166            }
167        }
168    }
169
170    @Override
171    public void leaveToken(DetailAST ast)
172    {
173        final int type = ast.getType();
174        final DetailAST parent = ast.getParent();
175
176        if ((type == TokenTypes.ASSIGN)
177            && (parent.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR))
178        {
179            // shouldn't process assign in annotation pairs
180            return;
181        }
182
183        // An expression is surrounded by parentheses.
184        if (type == TokenTypes.EXPR) {
185
186            // If 'parentToSkip' == 'ast', then we've already logged a
187            // warning about an immediate child node in visitToken, so we don't
188            // need to log another one here.
189
190            if ((parentToSkip != ast) && exprSurrounded(ast)) {
191                if (assignDepth >= 1) {
192                    log(ast, "unnecessary.paren.assign");
193                }
194                else if (ast.getParent().getType()
195                    == TokenTypes.LITERAL_RETURN)
196                {
197                    log(ast, "unnecessary.paren.return");
198                }
199                else {
200                    log(ast, "unnecessary.paren.expr");
201                }
202            }
203
204            parentToSkip = null;
205        }
206        else if (inTokenList(type, ASSIGNMENTS)) {
207            assignDepth--;
208        }
209
210        super.leaveToken(ast);
211    }
212
213    /**
214     * Tests if the given <code>DetailAST</code> is surrounded by parentheses.
215     * In short, does <code>ast</code> have a previous sibling whose type is
216     * <code>TokenTypes.LPAREN</code> and a next sibling whose type is <code>
217     * TokenTypes.RPAREN</code>.
218     * @param ast the <code>DetailAST</code> to check if it is surrounded by
219     *        parentheses.
220     * @return <code>true</code> if <code>ast</code> is surrounded by
221     *         parentheses.
222     */
223    private boolean isSurrounded(DetailAST ast)
224    {
225        final DetailAST prev = ast.getPreviousSibling();
226        final DetailAST next = ast.getNextSibling();
227
228        return (prev != null) && (prev.getType() == TokenTypes.LPAREN)
229            && (next != null) && (next.getType() == TokenTypes.RPAREN);
230    }
231
232    /**
233     * Tests if the given expression node is surrounded by parentheses.
234     * @param ast a <code>DetailAST</code> whose type is
235     *        <code>TokenTypes.EXPR</code>.
236     * @return <code>true</code> if the expression is surrounded by
237     *         parentheses.
238     * @throws IllegalArgumentException if <code>ast.getType()</code> is not
239     *         equal to <code>TokenTypes.EXPR</code>.
240     */
241    private boolean exprSurrounded(DetailAST ast)
242    {
243        if (ast.getType() != TokenTypes.EXPR) {
244            throw new IllegalArgumentException("Not an expression node.");
245        }
246        boolean surrounded = false;
247        if (ast.getChildCount() >= MIN_CHILDREN_FOR_MATCH) {
248            final AST n1 = ast.getFirstChild();
249            final AST nn = ast.getLastChild();
250
251            surrounded = (n1.getType() == TokenTypes.LPAREN)
252                && (nn.getType() == TokenTypes.RPAREN);
253        }
254        return surrounded;
255    }
256
257    /**
258     * Check if the given token type can be found in an array of token types.
259     * @param type the token type.
260     * @param tokens an array of token types to search.
261     * @return <code>true</code> if <code>type</code> was found in <code>
262     *         tokens</code>.
263     */
264    private boolean inTokenList(int type, int[] tokens)
265    {
266        // NOTE: Given the small size of the two arrays searched, I'm not sure
267        //       it's worth bothering with doing a binary search or using a
268        //       HashMap to do the searches.
269
270        boolean found = false;
271        for (int i = 0; (i < tokens.length) && !found; i++) {
272            found = tokens[i] == type;
273        }
274        return found;
275    }
276
277    /**
278     * Returns the specified string chopped to <code>MAX_QUOTED_LENGTH</code>
279     * plus an ellipsis (...) if the length of the string exceeds <code>
280     * MAX_QUOTED_LENGTH</code>.
281     * @param string the string to potentially chop.
282     * @return the chopped string if <code>string</code> is longer than
283     *         <code>MAX_QUOTED_LENGTH</code>; otherwise <code>string</code>.
284     */
285    private String chopString(String string)
286    {
287        if (string.length() > MAX_QUOTED_LENGTH) {
288            return string.substring(0, MAX_QUOTED_LENGTH) + "...\"";
289        }
290        return string;
291    }
292}