Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.UnnecessaryParenthesesCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
UnnecessaryParenthesesCheck
98%
58/59
91%
53/58
5.571
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 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 antlr.collections.AST;
 23  
 import com.puppycrawl.tools.checkstyle.api.Check;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 
 27  
 /**
 28  
  * <p>
 29  
  * Checks if unnecessary parentheses are used in a statement or expression.
 30  
  * The check will flag the following with warnings:
 31  
  * </p>
 32  
  * <pre>
 33  
  *     return (x);          // parens around identifier
 34  
  *     return (x + 1);      // parens around return value
 35  
  *     int x = (y / 2 + 1); // parens around assignment rhs
 36  
  *     for (int i = (0); i &lt; 10; i++) {  // parens around literal
 37  
  *     t -= (z + 1);        // parens around assignment rhs</pre>
 38  
  * <p>
 39  
  * The check is not "type aware", that is to say, it can't tell if parentheses
 40  
  * are unnecessary based on the types in an expression.  It also doesn't know
 41  
  * about operator precedence and associatvity; therefore it won't catch
 42  
  * something like
 43  
  * </p>
 44  
  * <pre>
 45  
  *     int x = (a + b) + c;</pre>
 46  
  * <p>
 47  
  * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are
 48  
  * all <code>int</code> variables, the parentheses around <code>a + b</code>
 49  
  * are not needed.
 50  
  * </p>
 51  
  *
 52  
  * @author Eric Roe
 53  
  */
 54  2
 public class UnnecessaryParenthesesCheck extends Check
 55  
 {
 56  
     /** The minimum number of child nodes to consider for a match. */
 57  
     private static final int MIN_CHILDREN_FOR_MATCH = 3;
 58  
     /** The maximum string length before we chop the string. */
 59  
     private static final int MAX_QUOTED_LENGTH = 25;
 60  
 
 61  
     /** Token types for literals. */
 62  1
     private static final int [] LITERALS = {
 63  
         TokenTypes.NUM_DOUBLE,
 64  
         TokenTypes.NUM_FLOAT,
 65  
         TokenTypes.NUM_INT,
 66  
         TokenTypes.NUM_LONG,
 67  
         TokenTypes.STRING_LITERAL,
 68  
         TokenTypes.LITERAL_NULL,
 69  
         TokenTypes.LITERAL_FALSE,
 70  
         TokenTypes.LITERAL_TRUE,
 71  
     };
 72  
 
 73  
     /** Token types for assignment operations. */
 74  1
     private static final int [] ASSIGNMENTS = {
 75  
         TokenTypes.ASSIGN,
 76  
         TokenTypes.BAND_ASSIGN,
 77  
         TokenTypes.BOR_ASSIGN,
 78  
         TokenTypes.BSR_ASSIGN,
 79  
         TokenTypes.BXOR_ASSIGN,
 80  
         TokenTypes.DIV_ASSIGN,
 81  
         TokenTypes.MINUS_ASSIGN,
 82  
         TokenTypes.MOD_ASSIGN,
 83  
         TokenTypes.PLUS_ASSIGN,
 84  
         TokenTypes.SL_ASSIGN,
 85  
         TokenTypes.SR_ASSIGN,
 86  
         TokenTypes.STAR_ASSIGN,
 87  
     };
 88  
 
 89  
     /**
 90  
      * Used to test if logging a warning in a parent node may be skipped
 91  
      * because a warning was already logged on an immediate child node.
 92  
      */
 93  
     private DetailAST mParentToSkip;
 94  
     /** Depth of nested assignments.  Normally this will be 0 or 1. */
 95  
     private int mAssignDepth;
 96  
 
 97  
     @Override
 98  
     public int[] getDefaultTokens()
 99  
     {
 100  2
         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 aAST)
 128  
     {
 129  327
         final int type = aAST.getType();
 130  327
         final boolean surrounded = isSurrounded(aAST);
 131  327
         final DetailAST parent = aAST.getParent();
 132  
 
 133  327
         if ((type == TokenTypes.ASSIGN)
 134  
             && (parent.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR))
 135  
         {
 136  
             // shouldn't process assign in annotation pairs
 137  2
             return;
 138  
         }
 139  
 
 140  
         // An identifier surrounded by parentheses.
 141  325
         if (surrounded && (type == TokenTypes.IDENT)) {
 142  9
             mParentToSkip = aAST.getParent();
 143  9
             log(aAST, "unnecessary.paren.ident", aAST.getText());
 144  9
             return;
 145  
         }
 146  
 
 147  
         // A literal (numeric or string) surrounded by parentheses.
 148  316
         if (surrounded && inTokenList(type, LITERALS)) {
 149  11
             mParentToSkip = aAST.getParent();
 150  11
             if (type == TokenTypes.STRING_LITERAL) {
 151  3
                 log(aAST, "unnecessary.paren.string",
 152  
                     chopString(aAST.getText()));
 153  
             }
 154  
             else {
 155  8
                 log(aAST, "unnecessary.paren.literal", aAST.getText());
 156  
             }
 157  11
             return;
 158  
         }
 159  
 
 160  
         // The rhs of an assignment surrounded by parentheses.
 161  305
         if (inTokenList(type, ASSIGNMENTS)) {
 162  37
             mAssignDepth++;
 163  37
             final DetailAST last = aAST.getLastChild();
 164  37
             if (last.getType() == TokenTypes.RPAREN) {
 165  13
                 log(aAST, "unnecessary.paren.assign");
 166  
             }
 167  
         }
 168  305
     }
 169  
 
 170  
     @Override
 171  
     public void leaveToken(DetailAST aAST)
 172  
     {
 173  327
         final int type = aAST.getType();
 174  327
         final DetailAST parent = aAST.getParent();
 175  
 
 176  327
         if ((type == TokenTypes.ASSIGN)
 177  
             && (parent.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR))
 178  
         {
 179  
             // shouldn't process assign in annotation pairs
 180  2
             return;
 181  
         }
 182  
 
 183  
         // An expression is surrounded by parentheses.
 184  325
         if (type == TokenTypes.EXPR) {
 185  
 
 186  
             // If 'mParentToSkip' == 'aAST', 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  63
             if ((mParentToSkip != aAST) && exprSurrounded(aAST)) {
 191  11
                 if (mAssignDepth >= 1) {
 192  5
                     log(aAST, "unnecessary.paren.assign");
 193  
                 }
 194  6
                 else if (aAST.getParent().getType()
 195  
                     == TokenTypes.LITERAL_RETURN)
 196  
                 {
 197  2
                     log(aAST, "unnecessary.paren.return");
 198  
                 }
 199  
                 else {
 200  4
                     log(aAST, "unnecessary.paren.expr");
 201  
                 }
 202  
             }
 203  
 
 204  63
             mParentToSkip = null;
 205  
         }
 206  262
         else if (inTokenList(type, ASSIGNMENTS)) {
 207  37
             mAssignDepth--;
 208  
         }
 209  
 
 210  325
         super.leaveToken(aAST);
 211  325
     }
 212  
 
 213  
     /**
 214  
      * Tests if the given <code>DetailAST</code> is surrounded by parentheses.
 215  
      * In short, does <code>aAST</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 aAST the <code>DetailAST</code> to check if it is surrounded by
 219  
      *        parentheses.
 220  
      * @return <code>true</code> if <code>aAST</code> is surrounded by
 221  
      *         parentheses.
 222  
      */
 223  
     private boolean isSurrounded(DetailAST aAST)
 224  
     {
 225  327
         final DetailAST prev = aAST.getPreviousSibling();
 226  327
         final DetailAST next = aAST.getNextSibling();
 227  
 
 228  327
         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 aAST 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>aAST.getType()</code> is not
 239  
      *         equal to <code>TokenTypes.EXPR</code>.
 240  
      */
 241  
     private boolean exprSurrounded(DetailAST aAST)
 242  
     {
 243  58
         if (aAST.getType() != TokenTypes.EXPR) {
 244  0
             throw new IllegalArgumentException("Not an expression node.");
 245  
         }
 246  58
         boolean surrounded = false;
 247  58
         if (aAST.getChildCount() >= MIN_CHILDREN_FOR_MATCH) {
 248  11
             final AST n1 = aAST.getFirstChild();
 249  11
             final AST nn = aAST.getLastChild();
 250  
 
 251  11
             surrounded = (n1.getType() == TokenTypes.LPAREN)
 252  
                 && (nn.getType() == TokenTypes.RPAREN);
 253  
         }
 254  58
         return surrounded;
 255  
     }
 256  
 
 257  
     /**
 258  
      * Check if the given token type can be found in an array of token types.
 259  
      * @param aType the token type.
 260  
      * @param aTokens an array of token types to search.
 261  
      * @return <code>true</code> if <code>aType</code> was found in <code>
 262  
      *         aTokens</code>.
 263  
      */
 264  
     private boolean inTokenList(int aType, int [] aTokens)
 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  583
         boolean found = false;
 271  6769
         for (int i = 0; (i < aTokens.length) && !found; i++) {
 272  6186
             found = aTokens[i] == aType;
 273  
         }
 274  583
         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 aString the string to potentially chop.
 282  
      * @return the chopped string if <code>aString</code> is longer than
 283  
      *         <code>MAX_QUOTED_LENGTH</code>; otherwise <code>aString</code>.
 284  
      */
 285  
     private String chopString(String aString)
 286  
     {
 287  3
         if (aString.length() > MAX_QUOTED_LENGTH) {
 288  1
             return aString.substring(0, MAX_QUOTED_LENGTH) + "...\"";
 289  
         }
 290  2
         return aString;
 291  
     }
 292  
 }