1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks;
21
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28
29 import com.puppycrawl.tools.checkstyle.StatelessCheck;
30 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
32 import com.puppycrawl.tools.checkstyle.api.DetailAST;
33 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 @StatelessCheck
105 public class SuppressWarningsHolder
106 extends AbstractCheck {
107
108
109
110
111
112 public static final String MSG_KEY = "suppress.warnings.invalid.target";
113
114
115
116
117
118
119
120
121 private static final String CHECKSTYLE_PREFIX = "checkstyle:";
122
123
124 private static final String JAVA_LANG_PREFIX = "java.lang.";
125
126
127 private static final String CHECK_SUFFIX = "Check";
128
129
130 private static final String ALL_WARNING_MATCHING_ID = "all";
131
132
133 private static final Map<String, String> CHECK_ALIAS_MAP = new HashMap<>();
134
135
136
137
138
139 private static final ThreadLocal<List<Entry>> ENTRIES =
140 ThreadLocal.withInitial(LinkedList::new);
141
142
143
144
145
146
147
148
149
150 public static String getDefaultAlias(String sourceName) {
151 int endIndex = sourceName.length();
152 if (sourceName.endsWith(CHECK_SUFFIX)) {
153 endIndex -= CHECK_SUFFIX.length();
154 }
155 final int startIndex = sourceName.lastIndexOf('.') + 1;
156 return sourceName.substring(startIndex, endIndex).toLowerCase(Locale.ENGLISH);
157 }
158
159
160
161
162
163
164
165
166
167 public static String getAlias(String sourceName) {
168 String checkAlias = CHECK_ALIAS_MAP.get(sourceName);
169 if (checkAlias == null) {
170 checkAlias = getDefaultAlias(sourceName);
171 }
172 return checkAlias;
173 }
174
175
176
177
178
179
180
181 private static void registerAlias(String sourceName, String checkAlias) {
182 CHECK_ALIAS_MAP.put(sourceName, checkAlias);
183 }
184
185
186
187
188
189
190 public void setAliasList(String... aliasList) {
191 for (String sourceAlias : aliasList) {
192 final int index = sourceAlias.indexOf('=');
193 if (index > 0) {
194 registerAlias(sourceAlias.substring(0, index), sourceAlias
195 .substring(index + 1));
196 }
197 else if (!sourceAlias.isEmpty()) {
198 throw new IllegalArgumentException(
199 "'=' expected in alias list item: " + sourceAlias);
200 }
201 }
202 }
203
204
205
206
207
208
209
210
211 public static boolean isSuppressed(AuditEvent event) {
212 final List<Entry> entries = ENTRIES.get();
213 final String sourceName = event.getSourceName();
214 final String checkAlias = getAlias(sourceName);
215 final int line = event.getLine();
216 final int column = event.getColumn();
217 boolean suppressed = false;
218 for (Entry entry : entries) {
219 final boolean afterStart = isSuppressedAfterEventStart(line, column, entry);
220 final boolean beforeEnd = isSuppressedBeforeEventEnd(line, column, entry);
221 final boolean nameMatches =
222 ALL_WARNING_MATCHING_ID.equals(entry.getCheckName())
223 || entry.getCheckName().equalsIgnoreCase(checkAlias);
224 final boolean idMatches = event.getModuleId() != null
225 && event.getModuleId().equals(entry.getCheckName());
226 if (afterStart && beforeEnd && (nameMatches || idMatches)) {
227 suppressed = true;
228 break;
229 }
230 }
231 return suppressed;
232 }
233
234
235
236
237
238
239
240
241
242
243 private static boolean isSuppressedAfterEventStart(int line, int column, Entry entry) {
244 return entry.getFirstLine() < line
245 || entry.getFirstLine() == line
246 && (column == 0 || entry.getFirstColumn() <= column);
247 }
248
249
250
251
252
253
254
255
256
257
258 private static boolean isSuppressedBeforeEventEnd(int line, int column, Entry entry) {
259 return entry.getLastLine() > line
260 || entry.getLastLine() == line && entry
261 .getLastColumn() >= column;
262 }
263
264 @Override
265 public int[] getDefaultTokens() {
266 return getRequiredTokens();
267 }
268
269 @Override
270 public int[] getAcceptableTokens() {
271 return getRequiredTokens();
272 }
273
274 @Override
275 public int[] getRequiredTokens() {
276 return new int[] {TokenTypes.ANNOTATION};
277 }
278
279 @Override
280 public void beginTree(DetailAST rootAST) {
281 ENTRIES.get().clear();
282 }
283
284 @Override
285 public void visitToken(DetailAST ast) {
286
287
288 String identifier = getIdentifier(getNthChild(ast, 1));
289 if (identifier.startsWith(JAVA_LANG_PREFIX)) {
290 identifier = identifier.substring(JAVA_LANG_PREFIX.length());
291 }
292 if ("SuppressWarnings".equals(identifier)) {
293 final List<String> values = getAllAnnotationValues(ast);
294 if (!isAnnotationEmpty(values)) {
295 final DetailAST targetAST = getAnnotationTarget(ast);
296
297 if (targetAST == null) {
298 log(ast.getLineNo(), MSG_KEY);
299 }
300 else {
301
302 final int firstLine = targetAST.getLineNo();
303 final int firstColumn = targetAST.getColumnNo();
304 final DetailAST nextAST = targetAST.getNextSibling();
305 final int lastLine;
306 final int lastColumn;
307 if (nextAST == null) {
308 lastLine = Integer.MAX_VALUE;
309 lastColumn = Integer.MAX_VALUE;
310 }
311 else {
312 lastLine = nextAST.getLineNo();
313 lastColumn = nextAST.getColumnNo() - 1;
314 }
315
316
317 final List<Entry> entries = ENTRIES.get();
318 for (String value : values) {
319 String checkName = value;
320
321 checkName = removeCheckstylePrefixIfExists(checkName);
322 entries.add(new Entry(checkName, firstLine, firstColumn,
323 lastLine, lastColumn));
324 }
325 }
326 }
327 }
328 }
329
330
331
332
333
334
335
336
337 private static String removeCheckstylePrefixIfExists(String checkName) {
338 String result = checkName;
339 if (checkName.startsWith(CHECKSTYLE_PREFIX)) {
340 result = checkName.substring(CHECKSTYLE_PREFIX.length());
341 }
342 return result;
343 }
344
345
346
347
348
349
350 private static List<String> getAllAnnotationValues(DetailAST ast) {
351
352 List<String> values = null;
353 final DetailAST lparenAST = ast.findFirstToken(TokenTypes.LPAREN);
354 if (lparenAST != null) {
355 final DetailAST nextAST = lparenAST.getNextSibling();
356 final int nextType = nextAST.getType();
357 switch (nextType) {
358 case TokenTypes.EXPR:
359 case TokenTypes.ANNOTATION_ARRAY_INIT:
360 values = getAnnotationValues(nextAST);
361 break;
362
363 case TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR:
364
365
366 values = getAnnotationValues(getNthChild(nextAST, 2));
367 break;
368
369 case TokenTypes.RPAREN:
370
371 break;
372
373 default:
374
375 throw new IllegalArgumentException("Unexpected AST: " + nextAST);
376 }
377 }
378 return values;
379 }
380
381
382
383
384
385
386 private static boolean isAnnotationEmpty(List<String> values) {
387 return values == null;
388 }
389
390
391
392
393
394
395 private static DetailAST getAnnotationTarget(DetailAST ast) {
396 final DetailAST targetAST;
397 final DetailAST parentAST = ast.getParent();
398 switch (parentAST.getType()) {
399 case TokenTypes.MODIFIERS:
400 case TokenTypes.ANNOTATIONS:
401 targetAST = getAcceptableParent(parentAST);
402 break;
403 default:
404
405 throw new IllegalArgumentException("Unexpected container AST: " + parentAST);
406 }
407 return targetAST;
408 }
409
410
411
412
413
414
415
416
417
418 private static DetailAST getAcceptableParent(DetailAST child) {
419 final DetailAST result;
420 final DetailAST parent = child.getParent();
421 switch (parent.getType()) {
422 case TokenTypes.ANNOTATION_DEF:
423 case TokenTypes.PACKAGE_DEF:
424 case TokenTypes.CLASS_DEF:
425 case TokenTypes.INTERFACE_DEF:
426 case TokenTypes.ENUM_DEF:
427 case TokenTypes.ENUM_CONSTANT_DEF:
428 case TokenTypes.CTOR_DEF:
429 case TokenTypes.METHOD_DEF:
430 case TokenTypes.PARAMETER_DEF:
431 case TokenTypes.VARIABLE_DEF:
432 case TokenTypes.ANNOTATION_FIELD_DEF:
433 case TokenTypes.TYPE:
434 case TokenTypes.LITERAL_NEW:
435 case TokenTypes.LITERAL_THROWS:
436 case TokenTypes.TYPE_ARGUMENT:
437 case TokenTypes.IMPLEMENTS_CLAUSE:
438 case TokenTypes.DOT:
439 result = parent;
440 break;
441 default:
442
443 result = null;
444 }
445 return result;
446 }
447
448
449
450
451
452
453
454 private static DetailAST getNthChild(DetailAST ast, int index) {
455 DetailAST child = ast.getFirstChild();
456 for (int i = 0; i < index && child != null; ++i) {
457 child = child.getNextSibling();
458 }
459 return child;
460 }
461
462
463
464
465
466
467
468 private static String getIdentifier(DetailAST ast) {
469 if (ast == null) {
470 throw new IllegalArgumentException("Identifier AST expected, but get null.");
471 }
472 final String identifier;
473 if (ast.getType() == TokenTypes.IDENT) {
474 identifier = ast.getText();
475 }
476 else {
477 identifier = getIdentifier(ast.getFirstChild()) + "."
478 + getIdentifier(ast.getLastChild());
479 }
480 return identifier;
481 }
482
483
484
485
486
487
488
489
490 private static String getStringExpr(DetailAST ast) {
491 final DetailAST firstChild = ast.getFirstChild();
492 String expr = "";
493
494 switch (firstChild.getType()) {
495 case TokenTypes.STRING_LITERAL:
496
497 final String quotedText = firstChild.getText();
498 expr = quotedText.substring(1, quotedText.length() - 1);
499 break;
500 case TokenTypes.IDENT:
501 expr = firstChild.getText();
502 break;
503 case TokenTypes.DOT:
504 expr = firstChild.getLastChild().getText();
505 break;
506 default:
507
508 }
509 return expr;
510 }
511
512
513
514
515
516
517
518
519 private static List<String> getAnnotationValues(DetailAST ast) {
520 final List<String> annotationValues;
521 switch (ast.getType()) {
522 case TokenTypes.EXPR:
523 annotationValues = Collections.singletonList(getStringExpr(ast));
524 break;
525 case TokenTypes.ANNOTATION_ARRAY_INIT:
526 annotationValues = findAllExpressionsInChildren(ast);
527 break;
528 default:
529 throw new IllegalArgumentException(
530 "Expression or annotation array initializer AST expected: " + ast);
531 }
532 return annotationValues;
533 }
534
535
536
537
538
539
540 private static List<String> findAllExpressionsInChildren(DetailAST parent) {
541 final List<String> valueList = new LinkedList<>();
542 DetailAST childAST = parent.getFirstChild();
543 while (childAST != null) {
544 if (childAST.getType() == TokenTypes.EXPR) {
545 valueList.add(getStringExpr(childAST));
546 }
547 childAST = childAST.getNextSibling();
548 }
549 return valueList;
550 }
551
552
553 private static class Entry {
554
555
556 private final String checkName;
557
558 private final int firstLine;
559
560 private final int firstColumn;
561
562 private final int lastLine;
563
564 private final int lastColumn;
565
566
567
568
569
570
571
572
573
574 Entry(String checkName, int firstLine, int firstColumn,
575 int lastLine, int lastColumn) {
576 this.checkName = checkName;
577 this.firstLine = firstLine;
578 this.firstColumn = firstColumn;
579 this.lastLine = lastLine;
580 this.lastColumn = lastColumn;
581 }
582
583
584
585
586
587 public String getCheckName() {
588 return checkName;
589 }
590
591
592
593
594
595 public int getFirstLine() {
596 return firstLine;
597 }
598
599
600
601
602
603 public int getFirstColumn() {
604 return firstColumn;
605 }
606
607
608
609
610
611 public int getLastLine() {
612 return lastLine;
613 }
614
615
616
617
618
619 public int getLastColumn() {
620 return lastColumn;
621 }
622
623 }
624
625 }