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