Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.UnnecessaryParenthesesCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
UnnecessaryParenthesesCheck
100%
59/59
100%
58/58
3.5
 
 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  11
 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  1
     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  1
     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  10
         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  7
         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  13
         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  375
         final int type = ast.getType();
 207  375
         final DetailAST parent = ast.getParent();
 208  
 
 209  375
         if (type == TokenTypes.LAMBDA && isLambdaSingleParameterSurrounded(ast)) {
 210  8
             log(ast, MSG_LAMBDA, ast.getText());
 211  
         }
 212  367
         else if (type != TokenTypes.ASSIGN
 213  33
             || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
 214  
 
 215  365
             final boolean surrounded = isSurrounded(ast);
 216  
             // An identifier surrounded by parentheses.
 217  365
             if (surrounded && type == TokenTypes.IDENT) {
 218  9
                 parentToSkip = ast.getParent();
 219  9
                 log(ast, MSG_IDENT, ast.getText());
 220  
             }
 221  
             // A literal (numeric or string) surrounded by parentheses.
 222  356
             else if (surrounded && isInTokenList(type, LITERALS)) {
 223  12
                 parentToSkip = ast.getParent();
 224  12
                 if (type == TokenTypes.STRING_LITERAL) {
 225  8
                     log(ast, MSG_STRING,
 226  4
                         chopString(ast.getText()));
 227  
                 }
 228  
                 else {
 229  8
                     log(ast, MSG_LITERAL, ast.getText());
 230  
                 }
 231  
             }
 232  
             // The rhs of an assignment surrounded by parentheses.
 233  344
             else if (isInTokenList(type, ASSIGNMENTS)) {
 234  38
                 assignDepth++;
 235  38
                 final DetailAST last = ast.getLastChild();
 236  38
                 if (last.getType() == TokenTypes.RPAREN) {
 237  14
                     log(ast, MSG_ASSIGN);
 238  
                 }
 239  
             }
 240  
         }
 241  375
     }
 242  
 
 243  
     @Override
 244  
     public void leaveToken(DetailAST ast) {
 245  375
         final int type = ast.getType();
 246  375
         final DetailAST parent = ast.getParent();
 247  
 
 248  
         // shouldn't process assign in annotation pairs
 249  375
         if (type != TokenTypes.ASSIGN
 250  33
             || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
 251  
             // An expression is surrounded by parentheses.
 252  373
             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  65
                 if (parentToSkip != ast && isExprSurrounded(ast)) {
 259  11
                     if (assignDepth >= 1) {
 260  5
                         log(ast, MSG_ASSIGN);
 261  
                     }
 262  6
                     else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) {
 263  2
                         log(ast, MSG_RETURN);
 264  
                     }
 265  
                     else {
 266  4
                         log(ast, MSG_EXPR);
 267  
                     }
 268  
                 }
 269  
 
 270  65
                 parentToSkip = null;
 271  
             }
 272  308
             else if (isInTokenList(type, ASSIGNMENTS)) {
 273  38
                 assignDepth--;
 274  
             }
 275  
         }
 276  375
     }
 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  365
         final DetailAST prev = ast.getPreviousSibling();
 292  365
         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  60
         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  20
         final DetailAST firstChild = ast.getFirstChild();
 316  40
         return firstChild.getType() == TokenTypes.LPAREN
 317  14
                 && firstChild.getNextSibling().getChildCount(TokenTypes.PARAMETER_DEF) == 1
 318  10
                 && firstChild.getNextSibling().getFirstChild().findFirstToken(TokenTypes.TYPE)
 319  10
                         .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  669
         boolean found = false;
 335  7858
         for (int i = 0; i < tokens.length && !found; i++) {
 336  7189
             found = tokens[i] == type;
 337  
         }
 338  669
         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  4
         String result = value;
 351  4
         if (value.length() > MAX_QUOTED_LENGTH) {
 352  1
             result = value.substring(0, MAX_QUOTED_LENGTH) + "...\"";
 353  
         }
 354  4
         return result;
 355  
     }
 356  
 }