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.List;
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.api.AuditEvent;
34 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
35 import com.puppycrawl.tools.checkstyle.api.FileText;
36 import com.puppycrawl.tools.checkstyle.api.Filter;
37 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public class SuppressWithPlainTextCommentFilter extends AutomaticBean implements Filter {
59
60
61 private static final String DEFAULT_OFF_FORMAT = "// CHECKSTYLE:OFF";
62
63
64 private static final String DEFAULT_ON_FORMAT = "// CHECKSTYLE:ON";
65
66
67 private static final String DEFAULT_CHECK_FORMAT = ".*";
68
69
70 private Pattern offCommentFormat = CommonUtil.createPattern(DEFAULT_OFF_FORMAT);
71
72
73 private Pattern onCommentFormat = CommonUtil.createPattern(DEFAULT_ON_FORMAT);
74
75
76 private String checkFormat = DEFAULT_CHECK_FORMAT;
77
78
79 private String messageFormat;
80
81
82
83
84
85 public final void setOffCommentFormat(Pattern pattern) {
86 offCommentFormat = pattern;
87 }
88
89
90
91
92
93 public final void setOnCommentFormat(Pattern pattern) {
94 onCommentFormat = pattern;
95 }
96
97
98
99
100
101 public final void setCheckFormat(String format) {
102 checkFormat = format;
103 }
104
105
106
107
108
109 public final void setMessageFormat(String format) {
110 messageFormat = format;
111 }
112
113 @Override
114 public boolean accept(AuditEvent event) {
115 boolean accepted = true;
116 if (event.getLocalizedMessage() != null) {
117 final FileText fileText = getFileText(event.getFileName());
118 if (fileText != null) {
119 final List<Suppression> suppressions = getSuppressions(fileText);
120 accepted = getNearestSuppression(suppressions, event) == null;
121 }
122 }
123 return accepted;
124 }
125
126 @Override
127 protected void finishLocalSetup() {
128
129 }
130
131
132
133
134
135
136 private static FileText getFileText(String fileName) {
137 final File file = new File(fileName);
138 FileText result = null;
139
140
141 if (!file.isDirectory()) {
142 try {
143 result = new FileText(file, StandardCharsets.UTF_8.name());
144 }
145 catch (IOException ex) {
146 throw new IllegalStateException("Cannot read source file: " + fileName, ex);
147 }
148 }
149
150 return result;
151 }
152
153
154
155
156
157
158 private List<Suppression> getSuppressions(FileText fileText) {
159 final List<Suppression> suppressions = new ArrayList<>();
160 for (int lineNo = 0; lineNo < fileText.size(); lineNo++) {
161 final Optional<Suppression> suppression = getSuppression(fileText, lineNo);
162 suppression.ifPresent(suppressions::add);
163 }
164 return suppressions;
165 }
166
167
168
169
170
171
172
173 private Optional<Suppression> getSuppression(FileText fileText, int lineNo) {
174 final String line = fileText.get(lineNo);
175 final Matcher onCommentMatcher = onCommentFormat.matcher(line);
176 final Matcher offCommentMatcher = offCommentFormat.matcher(line);
177
178 Suppression suppression = null;
179 if (onCommentMatcher.find()) {
180 suppression = new Suppression(onCommentMatcher.group(0),
181 lineNo + 1, onCommentMatcher.start(), SuppressionType.ON, this);
182 }
183 if (offCommentMatcher.find()) {
184 suppression = new Suppression(offCommentMatcher.group(0),
185 lineNo + 1, offCommentMatcher.start(), SuppressionType.OFF, this);
186 }
187
188 return Optional.ofNullable(suppression);
189 }
190
191
192
193
194
195
196
197
198
199 private static Suppression getNearestSuppression(List<Suppression> suppressions,
200 AuditEvent event) {
201 return suppressions
202 .stream()
203 .filter(suppression -> suppression.isMatch(event))
204 .reduce((first, second) -> second)
205 .filter(suppression -> suppression.suppressionType != SuppressionType.ON)
206 .orElse(null);
207 }
208
209
210 private enum SuppressionType {
211
212
213 ON,
214
215 OFF,
216
217 }
218
219
220 public static class Suppression {
221
222
223 private final Pattern eventSourceRegexp;
224
225 private final Pattern eventMessageRegexp;
226
227
228 private final String text;
229
230 private final int lineNo;
231
232 private final int columnNo;
233
234 private final SuppressionType suppressionType;
235
236
237
238
239
240
241
242
243
244 protected Suppression(
245 String text,
246 int lineNo,
247 int columnNo,
248 SuppressionType suppressionType,
249 SuppressWithPlainTextCommentFilter filter
250 ) {
251 this.text = text;
252 this.lineNo = lineNo;
253 this.columnNo = columnNo;
254 this.suppressionType = suppressionType;
255
256
257
258 String format = "";
259 try {
260 if (this.suppressionType == SuppressionType.ON) {
261 format = CommonUtil.fillTemplateWithStringsByRegexp(
262 filter.checkFormat, text, filter.onCommentFormat);
263 eventSourceRegexp = Pattern.compile(format);
264 if (filter.messageFormat == null) {
265 eventMessageRegexp = null;
266 }
267 else {
268 format = CommonUtil.fillTemplateWithStringsByRegexp(
269 filter.messageFormat, text, filter.onCommentFormat);
270 eventMessageRegexp = Pattern.compile(format);
271 }
272 }
273 else {
274 format = CommonUtil.fillTemplateWithStringsByRegexp(
275 filter.checkFormat, text, filter.offCommentFormat);
276 eventSourceRegexp = Pattern.compile(format);
277 if (filter.messageFormat == null) {
278 eventMessageRegexp = null;
279 }
280 else {
281 format = CommonUtil.fillTemplateWithStringsByRegexp(
282 filter.messageFormat, text, filter.offCommentFormat);
283 eventMessageRegexp = Pattern.compile(format);
284 }
285 }
286 }
287 catch (final PatternSyntaxException ex) {
288 throw new IllegalArgumentException(
289 "unable to parse expanded comment " + format, ex);
290 }
291 }
292
293
294
295
296
297
298 @Override
299 public boolean equals(Object other) {
300 if (this == other) {
301 return true;
302 }
303 if (other == null || getClass() != other.getClass()) {
304 return false;
305 }
306 final Suppression suppression = (Suppression) other;
307 return Objects.equals(lineNo, suppression.lineNo)
308 && Objects.equals(columnNo, suppression.columnNo)
309 && Objects.equals(suppressionType, suppression.suppressionType)
310 && Objects.equals(text, suppression.text)
311 && Objects.equals(eventSourceRegexp, suppression.eventSourceRegexp)
312 && Objects.equals(eventMessageRegexp, suppression.eventMessageRegexp);
313 }
314
315 @Override
316 public int hashCode() {
317 return Objects.hash(
318 text, lineNo, columnNo, suppressionType, eventSourceRegexp, eventMessageRegexp);
319 }
320
321
322
323
324
325
326 private boolean isMatch(AuditEvent event) {
327 boolean match = false;
328 if (isInScopeOfSuppression(event)) {
329 final Matcher sourceNameMatcher = eventSourceRegexp.matcher(event.getSourceName());
330 if (sourceNameMatcher.find()) {
331 match = eventMessageRegexp == null
332 || eventMessageRegexp.matcher(event.getMessage()).find();
333 }
334 else {
335 match = event.getModuleId() != null
336 && eventSourceRegexp.matcher(event.getModuleId()).find();
337 }
338 }
339 return match;
340 }
341
342
343
344
345
346
347 private boolean isInScopeOfSuppression(AuditEvent event) {
348 return lineNo <= event.getLine();
349 }
350
351 }
352
353 }