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