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;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck.MSG_INVALID_PATTERN;
24  import static org.mockito.ArgumentMatchers.any;
25  import static org.mockito.Mockito.CALLS_REAL_METHODS;
26  import static org.mockito.Mockito.doThrow;
27  import static org.mockito.Mockito.mock;
28  
29  import java.io.File;
30  import java.io.Writer;
31  import java.nio.charset.StandardCharsets;
32  import java.nio.file.Files;
33  import java.nio.file.StandardCopyOption;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.HashMap;
39  import java.util.HashSet;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.Set;
43  import java.util.UUID;
44  import java.util.regex.Matcher;
45  import java.util.regex.Pattern;
46  
47  import org.junit.jupiter.api.Test;
48  import org.junit.jupiter.api.io.TempDir;
49  import org.mockito.MockedConstruction;
50  import org.mockito.MockedStatic;
51  import org.mockito.Mockito;
52  import org.mockito.internal.util.Checks;
53  
54  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
55  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
56  import com.puppycrawl.tools.checkstyle.api.Configuration;
57  import com.puppycrawl.tools.checkstyle.api.Context;
58  import com.puppycrawl.tools.checkstyle.api.DetailAST;
59  import com.puppycrawl.tools.checkstyle.api.FileContents;
60  import com.puppycrawl.tools.checkstyle.api.FileText;
61  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
62  import com.puppycrawl.tools.checkstyle.checks.NoCodeInFileCheck;
63  import com.puppycrawl.tools.checkstyle.checks.coding.EmptyStatementCheck;
64  import com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck;
65  import com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck;
66  import com.puppycrawl.tools.checkstyle.checks.indentation.CommentsIndentationCheck;
67  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck;
68  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck;
69  import com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck;
70  import com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck;
71  import com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck;
72  import com.puppycrawl.tools.checkstyle.checks.naming.TypeNameCheck;
73  import com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAfterCheck;
74  import com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAroundCheck;
75  import com.puppycrawl.tools.checkstyle.filters.SuppressWithNearbyCommentFilter;
76  import com.puppycrawl.tools.checkstyle.filters.SuppressionXpathFilter;
77  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
78  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
79  
80  /**
81   * TreeWalkerTest.
82   *
83   * @noinspection ClassWithTooManyDependencies because we are less strict with tests.
84   * @noinspectionreason ClassWithTooManyDependencies - complex tests require a
85   *      large number of imports
86   */
87  public class TreeWalkerTest extends AbstractModuleTestSupport {
88  
89      @TempDir
90      public File temporaryFolder;
91  
92      @Override
93      protected String getPackageLocation() {
94          return "com/puppycrawl/tools/checkstyle/treewalker";
95      }
96  
97      @Test
98      public void testProperFileExtension() throws Exception {
99          final String path = getPath("InputTreeWalkerProperFileExtension.java");
100         final String[] expected = {
101             "10:27: " + getCheckMessage(ConstantNameCheck.class,
102                         MSG_INVALID_PATTERN, "k", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
103         };
104         verifyWithInlineConfigParserTwice(path, expected);
105     }
106 
107     /**
108      * This test is needed for 100% coverage.
109      * The Pitest reports some conditions as redundant, for example:
110      * <pre>
111      *     if (!collection.isEmpty()) { // This may be omitted.
112      *         Object value = doSomeHardJob();
113      *         for (Item item : collection) {
114      *             item.accept(value);
115      *         }
116      *     }
117      * </pre>
118      * But we really want to avoid calls to {@code doSomeHardJob} method.
119      * To make this condition mandatory, we need to broke one branch.
120      * In this case, mocking {@code TreeWalkerAuditEvent} will cause
121      * {@code getFilteredViolations} to fail. This prevents the condition
122      * <pre>
123      *     if (filters.isEmpty())
124      * </pre>
125      * in {@link TreeWalker#processFiltered(File, FileText)} to survive with Pitest mutations.
126      *
127      * @throws Exception if an error occurs
128      */
129     @Test
130     public void testNoAuditEventsWithoutFilters() throws Exception {
131         final String[] expected = {
132             "10:1: " + getCheckMessage(OneTopLevelClassCheck.class,
133                     OneTopLevelClassCheck.MSG_KEY, "InputTreeWalkerInner"),
134         };
135         try (MockedConstruction<TreeWalkerAuditEvent> mocked =
136                  Mockito.mockConstruction(TreeWalkerAuditEvent.class, (mock, context) -> {
137                      throw new CheckstyleException("No audit events expected");
138                  })) {
139             verifyWithInlineConfigParserTwice(getPath("InputTreeWalker.java"), expected);
140         }
141     }
142 
143     /**
144      * This test is needed for 100% coverage. The method {@link Mockito#mockStatic} is used to
145      * ensure that the {@code if (!ordinaryChecks.isEmpty())} condition cannot be removed.
146      */
147     @Test
148     public void testConditionRequiredWithoutOrdinaryChecks() throws Exception {
149         final String[] expected = {
150             "7:5: " + getCheckMessage(JavadocParagraphCheck.class,
151                     JavadocParagraphCheck.MSG_REDUNDANT_PARAGRAPH),
152         };
153         final String path = getPath("InputTreeWalkerJavadoc.java");
154         final DetailAST mockAst = mock();
155         final DetailAST realAst = JavaParser.parseFile(new File(path),
156                 JavaParser.Options.WITH_COMMENTS);
157         // Ensure that there is no calls to walk(..., AstState.ORDINARY)
158         doThrow(IllegalStateException.class).when(mockAst).getFirstChild();
159         try (MockedStatic<JavaParser> parser = Mockito.mockStatic(JavaParser.class)) {
160             parser.when(() -> JavaParser.parse(any(FileContents.class))).thenReturn(mockAst);
161             // This will re-enable walk(..., AstState.WITH_COMMENTS)
162             parser.when(() -> JavaParser.appendHiddenCommentNodes(mockAst)).thenReturn(realAst);
163 
164             verifyWithInlineConfigParserTwice(path, expected);
165         }
166     }
167 
168     /**
169      * This test is needed for 100% coverage. The method {@link Mockito#mockStatic} is used to
170      * ensure that the {@code if (!commentChecks.isEmpty())} condition cannot be removed.
171      */
172     @Test
173     public void testConditionRequiredWithoutCommentChecks() throws Exception {
174         final String[] expected = {
175             "10:1: " + getCheckMessage(OneTopLevelClassCheck.class,
176                     OneTopLevelClassCheck.MSG_KEY, "InputTreeWalkerInner"),
177         };
178         try (MockedStatic<JavaParser> parser =
179                      Mockito.mockStatic(JavaParser.class, CALLS_REAL_METHODS)) {
180             // Ensure that there is no calls to walk(..., AstState.WITH_COMMENTS)
181             parser.when(() -> JavaParser.appendHiddenCommentNodes(any(DetailAST.class)))
182                     .thenThrow(IllegalStateException.class);
183 
184             verifyWithInlineConfigParserTwice(getPath("InputTreeWalker.java"), expected);
185         }
186     }
187 
188     @Test
189     public void testImproperFileExtension() throws Exception {
190         final String regularFilePath = getPath("InputTreeWalkerImproperFileExtension.java");
191         final File originalFile = new File(regularFilePath);
192         final File tempFile = new File(temporaryFolder, "file.pdf");
193         Files.copy(originalFile.toPath(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
194         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
195         verifyWithInlineConfigParserTwice(tempFile.getPath(), expected);
196     }
197 
198     @Test
199     public void testAcceptableTokens()
200             throws Exception {
201         final DefaultConfiguration checkConfig =
202             createModuleConfig(HiddenFieldCheck.class);
203         checkConfig.addProperty("tokens", "VARIABLE_DEF, ENUM_DEF, CLASS_DEF, METHOD_DEF,"
204                 + "IMPORT");
205         try {
206             execute(checkConfig, getPath("InputTreeWalker.java"));
207             assertWithMessage("CheckstyleException is expected").fail();
208         }
209         catch (CheckstyleException exc) {
210             final String errorMsg = exc.getMessage();
211             final Pattern expected = Pattern.compile(Pattern.quote("cannot initialize module"
212                     + " com.puppycrawl.tools.checkstyle.TreeWalker - Token ")
213                     + "\"(ENUM_DEF|CLASS_DEF|METHOD_DEF|IMPORT)\""
214                     + Pattern.quote(" was not found in Acceptable tokens list in check"
215                     + " com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck"));
216 
217             final Matcher errorMsgMatcher = expected.matcher(errorMsg);
218             assertWithMessage("Failure for: " + errorMsg)
219                     .that(errorMsgMatcher.matches())
220                     .isTrue();
221         }
222     }
223 
224     @Test
225     public void testOnEmptyFile() throws Exception {
226         final DefaultConfiguration checkConfig = createModuleConfig(HiddenFieldCheck.class);
227         final String uniqueFileName = "file_" + UUID.randomUUID() + ".java";
228         final File emptyFile = new File(temporaryFolder, uniqueFileName);
229         emptyFile.createNewFile();
230         execute(checkConfig, emptyFile.getPath());
231         final long fileSize = Files.size(emptyFile.toPath());
232         assertWithMessage("File should be empty")
233                 .that(fileSize)
234                 .isEqualTo(0);
235     }
236 
237     @Test
238     public void testWithCheckNotHavingTreeWalkerAsParent() throws Exception {
239         final DefaultConfiguration checkConfig = createModuleConfig(JavadocPackageCheck.class);
240 
241         try {
242             final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
243             final File filePath = new File(temporaryFolder, uniqueFileName);
244             execute(createTreeWalkerConfig(checkConfig), filePath.toString());
245             assertWithMessage("CheckstyleException is expected").fail();
246         }
247         catch (CheckstyleException exception) {
248             assertWithMessage("Error message is unexpected")
249                     .that(exception.getMessage())
250                     .contains("TreeWalker is not allowed as a parent of");
251         }
252     }
253 
254     @Test
255     public void testSetupChildExceptions() {
256         final TreeWalker treeWalker = new TreeWalker();
257         final PackageObjectFactory factory = new PackageObjectFactory(
258                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
259         treeWalker.setModuleFactory(factory);
260 
261         final Configuration config = new DefaultConfiguration("java.lang.String");
262         try {
263             treeWalker.setupChild(config);
264             assertWithMessage("Exception is expected").fail();
265         }
266         catch (CheckstyleException exc) {
267             assertWithMessage("Error message is not expected")
268                 .that(exc.getMessage())
269                 .isEqualTo("TreeWalker is not allowed as a parent of java.lang.String "
270                     + "Please review 'Parent Module' section for this Check in "
271                     + "web documentation if Check is standard.");
272         }
273     }
274 
275     @Test
276     public void testSettersForParameters() throws Exception {
277         final TreeWalker treeWalker = new TreeWalker();
278         final DefaultConfiguration config = new DefaultConfiguration("default config");
279         treeWalker.setTabWidth(1);
280         treeWalker.configure(config);
281 
282         final int tabWidth = TestUtil.getInternalState(treeWalker, "tabWidth", Integer.class);
283         assertWithMessage("Invalid setter result")
284             .that(tabWidth)
285             .isEqualTo(1);
286         final Object configuration = TestUtil.getInternalState(treeWalker, "configuration",
287                 Object.class);
288         assertWithMessage("Invalid configuration")
289             .that(configuration)
290             .isEqualTo(config);
291     }
292 
293     @Test
294     public void testForInvalidCheckImplementation() throws Exception {
295         final DefaultConfiguration checkConfig = createModuleConfig(BadJavaDocCheck.class);
296         final String uniqueFileName = "file_" + UUID.randomUUID() + ".java";
297         final File pathToEmptyFile = new File(temporaryFolder, uniqueFileName);
298 
299         try {
300             execute(checkConfig, pathToEmptyFile.toString());
301             assertWithMessage("Exception is expected").fail();
302         }
303         catch (CheckstyleException exc) {
304             assertWithMessage("Error message is unexpected")
305                     .that(exc.getMessage())
306                     .isEqualTo("cannot initialize module com.puppycrawl.tools.checkstyle."
307                             + "TreeWalker - Check 'com.puppycrawl.tools.checkstyle."
308                             + "TreeWalkerTest$BadJavaDocCheck' waits for comment type token "
309                             + "('SINGLE_LINE_COMMENT') and should override "
310                             + "'isCommentNodesRequired()' method to return 'true'");
311             assertWithMessage("Error message is unexpected")
312                     .that(exc.getMessage())
313                     .contains("isCommentNodesRequired");
314         }
315     }
316 
317     @Test
318     public void testProcessNonJavaFiles() throws Exception {
319         final TreeWalker treeWalker = new TreeWalker();
320         final PackageObjectFactory factory = new PackageObjectFactory(
321             new HashSet<>(), Thread.currentThread().getContextClassLoader());
322         treeWalker.setModuleFactory(factory);
323         treeWalker.configure(new DefaultConfiguration("default config"));
324         final DefaultConfiguration childConfig = createModuleConfig(JavadocParagraphCheck.class);
325         treeWalker.setupChild(childConfig);
326         final File file = new File("input.java");
327         final List<String> lines =
328             new ArrayList<>(Arrays.asList("package com.puppycrawl.tools.checkstyle;", "",
329                 "error public class InputTreeWalkerFileWithViolation {}"));
330         final FileText fileText = new FileText(file, lines);
331         treeWalker.setFileContents(new FileContents(fileText));
332         try {
333             treeWalker.processFiltered(file, fileText);
334             assertWithMessage("Exception expected").fail();
335         }
336         catch (CheckstyleException exc) {
337             assertWithMessage("Invalid exception message")
338                 .that(exc.getMessage())
339                 .isEqualTo("IllegalStateException occurred while parsing file input.java.");
340         }
341     }
342 
343     @Test
344     public void testProcessNonJavaFilesWithoutException() throws Exception {
345         final TreeWalker treeWalker = new TreeWalker();
346         treeWalker.setTabWidth(1);
347         treeWalker.configure(new DefaultConfiguration("default config"));
348         final File file = new File(getPath("InputTreeWalkerNotJava.xml"));
349         final FileText fileText = new FileText(file, StandardCharsets.ISO_8859_1.name());
350         treeWalker.processFiltered(file, fileText);
351         final Collection<Checks> checks =
352                 TestUtil.getInternalStateCollectionChecks(treeWalker, "ordinaryChecks");
353         assertWithMessage("No checks -> No parsing")
354             .that(checks)
355             .isEmpty();
356     }
357 
358     @Test
359     public void testWithCacheWithNoViolation() throws Exception {
360         final String path = getPath("InputTreeWalkerWithCacheWithNoViolation.java");
361         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
362         verifyWithInlineConfigParserTwice(path, expected);
363     }
364 
365     @Test
366     public void testProcessWithParserThrowable() throws Exception {
367         final TreeWalker treeWalker = new TreeWalker();
368         treeWalker.configure(createModuleConfig(TypeNameCheck.class));
369         final PackageObjectFactory factory = new PackageObjectFactory(
370             new HashSet<>(), Thread.currentThread().getContextClassLoader());
371         treeWalker.setModuleFactory(factory);
372         treeWalker.setupChild(createModuleConfig(TypeNameCheck.class));
373         final File file = new File(temporaryFolder, "file.java");
374         final List<String> lines = new ArrayList<>();
375         lines.add(" classD a {} ");
376         final FileText fileText = new FileText(file, lines);
377         treeWalker.setFileContents(new FileContents(fileText));
378         try {
379             treeWalker.processFiltered(file, fileText);
380             assertWithMessage("Exception is expected").fail();
381         }
382         catch (CheckstyleException exception) {
383             assertWithMessage("Error message is unexpected")
384                     .that(exception.getMessage())
385                     .contains("occurred while parsing file");
386         }
387     }
388 
389     @Test
390     public void testProcessWithRecognitionException() throws Exception {
391         final TreeWalker treeWalker = new TreeWalker();
392         treeWalker.configure(createModuleConfig(TypeNameCheck.class));
393         final PackageObjectFactory factory = new PackageObjectFactory(
394             new HashSet<>(), Thread.currentThread().getContextClassLoader());
395         treeWalker.setModuleFactory(factory);
396         treeWalker.setupChild(createModuleConfig(TypeNameCheck.class));
397         final File file = new File(temporaryFolder, "file.java");
398         final List<String> lines = new ArrayList<>();
399         lines.add(" class a%$# {} ");
400         final FileText fileText = new FileText(file, lines);
401         treeWalker.setFileContents(new FileContents(fileText));
402         try {
403             treeWalker.processFiltered(file, fileText);
404             assertWithMessage("Exception is expected").fail();
405         }
406         catch (CheckstyleException exception) {
407             assertWithMessage("Error message is unexpected")
408                     .that(exception.getMessage())
409                     .contains("IllegalStateException occurred while parsing file");
410         }
411     }
412 
413     @Test
414     public void testRequiredTokenIsEmptyIntArray() throws Exception {
415         final File file = new File(temporaryFolder, "file.java");
416         try (Writer writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
417             final String configComment = """
418                     /*
419                     com.puppycrawl.tools.checkstyle.TreeWalkerTest\
420                     $RequiredTokenIsEmptyIntArray
421 
422                     */
423                     """;
424             writer.write(configComment);
425         }
426         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
427         verifyWithInlineConfigParserTwice(file.getPath(), expected);
428     }
429 
430     @Test
431     public void testBehaviourWithZeroChecks() throws Exception {
432         final TreeWalker treeWalker = new TreeWalker();
433         final PackageObjectFactory factory = new PackageObjectFactory(
434                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
435         treeWalker.setModuleFactory(factory);
436         // create file that should throw exception
437         final File file = new File(temporaryFolder, "file.java");
438         final FileText fileText = new FileText(file, new ArrayList<>());
439 
440         treeWalker.processFiltered(file, fileText);
441         final Collection<Checks> checks =
442                 TestUtil.getInternalStateCollectionChecks(treeWalker, "ordinaryChecks");
443         assertWithMessage("No checks -> No parsing")
444             .that(checks)
445             .isEmpty();
446     }
447 
448     @Test
449     public void testBehaviourWithOrdinaryAndCommentChecks() throws Exception {
450         final TreeWalker treeWalker = new TreeWalker();
451         treeWalker.configure(createModuleConfig(TypeNameCheck.class));
452         treeWalker.configure(createModuleConfig(CommentsIndentationCheck.class));
453         final PackageObjectFactory factory = new PackageObjectFactory(
454                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
455         treeWalker.setModuleFactory(factory);
456         treeWalker.setupChild(createModuleConfig(TypeNameCheck.class));
457         treeWalker.setupChild(createModuleConfig(CommentsIndentationCheck.class));
458         final File file = new File(temporaryFolder, "file.java");
459         final List<String> lines = new ArrayList<>();
460         lines.add(" class a%$# {} ");
461         final FileText fileText = new FileText(file, lines);
462         treeWalker.setFileContents(new FileContents(fileText));
463 
464         try {
465             treeWalker.processFiltered(file, fileText);
466             assertWithMessage("file is not compilable, exception is expected").fail();
467         }
468         catch (CheckstyleException exception) {
469             final String message = "IllegalStateException occurred while parsing file";
470             assertWithMessage("Error message is unexpected")
471                     .that(exception.getMessage())
472                     .contains(message);
473         }
474     }
475 
476     @Test
477     public void testSetupChild() throws Exception {
478         final TreeWalker treeWalker = new TreeWalker();
479         final PackageObjectFactory factory = new PackageObjectFactory(
480                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
481         treeWalker.setModuleFactory(factory);
482         treeWalker.setTabWidth(99);
483         treeWalker.finishLocalSetup();
484 
485         final Configuration config = new DefaultConfiguration(
486                 XpathFileGeneratorAstFilter.class.getName());
487 
488         treeWalker.setupChild(config);
489 
490         final Set<TreeWalkerFilter> filters =
491                 TestUtil.getInternalStateSetTreeWalkerFilter(treeWalker, "filters");
492         final int tabWidth = TestUtil.getInternalState(filters.iterator().next(),
493                 "tabWidth", Integer.class);
494 
495         assertWithMessage("expected tab width")
496             .that(tabWidth)
497             .isEqualTo(99);
498     }
499 
500     @Test
501     public void testBehaviourWithChecksAndFilters() throws Exception {
502 
503         final String[] expected = {
504             "17:17: " + getCheckMessage(MemberNameCheck.class, "name.invalidPattern", "P",
505                     "^[a-z][a-zA-Z0-9]*$"),
506             "12:17: " + getCheckMessage(MemberNameCheck.class, "name.invalidPattern", "I",
507                     "^[a-z][a-zA-Z0-9]*$"),
508         };
509 
510         verifyWithInlineConfigParserTwice(
511                 getPath("InputTreeWalkerSuppressionCommentFilter.java"),
512                 expected);
513     }
514 
515     @Test
516     public void testMultiCheckOrder() throws Exception {
517 
518         final String[] expected = {
519             "13:9: " + getCheckMessage(WhitespaceAfterCheck.class, "ws.notFollowed", "if"),
520             "13:9: " + getCheckMessage(WhitespaceAroundCheck.class, "ws.notFollowed", "if"),
521         };
522 
523         verifyWithInlineConfigParserTwice(
524                 getPath("InputTreeWalkerMultiCheckOrder.java"),
525                 expected);
526     }
527 
528     @Test
529     public void testMultiCheckOfSameTypeNoIdResultsInOrderingByHash() throws Exception {
530 
531         final String[] expected = {
532             "15:28: " + getCheckMessage(ParameterNameCheck.class,
533                     "name.invalidPattern", "V2", "^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"),
534             "17:25: " + getCheckMessage(ParameterNameCheck.class,
535                     "name.invalidPattern", "b", "^[a-z][a-z0-9][a-zA-Z0-9]*$"),
536         };
537 
538         verifyWithInlineConfigParserTwice(
539                 getPath("InputTreeWalkerMultiCheckOrder2.java"),
540                 expected);
541     }
542 
543     @Test
544     public void testFinishLocalSetupFullyInitialized() {
545         final TreeWalker treeWalker = new TreeWalker();
546         treeWalker.setSeverity("error");
547         treeWalker.setTabWidth(100);
548         treeWalker.finishLocalSetup();
549 
550         final Context context = TestUtil.getInternalState(treeWalker, "childContext",
551                 Context.class);
552         assertWithMessage("Severity differs from expected")
553             .that(context.get("severity"))
554             .isEqualTo("error");
555         assertWithMessage("Tab width differs from expected")
556             .that(context.get("tabWidth"))
557             .isEqualTo(String.valueOf(100));
558     }
559 
560     @Test
561     public void testCheckInitIsCalledInTreeWalker() throws Exception {
562         final DefaultConfiguration checkConfig =
563                 createModuleConfig(VerifyInitCheck.class);
564         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
565         final File file = new File(temporaryFolder, uniqueFileName);
566         execute(checkConfig, file.getPath());
567         assertWithMessage("Init was not called")
568                 .that(VerifyInitCheck.isInitWasCalled())
569                 .isTrue();
570     }
571 
572     @Test
573     public void testCheckDestroyIsCalledInTreeWalker() throws Exception {
574         VerifyDestroyCheck.resetDestroyWasCalled();
575         final DefaultConfiguration checkConfig =
576                 createModuleConfig(VerifyDestroyCheck.class);
577         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
578         final File file = new File(temporaryFolder, uniqueFileName);
579         execute(checkConfig, file.getPath());
580         assertWithMessage("Destroy was not called")
581                 .that(VerifyDestroyCheck.isDestroyWasCalled())
582                 .isTrue();
583     }
584 
585     @Test
586     public void testCommentCheckDestroyIsCalledInTreeWalker() throws Exception {
587         VerifyDestroyCheck.resetDestroyWasCalled();
588         final DefaultConfiguration checkConfig =
589                 createModuleConfig(VerifyDestroyCommentCheck.class);
590         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
591         final File file = new File(temporaryFolder, uniqueFileName);
592         execute(checkConfig, file.getPath());
593         assertWithMessage("Destroy was not called")
594                 .that(VerifyDestroyCheck.isDestroyWasCalled())
595                 .isTrue();
596     }
597 
598     @Test
599     public void testCacheWhenFileExternalResourceContentDoesNotChange() throws Exception {
600         final DefaultConfiguration filterConfig = createModuleConfig(SuppressionXpathFilter.class);
601         filterConfig.addProperty("file", getPath("InputTreeWalkerSuppressionXpathFilter.xml"));
602         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
603         treeWalkerConfig.addChild(filterConfig);
604 
605         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
606         final String uniqueFileName1 = "junit_" + UUID.randomUUID() + ".java";
607         final File cacheFile = new File(temporaryFolder, uniqueFileName1);
608         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
609 
610         final String uniqueFileName2 = "file_" + UUID.randomUUID() + ".java";
611         final File filePath = new File(temporaryFolder, uniqueFileName2);
612 
613         execute(checkerConfig, filePath.toString());
614         // One more time to use cache.
615         execute(checkerConfig, filePath.toString());
616 
617         assertWithMessage("External resource is not present in cache")
618                 .that(Files.readString(cacheFile.toPath()))
619                 .contains("InputTreeWalkerSuppressionXpathFilter.xml");
620     }
621 
622     @Test
623     public void testTreeWalkerFilterAbsolutePath() throws Exception {
624         // test is only valid when relative paths are given
625         final String filePath = "src/test/resources/" + getPackageLocation()
626                 + "/InputTreeWalkerSuppressionXpathFilterAbsolute.java";
627 
628         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
629         verifyWithInlineConfigParserTwice(filePath, expected);
630     }
631 
632     @Test
633     public void testExternalResourceFiltersWithNoExternalResource() throws Exception {
634         final DefaultConfiguration checkConfig = createModuleConfig(EmptyStatementCheck.class);
635         final DefaultConfiguration filterConfig =
636                 createModuleConfig(SuppressWithNearbyCommentFilter.class);
637         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
638         treeWalkerConfig.addChild(checkConfig);
639         treeWalkerConfig.addChild(filterConfig);
640 
641         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
642         final String uniqueFileName1 = "junit_" + UUID.randomUUID() + ".java";
643         final File cacheFile = new File(temporaryFolder, uniqueFileName1);
644         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
645         final String uniqueFileName2 = "junit_" + UUID.randomUUID() + ".java";
646         final File filePath = new File(temporaryFolder, uniqueFileName2);
647 
648         execute(checkerConfig, filePath.toString());
649 
650         final long cacheSize = Files.size(cacheFile.toPath());
651         assertWithMessage("cacheFile should not be empty")
652                 .that(cacheSize)
653                 .isNotEqualTo(0);
654     }
655 
656     /**
657      * This test is checking that Checks execution ordered by name.
658      *
659      * @throws Exception if file is not found
660      */
661     @Test
662     public void testOrderOfCheckExecution() throws Exception {
663 
664         final DefaultConfiguration configuration1 = createModuleConfig(AaCheck.class);
665         configuration1.addProperty("id", "2");
666         final DefaultConfiguration configuration2 = createModuleConfig(BbCheck.class);
667         configuration2.addProperty("id", "1");
668 
669         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
670         treeWalkerConfig.addChild(configuration2);
671         treeWalkerConfig.addChild(configuration1);
672 
673         final List<File> files =
674                 Collections.singletonList(new File(getPath("InputTreeWalker2.java")));
675         final Checker checker = createChecker(treeWalkerConfig);
676 
677         try {
678             checker.process(files);
679             assertWithMessage("exception is expected").fail();
680         }
681         catch (CheckstyleException exception) {
682             assertWithMessage("wrong order of Check executions")
683                     .that(exception.getCause().getMessage())
684                     .isEqualTo(AaCheck.class.toString());
685         }
686     }
687 
688     @Test
689     public void testSkipFileOnJavaParseExceptionTrue() throws Exception {
690         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
691         config.addProperty("skipFileOnJavaParseException", "true");
692         config.addProperty("javaParseExceptionSeverity", "ignore");
693         config.addChild(createModuleConfig(ConstantNameCheck.class));
694 
695         final File[] files = {
696             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
697             new File(getPath("InputTreeWalkerProperFileExtension.java")),
698             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException2.java")),
699         };
700 
701         final Checker checker = createChecker(config);
702         final Map<String, List<String>> expectedViolation = new HashMap<>();
703         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
704                 Collections.singletonList(
705                         "10:27: " + getCheckMessage(ConstantNameCheck.class,
706                         MSG_INVALID_PATTERN, "k", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$")));
707         verify(checker, files, expectedViolation);
708     }
709 
710     /**
711      * This test method is not using the regular {@code verify} methods
712      * because it is testing the behavior of {@code TreeWalker} when it should
713      * not skip files with parse exception. Instead, it should throw {@code exception}.
714      * The test verifies this behavior by attempting to process files
715      * that are known to cause a Java parse exception and asserting that
716      * the expected {@code exception} is indeed thrown.
717      *
718      */
719     @Test
720     public void testSkipFileOnJavaParseExceptionFalse() throws Exception {
721         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
722         config.addProperty("skipFileOnJavaParseException", "false");
723         config.addChild(createModuleConfig(ConstantNameCheck.class));
724 
725         final String[] files = {
726             getNonCompilablePath("InputTreeWalkerSkipParsingException2.java"),
727             getPath("InputTreeWalkerProperFileExtension.java"),
728             getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
729         };
730         final Exception ex = TestUtil.getExpectedThrowable(CheckstyleException.class,
731                 () -> execute(config, files),
732                 "Exception is expected");
733         assertWithMessage("Error message is unexpected")
734                 .that(ex.getMessage())
735                 .contains("Exception was thrown while processing");
736     }
737 
738     @Test
739     public void testSkipFileOnJavaParseExceptionConfigSeverityIgnore() throws Exception {
740         final String path =
741                 getNonCompilablePath(
742                         "InputTreeWalkerSkipParsingExceptionConfigSeverityIgnore.java");
743         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
744         verifyWithInlineXmlConfig(path, expected);
745     }
746 
747     @Test
748     public void testSkipFileOnJavaParseExceptionConfigSeverityDefault() throws Exception {
749         final String path =
750                 getNonCompilablePath(
751                         "InputTreeWalkerSkipParsingExceptionConfigSeverityDefault.java");
752         final String[] expected = {
753             "1: " + getCheckMessage(TreeWalker.PARSE_EXCEPTION_MSG, "IllegalStateException")
754                   + " occurred while parsing file " + path + ".",
755         };
756         verifyWithInlineXmlConfig(path, expected);
757     }
758 
759     @Test
760     public void testSkipFileOnJavaParseExceptionSkipChecks() throws Exception {
761         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
762         config.addProperty("skipFileOnJavaParseException", "true");
763         config.addProperty("javaParseExceptionSeverity", "ignore");
764         config.addChild(createModuleConfig(NoCodeInFileCheck.class));
765 
766         final Checker checker = createChecker(config);
767 
768         final File[] files = {
769             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
770             new File(getPath("InputTreeWalkerProperFileExtension.java")),
771             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException2.java")),
772         };
773         final Map<String, List<String>> expectedViolation = new HashMap<>();
774         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
775                 new ArrayList<>());
776 
777         verify(checker, files, expectedViolation);
778     }
779 
780     @Test
781     public void testJavaParseExceptionSeverityDefaultError() throws Exception {
782         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
783         config.addProperty("skipFileOnJavaParseException", "true");
784         config.addChild(createModuleConfig(NoCodeInFileCheck.class));
785 
786         final Checker checker = createChecker(config);
787 
788         final File[] files = {
789             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
790             new File(getPath("InputTreeWalkerProperFileExtension.java")),
791         };
792 
793         final Map<String, List<String>> expectedViolation = new HashMap<>();
794 
795         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
796                 new ArrayList<>());
797         expectedViolation.put(getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
798                 List.of("1: Java specific (TreeWalker-based) modules are skipped due to an "
799                         + "exception during parsing - "
800                         + "IllegalStateException occurred while parsing file "
801                         + getNonCompilablePath("InputTreeWalkerSkipParsingException.java") + "."));
802 
803         verify(checker, files, expectedViolation);
804     }
805 
806     public static class BadJavaDocCheck extends AbstractCheck {
807 
808         @Override
809         public int[] getDefaultTokens() {
810             return getAcceptableTokens();
811         }
812 
813         @Override
814         public int[] getAcceptableTokens() {
815             return new int[] {TokenTypes.SINGLE_LINE_COMMENT};
816         }
817 
818         @Override
819         public int[] getRequiredTokens() {
820             return getAcceptableTokens();
821         }
822 
823     }
824 
825     public static class VerifyInitCheck extends AbstractCheck {
826 
827         private static boolean initWasCalled;
828 
829         @Override
830         public int[] getDefaultTokens() {
831             return CommonUtil.EMPTY_INT_ARRAY;
832         }
833 
834         @Override
835         public int[] getAcceptableTokens() {
836             return getDefaultTokens();
837         }
838 
839         @Override
840         public int[] getRequiredTokens() {
841             return getDefaultTokens();
842         }
843 
844         @Override
845         public void init() {
846             super.init();
847             initWasCalled = true;
848         }
849 
850         public static boolean isInitWasCalled() {
851             return initWasCalled;
852         }
853 
854     }
855 
856     public static class VerifyDestroyCheck extends AbstractCheck {
857 
858         private static boolean destroyWasCalled;
859 
860         @Override
861         public int[] getDefaultTokens() {
862             return CommonUtil.EMPTY_INT_ARRAY;
863         }
864 
865         @Override
866         public int[] getAcceptableTokens() {
867             return getDefaultTokens();
868         }
869 
870         @Override
871         public int[] getRequiredTokens() {
872             return getDefaultTokens();
873         }
874 
875         @Override
876         public void destroy() {
877             super.destroy();
878             destroyWasCalled = true;
879         }
880 
881         public static void resetDestroyWasCalled() {
882             destroyWasCalled = false;
883         }
884 
885         public static boolean isDestroyWasCalled() {
886             return destroyWasCalled;
887         }
888 
889     }
890 
891     public static class VerifyDestroyCommentCheck extends VerifyDestroyCheck {
892 
893         @Override
894         public boolean isCommentNodesRequired() {
895             return true;
896         }
897 
898     }
899 
900     public static class AaCheck extends AbstractCheck {
901 
902         @Override
903         public int[] getDefaultTokens() {
904             return new int[0];
905         }
906 
907         @Override
908         public int[] getAcceptableTokens() {
909             return new int[0];
910         }
911 
912         @Override
913         public int[] getRequiredTokens() {
914             return new int[0];
915         }
916 
917         @Override
918         public void beginTree(DetailAST rootAST) {
919             throw new IllegalStateException(AaCheck.class.toString());
920         }
921 
922     }
923 
924     public static class BbCheck extends AbstractCheck {
925 
926         @Override
927         public int[] getDefaultTokens() {
928             return new int[0];
929         }
930 
931         @Override
932         public int[] getAcceptableTokens() {
933             return new int[0];
934         }
935 
936         @Override
937         public int[] getRequiredTokens() {
938             return new int[0];
939         }
940 
941         @Override
942         public void beginTree(DetailAST rootAST) {
943             throw new IllegalStateException(BbCheck.class.toString());
944         }
945 
946     }
947 
948     public static class RequiredTokenIsEmptyIntArray extends AbstractCheck {
949 
950         @Override
951         public int[] getRequiredTokens() {
952             return CommonUtil.EMPTY_INT_ARRAY;
953         }
954 
955         @Override
956         public int[] getDefaultTokens() {
957             return new int[] {TokenTypes.ANNOTATION};
958         }
959 
960         @Override
961         public int[] getAcceptableTokens() {
962             return CommonUtil.EMPTY_INT_ARRAY;
963         }
964 
965     }
966 
967 }