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 | } |