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                     /*
420                     com.puppycrawl.tools.checkstyle.TreeWalkerTest\
421                     $RequiredTokenIsEmptyIntArray
422 
423                     */
424                     """;
425             writer.write(configComment);
426         }
427         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
428         verifyWithInlineConfigParserTwice(file.getPath(), expected);
429     }
430 
431     @Test
432     public void testBehaviourWithZeroChecks() throws Exception {
433         final TreeWalker treeWalker = new TreeWalker();
434         final PackageObjectFactory factory = new PackageObjectFactory(
435                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
436         treeWalker.setModuleFactory(factory);
437         // create file that should throw exception
438         final File file = new File(temporaryFolder, "file.java");
439         final FileText fileText = new FileText(file, new ArrayList<>());
440 
441         treeWalker.processFiltered(file, fileText);
442         final Collection<Checks> checks =
443                 TestUtil.getInternalStateCollectionChecks(treeWalker, "ordinaryChecks");
444         assertWithMessage("No checks -> No parsing")
445             .that(checks)
446             .isEmpty();
447     }
448 
449     @Test
450     public void testBehaviourWithOrdinaryAndCommentChecks() throws Exception {
451         final TreeWalker treeWalker = new TreeWalker();
452         treeWalker.configure(createModuleConfig(TypeNameCheck.class));
453         treeWalker.configure(createModuleConfig(CommentsIndentationCheck.class));
454         final PackageObjectFactory factory = new PackageObjectFactory(
455                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
456         treeWalker.setModuleFactory(factory);
457         treeWalker.setupChild(createModuleConfig(TypeNameCheck.class));
458         treeWalker.setupChild(createModuleConfig(CommentsIndentationCheck.class));
459         final File file = new File(temporaryFolder, "file.java");
460         final List<String> lines = new ArrayList<>();
461         lines.add(" class a%$# {} ");
462         final FileText fileText = new FileText(file, lines);
463         treeWalker.setFileContents(new FileContents(fileText));
464 
465         try {
466             treeWalker.processFiltered(file, fileText);
467             assertWithMessage("file is not compilable, exception is expected").fail();
468         }
469         catch (CheckstyleException exception) {
470             final String message = "IllegalStateException occurred while parsing file";
471             assertWithMessage("Error message is unexpected")
472                     .that(exception.getMessage())
473                     .contains(message);
474         }
475     }
476 
477     @Test
478     public void testSetupChild() throws Exception {
479         final TreeWalker treeWalker = new TreeWalker();
480         final PackageObjectFactory factory = new PackageObjectFactory(
481                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
482         treeWalker.setModuleFactory(factory);
483         treeWalker.setTabWidth(99);
484         treeWalker.finishLocalSetup();
485 
486         final Configuration config = new DefaultConfiguration(
487                 XpathFileGeneratorAstFilter.class.getName());
488 
489         treeWalker.setupChild(config);
490 
491         final Set<TreeWalkerFilter> filters =
492                 TestUtil.getInternalStateSetTreeWalkerFilter(treeWalker, "filters");
493         final int tabWidth = TestUtil.getInternalState(filters.iterator().next(),
494                 "tabWidth", Integer.class);
495 
496         assertWithMessage("expected tab width")
497             .that(tabWidth)
498             .isEqualTo(99);
499     }
500 
501     @Test
502     public void testBehaviourWithChecksAndFilters() throws Exception {
503 
504         final String[] expected = {
505             "17:17: " + getCheckMessage(MemberNameCheck.class, "name.invalidPattern", "P",
506                     "^[a-z][a-zA-Z0-9]*$"),
507             "12:17: " + getCheckMessage(MemberNameCheck.class, "name.invalidPattern", "I",
508                     "^[a-z][a-zA-Z0-9]*$"),
509         };
510 
511         verifyWithInlineConfigParserTwice(
512                 getPath("InputTreeWalkerSuppressionCommentFilter.java"),
513                 expected);
514     }
515 
516     @Test
517     public void testMultiCheckOrder() throws Exception {
518 
519         final String[] expected = {
520             "13:9: " + getCheckMessage(WhitespaceAfterCheck.class, "ws.notFollowed", "if"),
521             "13:9: " + getCheckMessage(WhitespaceAroundCheck.class, "ws.notFollowed", "if"),
522         };
523 
524         verifyWithInlineConfigParserTwice(
525                 getPath("InputTreeWalkerMultiCheckOrder.java"),
526                 expected);
527     }
528 
529     @Test
530     public void testMultiCheckOfSameTypeNoIdResultsInOrderingByHash() throws Exception {
531 
532         final String[] expected = {
533             "15:28: " + getCheckMessage(ParameterNameCheck.class,
534                     "name.invalidPattern", "V2", "^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"),
535             "17:25: " + getCheckMessage(ParameterNameCheck.class,
536                     "name.invalidPattern", "b", "^[a-z][a-z0-9][a-zA-Z0-9]*$"),
537         };
538 
539         verifyWithInlineConfigParserTwice(
540                 getPath("InputTreeWalkerMultiCheckOrder2.java"),
541                 expected);
542     }
543 
544     @Test
545     public void testFinishLocalSetupFullyInitialized() {
546         final TreeWalker treeWalker = new TreeWalker();
547         treeWalker.setSeverity("error");
548         treeWalker.setTabWidth(100);
549         treeWalker.finishLocalSetup();
550 
551         final Context context = TestUtil.getInternalState(treeWalker, "childContext",
552                 Context.class);
553         assertWithMessage("Severity differs from expected")
554             .that(context.get("severity"))
555             .isEqualTo("error");
556         assertWithMessage("Tab width differs from expected")
557             .that(context.get("tabWidth"))
558             .isEqualTo(String.valueOf(100));
559     }
560 
561     @Test
562     public void testCheckInitIsCalledInTreeWalker() throws Exception {
563         final DefaultConfiguration checkConfig =
564                 createModuleConfig(VerifyInitCheck.class);
565         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
566         final File file = new File(temporaryFolder, uniqueFileName);
567         execute(checkConfig, file.getPath());
568         assertWithMessage("Init was not called")
569                 .that(VerifyInitCheck.isInitWasCalled())
570                 .isTrue();
571     }
572 
573     @Test
574     public void testCheckDestroyIsCalledInTreeWalker() throws Exception {
575         VerifyDestroyCheck.resetDestroyWasCalled();
576         final DefaultConfiguration checkConfig =
577                 createModuleConfig(VerifyDestroyCheck.class);
578         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
579         final File file = new File(temporaryFolder, uniqueFileName);
580         execute(checkConfig, file.getPath());
581         assertWithMessage("Destroy was not called")
582                 .that(VerifyDestroyCheck.isDestroyWasCalled())
583                 .isTrue();
584     }
585 
586     @Test
587     public void testCommentCheckDestroyIsCalledInTreeWalker() throws Exception {
588         VerifyDestroyCheck.resetDestroyWasCalled();
589         final DefaultConfiguration checkConfig =
590                 createModuleConfig(VerifyDestroyCommentCheck.class);
591         final String uniqueFileName = "file_" + UUID.randomUUID() + ".pdf";
592         final File file = new File(temporaryFolder, uniqueFileName);
593         execute(checkConfig, file.getPath());
594         assertWithMessage("Destroy was not called")
595                 .that(VerifyDestroyCheck.isDestroyWasCalled())
596                 .isTrue();
597     }
598 
599     @Test
600     public void testCacheWhenFileExternalResourceContentDoesNotChange() throws Exception {
601         final DefaultConfiguration filterConfig = createModuleConfig(SuppressionXpathFilter.class);
602         filterConfig.addProperty("file", getPath("InputTreeWalkerSuppressionXpathFilter.xml"));
603         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
604         treeWalkerConfig.addChild(filterConfig);
605 
606         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
607         final String uniqueFileName1 = "junit_" + UUID.randomUUID() + ".java";
608         final File cacheFile = new File(temporaryFolder, uniqueFileName1);
609         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
610 
611         final String uniqueFileName2 = "file_" + UUID.randomUUID() + ".java";
612         final File filePath = new File(temporaryFolder, uniqueFileName2);
613 
614         execute(checkerConfig, filePath.toString());
615         // One more time to use cache.
616         execute(checkerConfig, filePath.toString());
617 
618         assertWithMessage("External resource is not present in cache")
619                 .that(Files.readString(cacheFile.toPath()))
620                 .contains("InputTreeWalkerSuppressionXpathFilter.xml");
621     }
622 
623     @Test
624     public void testTreeWalkerFilterAbsolutePath() throws Exception {
625         // test is only valid when relative paths are given
626         final String filePath = "src/test/resources/" + getPackageLocation()
627                 + "/InputTreeWalkerSuppressionXpathFilterAbsolute.java";
628 
629         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
630         verifyWithInlineConfigParserTwice(filePath, expected);
631     }
632 
633     @Test
634     public void testExternalResourceFiltersWithNoExternalResource() throws Exception {
635         final DefaultConfiguration checkConfig = createModuleConfig(EmptyStatementCheck.class);
636         final DefaultConfiguration filterConfig =
637                 createModuleConfig(SuppressWithNearbyCommentFilter.class);
638         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
639         treeWalkerConfig.addChild(checkConfig);
640         treeWalkerConfig.addChild(filterConfig);
641 
642         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
643         final String uniqueFileName1 = "junit_" + UUID.randomUUID() + ".java";
644         final File cacheFile = new File(temporaryFolder, uniqueFileName1);
645         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
646         final String uniqueFileName2 = "junit_" + UUID.randomUUID() + ".java";
647         final File filePath = new File(temporaryFolder, uniqueFileName2);
648 
649         execute(checkerConfig, filePath.toString());
650 
651         final long cacheSize = Files.size(cacheFile.toPath());
652         assertWithMessage("cacheFile should not be empty")
653                 .that(cacheSize)
654                 .isNotEqualTo(0);
655     }
656 
657     /**
658      * This test is checking that Checks execution ordered by name.
659      *
660      * @throws Exception if file is not found
661      */
662     @Test
663     public void testOrderOfCheckExecution() throws Exception {
664 
665         final DefaultConfiguration configuration1 = createModuleConfig(AaCheck.class);
666         configuration1.addProperty("id", "2");
667         final DefaultConfiguration configuration2 = createModuleConfig(BbCheck.class);
668         configuration2.addProperty("id", "1");
669 
670         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
671         treeWalkerConfig.addChild(configuration2);
672         treeWalkerConfig.addChild(configuration1);
673 
674         final List<File> files =
675                 Collections.singletonList(new File(getPath("InputTreeWalker2.java")));
676         final Checker checker = createChecker(treeWalkerConfig);
677 
678         try {
679             checker.process(files);
680             assertWithMessage("exception is expected").fail();
681         }
682         catch (CheckstyleException exception) {
683             assertWithMessage("wrong order of Check executions")
684                     .that(exception.getCause().getMessage())
685                     .isEqualTo(AaCheck.class.toString());
686         }
687     }
688 
689     @Test
690     public void testSkipFileOnJavaParseExceptionTrue() throws Exception {
691         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
692         config.addProperty("skipFileOnJavaParseException", "true");
693         config.addProperty("javaParseExceptionSeverity", "ignore");
694         config.addChild(createModuleConfig(ConstantNameCheck.class));
695 
696         final File[] files = {
697             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
698             new File(getPath("InputTreeWalkerProperFileExtension.java")),
699             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException2.java")),
700         };
701 
702         final Checker checker = createChecker(config);
703         final Map<String, List<String>> expectedViolation = new HashMap<>();
704         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
705                 Collections.singletonList(
706                         "10:27: " + getCheckMessage(ConstantNameCheck.class,
707                         MSG_INVALID_PATTERN, "k", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$")));
708         verify(checker, files, expectedViolation);
709     }
710 
711     /**
712      * This test method is not using the regular {@code verify} methods
713      * because it is testing the behavior of {@code TreeWalker} when it should
714      * not skip files with parse exception. Instead, it should throw {@code exception}.
715      * The test verifies this behavior by attempting to process files
716      * that are known to cause a Java parse exception and asserting that
717      * the expected {@code exception} is indeed thrown.
718      *
719      */
720     @Test
721     public void testSkipFileOnJavaParseExceptionFalse() throws Exception {
722         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
723         config.addProperty("skipFileOnJavaParseException", "false");
724         config.addChild(createModuleConfig(ConstantNameCheck.class));
725 
726         final String[] files = {
727             getNonCompilablePath("InputTreeWalkerSkipParsingException2.java"),
728             getPath("InputTreeWalkerProperFileExtension.java"),
729             getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
730         };
731         final Exception ex = TestUtil.getExpectedThrowable(CheckstyleException.class,
732                 () -> execute(config, files),
733                 "Exception is expected");
734         assertWithMessage("Error message is unexpected")
735                 .that(ex.getMessage())
736                 .contains("Exception was thrown while processing");
737     }
738 
739     @Test
740     public void testSkipFileOnJavaParseExceptionConfigSeverityIgnore() throws Exception {
741         final String path =
742                 getNonCompilablePath(
743                         "InputTreeWalkerSkipParsingExceptionConfigSeverityIgnore.java");
744         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
745         verifyWithInlineXmlConfig(path, expected);
746     }
747 
748     @Test
749     public void testSkipFileOnJavaParseExceptionConfigSeverityDefault() throws Exception {
750         final String path =
751                 getNonCompilablePath(
752                         "InputTreeWalkerSkipParsingExceptionConfigSeverityDefault.java");
753         final String[] expected = {
754             "1: " + getCheckMessage(TreeWalker.PARSE_EXCEPTION_MSG, "IllegalStateException")
755                   + " occurred while parsing file " + path + ".",
756         };
757         verifyWithInlineXmlConfig(path, expected);
758     }
759 
760     @Test
761     public void testSkipFileOnJavaParseExceptionSkipChecks() throws Exception {
762         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
763         config.addProperty("skipFileOnJavaParseException", "true");
764         config.addProperty("javaParseExceptionSeverity", "ignore");
765         config.addChild(createModuleConfig(NoCodeInFileCheck.class));
766 
767         final Checker checker = createChecker(config);
768 
769         final File[] files = {
770             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
771             new File(getPath("InputTreeWalkerProperFileExtension.java")),
772             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException2.java")),
773         };
774         final Map<String, List<String>> expectedViolation = new HashMap<>();
775         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
776                 new ArrayList<>());
777 
778         verify(checker, files, expectedViolation);
779     }
780 
781     @Test
782     public void testJavaParseExceptionSeverityDefaultError() throws Exception {
783         final DefaultConfiguration config = createModuleConfig(TreeWalker.class);
784         config.addProperty("skipFileOnJavaParseException", "true");
785         config.addChild(createModuleConfig(NoCodeInFileCheck.class));
786 
787         final Checker checker = createChecker(config);
788 
789         final File[] files = {
790             new File(getNonCompilablePath("InputTreeWalkerSkipParsingException.java")),
791             new File(getPath("InputTreeWalkerProperFileExtension.java")),
792         };
793 
794         final Map<String, List<String>> expectedViolation = new HashMap<>();
795 
796         expectedViolation.put(getPath("InputTreeWalkerProperFileExtension.java"),
797                 new ArrayList<>());
798         expectedViolation.put(getNonCompilablePath("InputTreeWalkerSkipParsingException.java"),
799                 List.of("1: Java specific (TreeWalker-based) modules are skipped due to an "
800                         + "exception during parsing - "
801                         + "IllegalStateException occurred while parsing file "
802                         + getNonCompilablePath("InputTreeWalkerSkipParsingException.java") + "."));
803 
804         verify(checker, files, expectedViolation);
805     }
806 
807     public static class BadJavaDocCheck extends AbstractCheck {
808 
809         @Override
810         public int[] getDefaultTokens() {
811             return getAcceptableTokens();
812         }
813 
814         @Override
815         public int[] getAcceptableTokens() {
816             return new int[] {TokenTypes.SINGLE_LINE_COMMENT};
817         }
818 
819         @Override
820         public int[] getRequiredTokens() {
821             return getAcceptableTokens();
822         }
823 
824     }
825 
826     public static class VerifyInitCheck extends AbstractCheck {
827 
828         private static boolean initWasCalled;
829 
830         @Override
831         public int[] getDefaultTokens() {
832             return CommonUtil.EMPTY_INT_ARRAY;
833         }
834 
835         @Override
836         public int[] getAcceptableTokens() {
837             return getDefaultTokens();
838         }
839 
840         @Override
841         public int[] getRequiredTokens() {
842             return getDefaultTokens();
843         }
844 
845         @Override
846         public void init() {
847             super.init();
848             initWasCalled = true;
849         }
850 
851         public static boolean isInitWasCalled() {
852             return initWasCalled;
853         }
854 
855     }
856 
857     public static class VerifyDestroyCheck extends AbstractCheck {
858 
859         private static boolean destroyWasCalled;
860 
861         @Override
862         public int[] getDefaultTokens() {
863             return CommonUtil.EMPTY_INT_ARRAY;
864         }
865 
866         @Override
867         public int[] getAcceptableTokens() {
868             return getDefaultTokens();
869         }
870 
871         @Override
872         public int[] getRequiredTokens() {
873             return getDefaultTokens();
874         }
875 
876         @Override
877         public void destroy() {
878             super.destroy();
879             destroyWasCalled = true;
880         }
881 
882         public static void resetDestroyWasCalled() {
883             destroyWasCalled = false;
884         }
885 
886         public static boolean isDestroyWasCalled() {
887             return destroyWasCalled;
888         }
889 
890     }
891 
892     public static class VerifyDestroyCommentCheck extends VerifyDestroyCheck {
893 
894         @Override
895         public boolean isCommentNodesRequired() {
896             return true;
897         }
898 
899     }
900 
901     public static class AaCheck extends AbstractCheck {
902 
903         @Override
904         public int[] getDefaultTokens() {
905             return new int[0];
906         }
907 
908         @Override
909         public int[] getAcceptableTokens() {
910             return new int[0];
911         }
912 
913         @Override
914         public int[] getRequiredTokens() {
915             return new int[0];
916         }
917 
918         @Override
919         public void beginTree(DetailAST rootAST) {
920             throw new IllegalStateException(AaCheck.class.toString());
921         }
922 
923     }
924 
925     public static class BbCheck extends AbstractCheck {
926 
927         @Override
928         public int[] getDefaultTokens() {
929             return new int[0];
930         }
931 
932         @Override
933         public int[] getAcceptableTokens() {
934             return new int[0];
935         }
936 
937         @Override
938         public int[] getRequiredTokens() {
939             return new int[0];
940         }
941 
942         @Override
943         public void beginTree(DetailAST rootAST) {
944             throw new IllegalStateException(BbCheck.class.toString());
945         }
946 
947     }
948 
949     public static class RequiredTokenIsEmptyIntArray extends AbstractCheck {
950 
951         @Override
952         public int[] getRequiredTokens() {
953             return CommonUtil.EMPTY_INT_ARRAY;
954         }
955 
956         @Override
957         public int[] getDefaultTokens() {
958             return new int[] {TokenTypes.ANNOTATION};
959         }
960 
961         @Override
962         public int[] getAcceptableTokens() {
963             return CommonUtil.EMPTY_INT_ARRAY;
964         }
965 
966     }
967 
968 }