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