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