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