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