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.io.File;
23 import java.io.IOException;
24 import java.nio.charset.StandardCharsets;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import java.util.regex.PatternSyntaxException;
32
33 import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean;
34 import com.puppycrawl.tools.checkstyle.PropertyType;
35 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
36 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
37 import com.puppycrawl.tools.checkstyle.api.FileText;
38 import com.puppycrawl.tools.checkstyle.api.Filter;
39 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
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
112
113
114
115
116
117
118
119 public class SuppressWithPlainTextCommentFilter extends AbstractAutomaticBean implements Filter {
120
121
122 private static final String DEFAULT_OFF_FORMAT = "// CHECKSTYLE:OFF";
123
124
125 private static final String DEFAULT_ON_FORMAT = "// CHECKSTYLE:ON";
126
127
128 private static final String DEFAULT_CHECK_FORMAT = ".*";
129
130
131 private final Collection<Suppression> currentFileSuppressionCache = new ArrayList<>();
132
133
134 private String currentFileName = "";
135
136
137 private Pattern offCommentFormat = CommonUtil.createPattern(DEFAULT_OFF_FORMAT);
138
139
140 private Pattern onCommentFormat = CommonUtil.createPattern(DEFAULT_ON_FORMAT);
141
142
143 @XdocsPropertyType(PropertyType.PATTERN)
144 private String checkFormat = DEFAULT_CHECK_FORMAT;
145
146
147 @XdocsPropertyType(PropertyType.PATTERN)
148 private String messageFormat;
149
150
151 @XdocsPropertyType(PropertyType.PATTERN)
152 private String idFormat;
153
154
155
156
157
158
159
160 public final void setOffCommentFormat(Pattern pattern) {
161 offCommentFormat = pattern;
162 }
163
164
165
166
167
168
169
170 public final void setOnCommentFormat(Pattern pattern) {
171 onCommentFormat = pattern;
172 }
173
174
175
176
177
178
179
180 public final void setCheckFormat(String format) {
181 checkFormat = format;
182 }
183
184
185
186
187
188
189
190 public final void setMessageFormat(String format) {
191 messageFormat = format;
192 }
193
194
195
196
197
198
199
200 public final void setIdFormat(String format) {
201 idFormat = format;
202 }
203
204 @Override
205 public boolean accept(AuditEvent event) {
206 boolean accepted = true;
207 if (event.getViolation() != null) {
208 final String eventFileName = event.getFileName();
209
210 if (!currentFileName.equals(eventFileName)) {
211 currentFileName = eventFileName;
212 final FileText fileText = getFileText(eventFileName);
213 currentFileSuppressionCache.clear();
214 if (fileText != null) {
215 cacheSuppressions(fileText);
216 }
217 }
218
219 accepted = getNearestSuppression(currentFileSuppressionCache, event) == null;
220 }
221 return accepted;
222 }
223
224 @Override
225 protected void finishLocalSetup() {
226
227 }
228
229
230
231
232
233
234
235
236 private static FileText getFileText(String fileName) {
237 final File file = new File(fileName);
238 FileText result = null;
239
240
241 if (!file.isDirectory()) {
242 try {
243 result = new FileText(file, StandardCharsets.UTF_8.name());
244 }
245 catch (IOException exc) {
246 throw new IllegalStateException("Cannot read source file: " + fileName, exc);
247 }
248 }
249
250 return result;
251 }
252
253
254
255
256
257
258 private void cacheSuppressions(FileText fileText) {
259 for (int lineNo = 0; lineNo < fileText.size(); lineNo++) {
260 final Optional<Suppression> suppression = getSuppression(fileText, lineNo);
261 suppression.ifPresent(currentFileSuppressionCache::add);
262 }
263 }
264
265
266
267
268
269
270
271
272 private Optional<Suppression> getSuppression(FileText fileText, int lineNo) {
273 final String line = fileText.get(lineNo);
274 final Matcher onCommentMatcher = onCommentFormat.matcher(line);
275 final Matcher offCommentMatcher = offCommentFormat.matcher(line);
276
277 Suppression suppression = null;
278 if (onCommentMatcher.find()) {
279 suppression = new Suppression(onCommentMatcher.group(0),
280 lineNo + 1, SuppressionType.ON, this);
281 }
282 if (offCommentMatcher.find()) {
283 suppression = new Suppression(offCommentMatcher.group(0),
284 lineNo + 1, SuppressionType.OFF, this);
285 }
286
287 return Optional.ofNullable(suppression);
288 }
289
290
291
292
293
294
295
296
297
298
299 private static Suppression getNearestSuppression(Collection<Suppression> suppressions,
300 AuditEvent event) {
301 return suppressions
302 .stream()
303 .filter(suppression -> suppression.isMatch(event))
304 .reduce((first, second) -> second)
305 .filter(suppression -> suppression.suppressionType != SuppressionType.ON)
306 .orElse(null);
307 }
308
309
310 private enum SuppressionType {
311
312
313 ON,
314
315 OFF,
316
317 }
318
319
320 private static final class Suppression {
321
322
323 private final Pattern eventSourceRegexp;
324
325 private final Pattern eventMessageRegexp;
326
327 private final Pattern eventIdRegexp;
328
329
330 private final int lineNo;
331
332
333 private final SuppressionType suppressionType;
334
335
336
337
338
339
340
341
342
343
344 private Suppression(
345 String text,
346 int lineNo,
347 SuppressionType suppressionType,
348 SuppressWithPlainTextCommentFilter filter
349 ) {
350 this.lineNo = lineNo;
351 this.suppressionType = suppressionType;
352
353 final Pattern commentFormat;
354 if (this.suppressionType == SuppressionType.ON) {
355 commentFormat = filter.onCommentFormat;
356 }
357 else {
358 commentFormat = filter.offCommentFormat;
359 }
360
361
362
363 String format = "";
364 try {
365 format = CommonUtil.fillTemplateWithStringsByRegexp(
366 filter.checkFormat, text, commentFormat);
367 eventSourceRegexp = Pattern.compile(format);
368 if (filter.messageFormat == null) {
369 eventMessageRegexp = null;
370 }
371 else {
372 format = CommonUtil.fillTemplateWithStringsByRegexp(
373 filter.messageFormat, text, commentFormat);
374 eventMessageRegexp = Pattern.compile(format);
375 }
376 if (filter.idFormat == null) {
377 eventIdRegexp = null;
378 }
379 else {
380 format = CommonUtil.fillTemplateWithStringsByRegexp(
381 filter.idFormat, text, commentFormat);
382 eventIdRegexp = Pattern.compile(format);
383 }
384 }
385 catch (final PatternSyntaxException exc) {
386 throw new IllegalArgumentException(
387 "unable to parse expanded comment " + format, exc);
388 }
389 }
390
391
392
393
394
395
396
397
398 @Override
399 public boolean equals(Object other) {
400 if (this == other) {
401 return true;
402 }
403 if (other == null || getClass() != other.getClass()) {
404 return false;
405 }
406 final Suppression suppression = (Suppression) other;
407 return Objects.equals(lineNo, suppression.lineNo)
408 && Objects.equals(suppressionType, suppression.suppressionType)
409 && Objects.equals(eventSourceRegexp, suppression.eventSourceRegexp)
410 && Objects.equals(eventMessageRegexp, suppression.eventMessageRegexp)
411 && Objects.equals(eventIdRegexp, suppression.eventIdRegexp);
412 }
413
414 @Override
415 public int hashCode() {
416 return Objects.hash(
417 lineNo, suppressionType, eventSourceRegexp, eventMessageRegexp,
418 eventIdRegexp);
419 }
420
421
422
423
424
425
426
427 private boolean isMatch(AuditEvent event) {
428 return isInScopeOfSuppression(event)
429 && isCheckMatch(event)
430 && isIdMatch(event)
431 && isMessageMatch(event);
432 }
433
434
435
436
437
438
439
440 private boolean isInScopeOfSuppression(AuditEvent event) {
441 return lineNo <= event.getLine();
442 }
443
444
445
446
447
448
449
450 private boolean isCheckMatch(AuditEvent event) {
451 final Matcher checkMatcher = eventSourceRegexp.matcher(event.getSourceName());
452 return checkMatcher.find();
453 }
454
455
456
457
458
459
460
461 private boolean isIdMatch(AuditEvent event) {
462 boolean match = true;
463 if (eventIdRegexp != null) {
464 if (event.getModuleId() == null) {
465 match = false;
466 }
467 else {
468 final Matcher idMatcher = eventIdRegexp.matcher(event.getModuleId());
469 match = idMatcher.find();
470 }
471 }
472 return match;
473 }
474
475
476
477
478
479
480
481 private boolean isMessageMatch(AuditEvent event) {
482 boolean match = true;
483 if (eventMessageRegexp != null) {
484 final Matcher messageMatcher = eventMessageRegexp.matcher(event.getMessage());
485 match = messageMatcher.find();
486 }
487 return match;
488 }
489 }
490
491 }