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