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