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.TreeWalkerAuditEvent;
33 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
34 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
35 import com.puppycrawl.tools.checkstyle.api.FileContents;
36 import com.puppycrawl.tools.checkstyle.api.TextBlock;
37 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public class SuppressionCommentFilter
54 extends AutomaticBean
55 implements TreeWalkerFilter {
56
57
58
59
60 public enum TagType {
61
62
63
64
65 ON,
66
67
68
69 OFF,
70
71 }
72
73
74 private static final String DEFAULT_OFF_FORMAT = "CHECKSTYLE:OFF";
75
76
77 private static final String DEFAULT_ON_FORMAT = "CHECKSTYLE:ON";
78
79
80 private static final String DEFAULT_CHECK_FORMAT = ".*";
81
82
83 private final List<Tag> tags = new ArrayList<>();
84
85
86 private boolean checkC = true;
87
88
89
90
91 private boolean checkCPP = true;
92
93
94 private Pattern offCommentFormat = Pattern.compile(DEFAULT_OFF_FORMAT);
95
96
97 private Pattern onCommentFormat = Pattern.compile(DEFAULT_ON_FORMAT);
98
99
100 private String checkFormat = DEFAULT_CHECK_FORMAT;
101
102
103 private String messageFormat;
104
105
106
107
108
109
110
111
112 private WeakReference<FileContents> fileContentsReference = new WeakReference<>(null);
113
114
115
116
117
118 public final void setOffCommentFormat(Pattern pattern) {
119 offCommentFormat = pattern;
120 }
121
122
123
124
125
126 public final void setOnCommentFormat(Pattern pattern) {
127 onCommentFormat = pattern;
128 }
129
130
131
132
133
134 private FileContents getFileContents() {
135 return fileContentsReference.get();
136 }
137
138
139
140
141
142
143 public void setFileContents(FileContents fileContents) {
144 fileContentsReference = new WeakReference<>(fileContents);
145 }
146
147
148
149
150
151 public final void setCheckFormat(String format) {
152 checkFormat = format;
153 }
154
155
156
157
158
159 public void setMessageFormat(String format) {
160 messageFormat = format;
161 }
162
163
164
165
166
167
168
169 public void setCheckCPP(boolean checkCpp) {
170 checkCPP = checkCpp;
171 }
172
173
174
175
176
177 public void setCheckC(boolean checkC) {
178 this.checkC = checkC;
179 }
180
181 @Override
182 protected void finishLocalSetup() {
183
184 }
185
186 @Override
187 public boolean accept(TreeWalkerAuditEvent event) {
188 boolean accepted = true;
189
190 if (event.getLocalizedMessage() != null) {
191
192
193 final FileContents currentContents = event.getFileContents();
194
195 if (getFileContents() != currentContents) {
196 setFileContents(currentContents);
197 tagSuppressions();
198 }
199 final Tag matchTag = findNearestMatch(event);
200 accepted = matchTag == null || matchTag.getTagType() == TagType.ON;
201 }
202 return accepted;
203 }
204
205
206
207
208
209
210
211 private Tag findNearestMatch(TreeWalkerAuditEvent event) {
212 Tag result = null;
213 for (Tag tag : tags) {
214 if (tag.getLine() > event.getLine()
215 || tag.getLine() == event.getLine()
216 && tag.getColumn() > event.getColumn()) {
217 break;
218 }
219 if (tag.isMatch(event)) {
220 result = tag;
221 }
222 }
223 return result;
224 }
225
226
227
228
229
230 private void tagSuppressions() {
231 tags.clear();
232 final FileContents contents = getFileContents();
233 if (checkCPP) {
234 tagSuppressions(contents.getSingleLineComments().values());
235 }
236 if (checkC) {
237 final Collection<List<TextBlock>> cComments = contents
238 .getBlockComments().values();
239 cComments.forEach(this::tagSuppressions);
240 }
241 Collections.sort(tags);
242 }
243
244
245
246
247
248
249 private void tagSuppressions(Collection<TextBlock> comments) {
250 for (TextBlock comment : comments) {
251 final int startLineNo = comment.getStartLineNo();
252 final String[] text = comment.getText();
253 tagCommentLine(text[0], startLineNo, comment.getStartColNo());
254 for (int i = 1; i < text.length; i++) {
255 tagCommentLine(text[i], startLineNo + i, 0);
256 }
257 }
258 }
259
260
261
262
263
264
265
266
267 private void tagCommentLine(String text, int line, int column) {
268 final Matcher offMatcher = offCommentFormat.matcher(text);
269 if (offMatcher.find()) {
270 addTag(offMatcher.group(0), line, column, TagType.OFF);
271 }
272 else {
273 final Matcher onMatcher = onCommentFormat.matcher(text);
274 if (onMatcher.find()) {
275 addTag(onMatcher.group(0), line, column, TagType.ON);
276 }
277 }
278 }
279
280
281
282
283
284
285
286
287 private void addTag(String text, int line, int column, TagType reportingOn) {
288 final Tag tag = new Tag(line, column, text, reportingOn, this);
289 tags.add(tag);
290 }
291
292
293
294
295
296 public static class Tag
297 implements Comparable<Tag> {
298
299
300 private final String text;
301
302
303 private final int line;
304
305
306 private final int column;
307
308
309 private final TagType tagType;
310
311
312 private final Pattern tagCheckRegexp;
313
314
315 private final Pattern tagMessageRegexp;
316
317
318
319
320
321
322
323
324
325
326 public Tag(int line, int column, String text, TagType tagType,
327 SuppressionCommentFilter filter) {
328 this.line = line;
329 this.column = column;
330 this.text = text;
331 this.tagType = tagType;
332
333
334
335 String format = "";
336 try {
337 if (this.tagType == TagType.ON) {
338 format = CommonUtil.fillTemplateWithStringsByRegexp(
339 filter.checkFormat, text, filter.onCommentFormat);
340 tagCheckRegexp = Pattern.compile(format);
341 if (filter.messageFormat == null) {
342 tagMessageRegexp = null;
343 }
344 else {
345 format = CommonUtil.fillTemplateWithStringsByRegexp(
346 filter.messageFormat, text, filter.onCommentFormat);
347 tagMessageRegexp = Pattern.compile(format);
348 }
349 }
350 else {
351 format = CommonUtil.fillTemplateWithStringsByRegexp(
352 filter.checkFormat, text, filter.offCommentFormat);
353 tagCheckRegexp = Pattern.compile(format);
354 if (filter.messageFormat == null) {
355 tagMessageRegexp = null;
356 }
357 else {
358 format = CommonUtil.fillTemplateWithStringsByRegexp(
359 filter.messageFormat, text, filter.offCommentFormat);
360 tagMessageRegexp = Pattern.compile(format);
361 }
362 }
363 }
364 catch (final PatternSyntaxException ex) {
365 throw new IllegalArgumentException(
366 "unable to parse expanded comment " + format, ex);
367 }
368 }
369
370
371
372
373
374 public int getLine() {
375 return line;
376 }
377
378
379
380
381
382
383
384 public int getColumn() {
385 return column;
386 }
387
388
389
390
391
392
393 public TagType getTagType() {
394 return tagType;
395 }
396
397
398
399
400
401
402
403
404
405 @Override
406 public int compareTo(Tag object) {
407 final int result;
408 if (line == object.line) {
409 result = Integer.compare(column, object.column);
410 }
411 else {
412 result = Integer.compare(line, object.line);
413 }
414 return result;
415 }
416
417
418
419
420
421
422 @Override
423 public boolean equals(Object other) {
424 if (this == other) {
425 return true;
426 }
427 if (other == null || getClass() != other.getClass()) {
428 return false;
429 }
430 final Tag tag = (Tag) other;
431 return Objects.equals(line, tag.line)
432 && Objects.equals(column, tag.column)
433 && Objects.equals(tagType, tag.tagType)
434 && Objects.equals(text, tag.text)
435 && Objects.equals(tagCheckRegexp, tag.tagCheckRegexp)
436 && Objects.equals(tagMessageRegexp, tag.tagMessageRegexp);
437 }
438
439 @Override
440 public int hashCode() {
441 return Objects.hash(text, line, column, tagType, tagCheckRegexp, tagMessageRegexp);
442 }
443
444
445
446
447
448
449
450 public boolean isMatch(TreeWalkerAuditEvent event) {
451 boolean match = false;
452 final Matcher tagMatcher = tagCheckRegexp.matcher(event.getSourceName());
453 if (tagMatcher.find()) {
454 if (tagMessageRegexp == null) {
455 match = true;
456 }
457 else {
458 final Matcher messageMatcher = tagMessageRegexp.matcher(event.getMessage());
459 match = messageMatcher.find();
460 }
461 }
462 else if (event.getModuleId() != null) {
463 final Matcher idMatcher = tagCheckRegexp.matcher(event.getModuleId());
464 match = idMatcher.find();
465 }
466 return match;
467 }
468
469 @Override
470 public String toString() {
471 return "Tag[text='" + text + '\''
472 + ", line=" + line
473 + ", column=" + column
474 + ", type=" + tagType
475 + ", tagCheckRegexp=" + tagCheckRegexp
476 + ", tagMessageRegexp=" + tagMessageRegexp + ']';
477 }
478
479 }
480
481 }