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