View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
27  
28  /**
29   * <p>
30   * Checks if unnecessary parentheses are used in a statement or expression.
31   * The check will flag the following with warnings:
32   * </p>
33   * <pre>
34   *     return (x);          // parens around identifier
35   *     return (x + 1);      // parens around return value
36   *     int x = (y / 2 + 1); // parens around assignment rhs
37   *     for (int i = (0); i &lt; 10; i++) {  // parens around literal
38   *     t -= (z + 1);        // parens around assignment rhs</pre>
39   * <p>
40   * The check is not "type aware", that is to say, it can't tell if parentheses
41   * are unnecessary based on the types in an expression.  It also doesn't know
42   * about operator precedence and associativity; therefore it won't catch
43   * something like
44   * </p>
45   * <pre>
46   *     int x = (a + b) + c;</pre>
47   * <p>
48   * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are
49   * all {@code int} variables, the parentheses around {@code a + b}
50   * are not needed.
51   * </p>
52   *
53   * @author Eric Roe
54   */
55  @FileStatefulCheck
56  public class UnnecessaryParenthesesCheck extends AbstractCheck {
57  
58      /**
59       * A key is pointing to the warning message text in "messages.properties"
60       * file.
61       */
62      public static final String MSG_IDENT = "unnecessary.paren.ident";
63  
64      /**
65       * A key is pointing to the warning message text in "messages.properties"
66       * file.
67       */
68      public static final String MSG_ASSIGN = "unnecessary.paren.assign";
69  
70      /**
71       * A key is pointing to the warning message text in "messages.properties"
72       * file.
73       */
74      public static final String MSG_EXPR = "unnecessary.paren.expr";
75  
76      /**
77       * A key is pointing to the warning message text in "messages.properties"
78       * file.
79       */
80      public static final String MSG_LITERAL = "unnecessary.paren.literal";
81  
82      /**
83       * A key is pointing to the warning message text in "messages.properties"
84       * file.
85       */
86      public static final String MSG_STRING = "unnecessary.paren.string";
87  
88      /**
89       * A key is pointing to the warning message text in "messages.properties"
90       * file.
91       */
92      public static final String MSG_RETURN = "unnecessary.paren.return";
93  
94      /**
95       * A key is pointing to the warning message text in "messages.properties"
96       * file.
97       */
98      public static final String MSG_LAMBDA = "unnecessary.paren.lambda";
99  
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 }