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