001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.FileStatefulCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 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 * <ul> 053 * <li> 054 * Property {@code tokens} - tokens to check 055 * Type is {@code int[]}. 056 * Default value is: 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR"> 058 * EXPR</a>, 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#IDENT"> 060 * IDENT</a>, 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE"> 062 * NUM_DOUBLE</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT"> 064 * NUM_FLOAT</a>, 065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT"> 066 * NUM_INT</a>, 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG"> 068 * NUM_LONG</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STRING_LITERAL"> 070 * STRING_LITERAL</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NULL"> 072 * LITERAL_NULL</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FALSE"> 074 * LITERAL_FALSE</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRUE"> 076 * LITERAL_TRUE</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN"> 078 * ASSIGN</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND_ASSIGN"> 080 * BAND_ASSIGN</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR_ASSIGN"> 082 * BOR_ASSIGN</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR_ASSIGN"> 084 * BSR_ASSIGN</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR_ASSIGN"> 086 * BXOR_ASSIGN</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV_ASSIGN"> 088 * DIV_ASSIGN</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS_ASSIGN"> 090 * MINUS_ASSIGN</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD_ASSIGN"> 092 * MOD_ASSIGN</a>, 093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS_ASSIGN"> 094 * PLUS_ASSIGN</a>, 095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL_ASSIGN"> 096 * SL_ASSIGN</a>, 097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR_ASSIGN"> 098 * SR_ASSIGN</a>, 099 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR_ASSIGN"> 100 * STAR_ASSIGN</a>, 101 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA"> 102 * LAMBDA</a>. 103 * </li> 104 * </ul> 105 * <p> 106 * To configure the check: 107 * </p> 108 * <pre> 109 * <module name="UnnecessaryParentheses"/> 110 * </pre> 111 * <p> 112 * Which results in the following violations: 113 * </p> 114 * <pre> 115 * public int square(int a, int b){ 116 * int square = (a * b); //violation 117 * return (square); //violation 118 * } 119 * int sumOfSquares = 0; 120 * for(int i=(0); i<10; i++){ //violation 121 * int x = (i + 1); //violation 122 * sumOfSquares += (square(x * x)); //violation 123 * } 124 * double num = (10.0); //violation 125 * List<String> list = Arrays.asList("a1", "b1", "c1"); 126 * myList.stream() 127 * .filter((s) -> s.startsWith("c")) //violation 128 * .forEach(System.out::println); 129 * </pre> 130 * <p> 131 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 132 * </p> 133 * <p> 134 * Violation Message Keys: 135 * </p> 136 * <ul> 137 * <li> 138 * {@code unnecessary.paren.assign} 139 * </li> 140 * <li> 141 * {@code unnecessary.paren.expr} 142 * </li> 143 * <li> 144 * {@code unnecessary.paren.ident} 145 * </li> 146 * <li> 147 * {@code unnecessary.paren.lambda} 148 * </li> 149 * <li> 150 * {@code unnecessary.paren.literal} 151 * </li> 152 * <li> 153 * {@code unnecessary.paren.return} 154 * </li> 155 * <li> 156 * {@code unnecessary.paren.string} 157 * </li> 158 * </ul> 159 * 160 * @since 3.4 161 */ 162@FileStatefulCheck 163public class UnnecessaryParenthesesCheck extends AbstractCheck { 164 165 /** 166 * A key is pointing to the warning message text in "messages.properties" 167 * file. 168 */ 169 public static final String MSG_IDENT = "unnecessary.paren.ident"; 170 171 /** 172 * A key is pointing to the warning message text in "messages.properties" 173 * file. 174 */ 175 public static final String MSG_ASSIGN = "unnecessary.paren.assign"; 176 177 /** 178 * A key is pointing to the warning message text in "messages.properties" 179 * file. 180 */ 181 public static final String MSG_EXPR = "unnecessary.paren.expr"; 182 183 /** 184 * A key is pointing to the warning message text in "messages.properties" 185 * file. 186 */ 187 public static final String MSG_LITERAL = "unnecessary.paren.literal"; 188 189 /** 190 * A key is pointing to the warning message text in "messages.properties" 191 * file. 192 */ 193 public static final String MSG_STRING = "unnecessary.paren.string"; 194 195 /** 196 * A key is pointing to the warning message text in "messages.properties" 197 * file. 198 */ 199 public static final String MSG_RETURN = "unnecessary.paren.return"; 200 201 /** 202 * A key is pointing to the warning message text in "messages.properties" 203 * file. 204 */ 205 public static final String MSG_LAMBDA = "unnecessary.paren.lambda"; 206 207 /** The maximum string length before we chop the string. */ 208 private static final int MAX_QUOTED_LENGTH = 25; 209 210 /** Token types for literals. */ 211 private static final int[] LITERALS = { 212 TokenTypes.NUM_DOUBLE, 213 TokenTypes.NUM_FLOAT, 214 TokenTypes.NUM_INT, 215 TokenTypes.NUM_LONG, 216 TokenTypes.STRING_LITERAL, 217 TokenTypes.LITERAL_NULL, 218 TokenTypes.LITERAL_FALSE, 219 TokenTypes.LITERAL_TRUE, 220 }; 221 222 /** Token types for assignment operations. */ 223 private static final int[] ASSIGNMENTS = { 224 TokenTypes.ASSIGN, 225 TokenTypes.BAND_ASSIGN, 226 TokenTypes.BOR_ASSIGN, 227 TokenTypes.BSR_ASSIGN, 228 TokenTypes.BXOR_ASSIGN, 229 TokenTypes.DIV_ASSIGN, 230 TokenTypes.MINUS_ASSIGN, 231 TokenTypes.MOD_ASSIGN, 232 TokenTypes.PLUS_ASSIGN, 233 TokenTypes.SL_ASSIGN, 234 TokenTypes.SR_ASSIGN, 235 TokenTypes.STAR_ASSIGN, 236 }; 237 238 /** 239 * Used to test if logging a warning in a parent node may be skipped 240 * because a warning was already logged on an immediate child node. 241 */ 242 private DetailAST parentToSkip; 243 /** Depth of nested assignments. Normally this will be 0 or 1. */ 244 private int assignDepth; 245 246 @Override 247 public int[] getDefaultTokens() { 248 return new int[] { 249 TokenTypes.EXPR, 250 TokenTypes.IDENT, 251 TokenTypes.NUM_DOUBLE, 252 TokenTypes.NUM_FLOAT, 253 TokenTypes.NUM_INT, 254 TokenTypes.NUM_LONG, 255 TokenTypes.STRING_LITERAL, 256 TokenTypes.LITERAL_NULL, 257 TokenTypes.LITERAL_FALSE, 258 TokenTypes.LITERAL_TRUE, 259 TokenTypes.ASSIGN, 260 TokenTypes.BAND_ASSIGN, 261 TokenTypes.BOR_ASSIGN, 262 TokenTypes.BSR_ASSIGN, 263 TokenTypes.BXOR_ASSIGN, 264 TokenTypes.DIV_ASSIGN, 265 TokenTypes.MINUS_ASSIGN, 266 TokenTypes.MOD_ASSIGN, 267 TokenTypes.PLUS_ASSIGN, 268 TokenTypes.SL_ASSIGN, 269 TokenTypes.SR_ASSIGN, 270 TokenTypes.STAR_ASSIGN, 271 TokenTypes.LAMBDA, 272 }; 273 } 274 275 @Override 276 public int[] getAcceptableTokens() { 277 return new int[] { 278 TokenTypes.EXPR, 279 TokenTypes.IDENT, 280 TokenTypes.NUM_DOUBLE, 281 TokenTypes.NUM_FLOAT, 282 TokenTypes.NUM_INT, 283 TokenTypes.NUM_LONG, 284 TokenTypes.STRING_LITERAL, 285 TokenTypes.LITERAL_NULL, 286 TokenTypes.LITERAL_FALSE, 287 TokenTypes.LITERAL_TRUE, 288 TokenTypes.ASSIGN, 289 TokenTypes.BAND_ASSIGN, 290 TokenTypes.BOR_ASSIGN, 291 TokenTypes.BSR_ASSIGN, 292 TokenTypes.BXOR_ASSIGN, 293 TokenTypes.DIV_ASSIGN, 294 TokenTypes.MINUS_ASSIGN, 295 TokenTypes.MOD_ASSIGN, 296 TokenTypes.PLUS_ASSIGN, 297 TokenTypes.SL_ASSIGN, 298 TokenTypes.SR_ASSIGN, 299 TokenTypes.STAR_ASSIGN, 300 TokenTypes.LAMBDA, 301 }; 302 } 303 304 @Override 305 public int[] getRequiredTokens() { 306 // Check can work with any of acceptable tokens 307 return CommonUtil.EMPTY_INT_ARRAY; 308 } 309 310 // -@cs[CyclomaticComplexity] All logs should be in visit token. 311 @Override 312 public void visitToken(DetailAST ast) { 313 final int type = ast.getType(); 314 final DetailAST parent = ast.getParent(); 315 316 if (type == TokenTypes.LAMBDA && isLambdaSingleParameterSurrounded(ast)) { 317 log(ast, MSG_LAMBDA, ast.getText()); 318 } 319 else if (type != TokenTypes.ASSIGN 320 || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 321 final boolean surrounded = isSurrounded(ast); 322 // An identifier surrounded by parentheses. 323 if (surrounded && type == TokenTypes.IDENT) { 324 parentToSkip = ast.getParent(); 325 log(ast, MSG_IDENT, ast.getText()); 326 } 327 // A literal (numeric or string) surrounded by parentheses. 328 else if (surrounded && isInTokenList(type, LITERALS)) { 329 parentToSkip = ast.getParent(); 330 if (type == TokenTypes.STRING_LITERAL) { 331 log(ast, MSG_STRING, 332 chopString(ast.getText())); 333 } 334 else { 335 log(ast, MSG_LITERAL, ast.getText()); 336 } 337 } 338 // The rhs of an assignment surrounded by parentheses. 339 else if (isInTokenList(type, ASSIGNMENTS)) { 340 assignDepth++; 341 final DetailAST last = ast.getLastChild(); 342 if (last.getType() == TokenTypes.RPAREN) { 343 log(ast, MSG_ASSIGN); 344 } 345 } 346 } 347 } 348 349 @Override 350 public void leaveToken(DetailAST ast) { 351 final int type = ast.getType(); 352 final DetailAST parent = ast.getParent(); 353 354 // shouldn't process assign in annotation pairs 355 if (type != TokenTypes.ASSIGN 356 || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 357 // An expression is surrounded by parentheses. 358 if (type == TokenTypes.EXPR) { 359 // If 'parentToSkip' == 'ast', then we've already logged a 360 // warning about an immediate child node in visitToken, so we don't 361 // need to log another one here. 362 363 if (parentToSkip != ast && isExprSurrounded(ast)) { 364 if (assignDepth >= 1) { 365 log(ast, MSG_ASSIGN); 366 } 367 else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) { 368 log(ast, MSG_RETURN); 369 } 370 else { 371 log(ast, MSG_EXPR); 372 } 373 } 374 375 parentToSkip = null; 376 } 377 else if (isInTokenList(type, ASSIGNMENTS)) { 378 assignDepth--; 379 } 380 } 381 } 382 383 /** 384 * Tests if the given {@code DetailAST} is surrounded by parentheses. 385 * In short, does {@code ast} have a previous sibling whose type is 386 * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code 387 * TokenTypes.RPAREN}. 388 * 389 * @param ast the {@code DetailAST} to check if it is surrounded by 390 * parentheses. 391 * @return {@code true} if {@code ast} is surrounded by 392 * parentheses. 393 */ 394 private static boolean isSurrounded(DetailAST ast) { 395 // if previous sibling is left parenthesis, 396 // next sibling can't be other than right parenthesis 397 final DetailAST prev = ast.getPreviousSibling(); 398 return prev != null && prev.getType() == TokenTypes.LPAREN; 399 } 400 401 /** 402 * Tests if the given expression node is surrounded by parentheses. 403 * 404 * @param ast a {@code DetailAST} whose type is 405 * {@code TokenTypes.EXPR}. 406 * @return {@code true} if the expression is surrounded by 407 * parentheses. 408 */ 409 private static boolean isExprSurrounded(DetailAST ast) { 410 return ast.getFirstChild().getType() == TokenTypes.LPAREN; 411 } 412 413 /** 414 * Tests if the given lambda node has a single parameter, no defined type, and is surrounded 415 * by parentheses. 416 * 417 * @param ast a {@code DetailAST} whose type is 418 * {@code TokenTypes.LAMBDA}. 419 * @return {@code true} if the lambda has a single parameter, no defined type, and is 420 * surrounded by parentheses. 421 */ 422 private static boolean isLambdaSingleParameterSurrounded(DetailAST ast) { 423 final DetailAST firstChild = ast.getFirstChild(); 424 boolean result = false; 425 if (firstChild.getType() == TokenTypes.LPAREN) { 426 final DetailAST parameters = firstChild.getNextSibling(); 427 if (parameters.getChildCount(TokenTypes.PARAMETER_DEF) == 1 428 && !parameters.getFirstChild().findFirstToken(TokenTypes.TYPE).hasChildren()) { 429 result = true; 430 } 431 } 432 return result; 433 } 434 435 /** 436 * Check if the given token type can be found in an array of token types. 437 * 438 * @param type the token type. 439 * @param tokens an array of token types to search. 440 * @return {@code true} if {@code type} was found in {@code 441 * tokens}. 442 */ 443 private static boolean isInTokenList(int type, int... tokens) { 444 // NOTE: Given the small size of the two arrays searched, I'm not sure 445 // it's worth bothering with doing a binary search or using a 446 // HashMap to do the searches. 447 448 boolean found = false; 449 for (int i = 0; i < tokens.length && !found; i++) { 450 found = tokens[i] == type; 451 } 452 return found; 453 } 454 455 /** 456 * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH} 457 * plus an ellipsis (...) if the length of the string exceeds {@code 458 * MAX_QUOTED_LENGTH}. 459 * 460 * @param value the string to potentially chop. 461 * @return the chopped string if {@code string} is longer than 462 * {@code MAX_QUOTED_LENGTH}; otherwise {@code string}. 463 */ 464 private static String chopString(String value) { 465 String result = value; 466 if (value.length() > MAX_QUOTED_LENGTH) { 467 result = value.substring(0, MAX_QUOTED_LENGTH) + "...\""; 468 } 469 return result; 470 } 471 472}