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