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