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