//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.puppycrawl.tools.checkstyle.checks.coding; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; import com.puppycrawl.tools.checkstyle.utils.CommonUtils; /** * <p> * Checks if unnecessary parentheses are used in a statement or expression. * The check will flag the following with warnings: * </p> * <pre> * return (x); // parens around identifier * return (x + 1); // parens around return value * int x = (y / 2 + 1); // parens around assignment rhs * for (int i = (0); i < 10; i++) { // parens around literal * t -= (z + 1); // parens around assignment rhs</pre> * <p> * The check is not "type aware", that is to say, it can't tell if parentheses * are unnecessary based on the types in an expression. It also doesn't know * about operator precedence and associativity; therefore it won't catch * something like * </p> * <pre> * int x = (a + b) + c;</pre> * <p> * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are * all {@code int} variables, the parentheses around {@code a + b} * are not needed. * </p> * * @author Eric Roe */ public class UnnecessaryParenthesesCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_IDENT = "unnecessary.paren.ident"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_ASSIGN = "unnecessary.paren.assign"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_EXPR = "unnecessary.paren.expr"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_LITERAL = "unnecessary.paren.literal"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_STRING = "unnecessary.paren.string"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_RETURN = "unnecessary.paren.return"; /** The maximum string length before we chop the string. */ private static final int MAX_QUOTED_LENGTH = 25; /** Token types for literals. */ private static final int[] LITERALS = { TokenTypes.NUM_DOUBLE, TokenTypes.NUM_FLOAT, TokenTypes.NUM_INT, TokenTypes.NUM_LONG, TokenTypes.STRING_LITERAL, TokenTypes.LITERAL_NULL, TokenTypes.LITERAL_FALSE, TokenTypes.LITERAL_TRUE, }; /** Token types for assignment operations. */ private static final int[] ASSIGNMENTS = { TokenTypes.ASSIGN, TokenTypes.BAND_ASSIGN, TokenTypes.BOR_ASSIGN, TokenTypes.BSR_ASSIGN, TokenTypes.BXOR_ASSIGN, TokenTypes.DIV_ASSIGN, TokenTypes.MINUS_ASSIGN, TokenTypes.MOD_ASSIGN, TokenTypes.PLUS_ASSIGN, TokenTypes.SL_ASSIGN, TokenTypes.SR_ASSIGN, TokenTypes.STAR_ASSIGN, }; /** * Used to test if logging a warning in a parent node may be skipped * because a warning was already logged on an immediate child node. */ private DetailAST parentToSkip; /** Depth of nested assignments. Normally this will be 0 or 1. */ private int assignDepth; @Override public int[] getDefaultTokens() { return new int[] { TokenTypes.EXPR, TokenTypes.IDENT, TokenTypes.NUM_DOUBLE, TokenTypes.NUM_FLOAT, TokenTypes.NUM_INT, TokenTypes.NUM_LONG, TokenTypes.STRING_LITERAL, TokenTypes.LITERAL_NULL, TokenTypes.LITERAL_FALSE, TokenTypes.LITERAL_TRUE, TokenTypes.ASSIGN, TokenTypes.BAND_ASSIGN, TokenTypes.BOR_ASSIGN, TokenTypes.BSR_ASSIGN, TokenTypes.BXOR_ASSIGN, TokenTypes.DIV_ASSIGN, TokenTypes.MINUS_ASSIGN, TokenTypes.MOD_ASSIGN, TokenTypes.PLUS_ASSIGN, TokenTypes.SL_ASSIGN, TokenTypes.SR_ASSIGN, TokenTypes.STAR_ASSIGN, }; } @Override public int[] getAcceptableTokens() { return new int[] { TokenTypes.EXPR, TokenTypes.IDENT, TokenTypes.NUM_DOUBLE, TokenTypes.NUM_FLOAT, TokenTypes.NUM_INT, TokenTypes.NUM_LONG, TokenTypes.STRING_LITERAL, TokenTypes.LITERAL_NULL, TokenTypes.LITERAL_FALSE, TokenTypes.LITERAL_TRUE, TokenTypes.ASSIGN, TokenTypes.BAND_ASSIGN, TokenTypes.BOR_ASSIGN, TokenTypes.BSR_ASSIGN, TokenTypes.BXOR_ASSIGN, TokenTypes.DIV_ASSIGN, TokenTypes.MINUS_ASSIGN, TokenTypes.MOD_ASSIGN, TokenTypes.PLUS_ASSIGN, TokenTypes.SL_ASSIGN, TokenTypes.SR_ASSIGN, TokenTypes.STAR_ASSIGN, }; } @Override public int[] getRequiredTokens() { // Check can work with any of acceptable tokens return CommonUtils.EMPTY_INT_ARRAY; } @Override public void visitToken(DetailAST ast) { final int type = ast.getType(); final DetailAST parent = ast.getParent(); if (type != TokenTypes.ASSIGN || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { final boolean surrounded = isSurrounded(ast); // An identifier surrounded by parentheses. if (surrounded && type == TokenTypes.IDENT) { parentToSkip = ast.getParent(); log(ast, MSG_IDENT, ast.getText()); } // A literal (numeric or string) surrounded by parentheses. else if (surrounded && isInTokenList(type, LITERALS)) { parentToSkip = ast.getParent(); if (type == TokenTypes.STRING_LITERAL) { log(ast, MSG_STRING, chopString(ast.getText())); } else { log(ast, MSG_LITERAL, ast.getText()); } } // The rhs of an assignment surrounded by parentheses. else if (isInTokenList(type, ASSIGNMENTS)) { assignDepth++; final DetailAST last = ast.getLastChild(); if (last.getType() == TokenTypes.RPAREN) { log(ast, MSG_ASSIGN); } } } } @Override public void leaveToken(DetailAST ast) { final int type = ast.getType(); final DetailAST parent = ast.getParent(); // shouldn't process assign in annotation pairs if (type != TokenTypes.ASSIGN || parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { // An expression is surrounded by parentheses. if (type == TokenTypes.EXPR) { // If 'parentToSkip' == 'ast', then we've already logged a // warning about an immediate child node in visitToken, so we don't // need to log another one here. if (parentToSkip != ast && isExprSurrounded(ast)) { if (assignDepth >= 1) { log(ast, MSG_ASSIGN); } else if (ast.getParent().getType() == TokenTypes.LITERAL_RETURN) { log(ast, MSG_RETURN); } else { log(ast, MSG_EXPR); } } parentToSkip = null; } else if (isInTokenList(type, ASSIGNMENTS)) { assignDepth--; } super.leaveToken(ast); } } /** * Tests if the given {@code DetailAST} is surrounded by parentheses. * In short, does {@code ast} have a previous sibling whose type is * {@code TokenTypes.LPAREN} and a next sibling whose type is {@code * TokenTypes.RPAREN}. * @param ast the {@code DetailAST} to check if it is surrounded by * parentheses. * @return {@code true} if {@code ast} is surrounded by * parentheses. */ private static boolean isSurrounded(DetailAST ast) { // if previous sibling is left parenthesis, // next sibling can't be other than right parenthesis final DetailAST prev = ast.getPreviousSibling(); return prev != null && prev.getType() == TokenTypes.LPAREN; } /** * Tests if the given expression node is surrounded by parentheses. * @param ast a {@code DetailAST} whose type is * {@code TokenTypes.EXPR}. * @return {@code true} if the expression is surrounded by * parentheses. */ private static boolean isExprSurrounded(DetailAST ast) { return ast.getFirstChild().getType() == TokenTypes.LPAREN; } /** * Check if the given token type can be found in an array of token types. * @param type the token type. * @param tokens an array of token types to search. * @return {@code true} if {@code type} was found in {@code * tokens}. */ private static boolean isInTokenList(int type, int... tokens) { // NOTE: Given the small size of the two arrays searched, I'm not sure // it's worth bothering with doing a binary search or using a // HashMap to do the searches. boolean found = false; for (int i = 0; i < tokens.length && !found; i++) { found = tokens[i] == type; } return found; } /** * Returns the specified string chopped to {@code MAX_QUOTED_LENGTH} * plus an ellipsis (...) if the length of the string exceeds {@code * MAX_QUOTED_LENGTH}. * @param value the string to potentially chop. * @return the chopped string if {@code string} is longer than * {@code MAX_QUOTED_LENGTH}; otherwise {@code string}. */ private static String chopString(String value) { if (value.length() > MAX_QUOTED_LENGTH) { return value.substring(0, MAX_QUOTED_LENGTH) + "...\""; } return value; } }