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.IOException;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.List;
30 import java.util.Optional;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 import java.util.regex.PatternSyntaxException;
34
35 import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean;
36 import com.puppycrawl.tools.checkstyle.PropertyType;
37 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
38 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
39 import com.puppycrawl.tools.checkstyle.api.FileText;
40 import com.puppycrawl.tools.checkstyle.api.Filter;
41 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
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 public class SuppressWithNearbyTextFilter extends AbstractAutomaticBean implements Filter {
97
98
99 private static final String DEFAULT_NEARBY_TEXT_PATTERN = "SUPPRESS CHECKSTYLE (\\w+)";
100
101
102 private static final String DEFAULT_CHECK_PATTERN = ".*";
103
104
105 private static final String DEFAULT_LINE_RANGE = "0";
106
107
108 private final List<Suppression> suppressions = new ArrayList<>();
109
110
111 @XdocsPropertyType(PropertyType.PATTERN)
112 private Pattern nearbyTextPattern = Pattern.compile(DEFAULT_NEARBY_TEXT_PATTERN);
113
114
115
116
117
118
119 @XdocsPropertyType(PropertyType.PATTERN)
120 private String checkPattern = DEFAULT_CHECK_PATTERN;
121
122
123 @XdocsPropertyType(PropertyType.PATTERN)
124 private String messagePattern;
125
126
127 @XdocsPropertyType(PropertyType.PATTERN)
128 private String idPattern;
129
130
131
132
133
134
135
136 private String lineRange = DEFAULT_LINE_RANGE;
137
138
139 private String cachedFileAbsolutePath = "";
140
141
142
143
144
145
146
147 public final void setNearbyTextPattern(Pattern pattern) {
148 nearbyTextPattern = pattern;
149 }
150
151
152
153
154
155
156
157
158
159 public final void setCheckPattern(String pattern) {
160 checkPattern = pattern;
161 }
162
163
164
165
166
167
168
169 public void setMessagePattern(String pattern) {
170 messagePattern = pattern;
171 }
172
173
174
175
176
177
178
179 public void setIdPattern(String pattern) {
180 idPattern = pattern;
181 }
182
183
184
185
186
187
188
189
190
191
192 public final void setLineRange(String format) {
193 lineRange = format;
194 }
195
196 @Override
197 public boolean accept(AuditEvent event) {
198 boolean accepted = true;
199
200 if (event.getViolation() != null) {
201 final String eventFileTextAbsolutePath = event.getFileName();
202
203 if (!cachedFileAbsolutePath.equals(eventFileTextAbsolutePath)) {
204 final FileText currentFileText = getFileText(eventFileTextAbsolutePath);
205
206 if (currentFileText != null) {
207 cachedFileAbsolutePath = currentFileText.getFile().getAbsolutePath();
208 collectSuppressions(currentFileText);
209 }
210 }
211
212 final Optional<Suppression> nearestSuppression =
213 getNearestSuppression(suppressions, event);
214 accepted = nearestSuppression.isEmpty();
215 }
216 return accepted;
217 }
218
219 @Override
220 protected void finishLocalSetup() {
221
222 }
223
224
225
226
227
228
229
230
231 private static FileText getFileText(String fileName) {
232 final Path path = Paths.get(fileName);
233 FileText result = null;
234
235
236 if (!Files.isDirectory(path)) {
237 try {
238 result = new FileText(path.toFile(), StandardCharsets.UTF_8.name());
239 }
240 catch (IOException exc) {
241 throw new IllegalStateException("Cannot read source file: " + fileName, exc);
242 }
243 }
244
245 return result;
246 }
247
248
249
250
251
252
253 private void collectSuppressions(FileText fileText) {
254 suppressions.clear();
255
256 for (int lineNo = 0; lineNo < fileText.size(); lineNo++) {
257 final Suppression suppression = getSuppression(fileText, lineNo);
258 if (suppression != null) {
259 suppressions.add(suppression);
260 }
261 }
262 }
263
264
265
266
267
268
269
270
271 private Suppression getSuppression(FileText fileText, int lineNo) {
272 final String line = fileText.get(lineNo);
273 final Matcher nearbyTextMatcher = nearbyTextPattern.matcher(line);
274
275 Suppression suppression = null;
276 if (nearbyTextMatcher.find()) {
277 final String text = nearbyTextMatcher.group(0);
278 suppression = new Suppression(text, lineNo + 1, this);
279 }
280
281 return suppression;
282 }
283
284
285
286
287
288
289
290
291
292
293 private static Optional<Suppression> getNearestSuppression(Collection<Suppression> suppressions,
294 AuditEvent event) {
295 return suppressions
296 .stream()
297 .filter(suppression -> suppression.isMatch(event))
298 .findFirst();
299 }
300
301
302 private static final class Suppression {
303
304
305 private final int firstLine;
306
307
308 private final int lastLine;
309
310
311 private final Pattern eventSourceRegexp;
312
313
314 private Pattern eventMessageRegexp;
315
316
317 private Pattern eventIdRegexp;
318
319
320
321
322
323
324
325
326
327 private Suppression(
328 String text,
329 int lineNo,
330 SuppressWithNearbyTextFilter filter
331 ) {
332 final Pattern nearbyTextPattern = filter.nearbyTextPattern;
333 final String lineRange = filter.lineRange;
334 String format = "";
335 try {
336 format = CommonUtil.fillTemplateWithStringsByRegexp(
337 filter.checkPattern, text, nearbyTextPattern);
338 eventSourceRegexp = Pattern.compile(format);
339 if (filter.messagePattern != null) {
340 format = CommonUtil.fillTemplateWithStringsByRegexp(
341 filter.messagePattern, text, nearbyTextPattern);
342 eventMessageRegexp = Pattern.compile(format);
343 }
344 if (filter.idPattern != null) {
345 format = CommonUtil.fillTemplateWithStringsByRegexp(
346 filter.idPattern, text, nearbyTextPattern);
347 eventIdRegexp = Pattern.compile(format);
348 }
349 format = CommonUtil.fillTemplateWithStringsByRegexp(lineRange,
350 text, nearbyTextPattern);
351
352 final int range = parseRange(format, lineRange, text);
353
354 firstLine = Math.min(lineNo, lineNo + range);
355 lastLine = Math.max(lineNo, lineNo + range);
356 }
357 catch (final PatternSyntaxException exc) {
358 throw new IllegalArgumentException(
359 "unable to parse expanded comment " + format, exc);
360 }
361 }
362
363
364
365
366
367
368
369
370
371
372 private static int parseRange(String format, String lineRange, String text) {
373 try {
374 return Integer.parseInt(format);
375 }
376 catch (final NumberFormatException exc) {
377 throw new IllegalArgumentException("unable to parse line range from '" + text
378 + "' using " + lineRange, exc);
379 }
380 }
381
382
383
384
385
386
387
388
389 private boolean isMatch(AuditEvent event) {
390 return isInScopeOfSuppression(event)
391 && isCheckMatch(event)
392 && isIdMatch(event)
393 && isMessageMatch(event);
394 }
395
396
397
398
399
400
401
402 private boolean isInScopeOfSuppression(AuditEvent event) {
403 final int eventLine = event.getLine();
404 return eventLine >= firstLine && eventLine <= lastLine;
405 }
406
407
408
409
410
411
412
413 private boolean isCheckMatch(AuditEvent event) {
414 final Matcher checkMatcher = eventSourceRegexp.matcher(event.getSourceName());
415 return checkMatcher.find();
416 }
417
418
419
420
421
422
423
424 private boolean isIdMatch(AuditEvent event) {
425 boolean match = true;
426 if (eventIdRegexp != null) {
427 if (event.getModuleId() == null) {
428 match = false;
429 }
430 else {
431 final Matcher idMatcher = eventIdRegexp.matcher(event.getModuleId());
432 match = idMatcher.find();
433 }
434 }
435 return match;
436 }
437
438
439
440
441
442
443
444 private boolean isMessageMatch(AuditEvent event) {
445 boolean match = true;
446 if (eventMessageRegexp != null) {
447 final Matcher messageMatcher = eventMessageRegexp.matcher(event.getMessage());
448 match = messageMatcher.find();
449 }
450 return match;
451 }
452 }
453 }