1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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.CommonUtil;
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   * <ul>
53   * <li>
54   * Property {@code tokens} - tokens to check
55   * Default value is:
56   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR">
57   * EXPR</a>,
58   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#IDENT">
59   * IDENT</a>,
60   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE">
61   * NUM_DOUBLE</a>,
62   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT">
63   * NUM_FLOAT</a>,
64   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT">
65   * NUM_INT</a>,
66   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG">
67   * NUM_LONG</a>,
68   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STRING_LITERAL">
69   * STRING_LITERAL</a>,
70   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NULL">
71   * LITERAL_NULL</a>,
72   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FALSE">
73   * LITERAL_FALSE</a>,
74   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRUE">
75   * LITERAL_TRUE</a>,
76   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN">
77   * ASSIGN</a>,
78   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND_ASSIGN">
79   * BAND_ASSIGN</a>,
80   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR_ASSIGN">
81   * BOR_ASSIGN</a>,
82   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR_ASSIGN">
83   * BSR_ASSIGN</a>,
84   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR_ASSIGN">
85   * BXOR_ASSIGN</a>,
86   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV_ASSIGN">
87   * DIV_ASSIGN</a>,
88   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS_ASSIGN">
89   * MINUS_ASSIGN</a>,
90   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD_ASSIGN">
91   * MOD_ASSIGN</a>,
92   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS_ASSIGN">
93   * PLUS_ASSIGN</a>,
94   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL_ASSIGN">
95   * SL_ASSIGN</a>,
96   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR_ASSIGN">
97   * SR_ASSIGN</a>,
98   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR_ASSIGN">
99   * 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  *
111  * @since 3.4
112  */
113 @FileStatefulCheck
114 public class UnnecessaryParenthesesCheck extends AbstractCheck {
115 
116     /**
117      * A key is pointing to the warning message text in "messages.properties"
118      * file.
119      */
120     public static final String MSG_IDENT = "unnecessary.paren.ident";
121 
122     /**
123      * A key is pointing to the warning message text in "messages.properties"
124      * file.
125      */
126     public static final String MSG_ASSIGN = "unnecessary.paren.assign";
127 
128     /**
129      * A key is pointing to the warning message text in "messages.properties"
130      * file.
131      */
132     public static final String MSG_EXPR = "unnecessary.paren.expr";
133 
134     /**
135      * A key is pointing to the warning message text in "messages.properties"
136      * file.
137      */
138     public static final String MSG_LITERAL = "unnecessary.paren.literal";
139 
140     /**
141      * A key is pointing to the warning message text in "messages.properties"
142      * file.
143      */
144     public static final String MSG_STRING = "unnecessary.paren.string";
145 
146     /**
147      * A key is pointing to the warning message text in "messages.properties"
148      * file.
149      */
150     public static final String MSG_RETURN = "unnecessary.paren.return";
151 
152     /**
153      * A key is pointing to the warning message text in "messages.properties"
154      * file.
155      */
156     public static final String MSG_LAMBDA = "unnecessary.paren.lambda";
157 
158     /** The maximum string length before we chop the string. */
159     private static final int MAX_QUOTED_LENGTH = 25;
160 
161     /** Token types for literals. */
162     private static final int[] LITERALS = {
163         TokenTypes.NUM_DOUBLE,
164         TokenTypes.NUM_FLOAT,
165         TokenTypes.NUM_INT,
166         TokenTypes.NUM_LONG,
167         TokenTypes.STRING_LITERAL,
168         TokenTypes.LITERAL_NULL,
169         TokenTypes.LITERAL_FALSE,
170         TokenTypes.LITERAL_TRUE,
171     };
172 
173     /** Token types for assignment operations. */
174     private static final int[] ASSIGNMENTS = {
175         TokenTypes.ASSIGN,
176         TokenTypes.BAND_ASSIGN,
177         TokenTypes.BOR_ASSIGN,
178         TokenTypes.BSR_ASSIGN,
179         TokenTypes.BXOR_ASSIGN,
180         TokenTypes.DIV_ASSIGN,
181         TokenTypes.MINUS_ASSIGN,
182         TokenTypes.MOD_ASSIGN,
183         TokenTypes.PLUS_ASSIGN,
184         TokenTypes.SL_ASSIGN,
185         TokenTypes.SR_ASSIGN,
186         TokenTypes.STAR_ASSIGN,
187     };
188 
189     /**
190      * Used to test if logging a warning in a parent node may be skipped
191      * because a warning was already logged on an immediate child node.
192      */
193     private DetailAST parentToSkip;
194     /** Depth of nested assignments.  Normally this will be 0 or 1. */
195     private int assignDepth;
196 
197     @Override
198     public int[] getDefaultTokens() {
199         return new int[] {
200             TokenTypes.EXPR,
201             TokenTypes.IDENT,
202             TokenTypes.NUM_DOUBLE,
203             TokenTypes.NUM_FLOAT,
204             TokenTypes.NUM_INT,
205             TokenTypes.NUM_LONG,
206             TokenTypes.STRING_LITERAL,
207             TokenTypes.LITERAL_NULL,
208             TokenTypes.LITERAL_FALSE,
209             TokenTypes.LITERAL_TRUE,
210             TokenTypes.ASSIGN,
211             TokenTypes.BAND_ASSIGN,
212             TokenTypes.BOR_ASSIGN,
213             TokenTypes.BSR_ASSIGN,
214             TokenTypes.BXOR_ASSIGN,
215             TokenTypes.DIV_ASSIGN,
216             TokenTypes.MINUS_ASSIGN,
217             TokenTypes.MOD_ASSIGN,
218             TokenTypes.PLUS_ASSIGN,
219             TokenTypes.SL_ASSIGN,
220             TokenTypes.SR_ASSIGN,
221             TokenTypes.STAR_ASSIGN,
222             TokenTypes.LAMBDA,
223         };
224     }
225 
226     @Override
227     public int[] getAcceptableTokens() {
228         return new int[] {
229             TokenTypes.EXPR,
230             TokenTypes.IDENT,
231             TokenTypes.NUM_DOUBLE,
232             TokenTypes.NUM_FLOAT,
233             TokenTypes.NUM_INT,
234             TokenTypes.NUM_LONG,
235             TokenTypes.STRING_LITERAL,
236             TokenTypes.LITERAL_NULL,
237             TokenTypes.LITERAL_FALSE,
238             TokenTypes.LITERAL_TRUE,
239             TokenTypes.ASSIGN,
240             TokenTypes.BAND_ASSIGN,
241             TokenTypes.BOR_ASSIGN,
242             TokenTypes.BSR_ASSIGN,
243             TokenTypes.BXOR_ASSIGN,
244             TokenTypes.DIV_ASSIGN,
245             TokenTypes.MINUS_ASSIGN,
246             TokenTypes.MOD_ASSIGN,
247             TokenTypes.PLUS_ASSIGN,
248             TokenTypes.SL_ASSIGN,
249             TokenTypes.SR_ASSIGN,
250             TokenTypes.STAR_ASSIGN,
251             TokenTypes.LAMBDA,
252         };
253     }
254 
255     @Override
256     public int[] getRequiredTokens() {
257         // Check can work with any of acceptable tokens
258         return CommonUtil.EMPTY_INT_ARRAY;
259     }
260 
261     // -@cs[CyclomaticComplexity] All logs should be in visit token.
262     @Override
263     public void visitToken(DetailAST ast) {
264         final int type = ast.getType();
265         final DetailAST parent = ast.getParent();
266 
267         if (type == TokenTypes.LAMBDA && isLambdaSingleParameterSurrounded(ast)) {
268             log(ast, MSG_LAMBDA, ast.getText());
269         }
270         else if (type != TokenTypes.ASSIGN
271             || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
272             final boolean surrounded = isSurrounded(ast);
273             // An identifier surrounded by parentheses.
274             if (surrounded && type == TokenTypes.IDENT) {
275                 parentToSkip = ast.getParent();
276                 log(ast, MSG_IDENT, ast.getText());
277             }
278             // A literal (numeric or string) surrounded by parentheses.
279             else if (surrounded && isInTokenList(type, LITERALS)) {
280                 parentToSkip = ast.getParent();
281                 if (type == TokenTypes.STRING_LITERAL) {
282                     log(ast, MSG_STRING,
283                         chopString(ast.getText()));
284                 }
285                 else {
286                     log(ast, MSG_LITERAL, ast.getText());
287                 }
288             }
289             // The rhs of an assignment surrounded by parentheses.
290             else if (isInTokenList(type, ASSIGNMENTS)) {
291                 assignDepth++;
292                 final DetailAST last = ast.getLastChild();
293                 if (last.getType() == TokenTypes.RPAREN) {
294                     log(ast, MSG_ASSIGN);
295                 }
296             }
297         }
298     }
299 
300     @Override
301     public void leaveToken(DetailAST ast) {
302         final int type = ast.getType();
303         final DetailAST parent = ast.getParent();
304 
305         // shouldn't process assign in annotation pairs
306         if (type != TokenTypes.ASSIGN
307             || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
308             // An expression is surrounded by parentheses.
309             if (type == TokenTypes.EXPR) {
310                 // If 'parentToSkip' == 'ast', then we've already logged a
311                 // warning about an immediate child node in visitToken, so we don't
312                 // need to log another one here.
313 
314                 if (parentToSkip != ast && isExprSurrounded(ast)) {
315                     if (assignDepth >= 1) {
316                         log(ast, MSG_ASSIGN);
317                     }
318                     else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) {
319                         log(ast, MSG_RETURN);
320                     }
321                     else {
322                         log(ast, MSG_EXPR);
323                     }
324                 }
325 
326                 parentToSkip = null;
327             }
328             else if (isInTokenList(type, ASSIGNMENTS)) {
329                 assignDepth--;
330             }
331         }
332     }
333 
334     /**
335      * Tests if the given {@code DetailAST} is surrounded by parentheses.
336      * In short, does {@code ast} have a previous sibling whose type is
337      * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code
338      * TokenTypes.RPAREN}.
339      * @param ast the {@code DetailAST} to check if it is surrounded by
340      *        parentheses.
341      * @return {@code true} if {@code ast} is surrounded by
342      *         parentheses.
343      */
344     private static boolean isSurrounded(DetailAST ast) {
345         // if previous sibling is left parenthesis,
346         // next sibling can't be other than right parenthesis
347         final DetailAST prev = ast.getPreviousSibling();
348         return prev != null && prev.getType() == TokenTypes.LPAREN;
349     }
350 
351     /**
352      * Tests if the given expression node is surrounded by parentheses.
353      * @param ast a {@code DetailAST} whose type is
354      *        {@code TokenTypes.EXPR}.
355      * @return {@code true} if the expression is surrounded by
356      *         parentheses.
357      */
358     private static boolean isExprSurrounded(DetailAST ast) {
359         return ast.getFirstChild().getType() == TokenTypes.LPAREN;
360     }
361 
362     /**
363      * Tests if the given lambda node has a single parameter, no defined type, and is surrounded
364      * by parentheses.
365      * @param ast a {@code DetailAST} whose type is
366      *        {@code TokenTypes.LAMBDA}.
367      * @return {@code true} if the lambda has a single parameter, no defined type, and is
368      *         surrounded by parentheses.
369      */
370     private static boolean isLambdaSingleParameterSurrounded(DetailAST ast) {
371         final DetailAST firstChild = ast.getFirstChild();
372         return firstChild.getType() == TokenTypes.LPAREN
373                 && firstChild.getNextSibling().getChildCount(TokenTypes.PARAMETER_DEF) == 1
374                 && firstChild.getNextSibling().getFirstChild().findFirstToken(TokenTypes.TYPE)
375                         .getChildCount() == 0;
376     }
377 
378     /**
379      * Check if the given token type can be found in an array of token types.
380      * @param type the token type.
381      * @param tokens an array of token types to search.
382      * @return {@code true} if {@code type} was found in {@code
383      *         tokens}.
384      */
385     private static boolean isInTokenList(int type, int... tokens) {
386         // NOTE: Given the small size of the two arrays searched, I'm not sure
387         //       it's worth bothering with doing a binary search or using a
388         //       HashMap to do the searches.
389 
390         boolean found = false;
391         for (int i = 0; i < tokens.length && !found; i++) {
392             found = tokens[i] == type;
393         }
394         return found;
395     }
396 
397     /**
398      * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH}
399      * plus an ellipsis (...) if the length of the string exceeds {@code
400      * MAX_QUOTED_LENGTH}.
401      * @param value the string to potentially chop.
402      * @return the chopped string if {@code string} is longer than
403      *         {@code MAX_QUOTED_LENGTH}; otherwise {@code string}.
404      */
405     private static String chopString(String value) {
406         String result = value;
407         if (value.length() > MAX_QUOTED_LENGTH) {
408             result = value.substring(0, MAX_QUOTED_LENGTH) + "...\"";
409         }
410         return result;
411     }
412 
413 }