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