1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.coding; 21 22 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 24 import com.puppycrawl.tools.checkstyle.api.DetailAST; 25 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 26 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 27 28 /** 29 * <p> 30 * Checks if unnecessary parentheses are used in a statement or expression. 31 * The check will flag the following with warnings: 32 * </p> 33 * <pre> 34 * return (x); // parens around identifier 35 * return (x + 1); // parens around return value 36 * int x = (y / 2 + 1); // parens around assignment rhs 37 * for (int i = (0); i < 10; i++) { // parens around literal 38 * t -= (z + 1); // parens around assignment rhs</pre> 39 * <p> 40 * The check is not "type aware", that is to say, it can't tell if parentheses 41 * are unnecessary based on the types in an expression. It also doesn't know 42 * about operator precedence and associativity; therefore it won't catch 43 * something like 44 * </p> 45 * <pre> 46 * int x = (a + b) + c;</pre> 47 * <p> 48 * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are 49 * all {@code int} variables, the parentheses around {@code a + b} 50 * are not needed. 51 * </p> 52 * <ul> 53 * <li> 54 * Property {@code tokens} - tokens to check 55 * Default value is: 56 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EXPR"> 57 * EXPR</a>, 58 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#IDENT"> 59 * IDENT</a>, 60 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_DOUBLE"> 61 * NUM_DOUBLE</a>, 62 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_FLOAT"> 63 * NUM_FLOAT</a>, 64 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_INT"> 65 * NUM_INT</a>, 66 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NUM_LONG"> 67 * NUM_LONG</a>, 68 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STRING_LITERAL"> 69 * STRING_LITERAL</a>, 70 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NULL"> 71 * LITERAL_NULL</a>, 72 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FALSE"> 73 * LITERAL_FALSE</a>, 74 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRUE"> 75 * LITERAL_TRUE</a>, 76 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN"> 77 * ASSIGN</a>, 78 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND_ASSIGN"> 79 * BAND_ASSIGN</a>, 80 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR_ASSIGN"> 81 * BOR_ASSIGN</a>, 82 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR_ASSIGN"> 83 * BSR_ASSIGN</a>, 84 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR_ASSIGN"> 85 * BXOR_ASSIGN</a>, 86 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV_ASSIGN"> 87 * DIV_ASSIGN</a>, 88 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS_ASSIGN"> 89 * MINUS_ASSIGN</a>, 90 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD_ASSIGN"> 91 * MOD_ASSIGN</a>, 92 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS_ASSIGN"> 93 * PLUS_ASSIGN</a>, 94 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL_ASSIGN"> 95 * SL_ASSIGN</a>, 96 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR_ASSIGN"> 97 * SR_ASSIGN</a>, 98 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR_ASSIGN"> 99 * 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 * 111 * @since 3.4 112 */ 113 @FileStatefulCheck 114 public class UnnecessaryParenthesesCheck extends AbstractCheck { 115 116 /** 117 * A key is pointing to the warning message text in "messages.properties" 118 * file. 119 */ 120 public static final String MSG_IDENT = "unnecessary.paren.ident"; 121 122 /** 123 * A key is pointing to the warning message text in "messages.properties" 124 * file. 125 */ 126 public static final String MSG_ASSIGN = "unnecessary.paren.assign"; 127 128 /** 129 * A key is pointing to the warning message text in "messages.properties" 130 * file. 131 */ 132 public static final String MSG_EXPR = "unnecessary.paren.expr"; 133 134 /** 135 * A key is pointing to the warning message text in "messages.properties" 136 * file. 137 */ 138 public static final String MSG_LITERAL = "unnecessary.paren.literal"; 139 140 /** 141 * A key is pointing to the warning message text in "messages.properties" 142 * file. 143 */ 144 public static final String MSG_STRING = "unnecessary.paren.string"; 145 146 /** 147 * A key is pointing to the warning message text in "messages.properties" 148 * file. 149 */ 150 public static final String MSG_RETURN = "unnecessary.paren.return"; 151 152 /** 153 * A key is pointing to the warning message text in "messages.properties" 154 * file. 155 */ 156 public static final String MSG_LAMBDA = "unnecessary.paren.lambda"; 157 158 /** The maximum string length before we chop the string. */ 159 private static final int MAX_QUOTED_LENGTH = 25; 160 161 /** Token types for literals. */ 162 private static final int[] LITERALS = { 163 TokenTypes.NUM_DOUBLE, 164 TokenTypes.NUM_FLOAT, 165 TokenTypes.NUM_INT, 166 TokenTypes.NUM_LONG, 167 TokenTypes.STRING_LITERAL, 168 TokenTypes.LITERAL_NULL, 169 TokenTypes.LITERAL_FALSE, 170 TokenTypes.LITERAL_TRUE, 171 }; 172 173 /** Token types for assignment operations. */ 174 private static final int[] ASSIGNMENTS = { 175 TokenTypes.ASSIGN, 176 TokenTypes.BAND_ASSIGN, 177 TokenTypes.BOR_ASSIGN, 178 TokenTypes.BSR_ASSIGN, 179 TokenTypes.BXOR_ASSIGN, 180 TokenTypes.DIV_ASSIGN, 181 TokenTypes.MINUS_ASSIGN, 182 TokenTypes.MOD_ASSIGN, 183 TokenTypes.PLUS_ASSIGN, 184 TokenTypes.SL_ASSIGN, 185 TokenTypes.SR_ASSIGN, 186 TokenTypes.STAR_ASSIGN, 187 }; 188 189 /** 190 * Used to test if logging a warning in a parent node may be skipped 191 * because a warning was already logged on an immediate child node. 192 */ 193 private DetailAST parentToSkip; 194 /** Depth of nested assignments. Normally this will be 0 or 1. */ 195 private int assignDepth; 196 197 @Override 198 public int[] getDefaultTokens() { 199 return new int[] { 200 TokenTypes.EXPR, 201 TokenTypes.IDENT, 202 TokenTypes.NUM_DOUBLE, 203 TokenTypes.NUM_FLOAT, 204 TokenTypes.NUM_INT, 205 TokenTypes.NUM_LONG, 206 TokenTypes.STRING_LITERAL, 207 TokenTypes.LITERAL_NULL, 208 TokenTypes.LITERAL_FALSE, 209 TokenTypes.LITERAL_TRUE, 210 TokenTypes.ASSIGN, 211 TokenTypes.BAND_ASSIGN, 212 TokenTypes.BOR_ASSIGN, 213 TokenTypes.BSR_ASSIGN, 214 TokenTypes.BXOR_ASSIGN, 215 TokenTypes.DIV_ASSIGN, 216 TokenTypes.MINUS_ASSIGN, 217 TokenTypes.MOD_ASSIGN, 218 TokenTypes.PLUS_ASSIGN, 219 TokenTypes.SL_ASSIGN, 220 TokenTypes.SR_ASSIGN, 221 TokenTypes.STAR_ASSIGN, 222 TokenTypes.LAMBDA, 223 }; 224 } 225 226 @Override 227 public int[] getAcceptableTokens() { 228 return new int[] { 229 TokenTypes.EXPR, 230 TokenTypes.IDENT, 231 TokenTypes.NUM_DOUBLE, 232 TokenTypes.NUM_FLOAT, 233 TokenTypes.NUM_INT, 234 TokenTypes.NUM_LONG, 235 TokenTypes.STRING_LITERAL, 236 TokenTypes.LITERAL_NULL, 237 TokenTypes.LITERAL_FALSE, 238 TokenTypes.LITERAL_TRUE, 239 TokenTypes.ASSIGN, 240 TokenTypes.BAND_ASSIGN, 241 TokenTypes.BOR_ASSIGN, 242 TokenTypes.BSR_ASSIGN, 243 TokenTypes.BXOR_ASSIGN, 244 TokenTypes.DIV_ASSIGN, 245 TokenTypes.MINUS_ASSIGN, 246 TokenTypes.MOD_ASSIGN, 247 TokenTypes.PLUS_ASSIGN, 248 TokenTypes.SL_ASSIGN, 249 TokenTypes.SR_ASSIGN, 250 TokenTypes.STAR_ASSIGN, 251 TokenTypes.LAMBDA, 252 }; 253 } 254 255 @Override 256 public int[] getRequiredTokens() { 257 // Check can work with any of acceptable tokens 258 return CommonUtil.EMPTY_INT_ARRAY; 259 } 260 261 // -@cs[CyclomaticComplexity] All logs should be in visit token. 262 @Override 263 public void visitToken(DetailAST ast) { 264 final int type = ast.getType(); 265 final DetailAST parent = ast.getParent(); 266 267 if (type == TokenTypes.LAMBDA && isLambdaSingleParameterSurrounded(ast)) { 268 log(ast, MSG_LAMBDA, ast.getText()); 269 } 270 else if (type != TokenTypes.ASSIGN 271 || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 272 final boolean surrounded = isSurrounded(ast); 273 // An identifier surrounded by parentheses. 274 if (surrounded && type == TokenTypes.IDENT) { 275 parentToSkip = ast.getParent(); 276 log(ast, MSG_IDENT, ast.getText()); 277 } 278 // A literal (numeric or string) surrounded by parentheses. 279 else if (surrounded && isInTokenList(type, LITERALS)) { 280 parentToSkip = ast.getParent(); 281 if (type == TokenTypes.STRING_LITERAL) { 282 log(ast, MSG_STRING, 283 chopString(ast.getText())); 284 } 285 else { 286 log(ast, MSG_LITERAL, ast.getText()); 287 } 288 } 289 // The rhs of an assignment surrounded by parentheses. 290 else if (isInTokenList(type, ASSIGNMENTS)) { 291 assignDepth++; 292 final DetailAST last = ast.getLastChild(); 293 if (last.getType() == TokenTypes.RPAREN) { 294 log(ast, MSG_ASSIGN); 295 } 296 } 297 } 298 } 299 300 @Override 301 public void leaveToken(DetailAST ast) { 302 final int type = ast.getType(); 303 final DetailAST parent = ast.getParent(); 304 305 // shouldn't process assign in annotation pairs 306 if (type != TokenTypes.ASSIGN 307 || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 308 // An expression is surrounded by parentheses. 309 if (type == TokenTypes.EXPR) { 310 // If 'parentToSkip' == 'ast', then we've already logged a 311 // warning about an immediate child node in visitToken, so we don't 312 // need to log another one here. 313 314 if (parentToSkip != ast && isExprSurrounded(ast)) { 315 if (assignDepth >= 1) { 316 log(ast, MSG_ASSIGN); 317 } 318 else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) { 319 log(ast, MSG_RETURN); 320 } 321 else { 322 log(ast, MSG_EXPR); 323 } 324 } 325 326 parentToSkip = null; 327 } 328 else if (isInTokenList(type, ASSIGNMENTS)) { 329 assignDepth--; 330 } 331 } 332 } 333 334 /** 335 * Tests if the given {@code DetailAST} is surrounded by parentheses. 336 * In short, does {@code ast} have a previous sibling whose type is 337 * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code 338 * TokenTypes.RPAREN}. 339 * @param ast the {@code DetailAST} to check if it is surrounded by 340 * parentheses. 341 * @return {@code true} if {@code ast} is surrounded by 342 * parentheses. 343 */ 344 private static boolean isSurrounded(DetailAST ast) { 345 // if previous sibling is left parenthesis, 346 // next sibling can't be other than right parenthesis 347 final DetailAST prev = ast.getPreviousSibling(); 348 return prev != null && prev.getType() == TokenTypes.LPAREN; 349 } 350 351 /** 352 * Tests if the given expression node is surrounded by parentheses. 353 * @param ast a {@code DetailAST} whose type is 354 * {@code TokenTypes.EXPR}. 355 * @return {@code true} if the expression is surrounded by 356 * parentheses. 357 */ 358 private static boolean isExprSurrounded(DetailAST ast) { 359 return ast.getFirstChild().getType() == TokenTypes.LPAREN; 360 } 361 362 /** 363 * Tests if the given lambda node has a single parameter, no defined type, and is surrounded 364 * by parentheses. 365 * @param ast a {@code DetailAST} whose type is 366 * {@code TokenTypes.LAMBDA}. 367 * @return {@code true} if the lambda has a single parameter, no defined type, and is 368 * surrounded by parentheses. 369 */ 370 private static boolean isLambdaSingleParameterSurrounded(DetailAST ast) { 371 final DetailAST firstChild = ast.getFirstChild(); 372 return firstChild.getType() == TokenTypes.LPAREN 373 && firstChild.getNextSibling().getChildCount(TokenTypes.PARAMETER_DEF) == 1 374 && firstChild.getNextSibling().getFirstChild().findFirstToken(TokenTypes.TYPE) 375 .getChildCount() == 0; 376 } 377 378 /** 379 * Check if the given token type can be found in an array of token types. 380 * @param type the token type. 381 * @param tokens an array of token types to search. 382 * @return {@code true} if {@code type} was found in {@code 383 * tokens}. 384 */ 385 private static boolean isInTokenList(int type, int... tokens) { 386 // NOTE: Given the small size of the two arrays searched, I'm not sure 387 // it's worth bothering with doing a binary search or using a 388 // HashMap to do the searches. 389 390 boolean found = false; 391 for (int i = 0; i < tokens.length && !found; i++) { 392 found = tokens[i] == type; 393 } 394 return found; 395 } 396 397 /** 398 * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH} 399 * plus an ellipsis (...) if the length of the string exceeds {@code 400 * MAX_QUOTED_LENGTH}. 401 * @param value the string to potentially chop. 402 * @return the chopped string if {@code string} is longer than 403 * {@code MAX_QUOTED_LENGTH}; otherwise {@code string}. 404 */ 405 private static String chopString(String value) { 406 String result = value; 407 if (value.length() > MAX_QUOTED_LENGTH) { 408 result = value.substring(0, MAX_QUOTED_LENGTH) + "...\""; 409 } 410 return result; 411 } 412 413 }