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