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