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