Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DescendantTokenCheck |
|
| 2.5;2.5 |
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 | package com.puppycrawl.tools.checkstyle.checks; | |
20 | ||
21 | import antlr.collections.AST; | |
22 | import com.puppycrawl.tools.checkstyle.api.Check; | |
23 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
24 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
25 | import java.util.Arrays; | |
26 | import java.util.Set; | |
27 | ||
28 | /** | |
29 | * <p> | |
30 | * Checks for restricted tokens beneath other tokens. | |
31 | * </p> | |
32 | * <p> | |
33 | * Examples of how to configure the check: | |
34 | * </p> | |
35 | * <pre> | |
36 | * <!-- String literal equality check --> | |
37 | * <module name="DescendantToken"> | |
38 | * <property name="tokens" value="EQUAL,NOT_EQUAL"/> | |
39 | * <property name="limitedTokens" value="STRING_LITERAL"/> | |
40 | * <property name="maximumNumber" value="0"/> | |
41 | * <property name="maximumDepth" value="1"/> | |
42 | * </module> | |
43 | * | |
44 | * <!-- Switch with no default --> | |
45 | * <module name="DescendantToken"> | |
46 | * <property name="tokens" value="LITERAL_SWITCH"/> | |
47 | * <property name="maximumDepth" value="2"/> | |
48 | * <property name="limitedTokens" value="LITERAL_DEFAULT"/> | |
49 | * <property name="minimumNumber" value="1"/> | |
50 | * </module> | |
51 | * | |
52 | * <!-- Assert statement may have side effects --> | |
53 | * <module name="DescendantToken"> | |
54 | * <property name="tokens" value="LITERAL_ASSERT"/> | |
55 | * <property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC, | |
56 | * POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN, | |
57 | * BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN, | |
58 | * METHOD_CALL"/> | |
59 | * <property name="maximumNumber" value="0"/> | |
60 | * </module> | |
61 | * | |
62 | * <!-- Initialiser in for performs no setup - use while instead? --> | |
63 | * <module name="DescendantToken"> | |
64 | * <property name="tokens" value="FOR_INIT"/> | |
65 | * <property name="limitedTokens" value="EXPR"/> | |
66 | * <property name="minimumNumber" value="1"/> | |
67 | * </module> | |
68 | * | |
69 | * <!-- Condition in for performs no check --> | |
70 | * <module name="DescendantToken"> | |
71 | * <property name="tokens" value="FOR_CONDITION"/> | |
72 | * <property name="limitedTokens" value="EXPR"/> | |
73 | * <property name="minimumNumber" value="1"/> | |
74 | * </module> | |
75 | * | |
76 | * <!-- Switch within switch --> | |
77 | * <module name="DescendantToken"> | |
78 | * <property name="tokens" value="LITERAL_SWITCH"/> | |
79 | * <property name="limitedTokens" value="LITERAL_SWITCH"/> | |
80 | * <property name="maximumNumber" value="0"/> | |
81 | * <property name="minimumDepth" value="1"/> | |
82 | * </module> | |
83 | * | |
84 | * <!-- Return from within a catch or finally block --> | |
85 | * <module name="DescendantToken"> | |
86 | * <property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/> | |
87 | * <property name="limitedTokens" value="LITERAL_RETURN"/> | |
88 | * <property name="maximumNumber" value="0"/> | |
89 | * </module> | |
90 | * | |
91 | * <!-- Try within catch or finally block --> | |
92 | * <module name="DescendantToken"> | |
93 | * <property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/> | |
94 | * <property name="limitedTokens" value="LITERAL_TRY"/> | |
95 | * <property name="maximumNumber" value="0"/> | |
96 | * </module> | |
97 | * | |
98 | * <!-- Too many cases within a switch --> | |
99 | * <module name="DescendantToken"> | |
100 | * <property name="tokens" value="LITERAL_SWITCH"/> | |
101 | * <property name="limitedTokens" value="LITERAL_CASE"/> | |
102 | * <property name="maximumDepth" value="2"/> | |
103 | * <property name="maximumNumber" value="10"/> | |
104 | * </module> | |
105 | * | |
106 | * <!-- Too many local variables within a method --> | |
107 | * <module name="DescendantToken"> | |
108 | * <property name="tokens" value="METHOD_DEF"/> | |
109 | * <property name="limitedTokens" value="VARIABLE_DEF"/> | |
110 | * <property name="maximumDepth" value="2"/> | |
111 | * <property name="maximumNumber" value="10"/> | |
112 | * </module> | |
113 | * | |
114 | * <!-- Too many returns from within a method --> | |
115 | * <module name="DescendantToken"> | |
116 | * <property name="tokens" value="METHOD_DEF"/> | |
117 | * <property name="limitedTokens" value="LITERAL_RETURN"/> | |
118 | * <property name="maximumNumber" value="3"/> | |
119 | * </module> | |
120 | * | |
121 | * <!-- Too many fields within an interface --> | |
122 | * <module name="DescendantToken"> | |
123 | * <property name="tokens" value="INTERFACE_DEF"/> | |
124 | * <property name="limitedTokens" value="VARIABLE_DEF"/> | |
125 | * <property name="maximumDepth" value="2"/> | |
126 | * <property name="maximumNumber" value="0"/> | |
127 | * </module> | |
128 | * | |
129 | * <!-- Limit the number of exceptions a method can throw --> | |
130 | * <module name="DescendantToken"> | |
131 | * <property name="tokens" value="LITERAL_THROWS"/> | |
132 | * <property name="limitedTokens" value="IDENT"/> | |
133 | * <property name="maximumNumber" value="1"/> | |
134 | * </module> | |
135 | * | |
136 | * <!-- Limit the number of expressions in a method --> | |
137 | * <module name="DescendantToken"> | |
138 | * <property name="tokens" value="METHOD_DEF"/> | |
139 | * <property name="limitedTokens" value="EXPR"/> | |
140 | * <property name="maximumNumber" value="200"/> | |
141 | * </module> | |
142 | * | |
143 | * <!-- Disallow empty statements --> | |
144 | * <module name="DescendantToken"> | |
145 | * <property name="tokens" value="EMPTY_STAT"/> | |
146 | * <property name="limitedTokens" value="EMPTY_STAT"/> | |
147 | * <property name="maximumNumber" value="0"/> | |
148 | * <property name="maximumDepth" value="0"/> | |
149 | * <property name="maximumMessage" | |
150 | * value="Empty statement is not allowed."/> | |
151 | * </module> | |
152 | * | |
153 | * <!-- Too many fields within a class --> | |
154 | * <module name="DescendantToken"> | |
155 | * <property name="tokens" value="CLASS_DEF"/> | |
156 | * <property name="limitedTokens" value="VARIABLE_DEF"/> | |
157 | * <property name="maximumDepth" value="2"/> | |
158 | * <property name="maximumNumber" value="10"/> | |
159 | * </module> | |
160 | * </pre> | |
161 | * | |
162 | * @author Tim Tyler <tim@tt1.org> | |
163 | * @author Rick Giles | |
164 | */ | |
165 | 18 | public class DescendantTokenCheck extends Check |
166 | { | |
167 | /** minimum depth */ | |
168 | private int mMinimumDepth; | |
169 | /** maximum depth */ | |
170 | 18 | private int mMaximumDepth = Integer.MAX_VALUE; |
171 | /** minimum number */ | |
172 | private int mMinimumNumber; | |
173 | /** maximum number */ | |
174 | 18 | private int mMaximumNumber = Integer.MAX_VALUE; |
175 | /** Whether to sum the number of tokens found. */ | |
176 | private boolean mSumTokenCounts; | |
177 | /** limited tokens */ | |
178 | 18 | private int[] mLimitedTokens = new int[0]; |
179 | /** error message when minimum count not reached */ | |
180 | private String mMinimumMessage; | |
181 | /** error message when maximum count exceeded */ | |
182 | private String mMaximumMessage; | |
183 | ||
184 | /** | |
185 | * Counts of descendant tokens. | |
186 | * Indexed by (token ID - 1) for performance. | |
187 | */ | |
188 | 18 | private int[] mCounts = new int[0]; |
189 | ||
190 | @Override | |
191 | public int[] getDefaultTokens() | |
192 | { | |
193 | 1 | return new int[0]; |
194 | } | |
195 | ||
196 | @Override | |
197 | public void visitToken(DetailAST aAST) | |
198 | { | |
199 | //reset counts | |
200 | 61 | Arrays.fill(mCounts, 0); |
201 | 61 | countTokens(aAST, 0); |
202 | ||
203 | // name of this token | |
204 | 61 | final String name = TokenTypes.getTokenName(aAST.getType()); |
205 | ||
206 | 61 | if (mSumTokenCounts) { |
207 | 14 | int total = 0; |
208 | 42 | for (int element : mLimitedTokens) { |
209 | 28 | total += mCounts[element - 1]; |
210 | } | |
211 | 14 | if (total < mMinimumNumber) { |
212 | 0 | log(aAST.getLineNo(), aAST.getColumnNo(), |
213 | (null == mMinimumMessage) ? "descendant.token.sum.min" | |
214 | : mMinimumMessage, | |
215 | String.valueOf(total), | |
216 | String.valueOf(mMinimumNumber), name); | |
217 | } | |
218 | 14 | if (total > mMaximumNumber) { |
219 | 8 | log(aAST.getLineNo(), aAST.getColumnNo(), |
220 | (null == mMaximumMessage) ? "descendant.token.sum.max" | |
221 | : mMaximumMessage, | |
222 | String.valueOf(total), | |
223 | String.valueOf(mMaximumNumber), | |
224 | name); | |
225 | } | |
226 | 14 | } |
227 | else { | |
228 | 107 | for (int element : mLimitedTokens) { |
229 | 60 | final int tokenCount = mCounts[element - 1]; |
230 | 60 | if (tokenCount < mMinimumNumber) { |
231 | 4 | final String descendantName = TokenTypes |
232 | .getTokenName(element); | |
233 | 4 | log(aAST.getLineNo(), aAST.getColumnNo(), |
234 | (null == mMinimumMessage) ? "descendant.token.min" | |
235 | : mMinimumMessage, | |
236 | String.valueOf(tokenCount), | |
237 | String.valueOf(mMinimumNumber), | |
238 | name, | |
239 | descendantName); | |
240 | } | |
241 | 60 | if (tokenCount > mMaximumNumber) { |
242 | 29 | final String descendantName = TokenTypes |
243 | .getTokenName(element); | |
244 | 29 | log(aAST.getLineNo(), aAST.getColumnNo(), |
245 | (null == mMaximumMessage) ? "descendant.token.max" | |
246 | : mMaximumMessage, | |
247 | String.valueOf(tokenCount), | |
248 | String.valueOf(mMaximumNumber), | |
249 | name, | |
250 | descendantName); | |
251 | } | |
252 | } | |
253 | } | |
254 | 61 | } |
255 | ||
256 | /** | |
257 | * Counts the number of occurrences of descendant tokens. | |
258 | * @param aAST the root token for descendants. | |
259 | * @param aDepth the maximum depth of the counted descendants. | |
260 | */ | |
261 | private void countTokens(AST aAST, int aDepth) | |
262 | { | |
263 | 414 | if (aDepth <= mMaximumDepth) { |
264 | //update count | |
265 | 323 | if (aDepth >= mMinimumDepth) { |
266 | 313 | final int type = aAST.getType(); |
267 | 313 | if (type <= mCounts.length) { |
268 | 289 | mCounts[type - 1]++; |
269 | } | |
270 | } | |
271 | 323 | AST child = aAST.getFirstChild(); |
272 | 323 | final int nextDepth = aDepth + 1; |
273 | 676 | while (child != null) { |
274 | 353 | countTokens(child, nextDepth); |
275 | 353 | child = child.getNextSibling(); |
276 | } | |
277 | } | |
278 | 414 | } |
279 | ||
280 | @Override | |
281 | public int[] getAcceptableTokens() | |
282 | { | |
283 | // Any tokens set by property 'tokens' are acceptable | |
284 | 15 | final Set<String> tokenNames = getTokenNames(); |
285 | 15 | final int[] result = new int[tokenNames.size()]; |
286 | 15 | int i = 0; |
287 | 15 | for (String name : tokenNames) { |
288 | 21 | result[i++] = TokenTypes.getTokenId(name); |
289 | } | |
290 | 15 | return result; |
291 | } | |
292 | ||
293 | /** | |
294 | * Sets the tokens which occurance as descendant is limited. | |
295 | * @param aLimitedTokens - list of tokens to ignore. | |
296 | */ | |
297 | public void setLimitedTokens(String[] aLimitedTokens) | |
298 | { | |
299 | 17 | mLimitedTokens = new int[aLimitedTokens.length]; |
300 | ||
301 | 17 | int maxToken = 0; |
302 | 39 | for (int i = 0; i < aLimitedTokens.length; i++) { |
303 | 22 | mLimitedTokens[i] = TokenTypes.getTokenId(aLimitedTokens[i]); |
304 | 22 | if (mLimitedTokens[i] > maxToken) { |
305 | 20 | maxToken = mLimitedTokens[i]; |
306 | } | |
307 | } | |
308 | 17 | mCounts = new int[maxToken]; |
309 | 17 | } |
310 | ||
311 | /** | |
312 | * Sets the minimum depth for descendant counts. | |
313 | * @param aMinimumDepth the minimum depth for descendant counts. | |
314 | */ | |
315 | public void setMinimumDepth(int aMinimumDepth) | |
316 | { | |
317 | 1 | mMinimumDepth = aMinimumDepth; |
318 | 1 | } |
319 | ||
320 | /** | |
321 | * Sets the maximum depth for descendant counts. | |
322 | * @param aMaximumDepth the maximum depth for descendant counts. | |
323 | */ | |
324 | public void setMaximumDepth(int aMaximumDepth) | |
325 | { | |
326 | 10 | mMaximumDepth = aMaximumDepth; |
327 | 10 | } |
328 | ||
329 | /** | |
330 | * Sets a minimum count for descendants. | |
331 | * @param aMinimumNumber the minimum count for descendants. | |
332 | */ | |
333 | public void setMinimumNumber(int aMinimumNumber) | |
334 | { | |
335 | 4 | mMinimumNumber = aMinimumNumber; |
336 | 4 | } |
337 | ||
338 | /** | |
339 | * Sets a maximum count for descendants. | |
340 | * @param aMaximumNumber the maximum count for descendants. | |
341 | */ | |
342 | public void setMaximumNumber(int aMaximumNumber) | |
343 | { | |
344 | 13 | mMaximumNumber = aMaximumNumber; |
345 | 13 | } |
346 | ||
347 | /** | |
348 | * Sets the error message for minimum count not reached. | |
349 | * @param aMessage the error message for minimum count not reached. | |
350 | * Used as a <code>MessageFormat</code> pattern with arguments | |
351 | * <ul> | |
352 | * <li>{0} - token count</li> | |
353 | * <li>{1} - minimum number</li> | |
354 | * <li>{2} - name of token</li> | |
355 | * <li>{3} - name of limited token</li> | |
356 | * </ul> | |
357 | */ | |
358 | public void setMinimumMessage(String aMessage) | |
359 | { | |
360 | 3 | mMinimumMessage = aMessage; |
361 | 3 | } |
362 | ||
363 | /** | |
364 | * Sets the error message for maximum count exceeded. | |
365 | * @param aMessage the error message for maximum count exceeded. | |
366 | * Used as a <code>MessageFormat</code> pattern with arguments | |
367 | * <ul> | |
368 | * <li>{0} - token count</li> | |
369 | * <li>{1} - maximum number</li> | |
370 | * <li>{2} - name of token</li> | |
371 | * <li>{3} - name of limited token</li> | |
372 | * </ul> | |
373 | */ | |
374 | ||
375 | public void setMaximumMessage(String aMessage) | |
376 | { | |
377 | 9 | mMaximumMessage = aMessage; |
378 | 9 | } |
379 | ||
380 | /** | |
381 | * Sets whether to use the sum of the tokens found, rather than the | |
382 | * individual counts. | |
383 | * @param aSum whether to use the sum. | |
384 | */ | |
385 | public void setSumTokenCounts(boolean aSum) | |
386 | { | |
387 | 2 | mSumTokenCounts = aSum; |
388 | 2 | } |
389 | } |