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.coding.MagicNumberCheck.MSG_KEY;
24  import static com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck.MSG_JAVADOC_MISSING;
25  import static com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck.MSG_INVALID_PATTERN;
26  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
27  
28  import java.io.File;
29  import java.nio.charset.StandardCharsets;
30  import java.util.regex.Pattern;
31  import java.util.regex.PatternSyntaxException;
32  
33  import org.junit.jupiter.api.Test;
34  
35  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
36  import com.puppycrawl.tools.checkstyle.JavaParser;
37  import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
38  import com.puppycrawl.tools.checkstyle.api.FileContents;
39  import com.puppycrawl.tools.checkstyle.api.FileText;
40  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
41  import com.puppycrawl.tools.checkstyle.api.Violation;
42  import com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck;
43  import com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocTypeCheck;
44  import com.puppycrawl.tools.checkstyle.checks.naming.TypeNameCheck;
45  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
46  
47  public class SuppressionXpathSingleFilterTest
48          extends AbstractModuleTestSupport {
49  
50      @Override
51      public String getPackageLocation() {
52          return "com/puppycrawl/tools/checkstyle/filters/suppressionxpathsinglefilter";
53      }
54  
55      @Test
56      public void testMatching() throws Exception {
57          final String[] expected = {
58              "20:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
59          };
60  
61          final String[] suppressed = {
62              "20:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
63          };
64  
65          verifyFilterWithInlineConfigParser(
66              getPath("InputSuppressionXpathSingleFilterMatchingTokenType.java"), expected,
67              removeSuppressed(expected, suppressed));
68      }
69  
70      @Test
71      public void testNonMatchingTokenType() throws Exception {
72          final String[] expected = {
73              "20:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
74          };
75  
76          final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
77  
78          verifyFilterWithInlineConfigParser(
79              getPath("InputSuppressionXpathSingleFilterNonMatchingTokenType.java"), expected,
80              removeSuppressed(expected, suppressed));
81      }
82  
83      @Test
84      public void testNonMatchingLineNumber() throws Exception {
85          final String[] expected = {
86              "19:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
87              "22:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
88          };
89  
90          final String[] suppressed = {
91              "22:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
92          };
93  
94          verifyFilterWithInlineConfigParser(
95              getPath("InputSuppressionXpathSingleFilterNonMatchingLineNumber.java"), expected,
96              removeSuppressed(expected, suppressed));
97      }
98  
99      @Test
100     public void testNonMatchingColumnNumber() throws Exception {
101         final String[] expected = {
102             "23:11: " + getCheckMessage(TypeNameCheck.class, MSG_INVALID_PATTERN,
103                                         "testClass", "^[A-Z][a-zA-Z0-9]*$"),
104             "26:11: " + getCheckMessage(TypeNameCheck.class, MSG_INVALID_PATTERN,
105                                         "anotherTestClass", "^[A-Z][a-zA-Z0-9]*$"),
106         };
107         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
108 
109         verifyFilterWithInlineConfigParser(
110             getPath("InputSuppressionXpathSingleFilterNonMatchingColumnNumber.java"), expected,
111             removeSuppressed(expected, suppressed));
112     }
113 
114     @Test
115     public void testComplexQuery() throws Exception {
116         final String[] expected = {
117             "27:21: " + getCheckMessage(MagicNumberCheck.class, MSG_KEY, "3.14"),
118             "28:16: " + getCheckMessage(MagicNumberCheck.class, MSG_KEY, "123"),
119             "32:28: " + getCheckMessage(MagicNumberCheck.class, MSG_KEY, "123"),
120         };
121         final String[] suppressed = {
122             "27:21: " + getCheckMessage(MagicNumberCheck.class, MSG_KEY, "3.14"),
123         };
124         verifyFilterWithInlineConfigParser(
125             getPath("InputSuppressionXpathSingleFilterComplexQuery.java"), expected,
126             removeSuppressed(expected, suppressed));
127     }
128 
129     @Test
130     public void testIncorrectQuery() {
131         final String xpath = "1@#";
132         final IllegalArgumentException exc = getExpectedThrowable(
133                 IllegalArgumentException.class,
134                 () -> {
135                     createSuppressionXpathSingleFilter(
136                             "InputSuppressionXpathSingleFilterComplexQuery", "Test",
137                             null, null, xpath);
138                 });
139         assertWithMessage("Message should be: Unexpected xpath query")
140                 .that(exc.getMessage())
141                 .contains("Incorrect xpath query");
142     }
143 
144     @Test
145     public void testNoQuery() throws Exception {
146         final String[] expected = {
147             "19:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
148         };
149 
150         final String[] suppressed = {
151             "19:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
152         };
153 
154         verifyFilterWithInlineConfigParser(
155             getPath("InputSuppressionXpathSingleFilterNoQuery.java"), expected,
156             removeSuppressed(expected, suppressed));
157     }
158 
159     @Test
160     public void testNullFileName() throws Exception {
161         final String[] expected = {
162             "19:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
163         };
164 
165         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
166 
167         verifyFilterWithInlineConfigParser(
168             getPath("InputSuppressionXpathSingleFilterNullFileName.java"), expected,
169             removeSuppressed(expected, suppressed));
170     }
171 
172     @Test
173     public void testNonMatchingFileRegexp() throws Exception {
174         final String[] expected = {
175             "19:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
176         };
177 
178         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
179 
180         verifyFilterWithInlineConfigParser(
181             getPath("InputSuppressionXpathSingleFilterNonMatchingFileRegexp.java"), expected,
182             removeSuppressed(expected, suppressed));
183     }
184 
185     @Test
186     public void testInvalidFileRegexp() {
187         final SuppressionXpathSingleFilter filter = new SuppressionXpathSingleFilter();
188         final PatternSyntaxException exc = getExpectedThrowable(
189                 PatternSyntaxException.class,
190                 () -> {
191                     filter.setFiles("e[l");
192                 });
193         assertWithMessage("Message should be: Unclosed character class")
194                 .that(exc.getMessage())
195                 .contains("Unclosed character class");
196     }
197 
198     @Test
199     public void testInvalidCheckRegexp() {
200         final SuppressionXpathSingleFilter filter = new SuppressionXpathSingleFilter();
201         final PatternSyntaxException exc = getExpectedThrowable(
202                 PatternSyntaxException.class,
203                 () -> {
204                     filter.setChecks("e[l");
205                 });
206         assertWithMessage("Message should be: Unclosed character class")
207                 .that(exc.getMessage())
208                 .contains("Unclosed character class");
209     }
210 
211     @Test
212     public void testNullViolation() throws Exception {
213         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
214         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
215 
216         verifyFilterWithInlineConfigParser(
217             getPath("InputSuppressionXpathSingleFilterNullViolation.java"), expected,
218             removeSuppressed(expected, suppressed));
219     }
220 
221     @Test
222     public void testNonMatchingModuleId() throws Exception {
223         final String[] expected = {
224             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
225         };
226 
227         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
228 
229         verifyFilterWithInlineConfigParser(
230             getPath("InputSuppressionXpathSingleFilterNonMatchingModuleId.java"), expected,
231             removeSuppressed(expected, suppressed));
232     }
233 
234     @Test
235     public void testMatchingModuleId() throws Exception {
236         final String[] expected = {
237             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
238         };
239 
240         final String[] suppressed = {
241             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
242         };
243 
244         verifyFilterWithInlineConfigParser(
245             getPath("InputSuppressionXpathSingleFilterMatchingModuleId.java"), expected,
246             removeSuppressed(expected, suppressed));
247     }
248 
249     @Test
250     public void testNonMatchingChecks() throws Exception {
251         final String[] expected = {
252             "20:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
253         };
254 
255         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
256 
257         verifyFilterWithInlineConfigParser(
258             getPath("InputSuppressionXpathSingleFilterNonMatchingCheck.java"), expected,
259             removeSuppressed(expected, suppressed));
260     }
261 
262     @Test
263     public void testNonMatchingFileNameModuleIdAndCheck() throws Exception {
264         final String[] expected = {
265             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
266         };
267         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
268 
269         verifyFilterWithInlineConfigParser(
270             getPath("InputSuppressionXpathSingleFilterNonMatchingFileNameModuleIdAndCheck.java"),
271             expected, removeSuppressed(expected, suppressed));
272     }
273 
274     @Test
275     public void testNullModuleIdAndNonMatchingChecks() throws Exception {
276         final String[] expected = {
277             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
278         };
279         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
280 
281         verifyFilterWithInlineConfigParser(
282             getPath("InputSuppressionXpathSingleFilterNullModuleIdAndNonMatchingCheck.java"),
283             expected, removeSuppressed(expected, suppressed));
284     }
285 
286     @Test
287     public void testDecideByMessage() throws Exception {
288         final String[] expected = {
289             "29:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
290             "32:21: " + getCheckMessage(MagicNumberCheck.class, MSG_KEY, "3.14"),
291             "33:16: " + getCheckMessage(MagicNumberCheck.class, MSG_KEY, "123"),
292             "37:28: " + getCheckMessage(MagicNumberCheck.class, MSG_KEY, "123"),
293         };
294 
295         final String[] suppressed = {
296             "29:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
297         };
298 
299         verifyFilterWithInlineConfigParser(
300             getPath("InputSuppressionXpathSingleFilterDecideByMessage.java"),
301             expected, removeSuppressed(expected, suppressed));
302     }
303 
304     @Test
305     public void testThrowException() throws Exception {
306         final String xpath = "//CLASS_DEF[@text='InputSuppressionXpathSingleFilterComplexQuery']";
307         final SuppressionXpathSingleFilter filter =
308                 createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilterComplexQuery",
309                         "Test", null, null, xpath);
310         final Violation message =
311                 new Violation(3, 0, TokenTypes.CLASS_DEF, "",
312                         "", null, null, "id19",
313                         getClass(), null);
314         final FileContents fileContents = new FileContents(new FileText(
315             new File(getPath("InputSuppressionXpathSingleFilterComplexQuery.java")),
316             StandardCharsets.UTF_8.name()));
317         final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents,
318                 "InputSuppressionXpathSingleFilterComplexQuery.java", message, null);
319         final IllegalStateException exc = getExpectedThrowable(
320                 IllegalStateException.class,
321                 () -> {
322                     filter.accept(ev);
323                 });
324         assertWithMessage("Exception message does not match expected one")
325                 .that(exc.getMessage())
326                 .contains("Cannot initialize context and evaluate query");
327     }
328 
329     @Test
330     public void testAllNullConfiguration() throws Exception {
331         final String[] expected = {
332             "19:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
333         };
334 
335         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
336 
337         verifyFilterWithInlineConfigParser(
338                 getPath("InputSuppressionXpathSingleFilterAllNullConfiguration.java"),
339                 expected, removeSuppressed(expected, suppressed));
340     }
341 
342     @Test
343     public void testDecideByIdAndExpression() throws Exception {
344         final String[] expected = {
345             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
346         };
347 
348         final String[] suppressed = {
349             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
350         };
351 
352         verifyFilterWithInlineConfigParser(
353                 getPath("InputSuppressionXpathSingleFilterDecideByIdAndExpression.java"),
354                 expected, removeSuppressed(expected, suppressed));
355     }
356 
357     @Test
358     public void testDefaultFileProperty() throws Exception {
359         final String[] expected = {
360             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
361         };
362 
363         final String[] suppressed = {
364             "21:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
365         };
366 
367         verifyFilterWithInlineConfigParser(
368                 getPath("InputSuppressionXpathSingleFilterDefaultFileProperty.java"),
369                 expected, removeSuppressed(expected, suppressed));
370     }
371 
372     @Test
373     public void testDecideByCheck() throws Exception {
374         final String[] expected = {
375             "19:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
376         };
377 
378         final String[] suppressed = {
379             "19:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
380         };
381 
382         verifyFilterWithInlineConfigParser(
383                 getPath("InputSuppressionXpathSingleFilterDecideByCheck.java"),
384                 expected, removeSuppressed(expected, suppressed));
385     }
386 
387     @Test
388     public void testDecideById() throws Exception {
389         final String[] expected = {
390             "20:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
391         };
392 
393         final String[] suppressed = {
394             "20:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
395         };
396 
397         verifyFilterWithInlineConfigParser(
398                 getPath("InputSuppressionXpathSingleFilterDecideById.java"),
399                 expected, removeSuppressed(expected, suppressed));
400     }
401 
402     @Test
403     public void testNonMatchingCheckRegexp() throws Exception {
404         final String[] expected = {
405             "20:1: " + getCheckMessage(MissingJavadocTypeCheck.class, MSG_JAVADOC_MISSING),
406         };
407 
408         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
409 
410         verifyFilterWithInlineConfigParser(
411             getPath("InputSuppressionXpathSingleFilterNonMatchingCheckRegexp.java"), expected,
412             removeSuppressed(expected, suppressed));
413     }
414 
415     /**
416      * This test is required to cover pitest mutation
417      * for reset of 'file' field in SuppressionXpathSingleFilter.
418      * This not possible to reproduce by natural execution of Checkstyle
419      * as config is never changed in runtime.
420      * Projects that use us by api can reproduce this case.
421      * We need to allow users to reset module property to default state.
422      *
423      * @throws Exception when there is problem to load Input file
424      */
425     @Test
426     public void testUpdateFilterFileSettingInRunTime() throws Exception {
427         final File file = new File(getPath("InputSuppressionXpathSingleFilterComplexQuery.java"));
428 
429         final SuppressionXpathSingleFilter filter = new SuppressionXpathSingleFilter();
430         filter.setChecks("MagicNumber");
431         filter.finishLocalSetup();
432 
433         final Violation violation = new Violation(27, 21, TokenTypes.NUM_DOUBLE, "",
434                         "", null, null, null,
435                         MagicNumberCheck.class, null);
436 
437         final FileContents fileContents =
438                 new FileContents(new FileText(file, StandardCharsets.UTF_8.name()));
439 
440         final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents, file.getName(),
441                 violation, JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS));
442 
443         assertWithMessage("match ia expected as 'files' is defaulted")
444                 .that(filter.accept(ev))
445                 .isFalse();
446 
447         // set non-default value for files
448         filter.setFiles(Pattern.quote(file.getPath() + ".never.match"));
449         filter.finishLocalSetup();
450 
451         assertWithMessage("no match is expected due to weird value in 'files'")
452                 .that(filter.accept(ev))
453                 .isTrue();
454 
455         // reset files to default value of filter
456         filter.setFiles(null);
457         filter.finishLocalSetup();
458 
459         assertWithMessage("match is expected as 'files' is defaulted")
460                 .that(filter.accept(ev))
461                 .isFalse();
462     }
463 
464     /**
465      * This test is required to cover pitest mutation
466      * for reset of 'checks' field in SuppressionXpathSingleFilter.
467      * This not possible to reproduce by natural execution of Checkstyle
468      * as config is never changed in runtime.
469      * Projects that use us by api can reproduce this case.
470      * We need to allow users to reset module property to default state.
471      *
472      * @throws Exception when there is problem to load Input file
473      */
474     @Test
475     public void testUpdateFilterChecksSettingInRunTime() throws Exception {
476         final File file = new File(getPath("InputSuppressionXpathSingleFilterComplexQuery.java"));
477 
478         final SuppressionXpathSingleFilter filter = new SuppressionXpathSingleFilter();
479         filter.setChecks("MagicNumber");
480         filter.finishLocalSetup();
481 
482         final Violation violation = new Violation(27, 21, TokenTypes.NUM_DOUBLE, "",
483                 "", null, null, null,
484                 MagicNumberCheck.class, null);
485 
486         final FileContents fileContents =
487                 new FileContents(new FileText(file, StandardCharsets.UTF_8.name()));
488 
489         final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents, file.getName(),
490                 violation, JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS));
491 
492         assertWithMessage("match is expected as 'checks' is set")
493                 .that(filter.accept(ev))
494                 .isFalse();
495 
496         // reset checks to default value of filter
497         filter.setChecks(null);
498         filter.finishLocalSetup();
499 
500         assertWithMessage("no match is expected as whole filter is defaulted (empty)")
501                 .that(filter.accept(ev))
502                 .isTrue();
503     }
504 
505     /**
506      * This test is required to cover pitest mutation
507      * for reset of 'message' field in SuppressionXpathSingleFilter.
508      * This not possible to reproduce by natural execution of Checkstyle
509      * as config is never changed in runtime.
510      * Projects that use us by api can reproduce this case.
511      * We need to allow users to reset module property to default state.
512      *
513      * @throws Exception when there is problem to load Input file
514      */
515 
516     @Test
517     public void testSetMessageHandlesNullCorrectly() throws Exception {
518         final File file = new File(getPath("InputSuppressionXpathSingleFilterComplexQuery.java"));
519 
520         final SuppressionXpathSingleFilter filter = new SuppressionXpathSingleFilter();
521         filter.setMessage("MagicNumber");
522         filter.finishLocalSetup();
523 
524         final Violation violation = new Violation(27, 21, TokenTypes.NUM_DOUBLE, "",
525                 "", null, null, null,
526                 MagicNumberCheck.class, "MagicNumber");
527 
528         final FileContents fileContents =
529                 new FileContents(new FileText(file, StandardCharsets.UTF_8.name()));
530 
531         final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents, file.getName(),
532                 violation, JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS));
533 
534         assertWithMessage("match is expected as 'message' is set")
535                 .that(filter.accept(ev))
536                 .isFalse();
537 
538         filter.setMessage(null);
539         filter.finishLocalSetup();
540 
541         assertWithMessage("no match is expected as whole filter is defaulted (empty)")
542                 .that(filter.accept(ev))
543                 .isTrue();
544     }
545 
546     private static SuppressionXpathSingleFilter createSuppressionXpathSingleFilter(
547             String files, String checks, String message, String moduleId, String query) {
548         final SuppressionXpathSingleFilter filter = new SuppressionXpathSingleFilter();
549         filter.setFiles(files);
550         filter.setChecks(checks);
551         filter.setMessage(message);
552         filter.setId(moduleId);
553         filter.setQuery(query);
554         filter.finishLocalSetup();
555         return filter;
556     }
557 
558 }