001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 the original author or authors. 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 org.apache.commons.lang3.ArrayUtils; 023 024import com.puppycrawl.tools.checkstyle.api.Check; 025import com.puppycrawl.tools.checkstyle.api.DetailAST; 026import com.puppycrawl.tools.checkstyle.api.TokenTypes; 027 028/** 029 * <p> 030 * Checks if unnecessary parentheses are used in a statement or expression. 031 * The check will flag the following with warnings: 032 * </p> 033 * <pre> 034 * return (x); // parens around identifier 035 * return (x + 1); // parens around return value 036 * int x = (y / 2 + 1); // parens around assignment rhs 037 * for (int i = (0); i < 10; i++) { // parens around literal 038 * t -= (z + 1); // parens around assignment rhs</pre> 039 * <p> 040 * The check is not "type aware", that is to say, it can't tell if parentheses 041 * are unnecessary based on the types in an expression. It also doesn't know 042 * about operator precedence and associativity; therefore it won't catch 043 * something like 044 * </p> 045 * <pre> 046 * int x = (a + b) + c;</pre> 047 * <p> 048 * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are 049 * all {@code int} variables, the parentheses around {@code a + b} 050 * are not needed. 051 * </p> 052 * 053 * @author Eric Roe 054 */ 055public class UnnecessaryParenthesesCheck extends Check { 056 057 /** 058 * A key is pointing to the warning message text in "messages.properties" 059 * file. 060 */ 061 public static final String MSG_IDENT = "unnecessary.paren.ident"; 062 063 /** 064 * A key is pointing to the warning message text in "messages.properties" 065 * file. 066 */ 067 public static final String MSG_ASSIGN = "unnecessary.paren.assign"; 068 069 /** 070 * A key is pointing to the warning message text in "messages.properties" 071 * file. 072 */ 073 public static final String MSG_EXPR = "unnecessary.paren.expr"; 074 075 /** 076 * A key is pointing to the warning message text in "messages.properties" 077 * file. 078 */ 079 public static final String MSG_LITERAL = "unnecessary.paren.literal"; 080 081 /** 082 * A key is pointing to the warning message text in "messages.properties" 083 * file. 084 */ 085 public static final String MSG_STRING = "unnecessary.paren.string"; 086 087 /** 088 * A key is pointing to the warning message text in "messages.properties" 089 * file. 090 */ 091 public static final String MSG_RETURN = "unnecessary.paren.return"; 092 093 /** The maximum string length before we chop the string. */ 094 private static final int MAX_QUOTED_LENGTH = 25; 095 096 /** Token types for literals. */ 097 private static final int[] LITERALS = { 098 TokenTypes.NUM_DOUBLE, 099 TokenTypes.NUM_FLOAT, 100 TokenTypes.NUM_INT, 101 TokenTypes.NUM_LONG, 102 TokenTypes.STRING_LITERAL, 103 TokenTypes.LITERAL_NULL, 104 TokenTypes.LITERAL_FALSE, 105 TokenTypes.LITERAL_TRUE, 106 }; 107 108 /** Token types for assignment operations. */ 109 private static final int[] ASSIGNMENTS = { 110 TokenTypes.ASSIGN, 111 TokenTypes.BAND_ASSIGN, 112 TokenTypes.BOR_ASSIGN, 113 TokenTypes.BSR_ASSIGN, 114 TokenTypes.BXOR_ASSIGN, 115 TokenTypes.DIV_ASSIGN, 116 TokenTypes.MINUS_ASSIGN, 117 TokenTypes.MOD_ASSIGN, 118 TokenTypes.PLUS_ASSIGN, 119 TokenTypes.SL_ASSIGN, 120 TokenTypes.SR_ASSIGN, 121 TokenTypes.STAR_ASSIGN, 122 }; 123 124 /** 125 * Used to test if logging a warning in a parent node may be skipped 126 * because a warning was already logged on an immediate child node. 127 */ 128 private DetailAST parentToSkip; 129 /** Depth of nested assignments. Normally this will be 0 or 1. */ 130 private int assignDepth; 131 132 @Override 133 public int[] getDefaultTokens() { 134 return new int[] { 135 TokenTypes.EXPR, 136 TokenTypes.IDENT, 137 TokenTypes.NUM_DOUBLE, 138 TokenTypes.NUM_FLOAT, 139 TokenTypes.NUM_INT, 140 TokenTypes.NUM_LONG, 141 TokenTypes.STRING_LITERAL, 142 TokenTypes.LITERAL_NULL, 143 TokenTypes.LITERAL_FALSE, 144 TokenTypes.LITERAL_TRUE, 145 TokenTypes.ASSIGN, 146 TokenTypes.BAND_ASSIGN, 147 TokenTypes.BOR_ASSIGN, 148 TokenTypes.BSR_ASSIGN, 149 TokenTypes.BXOR_ASSIGN, 150 TokenTypes.DIV_ASSIGN, 151 TokenTypes.MINUS_ASSIGN, 152 TokenTypes.MOD_ASSIGN, 153 TokenTypes.PLUS_ASSIGN, 154 TokenTypes.SL_ASSIGN, 155 TokenTypes.SR_ASSIGN, 156 TokenTypes.STAR_ASSIGN, 157 }; 158 } 159 160 @Override 161 public int[] getAcceptableTokens() { 162 return new int[] { 163 TokenTypes.EXPR, 164 TokenTypes.IDENT, 165 TokenTypes.NUM_DOUBLE, 166 TokenTypes.NUM_FLOAT, 167 TokenTypes.NUM_INT, 168 TokenTypes.NUM_LONG, 169 TokenTypes.STRING_LITERAL, 170 TokenTypes.LITERAL_NULL, 171 TokenTypes.LITERAL_FALSE, 172 TokenTypes.LITERAL_TRUE, 173 TokenTypes.ASSIGN, 174 TokenTypes.BAND_ASSIGN, 175 TokenTypes.BOR_ASSIGN, 176 TokenTypes.BSR_ASSIGN, 177 TokenTypes.BXOR_ASSIGN, 178 TokenTypes.DIV_ASSIGN, 179 TokenTypes.MINUS_ASSIGN, 180 TokenTypes.MOD_ASSIGN, 181 TokenTypes.PLUS_ASSIGN, 182 TokenTypes.SL_ASSIGN, 183 TokenTypes.SR_ASSIGN, 184 TokenTypes.STAR_ASSIGN, 185 }; 186 } 187 188 @Override 189 public int[] getRequiredTokens() { 190 // Check can work with any of acceptable tokens 191 return ArrayUtils.EMPTY_INT_ARRAY; 192 } 193 194 @Override 195 public void visitToken(DetailAST ast) { 196 final int type = ast.getType(); 197 final DetailAST parent = ast.getParent(); 198 199 if (type != TokenTypes.ASSIGN 200 || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 201 202 final boolean surrounded = isSurrounded(ast); 203 // An identifier surrounded by parentheses. 204 if (surrounded && type == TokenTypes.IDENT) { 205 parentToSkip = ast.getParent(); 206 log(ast, MSG_IDENT, ast.getText()); 207 } 208 // A literal (numeric or string) surrounded by parentheses. 209 else if (surrounded && isInTokenList(type, LITERALS)) { 210 parentToSkip = ast.getParent(); 211 if (type == TokenTypes.STRING_LITERAL) { 212 log(ast, MSG_STRING, 213 chopString(ast.getText())); 214 } 215 else { 216 log(ast, MSG_LITERAL, ast.getText()); 217 } 218 } 219 // The rhs of an assignment surrounded by parentheses. 220 else if (isInTokenList(type, ASSIGNMENTS)) { 221 assignDepth++; 222 final DetailAST last = ast.getLastChild(); 223 if (last.getType() == TokenTypes.RPAREN) { 224 log(ast, MSG_ASSIGN); 225 } 226 } 227 } 228 } 229 230 @Override 231 public void leaveToken(DetailAST ast) { 232 final int type = ast.getType(); 233 final DetailAST parent = ast.getParent(); 234 235 if (type == TokenTypes.ASSIGN 236 && parent.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 237 // shouldn't process assign in annotation pairs 238 return; 239 } 240 241 // An expression is surrounded by parentheses. 242 if (type == TokenTypes.EXPR) { 243 244 // If 'parentToSkip' == 'ast', then we've already logged a 245 // warning about an immediate child node in visitToken, so we don't 246 // need to log another one here. 247 248 if (parentToSkip != ast && isExprSurrounded(ast)) { 249 if (assignDepth >= 1) { 250 log(ast, MSG_ASSIGN); 251 } 252 else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) { 253 log(ast, MSG_RETURN); 254 } 255 else { 256 log(ast, MSG_EXPR); 257 } 258 } 259 260 parentToSkip = null; 261 } 262 else if (isInTokenList(type, ASSIGNMENTS)) { 263 assignDepth--; 264 } 265 266 super.leaveToken(ast); 267 } 268 269 /** 270 * Tests if the given {@code DetailAST} is surrounded by parentheses. 271 * In short, does {@code ast} have a previous sibling whose type is 272 * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code 273 * TokenTypes.RPAREN}. 274 * @param ast the {@code DetailAST} to check if it is surrounded by 275 * parentheses. 276 * @return {@code true} if {@code ast} is surrounded by 277 * parentheses. 278 */ 279 private static boolean isSurrounded(DetailAST ast) { 280 // if previous sibling is left parenthesis, 281 // next sibling can't be other than right parenthesis 282 final DetailAST prev = ast.getPreviousSibling(); 283 return prev != null && prev.getType() == TokenTypes.LPAREN; 284 } 285 286 /** 287 * Tests if the given expression node is surrounded by parentheses. 288 * @param ast a {@code DetailAST} whose type is 289 * {@code TokenTypes.EXPR}. 290 * @return {@code true} if the expression is surrounded by 291 * parentheses. 292 */ 293 private static boolean isExprSurrounded(DetailAST ast) { 294 return ast.getFirstChild().getType() == TokenTypes.LPAREN; 295 } 296 297 /** 298 * Check if the given token type can be found in an array of token types. 299 * @param type the token type. 300 * @param tokens an array of token types to search. 301 * @return {@code true} if {@code type} was found in {@code 302 * tokens}. 303 */ 304 private static boolean isInTokenList(int type, int... tokens) { 305 // NOTE: Given the small size of the two arrays searched, I'm not sure 306 // it's worth bothering with doing a binary search or using a 307 // HashMap to do the searches. 308 309 boolean found = false; 310 for (int i = 0; i < tokens.length && !found; i++) { 311 found = tokens[i] == type; 312 } 313 return found; 314 } 315 316 /** 317 * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH} 318 * plus an ellipsis (...) if the length of the string exceeds {@code 319 * MAX_QUOTED_LENGTH}. 320 * @param value the string to potentially chop. 321 * @return the chopped string if {@code string} is longer than 322 * {@code MAX_QUOTED_LENGTH}; otherwise {@code string}. 323 */ 324 private static String chopString(String value) { 325 if (value.length() > MAX_QUOTED_LENGTH) { 326 return value.substring(0, MAX_QUOTED_LENGTH) + "...\""; 327 } 328 return value; 329 } 330}