| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| UnnecessaryParenthesesCheck |
|
| 5.571428571428571;5.571 |
| 1 | //////////////////////////////////////////////////////////////////////////////// | |
| 2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
| 3 | // Copyright (C) 2001-2014 Oliver Burn | |
| 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 antlr.collections.AST; | |
| 23 | import com.puppycrawl.tools.checkstyle.api.Check; | |
| 24 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
| 25 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
| 26 | ||
| 27 | /** | |
| 28 | * <p> | |
| 29 | * Checks if unnecessary parentheses are used in a statement or expression. | |
| 30 | * The check will flag the following with warnings: | |
| 31 | * </p> | |
| 32 | * <pre> | |
| 33 | * return (x); // parens around identifier | |
| 34 | * return (x + 1); // parens around return value | |
| 35 | * int x = (y / 2 + 1); // parens around assignment rhs | |
| 36 | * for (int i = (0); i < 10; i++) { // parens around literal | |
| 37 | * t -= (z + 1); // parens around assignment rhs</pre> | |
| 38 | * <p> | |
| 39 | * The check is not "type aware", that is to say, it can't tell if parentheses | |
| 40 | * are unnecessary based on the types in an expression. It also doesn't know | |
| 41 | * about operator precedence and associatvity; therefore it won't catch | |
| 42 | * something like | |
| 43 | * </p> | |
| 44 | * <pre> | |
| 45 | * int x = (a + b) + c;</pre> | |
| 46 | * <p> | |
| 47 | * In the above case, given that <em>a</em>, <em>b</em>, and <em>c</em> are | |
| 48 | * all <code>int</code> variables, the parentheses around <code>a + b</code> | |
| 49 | * are not needed. | |
| 50 | * </p> | |
| 51 | * | |
| 52 | * @author Eric Roe | |
| 53 | */ | |
| 54 | 2 | public class UnnecessaryParenthesesCheck extends Check |
| 55 | { | |
| 56 | /** The minimum number of child nodes to consider for a match. */ | |
| 57 | private static final int MIN_CHILDREN_FOR_MATCH = 3; | |
| 58 | /** The maximum string length before we chop the string. */ | |
| 59 | private static final int MAX_QUOTED_LENGTH = 25; | |
| 60 | ||
| 61 | /** Token types for literals. */ | |
| 62 | 1 | private static final int [] LITERALS = { |
| 63 | TokenTypes.NUM_DOUBLE, | |
| 64 | TokenTypes.NUM_FLOAT, | |
| 65 | TokenTypes.NUM_INT, | |
| 66 | TokenTypes.NUM_LONG, | |
| 67 | TokenTypes.STRING_LITERAL, | |
| 68 | TokenTypes.LITERAL_NULL, | |
| 69 | TokenTypes.LITERAL_FALSE, | |
| 70 | TokenTypes.LITERAL_TRUE, | |
| 71 | }; | |
| 72 | ||
| 73 | /** Token types for assignment operations. */ | |
| 74 | 1 | private static final int [] ASSIGNMENTS = { |
| 75 | TokenTypes.ASSIGN, | |
| 76 | TokenTypes.BAND_ASSIGN, | |
| 77 | TokenTypes.BOR_ASSIGN, | |
| 78 | TokenTypes.BSR_ASSIGN, | |
| 79 | TokenTypes.BXOR_ASSIGN, | |
| 80 | TokenTypes.DIV_ASSIGN, | |
| 81 | TokenTypes.MINUS_ASSIGN, | |
| 82 | TokenTypes.MOD_ASSIGN, | |
| 83 | TokenTypes.PLUS_ASSIGN, | |
| 84 | TokenTypes.SL_ASSIGN, | |
| 85 | TokenTypes.SR_ASSIGN, | |
| 86 | TokenTypes.STAR_ASSIGN, | |
| 87 | }; | |
| 88 | ||
| 89 | /** | |
| 90 | * Used to test if logging a warning in a parent node may be skipped | |
| 91 | * because a warning was already logged on an immediate child node. | |
| 92 | */ | |
| 93 | private DetailAST mParentToSkip; | |
| 94 | /** Depth of nested assignments. Normally this will be 0 or 1. */ | |
| 95 | private int mAssignDepth; | |
| 96 | ||
| 97 | @Override | |
| 98 | public int[] getDefaultTokens() | |
| 99 | { | |
| 100 | 2 | return new int [] { |
| 101 | TokenTypes.EXPR, | |
| 102 | TokenTypes.IDENT, | |
| 103 | TokenTypes.NUM_DOUBLE, | |
| 104 | TokenTypes.NUM_FLOAT, | |
| 105 | TokenTypes.NUM_INT, | |
| 106 | TokenTypes.NUM_LONG, | |
| 107 | TokenTypes.STRING_LITERAL, | |
| 108 | TokenTypes.LITERAL_NULL, | |
| 109 | TokenTypes.LITERAL_FALSE, | |
| 110 | TokenTypes.LITERAL_TRUE, | |
| 111 | TokenTypes.ASSIGN, | |
| 112 | TokenTypes.BAND_ASSIGN, | |
| 113 | TokenTypes.BOR_ASSIGN, | |
| 114 | TokenTypes.BSR_ASSIGN, | |
| 115 | TokenTypes.BXOR_ASSIGN, | |
| 116 | TokenTypes.DIV_ASSIGN, | |
| 117 | TokenTypes.MINUS_ASSIGN, | |
| 118 | TokenTypes.MOD_ASSIGN, | |
| 119 | TokenTypes.PLUS_ASSIGN, | |
| 120 | TokenTypes.SL_ASSIGN, | |
| 121 | TokenTypes.SR_ASSIGN, | |
| 122 | TokenTypes.STAR_ASSIGN, | |
| 123 | }; | |
| 124 | } | |
| 125 | ||
| 126 | @Override | |
| 127 | public void visitToken(DetailAST aAST) | |
| 128 | { | |
| 129 | 327 | final int type = aAST.getType(); |
| 130 | 327 | final boolean surrounded = isSurrounded(aAST); |
| 131 | 327 | final DetailAST parent = aAST.getParent(); |
| 132 | ||
| 133 | 327 | if ((type == TokenTypes.ASSIGN) |
| 134 | && (parent.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR)) | |
| 135 | { | |
| 136 | // shouldn't process assign in annotation pairs | |
| 137 | 2 | return; |
| 138 | } | |
| 139 | ||
| 140 | // An identifier surrounded by parentheses. | |
| 141 | 325 | if (surrounded && (type == TokenTypes.IDENT)) { |
| 142 | 9 | mParentToSkip = aAST.getParent(); |
| 143 | 9 | log(aAST, "unnecessary.paren.ident", aAST.getText()); |
| 144 | 9 | return; |
| 145 | } | |
| 146 | ||
| 147 | // A literal (numeric or string) surrounded by parentheses. | |
| 148 | 316 | if (surrounded && inTokenList(type, LITERALS)) { |
| 149 | 11 | mParentToSkip = aAST.getParent(); |
| 150 | 11 | if (type == TokenTypes.STRING_LITERAL) { |
| 151 | 3 | log(aAST, "unnecessary.paren.string", |
| 152 | chopString(aAST.getText())); | |
| 153 | } | |
| 154 | else { | |
| 155 | 8 | log(aAST, "unnecessary.paren.literal", aAST.getText()); |
| 156 | } | |
| 157 | 11 | return; |
| 158 | } | |
| 159 | ||
| 160 | // The rhs of an assignment surrounded by parentheses. | |
| 161 | 305 | if (inTokenList(type, ASSIGNMENTS)) { |
| 162 | 37 | mAssignDepth++; |
| 163 | 37 | final DetailAST last = aAST.getLastChild(); |
| 164 | 37 | if (last.getType() == TokenTypes.RPAREN) { |
| 165 | 13 | log(aAST, "unnecessary.paren.assign"); |
| 166 | } | |
| 167 | } | |
| 168 | 305 | } |
| 169 | ||
| 170 | @Override | |
| 171 | public void leaveToken(DetailAST aAST) | |
| 172 | { | |
| 173 | 327 | final int type = aAST.getType(); |
| 174 | 327 | final DetailAST parent = aAST.getParent(); |
| 175 | ||
| 176 | 327 | if ((type == TokenTypes.ASSIGN) |
| 177 | && (parent.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR)) | |
| 178 | { | |
| 179 | // shouldn't process assign in annotation pairs | |
| 180 | 2 | return; |
| 181 | } | |
| 182 | ||
| 183 | // An expression is surrounded by parentheses. | |
| 184 | 325 | if (type == TokenTypes.EXPR) { |
| 185 | ||
| 186 | // If 'mParentToSkip' == 'aAST', then we've already logged a | |
| 187 | // warning about an immediate child node in visitToken, so we don't | |
| 188 | // need to log another one here. | |
| 189 | ||
| 190 | 63 | if ((mParentToSkip != aAST) && exprSurrounded(aAST)) { |
| 191 | 11 | if (mAssignDepth >= 1) { |
| 192 | 5 | log(aAST, "unnecessary.paren.assign"); |
| 193 | } | |
| 194 | 6 | else if (aAST.getParent().getType() |
| 195 | == TokenTypes.LITERAL_RETURN) | |
| 196 | { | |
| 197 | 2 | log(aAST, "unnecessary.paren.return"); |
| 198 | } | |
| 199 | else { | |
| 200 | 4 | log(aAST, "unnecessary.paren.expr"); |
| 201 | } | |
| 202 | } | |
| 203 | ||
| 204 | 63 | mParentToSkip = null; |
| 205 | } | |
| 206 | 262 | else if (inTokenList(type, ASSIGNMENTS)) { |
| 207 | 37 | mAssignDepth--; |
| 208 | } | |
| 209 | ||
| 210 | 325 | super.leaveToken(aAST); |
| 211 | 325 | } |
| 212 | ||
| 213 | /** | |
| 214 | * Tests if the given <code>DetailAST</code> is surrounded by parentheses. | |
| 215 | * In short, does <code>aAST</code> have a previous sibling whose type is | |
| 216 | * <code>TokenTypes.LPAREN</code> and a next sibling whose type is <code> | |
| 217 | * TokenTypes.RPAREN</code>. | |
| 218 | * @param aAST the <code>DetailAST</code> to check if it is surrounded by | |
| 219 | * parentheses. | |
| 220 | * @return <code>true</code> if <code>aAST</code> is surrounded by | |
| 221 | * parentheses. | |
| 222 | */ | |
| 223 | private boolean isSurrounded(DetailAST aAST) | |
| 224 | { | |
| 225 | 327 | final DetailAST prev = aAST.getPreviousSibling(); |
| 226 | 327 | final DetailAST next = aAST.getNextSibling(); |
| 227 | ||
| 228 | 327 | return (prev != null) && (prev.getType() == TokenTypes.LPAREN) |
| 229 | && (next != null) && (next.getType() == TokenTypes.RPAREN); | |
| 230 | } | |
| 231 | ||
| 232 | /** | |
| 233 | * Tests if the given expression node is surrounded by parentheses. | |
| 234 | * @param aAST a <code>DetailAST</code> whose type is | |
| 235 | * <code>TokenTypes.EXPR</code>. | |
| 236 | * @return <code>true</code> if the expression is surrounded by | |
| 237 | * parentheses. | |
| 238 | * @throws IllegalArgumentException if <code>aAST.getType()</code> is not | |
| 239 | * equal to <code>TokenTypes.EXPR</code>. | |
| 240 | */ | |
| 241 | private boolean exprSurrounded(DetailAST aAST) | |
| 242 | { | |
| 243 | 58 | if (aAST.getType() != TokenTypes.EXPR) { |
| 244 | 0 | throw new IllegalArgumentException("Not an expression node."); |
| 245 | } | |
| 246 | 58 | boolean surrounded = false; |
| 247 | 58 | if (aAST.getChildCount() >= MIN_CHILDREN_FOR_MATCH) { |
| 248 | 11 | final AST n1 = aAST.getFirstChild(); |
| 249 | 11 | final AST nn = aAST.getLastChild(); |
| 250 | ||
| 251 | 11 | surrounded = (n1.getType() == TokenTypes.LPAREN) |
| 252 | && (nn.getType() == TokenTypes.RPAREN); | |
| 253 | } | |
| 254 | 58 | return surrounded; |
| 255 | } | |
| 256 | ||
| 257 | /** | |
| 258 | * Check if the given token type can be found in an array of token types. | |
| 259 | * @param aType the token type. | |
| 260 | * @param aTokens an array of token types to search. | |
| 261 | * @return <code>true</code> if <code>aType</code> was found in <code> | |
| 262 | * aTokens</code>. | |
| 263 | */ | |
| 264 | private boolean inTokenList(int aType, int [] aTokens) | |
| 265 | { | |
| 266 | // NOTE: Given the small size of the two arrays searched, I'm not sure | |
| 267 | // it's worth bothering with doing a binary search or using a | |
| 268 | // HashMap to do the searches. | |
| 269 | ||
| 270 | 583 | boolean found = false; |
| 271 | 6769 | for (int i = 0; (i < aTokens.length) && !found; i++) { |
| 272 | 6186 | found = aTokens[i] == aType; |
| 273 | } | |
| 274 | 583 | return found; |
| 275 | } | |
| 276 | ||
| 277 | /** | |
| 278 | * Returns the specified string chopped to <code>MAX_QUOTED_LENGTH</code> | |
| 279 | * plus an ellipsis (...) if the length of the string exceeds <code> | |
| 280 | * MAX_QUOTED_LENGTH</code>. | |
| 281 | * @param aString the string to potentially chop. | |
| 282 | * @return the chopped string if <code>aString</code> is longer than | |
| 283 | * <code>MAX_QUOTED_LENGTH</code>; otherwise <code>aString</code>. | |
| 284 | */ | |
| 285 | private String chopString(String aString) | |
| 286 | { | |
| 287 | 3 | if (aString.length() > MAX_QUOTED_LENGTH) { |
| 288 | 1 | return aString.substring(0, MAX_QUOTED_LENGTH) + "...\""; |
| 289 | } | |
| 290 | 2 | return aString; |
| 291 | } | |
| 292 | } |