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