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