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 < 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}