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