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 DetailAST current = ast.getParent();
401 while (current.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) {
402 current = current.getParent();
403 }
404 return switch (current.getType()) {
405 case TokenTypes.MODIFIERS, TokenTypes.ANNOTATIONS, TokenTypes.ANNOTATION,
406 TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR -> Optional.of(current.getParent());
407 case TokenTypes.LITERAL_DEFAULT -> Optional.empty();
408 default -> throw new IllegalArgumentException("Unexpected container AST: " + current);
409 };
410 }
411
412
413
414
415
416
417
418
419 private static DetailAST getNthChild(DetailAST ast, int index) {
420 DetailAST child = ast.getFirstChild();
421 for (int i = 0; i < index && child != null; ++i) {
422 child = child.getNextSibling();
423 }
424 return child;
425 }
426
427
428
429
430
431
432
433
434 private static String getIdentifier(DetailAST ast) {
435 if (ast == null) {
436 throw new IllegalArgumentException("Identifier AST expected, but get null.");
437 }
438 final String identifier;
439 if (ast.getType() == TokenTypes.IDENT) {
440 identifier = ast.getText();
441 }
442 else {
443 identifier = getIdentifier(ast.getFirstChild()) + "."
444 + getIdentifier(ast.getLastChild());
445 }
446 return identifier;
447 }
448
449
450
451
452
453
454
455
456
457 private static String getStringExpr(DetailAST ast) {
458 final DetailAST firstChild = ast.getFirstChild();
459
460 return switch (firstChild.getType()) {
461 case TokenTypes.STRING_LITERAL -> {
462
463 final String quotedText = firstChild.getText();
464 yield quotedText.substring(1, quotedText.length() - 1);
465 }
466 case TokenTypes.IDENT -> firstChild.getText();
467 case TokenTypes.DOT -> firstChild.getLastChild().getText();
468 case TokenTypes.TEXT_BLOCK_LITERAL_BEGIN -> {
469 final String textBlockContent = firstChild.getFirstChild().getText();
470 yield getContentWithoutPrecedingWhitespace(textBlockContent);
471 }
472 default ->
473
474 "";
475 };
476 }
477
478
479
480
481
482
483
484
485
486 private static List<String> getAnnotationValues(DetailAST ast) {
487 return switch (ast.getType()) {
488 case TokenTypes.EXPR -> Collections.singletonList(getStringExpr(ast));
489 case TokenTypes.ANNOTATION_ARRAY_INIT -> findAllExpressionsInChildren(ast);
490 default -> throw new IllegalArgumentException(
491 "Expression or annotation array initializer AST expected: " + ast);
492 };
493 }
494
495
496
497
498
499
500
501 private static List<String> findAllExpressionsInChildren(DetailAST parent) {
502 final List<String> valueList = new LinkedList<>();
503 DetailAST childAST = parent.getFirstChild();
504 while (childAST != null) {
505 if (childAST.getType() == TokenTypes.EXPR) {
506 valueList.add(getStringExpr(childAST));
507 }
508 childAST = childAST.getNextSibling();
509 }
510 return valueList;
511 }
512
513
514
515
516
517
518
519 private static String getContentWithoutPrecedingWhitespace(String textBlockContent) {
520 final String contentWithNoPrecedingNewline =
521 NEWLINE.matcher(textBlockContent).replaceAll("");
522 return WHITESPACE.matcher(contentWithNoPrecedingNewline).replaceAll("");
523 }
524
525 @Override
526 public void destroy() {
527 super.destroy();
528 ENTRIES.remove();
529 }
530
531
532 private static final class Entry {
533
534
535 private final String checkName;
536
537 private final int firstLine;
538
539 private final int firstColumn;
540
541 private final int lastLine;
542
543 private final int lastColumn;
544
545
546
547
548
549
550
551
552
553
554 private Entry(String checkName, int firstLine, int firstColumn,
555 int lastLine, int lastColumn) {
556 this.checkName = checkName;
557 this.firstLine = firstLine;
558 this.firstColumn = firstColumn;
559 this.lastLine = lastLine;
560 this.lastColumn = lastColumn;
561 }
562
563
564
565
566
567
568 public String getCheckName() {
569 return checkName;
570 }
571
572
573
574
575
576
577 public int getFirstLine() {
578 return firstLine;
579 }
580
581
582
583
584
585
586 public int getFirstColumn() {
587 return firstColumn;
588 }
589
590
591
592
593
594
595 public int getLastLine() {
596 return lastLine;
597 }
598
599
600
601
602
603
604 public int getLastColumn() {
605 return lastColumn;
606 }
607
608 }
609
610 }