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.lang.ref.WeakReference;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
26 import java.util.Objects;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29 import java.util.regex.PatternSyntaxException;
30
31 import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
32 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
33 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
34 import com.puppycrawl.tools.checkstyle.api.FileContents;
35 import com.puppycrawl.tools.checkstyle.api.TextBlock;
36 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
37
38
39
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 public class SuppressWithNearbyCommentFilter
72 extends AutomaticBean
73 implements TreeWalkerFilter {
74
75
76 private static final String DEFAULT_COMMENT_FORMAT =
77 "SUPPRESS CHECKSTYLE (\\w+)";
78
79
80 private static final String DEFAULT_CHECK_FORMAT = ".*";
81
82
83 private static final String DEFAULT_INFLUENCE_FORMAT = "0";
84
85
86 private final List<Tag> tags = new ArrayList<>();
87
88
89 private boolean checkC = true;
90
91
92
93
94 private boolean checkCPP = true;
95
96
97 private Pattern commentFormat = Pattern.compile(DEFAULT_COMMENT_FORMAT);
98
99
100 private String checkFormat = DEFAULT_CHECK_FORMAT;
101
102
103 private String messageFormat;
104
105
106 private String influenceFormat = DEFAULT_INFLUENCE_FORMAT;
107
108
109
110
111
112
113
114
115 private WeakReference<FileContents> fileContentsReference = new WeakReference<>(null);
116
117
118
119
120
121 public final void setCommentFormat(Pattern pattern) {
122 commentFormat = pattern;
123 }
124
125
126
127
128
129 private FileContents getFileContents() {
130 return fileContentsReference.get();
131 }
132
133
134
135
136
137
138 public void setFileContents(FileContents fileContents) {
139 fileContentsReference = new WeakReference<>(fileContents);
140 }
141
142
143
144
145
146 public final void setCheckFormat(String format) {
147 checkFormat = format;
148 }
149
150
151
152
153
154 public void setMessageFormat(String format) {
155 messageFormat = format;
156 }
157
158
159
160
161
162 public final void setInfluenceFormat(String format) {
163 influenceFormat = format;
164 }
165
166
167
168
169
170
171
172 public void setCheckCPP(boolean checkCpp) {
173 checkCPP = checkCpp;
174 }
175
176
177
178
179
180 public void setCheckC(boolean checkC) {
181 this.checkC = checkC;
182 }
183
184 @Override
185 protected void finishLocalSetup() {
186
187 }
188
189 @Override
190 public boolean accept(TreeWalkerAuditEvent event) {
191 boolean accepted = true;
192
193 if (event.getLocalizedMessage() != null) {
194
195
196 final FileContents currentContents = event.getFileContents();
197
198 if (getFileContents() != currentContents) {
199 setFileContents(currentContents);
200 tagSuppressions();
201 }
202 if (matchesTag(event)) {
203 accepted = false;
204 }
205 }
206 return accepted;
207 }
208
209
210
211
212
213
214 private boolean matchesTag(TreeWalkerAuditEvent event) {
215 boolean result = false;
216 for (final Tag tag : tags) {
217 if (tag.isMatch(event)) {
218 result = true;
219 break;
220 }
221 }
222 return result;
223 }
224
225
226
227
228
229 private void tagSuppressions() {
230 tags.clear();
231 final FileContents contents = getFileContents();
232 if (checkCPP) {
233 tagSuppressions(contents.getSingleLineComments().values());
234 }
235 if (checkC) {
236 final Collection<List<TextBlock>> cComments =
237 contents.getBlockComments().values();
238 cComments.forEach(this::tagSuppressions);
239 }
240 }
241
242
243
244
245
246
247 private void tagSuppressions(Collection<TextBlock> comments) {
248 for (final TextBlock comment : comments) {
249 final int startLineNo = comment.getStartLineNo();
250 final String[] text = comment.getText();
251 tagCommentLine(text[0], startLineNo);
252 for (int i = 1; i < text.length; i++) {
253 tagCommentLine(text[i], startLineNo + i);
254 }
255 }
256 }
257
258
259
260
261
262
263
264 private void tagCommentLine(String text, int line) {
265 final Matcher matcher = commentFormat.matcher(text);
266 if (matcher.find()) {
267 addTag(matcher.group(0), line);
268 }
269 }
270
271
272
273
274
275
276 private void addTag(String text, int line) {
277 final Tag tag = new Tag(text, line, this);
278 tags.add(tag);
279 }
280
281
282
283
284 public static class Tag {
285
286
287 private final String text;
288
289
290 private final int firstLine;
291
292
293 private final int lastLine;
294
295
296 private final Pattern tagCheckRegexp;
297
298
299 private final Pattern tagMessageRegexp;
300
301
302
303
304
305
306
307
308 public Tag(String text, int line, SuppressWithNearbyCommentFilter filter) {
309 this.text = text;
310
311
312
313 String format = "";
314 try {
315 format = CommonUtil.fillTemplateWithStringsByRegexp(
316 filter.checkFormat, text, filter.commentFormat);
317 tagCheckRegexp = Pattern.compile(format);
318 if (filter.messageFormat == null) {
319 tagMessageRegexp = null;
320 }
321 else {
322 format = CommonUtil.fillTemplateWithStringsByRegexp(
323 filter.messageFormat, text, filter.commentFormat);
324 tagMessageRegexp = Pattern.compile(format);
325 }
326 format = CommonUtil.fillTemplateWithStringsByRegexp(
327 filter.influenceFormat, text, filter.commentFormat);
328
329 if (CommonUtil.startsWithChar(format, '+')) {
330 format = format.substring(1);
331 }
332 final int influence = parseInfluence(format, filter.influenceFormat, text);
333
334 if (influence >= 1) {
335 firstLine = line;
336 lastLine = line + influence;
337 }
338 else {
339 firstLine = line + influence;
340 lastLine = line;
341 }
342 }
343 catch (final PatternSyntaxException ex) {
344 throw new IllegalArgumentException(
345 "unable to parse expanded comment " + format, ex);
346 }
347 }
348
349
350
351
352
353
354
355
356
357 private static int parseInfluence(String format, String influenceFormat, String text) {
358 try {
359 return Integer.parseInt(format);
360 }
361 catch (final NumberFormatException ex) {
362 throw new IllegalArgumentException("unable to parse influence from '" + text
363 + "' using " + influenceFormat, ex);
364 }
365 }
366
367 @Override
368 public boolean equals(Object other) {
369 if (this == other) {
370 return true;
371 }
372 if (other == null || getClass() != other.getClass()) {
373 return false;
374 }
375 final Tag tag = (Tag) other;
376 return Objects.equals(firstLine, tag.firstLine)
377 && Objects.equals(lastLine, tag.lastLine)
378 && Objects.equals(text, tag.text)
379 && Objects.equals(tagCheckRegexp, tag.tagCheckRegexp)
380 && Objects.equals(tagMessageRegexp, tag.tagMessageRegexp);
381 }
382
383 @Override
384 public int hashCode() {
385 return Objects.hash(text, firstLine, lastLine, tagCheckRegexp, tagMessageRegexp);
386 }
387
388
389
390
391
392
393
394 public boolean isMatch(TreeWalkerAuditEvent event) {
395 final int line = event.getLine();
396 boolean match = false;
397
398 if (line >= firstLine && line <= lastLine) {
399 final Matcher tagMatcher = tagCheckRegexp.matcher(event.getSourceName());
400
401 if (tagMatcher.find()) {
402 match = true;
403 }
404 else if (tagMessageRegexp == null) {
405 if (event.getModuleId() != null) {
406 final Matcher idMatcher = tagCheckRegexp.matcher(event.getModuleId());
407 match = idMatcher.find();
408 }
409 }
410 else {
411 final Matcher messageMatcher = tagMessageRegexp.matcher(event.getMessage());
412 match = messageMatcher.find();
413 }
414 }
415 return match;
416 }
417
418 @Override
419 public String toString() {
420 return "Tag[text='" + text + '\''
421 + ", firstLine=" + firstLine
422 + ", lastLine=" + lastLine
423 + ", tagCheckRegexp=" + tagCheckRegexp
424 + ", tagMessageRegexp=" + tagMessageRegexp
425 + ']';
426 }
427
428 }
429
430 }