Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AnnotationUseStyleCheck |
|
| 3.6666666666666665;3.667 | ||||
AnnotationUseStyleCheck$ClosingParens |
|
| 3.6666666666666665;3.667 | ||||
AnnotationUseStyleCheck$ElementStyle |
|
| 3.6666666666666665;3.667 | ||||
AnnotationUseStyleCheck$TrailingArrayComma |
|
| 3.6666666666666665;3.667 |
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.annotation; | |
20 | ||
21 | import org.apache.commons.beanutils.ConversionException; | |
22 | ||
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 | * This check controls the style with the usage of annotations. | |
29 | * | |
30 | * <p> | |
31 | * Annotations have three element styles starting with the least verbose. | |
32 | * <ul> | |
33 | * <li>{@link ElementStyle#COMPACT_NO_ARRAY COMPACT_NO_ARRAY}</li> | |
34 | * <li>{@link ElementStyle#COMPACT COMPACT}</li> | |
35 | * <li>{@link ElementStyle#EXPANDED EXPANDED}</li> | |
36 | * </ul> | |
37 | * To not enforce an element style | |
38 | * a {@link ElementStyle#IGNORE IGNORE} type is provided. The desired style | |
39 | * can be set through the <code>elementStyle</code> property. | |
40 | * </p> | |
41 | * | |
42 | * <p> | |
43 | * Using the EXPANDED style is more verbose. The expanded version | |
44 | * is sometimes referred to as "named parameters" in other languages. | |
45 | * </p> | |
46 | * | |
47 | * <p> | |
48 | * Using the COMPACT style is less verbose. This style can only | |
49 | * be used when there is an element called 'value' which is either | |
50 | * the sole element or all other elements have default valuess. | |
51 | * </p> | |
52 | * | |
53 | * <p> | |
54 | * Using the COMPACT_NO_ARRAY style is less verbose. It is similar | |
55 | * to the COMPACT style but single value arrays are flagged. With | |
56 | * annotations a single value array does not need to be placed in an | |
57 | * array initializer. This style can only be used when there is an | |
58 | * element called 'value' which is either the sole element or all other | |
59 | * elements have default values. | |
60 | * </p> | |
61 | * | |
62 | * <p> | |
63 | * The ending parenthesis are optional when using annotations with no elements. | |
64 | * To always require ending parenthesis use the | |
65 | * {@link ClosingParens#ALWAYS ALWAYS} type. To never have ending parenthesis | |
66 | * use the {@link ClosingParens#NEVER NEVER} type. To not enforce a | |
67 | * closing parenthesis preference a {@link ClosingParens#IGNORE IGNORE} type is | |
68 | * provided. Set this through the <code>closingParens</code> property. | |
69 | * </p> | |
70 | * | |
71 | * <p> | |
72 | * Annotations also allow you to specify arrays of elements in a standard | |
73 | * format. As with normal arrays, a trailing comma is optional. To always | |
74 | * require a trailing comma use the {@link TrailingArrayComma#ALWAYS ALWAYS} | |
75 | * type. To never have a trailing comma use the | |
76 | * {@link TrailingArrayComma#NEVER NEVER} type. To not enforce a trailing | |
77 | * array comma preference a {@link TrailingArrayComma#IGNORE IGNORE} type | |
78 | * is provided. Set this through the <code>trailingArrayComma</code> property. | |
79 | * </p> | |
80 | * | |
81 | * <p> | |
82 | * By default the ElementStyle is set to EXPANDED, the TrailingArrayComma | |
83 | * is set to NEVER, and the ClosingParans is set to ALWAYS. | |
84 | * </p> | |
85 | * | |
86 | * <p> | |
87 | * According to the JLS, it is legal to include a trailing comma | |
88 | * in arrays used in annotations but Sun's Java 5 & 6 compilers will not | |
89 | * compile with this syntax. This may in be a bug in Sun's compilers | |
90 | * since eclipse 3.4's built-in compiler does allow this syntax as | |
91 | * defined in the JLS. Note: this was tested with compilers included with | |
92 | * JDK versions 1.5.0.17 and 1.6.0.11 and the compiler included with eclipse | |
93 | * 3.4.1. | |
94 | * | |
95 | * See <a | |
96 | * href="http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html"> | |
97 | * Java Language specification, sections 9.7</a>. | |
98 | * </p> | |
99 | * | |
100 | * <p> | |
101 | * An example shown below is set to enforce an EXPANDED style, with a | |
102 | * trailing array comma set to NEVER and always including the closing | |
103 | * parenthesis. | |
104 | * </p> | |
105 | * | |
106 | * <pre> | |
107 | * <module name="AnnotationUseStyle"> | |
108 | * <property name="ElementStyle" | |
109 | * value="EXPANDED"/> | |
110 | * <property name="TrailingArrayComma" | |
111 | * value="NEVER"/> | |
112 | * <property name="ClosingParens" | |
113 | * value="ALWAYS"/> | |
114 | * </module> | |
115 | * </pre> | |
116 | * | |
117 | * @author Travis Schneeberger | |
118 | */ | |
119 | 10 | public final class AnnotationUseStyleCheck extends Check |
120 | { | |
121 | /** | |
122 | * the element name used to receive special linguistic support | |
123 | * for annotation use. | |
124 | */ | |
125 | private static final String ANNOTATION_ELEMENT_SINGLE_NAME = | |
126 | "value"; | |
127 | ||
128 | //not extending AbstractOptionCheck because check | |
129 | //has more than one option type. | |
130 | ||
131 | /** @see #setElementStyle(String) */ | |
132 | 10 | private ElementStyle mStyle = ElementStyle.COMPACT_NO_ARRAY; |
133 | ||
134 | //defaulting to NEVER because of the strange compiler behavior | |
135 | /** @see #setTrailingArrayComma(String) */ | |
136 | 10 | private TrailingArrayComma mComma = TrailingArrayComma.NEVER; |
137 | ||
138 | /** @see #setClosingParans(String) */ | |
139 | 10 | private ClosingParens mParens = ClosingParens.NEVER; |
140 | ||
141 | /** | |
142 | * Sets the ElementStyle from a string. | |
143 | * | |
144 | * @param aStyle string representation | |
145 | * @throws ConversionException if cannot convert string. | |
146 | */ | |
147 | public void setElementStyle(final String aStyle) | |
148 | { | |
149 | 10 | this.mStyle = this.getOption(ElementStyle.class, aStyle); |
150 | 10 | } |
151 | ||
152 | /** | |
153 | * Sets the TrailingArrayComma from a string. | |
154 | * | |
155 | * @param aComma string representation | |
156 | * @throws ConversionException if cannot convert string. | |
157 | */ | |
158 | public void setTrailingArrayComma(final String aComma) | |
159 | { | |
160 | 10 | this.mComma = this.getOption(TrailingArrayComma.class, aComma); |
161 | 10 | } |
162 | ||
163 | /** | |
164 | * Sets the ClosingParens from a string. | |
165 | * | |
166 | * @param aParens string representation | |
167 | * @throws ConversionException if cannot convert string. | |
168 | */ | |
169 | public void setClosingParens(final String aParens) | |
170 | { | |
171 | 10 | this.mParens = this.getOption(ClosingParens.class, aParens); |
172 | 10 | } |
173 | ||
174 | /** | |
175 | * Retrieves an {@link Enum Enum} type from a @{link String String}. | |
176 | * @param <T> the enum type | |
177 | * @param aEnumClass the enum class | |
178 | * @param aString the string representing the enum | |
179 | * @return the enum type | |
180 | */ | |
181 | private <T extends Enum<T>> T getOption(final Class<T> aEnumClass, | |
182 | final String aString) | |
183 | { | |
184 | try { | |
185 | 30 | return Enum.valueOf(aEnumClass, aString.trim().toUpperCase()); |
186 | } | |
187 | 0 | catch (final IllegalArgumentException iae) { |
188 | 0 | throw new ConversionException("unable to parse " + aString, iae); |
189 | } | |
190 | } | |
191 | ||
192 | /** {@inheritDoc} */ | |
193 | @Override | |
194 | public int[] getDefaultTokens() | |
195 | { | |
196 | 10 | return this.getRequiredTokens(); |
197 | } | |
198 | ||
199 | /** {@inheritDoc} */ | |
200 | @Override | |
201 | public int[] getRequiredTokens() | |
202 | { | |
203 | 10 | return new int[] { |
204 | TokenTypes.ANNOTATION, | |
205 | }; | |
206 | } | |
207 | ||
208 | /** {@inheritDoc} */ | |
209 | @Override | |
210 | public int[] getAcceptableTokens() | |
211 | { | |
212 | 0 | return this.getRequiredTokens(); |
213 | } | |
214 | ||
215 | /** {@inheritDoc} */ | |
216 | @Override | |
217 | public void visitToken(final DetailAST aAST) | |
218 | { | |
219 | 150 | this.checkStyleType(aAST); |
220 | 150 | this.checkCheckClosingParens(aAST); |
221 | 150 | this.checkTrailingComma(aAST); |
222 | 150 | } |
223 | ||
224 | /** | |
225 | * Checks to see if the | |
226 | * {@link ElementStyle AnnotationElementStyle} | |
227 | * is correct. | |
228 | * | |
229 | * @param aAnnotation the annotation token | |
230 | */ | |
231 | private void checkStyleType(final DetailAST aAnnotation) | |
232 | { | |
233 | 150 | if (ElementStyle.IGNORE.equals(this.mStyle) |
234 | || this.mStyle == null) | |
235 | { | |
236 | 90 | return; |
237 | } | |
238 | ||
239 | 60 | if (ElementStyle.COMPACT_NO_ARRAY.equals(this.mStyle)) { |
240 | 20 | this.checkCompactNoArrayStyle(aAnnotation); |
241 | } | |
242 | 40 | else if (ElementStyle.COMPACT.equals(this.mStyle)) { |
243 | 20 | this.checkCompactStyle(aAnnotation); |
244 | } | |
245 | 20 | else if (ElementStyle.EXPANDED.equals(this.mStyle)) { |
246 | 20 | this.checkExpandedStyle(aAnnotation); |
247 | } | |
248 | 60 | } |
249 | ||
250 | /** | |
251 | * Checks for expanded style type violations. | |
252 | * | |
253 | * @param aAnnotation the annotation token | |
254 | */ | |
255 | private void checkExpandedStyle(final DetailAST aAnnotation) | |
256 | { | |
257 | 20 | final int valuePairCount = |
258 | aAnnotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
259 | ||
260 | 20 | if (valuePairCount == 0 |
261 | && aAnnotation.branchContains(TokenTypes.EXPR)) | |
262 | { | |
263 | 7 | this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", |
264 | ElementStyle.EXPANDED); | |
265 | } | |
266 | 20 | } |
267 | ||
268 | /** | |
269 | * Checks for compact style type violations. | |
270 | * | |
271 | * @param aAnnotation the annotation token | |
272 | */ | |
273 | private void checkCompactStyle(final DetailAST aAnnotation) | |
274 | { | |
275 | 20 | final int valuePairCount = |
276 | aAnnotation.getChildCount( | |
277 | TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
278 | ||
279 | 20 | final DetailAST valuePair = |
280 | aAnnotation.findFirstToken( | |
281 | TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
282 | ||
283 | 20 | if (valuePairCount == 1 |
284 | && AnnotationUseStyleCheck.ANNOTATION_ELEMENT_SINGLE_NAME.equals( | |
285 | valuePair.getFirstChild().getText())) | |
286 | { | |
287 | 2 | this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", |
288 | ElementStyle.COMPACT); | |
289 | } | |
290 | 20 | } |
291 | ||
292 | /** | |
293 | * Checks for compact no array style type violations. | |
294 | * | |
295 | * @param aAnnotation the annotation token | |
296 | */ | |
297 | private void checkCompactNoArrayStyle(final DetailAST aAnnotation) | |
298 | { | |
299 | 20 | final DetailAST arrayInit = |
300 | aAnnotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); | |
301 | ||
302 | 20 | final int valuePairCount = |
303 | aAnnotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
304 | ||
305 | 20 | final DetailAST valuePair = |
306 | aAnnotation.findFirstToken(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR); | |
307 | ||
308 | //in compact style with one value | |
309 | 20 | if (arrayInit != null |
310 | && arrayInit.getChildCount(TokenTypes.EXPR) == 1) | |
311 | { | |
312 | 3 | this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", |
313 | ElementStyle.COMPACT_NO_ARRAY); | |
314 | } | |
315 | //in expanded style with one value and the correct element name | |
316 | 17 | else if (valuePairCount == 1) { |
317 | 5 | final DetailAST nestedArrayInit = |
318 | valuePair.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); | |
319 | ||
320 | 5 | if (nestedArrayInit != null |
321 | && AnnotationUseStyleCheck. | |
322 | ANNOTATION_ELEMENT_SINGLE_NAME.equals( | |
323 | valuePair.getFirstChild().getText()) | |
324 | && nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) | |
325 | { | |
326 | 2 | this.log(aAnnotation.getLineNo(), "annotation.incorrect.style", |
327 | ElementStyle.COMPACT_NO_ARRAY); | |
328 | } | |
329 | } | |
330 | 20 | } |
331 | ||
332 | /** | |
333 | * Checks to see if the trailing comma is present if required or | |
334 | * prohibited. | |
335 | * | |
336 | * @param aAnnotation the annotation token | |
337 | */ | |
338 | private void checkTrailingComma(final DetailAST aAnnotation) | |
339 | { | |
340 | 150 | if (TrailingArrayComma.IGNORE.equals(this.mComma) |
341 | || this.mComma == null) | |
342 | { | |
343 | 120 | return; |
344 | } | |
345 | ||
346 | 30 | DetailAST child = aAnnotation.getFirstChild(); |
347 | ||
348 | 204 | while (child != null) { |
349 | 174 | DetailAST arrayInit = null; |
350 | ||
351 | 174 | if (child.getType() |
352 | == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) | |
353 | { | |
354 | 32 | arrayInit = |
355 | child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT); | |
356 | } | |
357 | 142 | else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) { |
358 | 10 | arrayInit = child; |
359 | } | |
360 | ||
361 | 174 | if (arrayInit != null) { |
362 | 42 | this.logCommaViolation(arrayInit); |
363 | } | |
364 | 174 | child = child.getNextSibling(); |
365 | 174 | } |
366 | 30 | } |
367 | ||
368 | /** | |
369 | * logs a trailing array comma violation if one exists. | |
370 | * | |
371 | * @param aAST the array init | |
372 | * {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}. | |
373 | */ | |
374 | private void logCommaViolation(final DetailAST aAST) | |
375 | { | |
376 | 42 | final DetailAST rCurly = aAST.findFirstToken(TokenTypes.RCURLY); |
377 | ||
378 | //comma can be null if array is empty | |
379 | 42 | final DetailAST comma = rCurly.getPreviousSibling(); |
380 | ||
381 | 42 | if (TrailingArrayComma.ALWAYS.equals(this.mComma) |
382 | && (comma == null || comma.getType() != TokenTypes.COMMA)) | |
383 | { | |
384 | 13 | this.log(rCurly.getLineNo(), |
385 | rCurly.getColumnNo(), "annotation.trailing.comma.missing"); | |
386 | } | |
387 | 29 | else if (TrailingArrayComma.NEVER.equals(this.mComma) |
388 | && comma != null && comma.getType() == TokenTypes.COMMA) | |
389 | { | |
390 | 8 | this.log(comma.getLineNo(), |
391 | comma.getColumnNo(), "annotation.trailing.comma.present"); | |
392 | } | |
393 | 42 | } |
394 | ||
395 | /** | |
396 | * Checks to see if the closing parenthesis are present if required or | |
397 | * prohibited. | |
398 | * | |
399 | * @param aAST the annotation token | |
400 | */ | |
401 | private void checkCheckClosingParens(final DetailAST aAST) | |
402 | { | |
403 | 150 | if (ClosingParens.IGNORE.equals(this.mParens) |
404 | || this.mParens == null) | |
405 | { | |
406 | 110 | return; |
407 | } | |
408 | ||
409 | 40 | final DetailAST paren = aAST.getLastChild(); |
410 | 40 | final boolean parenExists = paren.getType() == TokenTypes.RPAREN; |
411 | ||
412 | 40 | if (ClosingParens.ALWAYS.equals(this.mParens) |
413 | && !parenExists) | |
414 | { | |
415 | 3 | this.log(aAST.getLineNo(), "annotation.parens.missing"); |
416 | } | |
417 | 37 | else if (ClosingParens.NEVER.equals(this.mParens) |
418 | && !aAST.branchContains(TokenTypes.EXPR) | |
419 | && parenExists) | |
420 | { | |
421 | 3 | this.log(aAST.getLineNo(), "annotation.parens.present"); |
422 | } | |
423 | 40 | } |
424 | ||
425 | /** | |
426 | * Defines the styles for defining elements in an annotation. | |
427 | * @author Travis Schneeberger | |
428 | */ | |
429 | 6 | public static enum ElementStyle { |
430 | ||
431 | /** | |
432 | * expanded example | |
433 | * | |
434 | * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. | |
435 | */ | |
436 | 1 | EXPANDED, |
437 | ||
438 | /** | |
439 | * compact example | |
440 | * | |
441 | * <pre>@SuppressWarnings({"unchecked","unused",})</pre> | |
442 | * <br/>or<br/> | |
443 | * <pre>@SuppressWarnings("unchecked")</pre>. | |
444 | */ | |
445 | 1 | COMPACT, |
446 | ||
447 | /** | |
448 | * compact example.] | |
449 | * | |
450 | * <pre>@SuppressWarnings("unchecked")</pre>. | |
451 | */ | |
452 | 1 | COMPACT_NO_ARRAY, |
453 | ||
454 | /** | |
455 | * mixed styles. | |
456 | */ | |
457 | 1 | IGNORE, |
458 | } | |
459 | ||
460 | /** | |
461 | * Defines the two styles for defining | |
462 | * elements in an annotation. | |
463 | * | |
464 | * @author Travis Schneeberger | |
465 | */ | |
466 | 5 | public static enum TrailingArrayComma { |
467 | ||
468 | /** | |
469 | * with comma example | |
470 | * | |
471 | * <pre>@SuppressWarnings(value={"unchecked","unused",})</pre>. | |
472 | */ | |
473 | 1 | ALWAYS, |
474 | ||
475 | /** | |
476 | * without comma example | |
477 | * | |
478 | * <pre>@SuppressWarnings(value={"unchecked","unused"})</pre>. | |
479 | */ | |
480 | 1 | NEVER, |
481 | ||
482 | /** | |
483 | * mixed styles. | |
484 | */ | |
485 | 1 | IGNORE, |
486 | } | |
487 | ||
488 | /** | |
489 | * Defines the two styles for defining | |
490 | * elements in an annotation. | |
491 | * | |
492 | * @author Travis Schneeberger | |
493 | */ | |
494 | 5 | public static enum ClosingParens { |
495 | ||
496 | /** | |
497 | * with parens example | |
498 | * | |
499 | * <pre>@Deprecated()</pre>. | |
500 | */ | |
501 | 1 | ALWAYS, |
502 | ||
503 | /** | |
504 | * without parens example | |
505 | * | |
506 | * <pre>@Deprecated</pre>. | |
507 | */ | |
508 | 1 | NEVER, |
509 | ||
510 | /** | |
511 | * mixed styles. | |
512 | */ | |
513 | 1 | IGNORE, |
514 | } | |
515 | } |