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