001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.api.AbstractCheck; 023import com.puppycrawl.tools.checkstyle.api.DetailAST; 024import com.puppycrawl.tools.checkstyle.api.TokenTypes; 025import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 026 027/** 028 * <p> 029 * Checks if unnecessary parentheses are used in a statement or expression. 030 * The check will flag the following with warnings: 031 * </p> 032 * <pre> 033 * return (x); // parens around identifier 034 * return (x + 1); // parens around return value 035 * int x = (y / 2 + 1); // parens around assignment rhs 036 * for (int i = (0); i < 10; i++) { // parens around literal 037 * t -= (z + 1); // parens around assignment rhs</pre> 038 * <p> 039 * The check is not "type aware", that is to say, it can't tell if parentheses 040 * are unnecessary based on the types in an expression. It also doesn't know 041 * about operator precedence and associativity; therefore it won't catch 042 * something like 043 * </p> 044 * <pre> 045 * int x = (a + b) + c;</pre> 046 * <p> 047 * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are 048 * all {@code int} variables, the parentheses around {@code a + b} 049 * are not needed. 050 * </p> 051 * 052 * @author Eric Roe 053 */ 054public class UnnecessaryParenthesesCheck extends AbstractCheck { 055 056 /** 057 * A key is pointing to the warning message text in "messages.properties" 058 * file. 059 */ 060 public static final String MSG_IDENT = "unnecessary.paren.ident"; 061 062 /** 063 * A key is pointing to the warning message text in "messages.properties" 064 * file. 065 */ 066 public static final String MSG_ASSIGN = "unnecessary.paren.assign"; 067 068 /** 069 * A key is pointing to the warning message text in "messages.properties" 070 * file. 071 */ 072 public static final String MSG_EXPR = "unnecessary.paren.expr"; 073 074 /** 075 * A key is pointing to the warning message text in "messages.properties" 076 * file. 077 */ 078 public static final String MSG_LITERAL = "unnecessary.paren.literal"; 079 080 /** 081 * A key is pointing to the warning message text in "messages.properties" 082 * file. 083 */ 084 public static final String MSG_STRING = "unnecessary.paren.string"; 085 086 /** 087 * A key is pointing to the warning message text in "messages.properties" 088 * file. 089 */ 090 public static final String MSG_RETURN = "unnecessary.paren.return"; 091 092 /** The maximum string length before we chop the string. */ 093 private static final int MAX_QUOTED_LENGTH = 25; 094 095 /** Token types for literals. */ 096 private static final int[] LITERALS = { 097 TokenTypes.NUM_DOUBLE, 098 TokenTypes.NUM_FLOAT, 099 TokenTypes.NUM_INT, 100 TokenTypes.NUM_LONG, 101 TokenTypes.STRING_LITERAL, 102 TokenTypes.LITERAL_NULL, 103 TokenTypes.LITERAL_FALSE, 104 TokenTypes.LITERAL_TRUE, 105 }; 106 107 /** Token types for assignment operations. */ 108 private static final int[] ASSIGNMENTS = { 109 TokenTypes.ASSIGN, 110 TokenTypes.BAND_ASSIGN, 111 TokenTypes.BOR_ASSIGN, 112 TokenTypes.BSR_ASSIGN, 113 TokenTypes.BXOR_ASSIGN, 114 TokenTypes.DIV_ASSIGN, 115 TokenTypes.MINUS_ASSIGN, 116 TokenTypes.MOD_ASSIGN, 117 TokenTypes.PLUS_ASSIGN, 118 TokenTypes.SL_ASSIGN, 119 TokenTypes.SR_ASSIGN, 120 TokenTypes.STAR_ASSIGN, 121 }; 122 123 /** 124 * Used to test if logging a warning in a parent node may be skipped 125 * because a warning was already logged on an immediate child node. 126 */ 127 private DetailAST parentToSkip; 128 /** Depth of nested assignments. Normally this will be 0 or 1. */ 129 private int assignDepth; 130 131 @Override 132 public int[] getDefaultTokens() { 133 return new int[] { 134 TokenTypes.EXPR, 135 TokenTypes.IDENT, 136 TokenTypes.NUM_DOUBLE, 137 TokenTypes.NUM_FLOAT, 138 TokenTypes.NUM_INT, 139 TokenTypes.NUM_LONG, 140 TokenTypes.STRING_LITERAL, 141 TokenTypes.LITERAL_NULL, 142 TokenTypes.LITERAL_FALSE, 143 TokenTypes.LITERAL_TRUE, 144 TokenTypes.ASSIGN, 145 TokenTypes.BAND_ASSIGN, 146 TokenTypes.BOR_ASSIGN, 147 TokenTypes.BSR_ASSIGN, 148 TokenTypes.BXOR_ASSIGN, 149 TokenTypes.DIV_ASSIGN, 150 TokenTypes.MINUS_ASSIGN, 151 TokenTypes.MOD_ASSIGN, 152 TokenTypes.PLUS_ASSIGN, 153 TokenTypes.SL_ASSIGN, 154 TokenTypes.SR_ASSIGN, 155 TokenTypes.STAR_ASSIGN, 156 }; 157 } 158 159 @Override 160 public int[] getAcceptableTokens() { 161 return new int[] { 162 TokenTypes.EXPR, 163 TokenTypes.IDENT, 164 TokenTypes.NUM_DOUBLE, 165 TokenTypes.NUM_FLOAT, 166 TokenTypes.NUM_INT, 167 TokenTypes.NUM_LONG, 168 TokenTypes.STRING_LITERAL, 169 TokenTypes.LITERAL_NULL, 170 TokenTypes.LITERAL_FALSE, 171 TokenTypes.LITERAL_TRUE, 172 TokenTypes.ASSIGN, 173 TokenTypes.BAND_ASSIGN, 174 TokenTypes.BOR_ASSIGN, 175 TokenTypes.BSR_ASSIGN, 176 TokenTypes.BXOR_ASSIGN, 177 TokenTypes.DIV_ASSIGN, 178 TokenTypes.MINUS_ASSIGN, 179 TokenTypes.MOD_ASSIGN, 180 TokenTypes.PLUS_ASSIGN, 181 TokenTypes.SL_ASSIGN, 182 TokenTypes.SR_ASSIGN, 183 TokenTypes.STAR_ASSIGN, 184 }; 185 } 186 187 @Override 188 public int[] getRequiredTokens() { 189 // Check can work with any of acceptable tokens 190 return CommonUtils.EMPTY_INT_ARRAY; 191 } 192 193 @Override 194 public void visitToken(DetailAST ast) { 195 final int type = ast.getType(); 196 final DetailAST parent = ast.getParent(); 197 198 if (type != TokenTypes.ASSIGN 199 || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 200 201 final boolean surrounded = isSurrounded(ast); 202 // An identifier surrounded by parentheses. 203 if (surrounded && type == TokenTypes.IDENT) { 204 parentToSkip = ast.getParent(); 205 log(ast, MSG_IDENT, ast.getText()); 206 } 207 // A literal (numeric or string) surrounded by parentheses. 208 else if (surrounded && isInTokenList(type, LITERALS)) { 209 parentToSkip = ast.getParent(); 210 if (type == TokenTypes.STRING_LITERAL) { 211 log(ast, MSG_STRING, 212 chopString(ast.getText())); 213 } 214 else { 215 log(ast, MSG_LITERAL, ast.getText()); 216 } 217 } 218 // The rhs of an assignment surrounded by parentheses. 219 else if (isInTokenList(type, ASSIGNMENTS)) { 220 assignDepth++; 221 final DetailAST last = ast.getLastChild(); 222 if (last.getType() == TokenTypes.RPAREN) { 223 log(ast, MSG_ASSIGN); 224 } 225 } 226 } 227 } 228 229 @Override 230 public void leaveToken(DetailAST ast) { 231 final int type = ast.getType(); 232 final DetailAST parent = ast.getParent(); 233 234 // shouldn't process assign in annotation pairs 235 if (type != TokenTypes.ASSIGN 236 || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { 237 // An expression is surrounded by parentheses. 238 if (type == TokenTypes.EXPR) { 239 240 // If 'parentToSkip' == 'ast', then we've already logged a 241 // warning about an immediate child node in visitToken, so we don't 242 // need to log another one here. 243 244 if (parentToSkip != ast && isExprSurrounded(ast)) { 245 if (assignDepth >= 1) { 246 log(ast, MSG_ASSIGN); 247 } 248 else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) { 249 log(ast, MSG_RETURN); 250 } 251 else { 252 log(ast, MSG_EXPR); 253 } 254 } 255 256 parentToSkip = null; 257 } 258 else if (isInTokenList(type, ASSIGNMENTS)) { 259 assignDepth--; 260 } 261 262 super.leaveToken(ast); 263 } 264 } 265 266 /** 267 * Tests if the given {@code DetailAST} is surrounded by parentheses. 268 * In short, does {@code ast} have a previous sibling whose type is 269 * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code 270 * TokenTypes.RPAREN}. 271 * @param ast the {@code DetailAST} to check if it is surrounded by 272 * parentheses. 273 * @return {@code true} if {@code ast} is surrounded by 274 * parentheses. 275 */ 276 private static boolean isSurrounded(DetailAST ast) { 277 // if previous sibling is left parenthesis, 278 // next sibling can't be other than right parenthesis 279 final DetailAST prev = ast.getPreviousSibling(); 280 return prev != null && prev.getType() == TokenTypes.LPAREN; 281 } 282 283 /** 284 * Tests if the given expression node is surrounded by parentheses. 285 * @param ast a {@code DetailAST} whose type is 286 * {@code TokenTypes.EXPR}. 287 * @return {@code true} if the expression is surrounded by 288 * parentheses. 289 */ 290 private static boolean isExprSurrounded(DetailAST ast) { 291 return ast.getFirstChild().getType() == TokenTypes.LPAREN; 292 } 293 294 /** 295 * Check if the given token type can be found in an array of token types. 296 * @param type the token type. 297 * @param tokens an array of token types to search. 298 * @return {@code true} if {@code type} was found in {@code 299 * tokens}. 300 */ 301 private static boolean isInTokenList(int type, int... tokens) { 302 // NOTE: Given the small size of the two arrays searched, I'm not sure 303 // it's worth bothering with doing a binary search or using a 304 // HashMap to do the searches. 305 306 boolean found = false; 307 for (int i = 0; i < tokens.length && !found; i++) { 308 found = tokens[i] == type; 309 } 310 return found; 311 } 312 313 /** 314 * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH} 315 * plus an ellipsis (...) if the length of the string exceeds {@code 316 * MAX_QUOTED_LENGTH}. 317 * @param value the string to potentially chop. 318 * @return the chopped string if {@code string} is longer than 319 * {@code MAX_QUOTED_LENGTH}; otherwise {@code string}. 320 */ 321 private static String chopString(String value) { 322 if (value.length() > MAX_QUOTED_LENGTH) { 323 return value.substring(0, MAX_QUOTED_LENGTH) + "...\""; 324 } 325 return value; 326 } 327}