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