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.filters;
21
22 import java.lang.ref.WeakReference;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 import java.util.regex.PatternSyntaxException;
31
32 import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean;
33 import com.puppycrawl.tools.checkstyle.PropertyType;
34 import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
35 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
36 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
37 import com.puppycrawl.tools.checkstyle.api.FileContents;
38 import com.puppycrawl.tools.checkstyle.api.TextBlock;
39 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public class SuppressionCommentFilter
83 extends AbstractAutomaticBean
84 implements TreeWalkerFilter {
85
86
87
88
89 public enum TagType {
90
91
92
93
94 ON,
95
96
97
98 OFF,
99
100 }
101
102
103 private static final String DEFAULT_OFF_FORMAT = "CHECKSTYLE:OFF";
104
105
106 private static final String DEFAULT_ON_FORMAT = "CHECKSTYLE:ON";
107
108
109 private static final String DEFAULT_CHECK_FORMAT = ".*";
110
111
112 private final List<Tag> tags = new ArrayList<>();
113
114
115 private boolean checkC = true;
116
117
118
119
120
121
122
123 private Pattern offCommentFormat = Pattern.compile(DEFAULT_OFF_FORMAT);
124
125
126 private Pattern onCommentFormat = Pattern.compile(DEFAULT_ON_FORMAT);
127
128
129 @XdocsPropertyType(PropertyType.PATTERN)
130 private String checkFormat = DEFAULT_CHECK_FORMAT;
131
132
133 @XdocsPropertyType(PropertyType.PATTERN)
134 private String messageFormat;
135
136
137 @XdocsPropertyType(PropertyType.PATTERN)
138 private String idFormat;
139
140
141
142
143
144
145
146
147 private WeakReference<FileContents> fileContentsReference = new WeakReference<>(null);
148
149
150
151
152
153
154
155 public final void setOffCommentFormat(Pattern pattern) {
156 offCommentFormat = pattern;
157 }
158
159
160
161
162
163
164
165 public final void setOnCommentFormat(Pattern pattern) {
166 onCommentFormat = pattern;
167 }
168
169
170
171
172
173
174 private FileContents getFileContents() {
175 return fileContentsReference.get();
176 }
177
178
179
180
181
182
183 private void setFileContents(FileContents fileContents) {
184 fileContentsReference = new WeakReference<>(fileContents);
185 }
186
187
188
189
190
191
192
193 public final void setCheckFormat(String format) {
194 checkFormat = format;
195 }
196
197
198
199
200
201
202
203 public void setMessageFormat(String format) {
204 messageFormat = format;
205 }
206
207
208
209
210
211
212
213 public void setIdFormat(String format) {
214 idFormat = format;
215 }
216
217
218
219
220
221
222
223
224
225 public void setCheckCPP(boolean checkCpp) {
226 checkCPP = checkCpp;
227 }
228
229
230
231
232
233
234
235 public void setCheckC(boolean checkC) {
236 this.checkC = checkC;
237 }
238
239 @Override
240 protected void finishLocalSetup() {
241
242 }
243
244 @Override
245 public boolean accept(TreeWalkerAuditEvent event) {
246 boolean accepted = true;
247
248 if (event.getViolation() != null) {
249
250
251 final FileContents currentContents = event.getFileContents();
252
253 if (getFileContents() != currentContents) {
254 setFileContents(currentContents);
255 tagSuppressions();
256 }
257 final Tag matchTag = findNearestMatch(event);
258 accepted = matchTag == null || matchTag.getTagType() == TagType.ON;
259 }
260 return accepted;
261 }
262
263
264
265
266
267
268
269
270 private Tag findNearestMatch(TreeWalkerAuditEvent event) {
271 Tag result = null;
272 for (Tag tag : tags) {
273 final int eventLine = event.getLine();
274 if (tag.getLine() > eventLine
275 || tag.getLine() == eventLine
276 && tag.getColumn() > event.getColumn()) {
277 break;
278 }
279 if (tag.isMatch(event)) {
280 result = tag;
281 }
282 }
283 return result;
284 }
285
286
287
288
289
290 private void tagSuppressions() {
291 tags.clear();
292 final FileContents contents = getFileContents();
293 if (checkCPP) {
294 tagSuppressions(contents.getSingleLineComments().values());
295 }
296 if (checkC) {
297 final Collection<List<TextBlock>> cComments = contents
298 .getBlockComments().values();
299 cComments.forEach(this::tagSuppressions);
300 }
301 Collections.sort(tags);
302 }
303
304
305
306
307
308
309
310 private void tagSuppressions(Collection<TextBlock> comments) {
311 for (TextBlock comment : comments) {
312 final int startLineNo = comment.getStartLineNo();
313 final String[] text = comment.getText();
314 tagCommentLine(text[0], startLineNo, comment.getStartColNo());
315 for (int i = 1; i < text.length; i++) {
316 tagCommentLine(text[i], startLineNo + i, 0);
317 }
318 }
319 }
320
321
322
323
324
325
326
327
328
329 private void tagCommentLine(String text, int line, int column) {
330 final Matcher offMatcher = offCommentFormat.matcher(text);
331 if (offMatcher.find()) {
332 addTag(offMatcher.group(0), line, column, TagType.OFF);
333 }
334 else {
335 final Matcher onMatcher = onCommentFormat.matcher(text);
336 if (onMatcher.find()) {
337 addTag(onMatcher.group(0), line, column, TagType.ON);
338 }
339 }
340 }
341
342
343
344
345
346
347
348
349
350 private void addTag(String text, int line, int column, TagType reportingOn) {
351 final Tag tag = new Tag(line, column, text, reportingOn, this);
352 tags.add(tag);
353 }
354
355
356
357
358
359 private static final class Tag
360 implements Comparable<Tag> {
361
362
363 private final String text;
364
365
366 private final int line;
367
368
369 private final int column;
370
371
372 private final TagType tagType;
373
374
375 private final Pattern tagCheckRegexp;
376
377
378 private final Pattern tagMessageRegexp;
379
380
381 private final Pattern tagIdRegexp;
382
383
384
385
386
387
388
389
390
391
392
393 private Tag(int line, int column, String text, TagType tagType,
394 SuppressionCommentFilter filter) {
395 this.line = line;
396 this.column = column;
397 this.text = text;
398 this.tagType = tagType;
399
400 final Pattern commentFormat;
401 if (this.tagType == TagType.ON) {
402 commentFormat = filter.onCommentFormat;
403 }
404 else {
405 commentFormat = filter.offCommentFormat;
406 }
407
408
409
410 String format = "";
411 try {
412 format = CommonUtil.fillTemplateWithStringsByRegexp(
413 filter.checkFormat, text, commentFormat);
414 tagCheckRegexp = Pattern.compile(format);
415
416 if (filter.messageFormat == null) {
417 tagMessageRegexp = null;
418 }
419 else {
420 format = CommonUtil.fillTemplateWithStringsByRegexp(
421 filter.messageFormat, text, commentFormat);
422 tagMessageRegexp = Pattern.compile(format);
423 }
424
425 if (filter.idFormat == null) {
426 tagIdRegexp = null;
427 }
428 else {
429 format = CommonUtil.fillTemplateWithStringsByRegexp(
430 filter.idFormat, text, commentFormat);
431 tagIdRegexp = Pattern.compile(format);
432 }
433 }
434 catch (final PatternSyntaxException exc) {
435 throw new IllegalArgumentException(
436 "unable to parse expanded comment " + format, exc);
437 }
438 }
439
440
441
442
443
444
445 public int getLine() {
446 return line;
447 }
448
449
450
451
452
453
454
455
456 public int getColumn() {
457 return column;
458 }
459
460
461
462
463
464
465
466 public TagType getTagType() {
467 return tagType;
468 }
469
470
471
472
473
474
475
476
477
478
479 @Override
480 public int compareTo(Tag object) {
481 final int result;
482 if (line == object.line) {
483 result = Integer.compare(column, object.column);
484 }
485 else {
486 result = Integer.compare(line, object.line);
487 }
488 return result;
489 }
490
491
492
493
494
495
496
497
498
499 @Override
500 public boolean equals(Object other) {
501 if (this == other) {
502 return true;
503 }
504 if (other == null || getClass() != other.getClass()) {
505 return false;
506 }
507 final Tag tag = (Tag) other;
508 return Objects.equals(line, tag.line)
509 && Objects.equals(column, tag.column)
510 && Objects.equals(tagType, tag.tagType)
511 && Objects.equals(text, tag.text)
512 && Objects.equals(tagCheckRegexp, tag.tagCheckRegexp)
513 && Objects.equals(tagMessageRegexp, tag.tagMessageRegexp)
514 && Objects.equals(tagIdRegexp, tag.tagIdRegexp);
515 }
516
517 @Override
518 public int hashCode() {
519 return Objects.hash(text, line, column, tagType, tagCheckRegexp, tagMessageRegexp,
520 tagIdRegexp);
521 }
522
523
524
525
526
527
528
529
530 public boolean isMatch(TreeWalkerAuditEvent event) {
531 return isCheckMatch(event) && isIdMatch(event) && isMessageMatch(event);
532 }
533
534
535
536
537
538
539
540 private boolean isCheckMatch(TreeWalkerAuditEvent event) {
541 final Matcher checkMatcher = tagCheckRegexp.matcher(event.getSourceName());
542 return checkMatcher.find();
543 }
544
545
546
547
548
549
550
551 private boolean isIdMatch(TreeWalkerAuditEvent event) {
552 boolean match = true;
553 if (tagIdRegexp != null) {
554 if (event.getModuleId() == null) {
555 match = false;
556 }
557 else {
558 final Matcher idMatcher = tagIdRegexp.matcher(event.getModuleId());
559 match = idMatcher.find();
560 }
561 }
562 return match;
563 }
564
565
566
567
568
569
570
571 private boolean isMessageMatch(TreeWalkerAuditEvent event) {
572 boolean match = true;
573 if (tagMessageRegexp != null) {
574 final Matcher messageMatcher = tagMessageRegexp.matcher(event.getMessage());
575 match = messageMatcher.find();
576 }
577 return match;
578 }
579
580 @Override
581 public String toString() {
582 return "Tag[text='" + text + '\''
583 + ", line=" + line
584 + ", column=" + column
585 + ", type=" + tagType
586 + ", tagCheckRegexp=" + tagCheckRegexp
587 + ", tagMessageRegexp=" + tagMessageRegexp
588 + ", tagIdRegexp=" + tagIdRegexp + ']';
589 }
590
591 }
592
593 }