View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.filters;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck.MSG_EXPECTED_TAG;
24  import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck.MSG_RETURN_EXPECTED;
25  import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck.MSG_UNUSED_TAG;
26  import static com.puppycrawl.tools.checkstyle.checks.whitespace.FileTabCharacterCheck.MSG_CONTAINS_TAB;
27  import static com.puppycrawl.tools.checkstyle.checks.whitespace.FileTabCharacterCheck.MSG_FILE_CONTAINS_TAB;
28  
29  import java.io.File;
30  import java.io.FileNotFoundException;
31  import java.io.IOException;
32  import java.nio.file.Files;
33  import java.nio.file.StandardCopyOption;
34  import java.util.Arrays;
35  import java.util.List;
36  
37  import org.junit.jupiter.api.Test;
38  import org.junit.jupiter.api.io.TempDir;
39  
40  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
41  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
42  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
43  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
44  import com.puppycrawl.tools.checkstyle.api.Configuration;
45  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
46  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
47  import com.puppycrawl.tools.checkstyle.api.Violation;
48  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck;
49  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck;
50  import com.puppycrawl.tools.checkstyle.checks.whitespace.FileTabCharacterCheck;
51  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
52  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
53  import nl.jqno.equalsverifier.EqualsVerifier;
54  import nl.jqno.equalsverifier.EqualsVerifierReport;
55  
56  public class SuppressWithPlainTextCommentFilterTest extends AbstractModuleTestSupport {
57  
58      private static final String MSG_REGEXP_EXCEEDED = "regexp.exceeded";
59  
60      @TempDir
61      public File temporaryFolder;
62  
63      @Override
64      protected String getPackageLocation() {
65          return "com/puppycrawl/tools/checkstyle/filters/suppresswithplaintextcommentfilter";
66      }
67  
68      @Test
69      public void testFilterWithDefaultConfig() throws Exception {
70          final String[] suppressed = {
71              "20:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
72              "28:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
73          };
74  
75          final String[] violationMessages = {
76              "20:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
77              "24:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
78              "28:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
79          };
80  
81          verifyFilterWithInlineConfigParser(
82              getPath("InputSuppressWithPlainTextCommentFilterWithDefaultCfg.java"),
83              violationMessages, removeSuppressed(violationMessages, suppressed));
84      }
85  
86      @Test
87      public void testChangeOffAndOnFormat() throws Exception {
88          final String[] suppressed = {
89              "20:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
90              "27:30: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
91          };
92  
93          final String[] violationMessage = {
94              "20:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
95              "24:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
96              "27:30: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
97              "30:13: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
98          };
99  
100         verifyFilterWithInlineConfigParser(
101             getPath("InputSuppressWithPlainTextCommentFilterWithCustomOnAndOffComments.java"),
102             violationMessage, removeSuppressed(violationMessage, suppressed));
103     }
104 
105     @Test
106     public void testSuppressionCommentsInXmlFile() throws Exception {
107         final DefaultConfiguration filterCfg =
108             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
109         filterCfg.addProperty("offCommentFormat", "CS-OFF");
110         filterCfg.addProperty("onCommentFormat", "CS-ON");
111 
112         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
113         checkCfg.addProperty("eachLine", "true");
114 
115         final String[] suppressed = {
116             "7:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
117         };
118 
119         final String[] violationMessages = {
120             "7:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
121             "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
122         };
123 
124         verifySuppressed(
125             "InputSuppressWithPlainTextCommentFilter.xml",
126             removeSuppressed(violationMessages, suppressed),
127             filterCfg, checkCfg
128         );
129     }
130 
131     @Test
132     public void testSuppressionCommentsInPropertiesFile() throws Exception {
133         final DefaultConfiguration filterCfg =
134             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
135         filterCfg.addProperty("offCommentFormat", "# CHECKSTYLE:OFF");
136         filterCfg.addProperty("onCommentFormat", "# CHECKSTYLE:ON");
137 
138         final DefaultConfiguration checkCfg = createModuleConfig(RegexpSinglelineCheck.class);
139         checkCfg.addProperty("format", "^key[0-9]=$");
140 
141         final String[] suppressed = {
142             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
143                 "^key[0-9]=$"),
144         };
145 
146         final String[] violationMessages = {
147             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
148                 "^key[0-9]=$"),
149             "4: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
150                 "^key[0-9]=$"),
151         };
152 
153         verifySuppressed(
154             "InputSuppressWithPlainTextCommentFilter.properties",
155             removeSuppressed(violationMessages, suppressed),
156             filterCfg, checkCfg
157         );
158     }
159 
160     @Test
161     public void testSuppressionCommentsInSqlFile() throws Exception {
162         final DefaultConfiguration filterCfg =
163             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
164         filterCfg.addProperty("offCommentFormat", "-- CHECKSTYLE OFF");
165         filterCfg.addProperty("onCommentFormat", "-- CHECKSTYLE ON");
166 
167         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
168         checkCfg.addProperty("eachLine", "true");
169 
170         final String[] suppressed = {
171             "2:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
172         };
173 
174         final String[] violationMessages = {
175             "2:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
176             "5:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
177         };
178 
179         verifySuppressed(
180             "InputSuppressWithPlainTextCommentFilter.sql",
181             removeSuppressed(violationMessages, suppressed),
182             filterCfg, checkCfg
183         );
184     }
185 
186     @Test
187     public void testSuppressionCommentsInJavaScriptFile() throws Exception {
188         final String[] suppressed = {
189             "22: " + getCheckMessage(RegexpSinglelineCheck.class,
190                     MSG_REGEXP_EXCEEDED, ".*\\s===.*"),
191         };
192 
193         final String[] violationMessages = {
194             "22: " + getCheckMessage(RegexpSinglelineCheck.class,
195                     MSG_REGEXP_EXCEEDED, ".*\\s===.*"),
196             "25: " + getCheckMessage(RegexpSinglelineCheck.class,
197                     MSG_REGEXP_EXCEEDED, ".*\\s===.*"),
198         };
199 
200         verifyFilterWithInlineConfigParser(
201             getPath("InputSuppressWithPlainTextCommentFilter.js"),
202             violationMessages,
203             removeSuppressed(violationMessages, suppressed)
204         );
205     }
206 
207     @Test
208     public void testInvalidCheckFormat() throws Exception {
209         final DefaultConfiguration filterCfg =
210             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
211         filterCfg.addProperty("checkFormat", "e[l");
212         filterCfg.addProperty("onCommentFormat", "// cs-on");
213         filterCfg.addProperty("offCommentFormat", "// cs-off");
214 
215         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
216         checkCfg.addProperty("eachLine", "true");
217 
218         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
219 
220         final String[] violationMessages = {
221             "5:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_FILE_CONTAINS_TAB),
222             "8:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
223             "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
224         };
225 
226         try {
227             verifySuppressed(
228                 "InputSuppressWithPlainTextCommentFilterWithCustomOnAndOffComments.java",
229                 removeSuppressed(violationMessages, suppressed),
230                 filterCfg, checkCfg
231             );
232             assertWithMessage("CheckstyleException is expected").fail();
233         }
234         catch (CheckstyleException exc) {
235             final IllegalArgumentException cause = (IllegalArgumentException) exc.getCause();
236             assertWithMessage("Invalid exception message")
237                 .that(cause)
238                 .hasMessageThat()
239                 .isEqualTo("unable to parse expanded comment e[l");
240         }
241     }
242 
243     @Test
244     public void testInvalidIdFormat() throws Exception {
245         final DefaultConfiguration filterCfg =
246             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
247         filterCfg.addProperty("idFormat", "e[l");
248         filterCfg.addProperty("onCommentFormat", "// cs-on");
249         filterCfg.addProperty("offCommentFormat", "// cs-off");
250 
251         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
252         checkCfg.addProperty("eachLine", "true");
253 
254         try {
255             verifySuppressed(
256                 "InputSuppressWithPlainTextCommentFilterWithCustomOnAndOffComments.java",
257                 CommonUtil.EMPTY_STRING_ARRAY, filterCfg, checkCfg
258             );
259             assertWithMessage("CheckstyleException is expected").fail();
260         }
261         catch (CheckstyleException exc) {
262             final IllegalArgumentException cause = (IllegalArgumentException) exc.getCause();
263             assertWithMessage("Invalid exception message")
264                 .that(cause)
265                 .hasMessageThat()
266                 .isEqualTo("unable to parse expanded comment e[l");
267         }
268     }
269 
270     @Test
271     public void testInvalidMessageFormat() throws Exception {
272         final DefaultConfiguration filterCfg =
273             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
274         filterCfg.addProperty("messageFormat", "e[l");
275         filterCfg.addProperty("onCommentFormat", "// cs-on");
276         filterCfg.addProperty("offCommentFormat", "// cs-off");
277 
278         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
279         checkCfg.addProperty("eachLine", "true");
280 
281         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
282 
283         final String[] violationMessages = {
284             "5:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_FILE_CONTAINS_TAB),
285             "8:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
286             "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
287         };
288 
289         try {
290             verifySuppressed(
291                 "InputSuppressWithPlainTextCommentFilterWithCustomOnAndOffComments.java",
292                 removeSuppressed(violationMessages, suppressed),
293                 filterCfg, checkCfg
294             );
295             assertWithMessage("CheckstyleException is expected").fail();
296         }
297         catch (CheckstyleException exc) {
298             final IllegalArgumentException cause = (IllegalArgumentException) exc.getCause();
299             assertWithMessage("Invalid exception message")
300                 .that(cause)
301                 .hasMessageThat()
302                 .isEqualTo("unable to parse expanded comment e[l");
303         }
304     }
305 
306     @Test
307     public void testInvalidMessageFormatInSqlFile() throws Exception {
308         final DefaultConfiguration filterCfg =
309             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
310         filterCfg.addProperty("onCommentFormat", "CSON (\\w+)");
311         filterCfg.addProperty("messageFormat", "e[l");
312 
313         final DefaultConfiguration checkCfg = createModuleConfig(RegexpSinglelineCheck.class);
314         checkCfg.addProperty("format", "^.*COUNT\\(\\*\\).*$");
315 
316         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
317 
318         final String[] violationMessages = {
319             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
320                     "^.*COUNT\\(\\*\\).*$"),
321         };
322 
323         try {
324             verifySuppressed(
325                 "InputSuppressWithPlainTextCommentFilterWithCustomOnComment.sql",
326                 removeSuppressed(violationMessages, suppressed),
327                 filterCfg, checkCfg
328             );
329             assertWithMessage("CheckstyleException is expected").fail();
330         }
331         catch (CheckstyleException exc) {
332             final IllegalArgumentException cause = (IllegalArgumentException) exc.getCause();
333             assertWithMessage("Invalid exception message")
334                 .that(cause)
335                 .hasMessageThat()
336                 .isEqualTo("unable to parse expanded comment e[l");
337         }
338     }
339 
340     @Test
341     public void testAcceptNullViolation() {
342         final SuppressWithPlainTextCommentFilter filter = new SuppressWithPlainTextCommentFilter();
343         final AuditEvent auditEvent = new AuditEvent(this);
344         assertWithMessage("Filter should accept audit event")
345                 .that(filter.accept(auditEvent))
346                 .isTrue();
347         assertWithMessage("File name should not be null")
348             .that(auditEvent.getFileName())
349             .isNull();
350     }
351 
352     /**
353      * Our goal is 100% test coverage, for this we use white-box testing.
354      * So we need access to the implementation details. For this reason, it is necessary
355      * to use reflection to gain access to the inner type {@code Suppression} here.
356      */
357     @Test
358     public void testEqualsAndHashCodeOfSuppressionClass() throws ClassNotFoundException {
359         final Class<?> suppressionClass = TestUtil.getInnerClassType(
360                 SuppressWithPlainTextCommentFilter.class, "Suppression");
361         final EqualsVerifierReport ev = EqualsVerifier
362                 .forClass(suppressionClass).usingGetClass()
363                 .report();
364         assertWithMessage("Error: " + ev.getMessage())
365                 .that(ev.isSuccessful())
366                 .isTrue();
367     }
368 
369     /**
370      * Calls the filter twice and removes input file in between to ensure that
371      * the file readed once will not read twice.
372      * We cannot use {@link AbstractModuleTestSupport#verifyFilterWithInlineConfigParser}
373      * because to kill pitest survival we need to remove target file between
374      * filter execution to accept violation
375      */
376     @Test
377     public void testCachingExecution() throws Exception {
378         final SuppressWithPlainTextCommentFilter filter = new SuppressWithPlainTextCommentFilter();
379         final String inputPath =
380                 getPath("InputSuppressWithPlainTextCommentFilterCustomMessageFormat.java");
381         final File tempFile = new File(temporaryFolder,
382                 "InputSuppressWithPlainTextCommentFilterCustomMessageFormat.java");
383         Files.copy(new File(inputPath).toPath(), tempFile.toPath(),
384                 StandardCopyOption.REPLACE_EXISTING);
385 
386         final AuditEvent auditEvent1 = new AuditEvent(
387                 tempFile.getPath(), tempFile.getPath(),
388                 new Violation(1, null, null, null, null,
389                         Object.class, null)
390         );
391         filter.accept(auditEvent1);
392         final boolean deleted = tempFile.delete();
393         assertWithMessage("Temporary file should be deleted.")
394                 .that(deleted).isTrue();
395         final AuditEvent auditEvent2 = new AuditEvent(
396                 tempFile.getPath(), tempFile.getPath(),
397                 new Violation(2, null, null, null, null,
398                         Object.class, null)
399         );
400         filter.accept(auditEvent2);
401 
402         assertWithMessage("Cache should handle missing file.")
403                 .that(tempFile.exists()).isFalse();
404     }
405 
406     /**
407      * Calls the filter on two consecutive input files and asserts that the
408      * 'currentFileSuppressions' internal field is cleared after each run.
409      * Our goal is to kill pitest survival and we need access to the implementation details
410      * so {@link AbstractModuleTestSupport#verifyFilterWithInlineConfigParser} is not used.
411      *
412      * @throws IOException if an error occurs while formatting the path to the input file.
413      */
414     @Test
415     public void testSuppressionsAreClearedEachRun() throws IOException {
416         final SuppressWithPlainTextCommentFilter filter = new SuppressWithPlainTextCommentFilter();
417         final Violation violation = new Violation(1, null, null,
418                 null, null, Object.class, null);
419 
420         final String fileName1 = getPath(
421                         "InputSuppressWithPlainTextCommentFilterCustomMessageFormat.java");
422         final AuditEvent event1 = new AuditEvent("", fileName1, violation);
423         filter.accept(event1);
424 
425         final List<?> suppressions1 = getSuppressionsAfterExecution(filter);
426         assertWithMessage("Invalid suppressions size")
427             .that(suppressions1)
428             .hasSize(4);
429 
430         final String fileName2 = getPath(
431                         "InputSuppressWithPlainTextCommentFilterWithDefaultCfg.java");
432         final AuditEvent event2 = new AuditEvent("", fileName2, violation);
433         filter.accept(event2);
434 
435         final List<?> suppressions2 = getSuppressionsAfterExecution(filter);
436         assertWithMessage("Invalid suppressions size")
437             .that(suppressions2)
438             .hasSize(6);
439     }
440 
441     @Test
442     public void testSuppressByCheck() throws Exception {
443         final String[] suppressedViolationMessages = {
444             "36:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
445         };
446 
447         final String[] expectedViolationMessages = {
448             "33: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
449                 ".*[a-zA-Z][0-9].*"),
450             "36: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
451                 ".*[a-zA-Z][0-9].*"),
452             "36:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
453             "38: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
454                 ".*[a-zA-Z][0-9].*"),
455             "41: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
456                 ".*[a-zA-Z][0-9].*"),
457         };
458 
459         verifyFilterWithInlineConfigParser(
460                 getPath("InputSuppressWithPlainTextCommentFilterSuppressById.java"),
461                 expectedViolationMessages,
462                 removeSuppressed(expectedViolationMessages, suppressedViolationMessages)
463         );
464     }
465 
466     @Test
467     public void testSuppressByModuleId() throws Exception {
468         final String[] suppressedViolationMessages = {
469             "33: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
470                 ".*[a-zA-Z][0-9].*"),
471             "36: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
472                 ".*[a-zA-Z][0-9].*"),
473             "38: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
474                 ".*[a-zA-Z][0-9].*"),
475         };
476 
477         final String[] expectedViolationMessages = {
478             "30: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
479                 ".*[a-zA-Z][0-9].*"),
480             "33: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
481                 ".*[a-zA-Z][0-9].*"),
482             "36:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
483             "36: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
484                 ".*[a-zA-Z][0-9].*"),
485             "38: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
486                 ".*[a-zA-Z][0-9].*"),
487             "41: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
488                 ".*[a-zA-Z][0-9].*"),
489         };
490 
491         verifyFilterWithInlineConfigParser(
492             getPath("InputSuppressWithPlainTextCommentFilterSuppressById2.java"),
493             expectedViolationMessages,
494             removeSuppressed(expectedViolationMessages, suppressedViolationMessages)
495         );
496     }
497 
498     @Test
499     public void testSuppressByCheckAndModuleId() throws Exception {
500         final String[] suppressedViolationMessages = {
501             "36:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
502         };
503 
504         final String[] expectedViolationMessages = {
505             "30: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
506                 ".*[a-zA-Z][0-9].*"),
507             "33: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
508                 ".*[a-zA-Z][0-9].*"),
509             "36:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
510             "36: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
511                 ".*[a-zA-Z][0-9].*"),
512             "38: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
513                 ".*[a-zA-Z][0-9].*"),
514             "41: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
515                 ".*[a-zA-Z][0-9].*"),
516         };
517 
518         verifyFilterWithInlineConfigParser(
519             getPath("InputSuppressWithPlainTextCommentFilterSuppressById3.java"),
520             expectedViolationMessages,
521             removeSuppressed(expectedViolationMessages, suppressedViolationMessages)
522         );
523     }
524 
525     @Test
526     public void testSuppressByCheckAndNonMatchingModuleId() throws Exception {
527         final String[] suppressedViolationMessages = CommonUtil.EMPTY_STRING_ARRAY;
528 
529         final String[] expectedViolationMessages = {
530             "30: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
531                 ".*[a-zA-Z][0-9].*"),
532             "33: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
533                 ".*[a-zA-Z][0-9].*"),
534             "36:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
535             "36: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
536                 ".*[a-zA-Z][0-9].*"),
537             "38: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
538                 ".*[a-zA-Z][0-9].*"),
539             "41: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
540                 ".*[a-zA-Z][0-9].*"),
541         };
542 
543         verifyFilterWithInlineConfigParser(
544             getPath("InputSuppressWithPlainTextCommentFilterSuppressById4.java"),
545             expectedViolationMessages,
546             removeSuppressed(expectedViolationMessages, suppressedViolationMessages)
547         );
548     }
549 
550     @Test
551     public void testSuppressByModuleIdWithNullModuleId() throws Exception {
552         final String[] suppressedViolationMessages = {
553             "33: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
554                 ".*[a-zA-Z][0-9].*"),
555             "36: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
556                 ".*[a-zA-Z][0-9].*"),
557             "38: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
558                 ".*[a-zA-Z][0-9].*"),
559         };
560 
561         final String[] expectedViolationMessages = {
562             "30: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
563                 ".*[a-zA-Z][0-9].*"),
564             "33: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
565                 ".*[a-zA-Z][0-9].*"),
566             "36:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
567             "36: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
568                 ".*[a-zA-Z][0-9].*"),
569             "38: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
570                 ".*[a-zA-Z][0-9].*"),
571             "41: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
572                 ".*[a-zA-Z][0-9].*"),
573         };
574 
575         verifyFilterWithInlineConfigParser(
576             getPath("InputSuppressWithPlainTextCommentFilterSuppressById5.java"),
577             expectedViolationMessages,
578             removeSuppressed(expectedViolationMessages, suppressedViolationMessages)
579         );
580     }
581 
582     @Test
583     public void testSuppressedByIdJavadocCheck() throws Exception {
584         final String[] suppressedViolationMessages = {
585             "28: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
586             "32:9: " + getCheckMessage(JavadocMethodCheck.class,
587                                        MSG_UNUSED_TAG, "@param", "unused"),
588             "39:22: " + getCheckMessage(JavadocMethodCheck.class,
589                                         MSG_EXPECTED_TAG, "@param", "a"),
590         };
591 
592         final String[] expectedViolationMessages = {
593             "28: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
594             "32:9: " + getCheckMessage(JavadocMethodCheck.class,
595                                        MSG_UNUSED_TAG, "@param", "unused"),
596             "39:22: " + getCheckMessage(JavadocMethodCheck.class,
597                                         MSG_EXPECTED_TAG, "@param", "a"),
598         };
599 
600         verifyFilterWithInlineConfigParser(
601             getPath("InputSuppressWithPlainTextCommentFilterSuppressByIdJavadocCheck.java"),
602             expectedViolationMessages,
603             removeSuppressed(expectedViolationMessages, suppressedViolationMessages)
604         );
605     }
606 
607     @Test
608     public void testAcceptThrowsIllegalStateExceptionAsFileNotFound() {
609         final Violation message = new Violation(1, 1, 1, TokenTypes.CLASS_DEF,
610             "messages.properties", "key", null, SeverityLevel.ERROR, null, getClass(), null);
611         final String fileName = "nonexisting_file";
612         final AuditEvent auditEvent = new AuditEvent(this, fileName, message);
613 
614         final SuppressWithPlainTextCommentFilter filter = new SuppressWithPlainTextCommentFilter();
615 
616         try {
617             filter.accept(auditEvent);
618             assertWithMessage(IllegalStateException.class.getSimpleName() + " is expected").fail();
619         }
620         catch (IllegalStateException exc) {
621             assertWithMessage("Invalid exception message")
622                 .that(exc.getMessage())
623                 .isEqualTo("Cannot read source file: " + fileName);
624 
625             final Throwable cause = exc.getCause();
626             assertWithMessage("Exception cause has invalid type")
627                     .that(cause)
628                     .isInstanceOf(FileNotFoundException.class);
629             assertWithMessage("Invalid exception message")
630                 .that(cause)
631                 .hasMessageThat()
632                 .isEqualTo(fileName + " (No such file or directory)");
633         }
634     }
635 
636     @Test
637     public void testFilterWithCustomMessageFormat() throws Exception {
638         final String[] suppressed = {
639             "34:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
640         };
641 
642         final String[] violationMessages = {
643             "32: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
644                 ".*[a-zA-Z][0-9].*"),
645             "34:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
646             "34: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
647                 ".*[a-zA-Z][0-9].*"),
648             "36: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
649                 ".*[a-zA-Z][0-9].*"),
650             "39: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
651                 ".*[a-zA-Z][0-9].*"),
652         };
653 
654         verifyFilterWithInlineConfigParser(
655             getPath("InputSuppressWithPlainTextCommentFilterCustomMessageFormat.java"),
656             violationMessages, removeSuppressed(violationMessages, suppressed)
657         );
658     }
659 
660     @Test
661     public void testFilterWithIdAndCustomMessageFormat() throws Exception {
662         final DefaultConfiguration filterCfg =
663             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
664         filterCfg.addProperty("offCommentFormat", "CHECKSTYLE stop (\\w+) (\\w+)");
665         filterCfg.addProperty("onCommentFormat", "CHECKSTYLE resume (\\w+) (\\w+)");
666         filterCfg.addProperty("idFormat", "$1");
667         filterCfg.addProperty("messageFormat", "$2");
668 
669         final DefaultConfiguration regexpCheckCfg = createModuleConfig(RegexpSinglelineCheck.class);
670         regexpCheckCfg.addProperty("id", "warning");
671         regexpCheckCfg.addProperty("format", "^.*COUNT\\(\\*\\).*$");
672 
673         final String[] suppressedViolationMessages = {
674             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
675                 "^.*COUNT\\(\\*\\).*$"),
676         };
677 
678         final String[] expectedViolationMessages = {
679             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
680                 "^.*COUNT\\(\\*\\).*$"),
681             "5: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
682                 "^.*COUNT\\(\\*\\).*$"),
683             "8: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
684                 "^.*COUNT\\(\\*\\).*$"),
685         };
686 
687         verifySuppressed(
688             "InputSuppressWithPlainTextCommentFilterCustomMessageFormat.sql",
689             removeSuppressed(expectedViolationMessages, suppressedViolationMessages),
690             filterCfg, regexpCheckCfg
691         );
692     }
693 
694     @Test
695     public void testFilterWithCheckAndCustomMessageFormat() throws Exception {
696         final DefaultConfiguration filterCfg =
697             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
698         filterCfg.addProperty("offCommentFormat", "CHECKSTYLE stop (\\w+) (\\w+)");
699         filterCfg.addProperty("onCommentFormat", "CHECKSTYLE resume (\\w+) (\\w+)");
700         filterCfg.addProperty("checkFormat", "RegexpSinglelineCheck");
701         filterCfg.addProperty("messageFormat", "$2");
702 
703         final DefaultConfiguration regexpCheckCfg = createModuleConfig(RegexpSinglelineCheck.class);
704         regexpCheckCfg.addProperty("id", "warning");
705         regexpCheckCfg.addProperty("format", "^.*COUNT\\(\\*\\).*$");
706 
707         final String[] suppressedViolationMessages = {
708             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
709                 "^.*COUNT\\(\\*\\).*$"),
710         };
711 
712         final String[] expectedViolationMessages = {
713             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
714                 "^.*COUNT\\(\\*\\).*$"),
715             "5: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
716                 "^.*COUNT\\(\\*\\).*$"),
717             "8: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
718                 "^.*COUNT\\(\\*\\).*$"),
719         };
720 
721         verifySuppressed(
722             "InputSuppressWithPlainTextCommentFilterCustomMessageFormat.sql",
723             removeSuppressed(expectedViolationMessages, suppressedViolationMessages),
724             filterCfg, regexpCheckCfg
725         );
726     }
727 
728     @Test
729     public void testFilterWithDirectory() throws IOException {
730         final SuppressWithPlainTextCommentFilter filter = new SuppressWithPlainTextCommentFilter();
731         final AuditEvent event = new AuditEvent(this, getPath(""), new Violation(1, 1,
732                 "bundle", "key", null, SeverityLevel.ERROR, "moduleId", getClass(),
733                 "customMessage"));
734 
735         assertWithMessage("filter should accept directory")
736                 .that(filter.accept(event))
737                 .isTrue();
738     }
739 
740     private static List<?> getSuppressionsAfterExecution(
741                             SuppressWithPlainTextCommentFilter filter) {
742         return TestUtil.getInternalState(filter, "currentFileSuppressionCache");
743     }
744 
745     private void verifySuppressed(String fileNameWithExtension, String[] violationMessages,
746                                   Configuration... childConfigs) throws Exception {
747         final DefaultConfiguration checkerConfig = createRootConfig(null);
748 
749         Arrays.stream(childConfigs).forEach(checkerConfig::addChild);
750 
751         final String fileExtension = CommonUtil.getFileExtension(fileNameWithExtension);
752         checkerConfig.addProperty("fileExtensions", fileExtension);
753 
754         verify(checkerConfig, getPath(fileNameWithExtension), violationMessages);
755     }
756 
757 }