View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  
24  import java.io.File;
25  import java.io.Writer;
26  import java.nio.file.Files;
27  import java.text.MessageFormat;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.BitSet;
31  import java.util.List;
32  import java.util.Locale;
33  import java.util.Set;
34  import java.util.function.Consumer;
35  
36  import org.antlr.v4.runtime.CommonToken;
37  import org.junit.jupiter.api.Test;
38  import org.junit.jupiter.api.io.TempDir;
39  
40  import com.puppycrawl.tools.checkstyle.api.DetailAST;
41  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
42  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
43  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
44  
45  /**
46   * TestCase to check DetailAST.
47   */
48  public class DetailAstImplTest extends AbstractModuleTestSupport {
49  
50      // Ignores file which are not meant to have root node intentionally.
51      public static final Set<String> NO_ROOT_FILES = Set.of(
52                   // fails with unexpected character
53                   "InputGrammar.java",
54                   // comment only files, no root
55                   "InputPackageDeclarationWithCommentOnly.java",
56                   "InputSingleSpaceSeparatorEmpty.java",
57                   "InputNoCodeInFile1.java",
58                   "InputNoCodeInFile2.java",
59                   "InputNoCodeInFile3.java",
60                   "InputNoCodeInFile5.java",
61                   "InputOuterTypeFilenameEmpty.java",
62                   "InputOneTopLevelClassEmpty.java",
63                   "InputMethodCountEmpty.java",
64                   "InputDeclarationOrderEmpty.java"
65          );
66  
67      @TempDir
68      public File temporaryFolder;
69  
70      @Override
71      public String getPackageLocation() {
72          return "com/puppycrawl/tools/checkstyle/api/detailast";
73      }
74  
75      private static void invokeSetParentMethod(DetailAST instance, DetailAstImpl parent)
76              throws Exception {
77          TestUtil.invokeVoidMethod(instance, "setParent", parent);
78      }
79  
80      @Test
81      public void testInitialize() {
82          final DetailAstImpl ast = new DetailAstImpl();
83          ast.setText("test");
84          ast.setType(1);
85          ast.setLineNo(2);
86          ast.setColumnNo(3);
87  
88          final DetailAstImpl copy = new DetailAstImpl();
89          copy.setText(ast.getText());
90          copy.setType(ast.getType());
91          copy.setLineNo(ast.getLineNo());
92          copy.setColumnNo(ast.getColumnNo());
93  
94          assertWithMessage("Invalid text")
95              .that(copy.getText())
96              .isEqualTo("test");
97          assertWithMessage("Invalid type")
98              .that(copy.getType())
99              .isEqualTo(1);
100         assertWithMessage("Invalid line number")
101             .that(copy.getLineNo())
102             .isEqualTo(2);
103         assertWithMessage("Invalid column number")
104             .that(copy.getColumnNo())
105             .isEqualTo(3);
106     }
107 
108     @Test
109     public void testInitializeToken() {
110         final CommonToken token = new CommonToken(1);
111         token.setText("test");
112         token.setLine(2);
113         token.setCharPositionInLine(3);
114 
115         final DetailAstImpl ast = new DetailAstImpl();
116         ast.initialize(token);
117 
118         assertWithMessage("Invalid text")
119             .that(ast.getText())
120             .isEqualTo("test");
121         assertWithMessage("Invalid type")
122             .that(ast.getType())
123             .isEqualTo(1);
124         assertWithMessage("Invalid line number")
125             .that(ast.getLineNo())
126             .isEqualTo(2);
127         assertWithMessage("Invalid column number")
128             .that(ast.getColumnNo())
129             .isEqualTo(3);
130     }
131 
132     @Test
133     public void testGetChildCount() throws Exception {
134         final DetailAstImpl root = new DetailAstImpl();
135         final DetailAstImpl firstLevelA = new DetailAstImpl();
136         final DetailAstImpl firstLevelB = new DetailAstImpl();
137         final DetailAstImpl secondLevelA = new DetailAstImpl();
138 
139         root.setFirstChild(firstLevelA);
140 
141         invokeSetParentMethod(firstLevelA, root);
142         firstLevelA.setFirstChild(secondLevelA);
143         firstLevelA.setNextSibling(firstLevelB);
144 
145         invokeSetParentMethod(firstLevelB, root);
146 
147         invokeSetParentMethod(secondLevelA, root);
148 
149         assertWithMessage("Invalid child count")
150             .that(secondLevelA.getChildCount())
151             .isEqualTo(0);
152         assertWithMessage("Invalid child count")
153             .that(firstLevelB.getChildCount())
154             .isEqualTo(0);
155         assertWithMessage("Invalid child count")
156             .that(firstLevelA.getChildCount())
157             .isEqualTo(1);
158         assertWithMessage("Invalid child count")
159             .that(root.getChildCount())
160             .isEqualTo(2);
161         assertWithMessage("Invalid child count")
162             .that(root.getNumberOfChildren())
163             .isEqualTo(2);
164 
165         assertWithMessage("Previous sibling should be null")
166             .that(root.getPreviousSibling())
167             .isNull();
168         assertWithMessage("Previous sibling should be null")
169             .that(firstLevelA.getPreviousSibling())
170             .isNull();
171         assertWithMessage("Previous sibling should be null")
172             .that(secondLevelA.getPreviousSibling())
173             .isNull();
174         assertWithMessage("Invalid previous sibling")
175             .that(firstLevelB.getPreviousSibling())
176             .isEqualTo(firstLevelA);
177     }
178 
179     @Test
180     public void testHasChildren() {
181         final DetailAstImpl root = new DetailAstImpl();
182         final DetailAstImpl child = new DetailAstImpl();
183         root.setFirstChild(child);
184 
185         assertWithMessage("Root node should have children")
186                 .that(root.hasChildren())
187                 .isTrue();
188         assertWithMessage("Child node should have no children")
189                 .that(child.hasChildren())
190                 .isFalse();
191     }
192 
193     @Test
194     public void testGetChildCountType() throws Exception {
195         final DetailAstImpl root = new DetailAstImpl();
196         final DetailAstImpl firstLevelA = new DetailAstImpl();
197         final DetailAstImpl firstLevelB = new DetailAstImpl();
198 
199         root.setFirstChild(firstLevelA);
200 
201         invokeSetParentMethod(firstLevelA, root);
202         firstLevelA.setNextSibling(firstLevelB);
203 
204         firstLevelA.setType(TokenTypes.IDENT);
205         firstLevelB.setType(TokenTypes.EXPR);
206 
207         invokeSetParentMethod(firstLevelB, root);
208 
209         final int childCountLevelB = firstLevelB.getChildCount(0);
210         assertWithMessage("Invalid child count")
211             .that(childCountLevelB)
212             .isEqualTo(0);
213         final int childCountLevelA = firstLevelA.getChildCount(TokenTypes.EXPR);
214         assertWithMessage("Invalid child count")
215             .that(childCountLevelA)
216             .isEqualTo(0);
217         final int identTypeCount = root.getChildCount(TokenTypes.IDENT);
218         assertWithMessage("Invalid child count")
219             .that(identTypeCount)
220             .isEqualTo(1);
221         final int exprTypeCount = root.getChildCount(TokenTypes.EXPR);
222         assertWithMessage("Invalid child count")
223             .that(exprTypeCount)
224             .isEqualTo(1);
225         final int invalidTypeCount = root.getChildCount(0);
226         assertWithMessage("Invalid child count")
227             .that(invalidTypeCount)
228             .isEqualTo(0);
229     }
230 
231     @Test
232     public void testSetSiblingNull() throws Exception {
233         final DetailAstImpl root = new DetailAstImpl();
234         final DetailAstImpl firstLevelA = new DetailAstImpl();
235 
236         root.setFirstChild(firstLevelA);
237 
238         assertWithMessage("Invalid child count")
239             .that(root.getChildCount())
240             .isEqualTo(1);
241 
242         invokeSetParentMethod(firstLevelA, root);
243         firstLevelA.addPreviousSibling(null);
244         firstLevelA.addNextSibling(null);
245 
246         assertWithMessage("Invalid child count")
247             .that(root.getChildCount())
248             .isEqualTo(1);
249     }
250 
251     @Test
252     public void testAddPreviousSibling() {
253         final DetailAST previousSibling = new DetailAstImpl();
254         final DetailAstImpl instance = new DetailAstImpl();
255         final DetailAstImpl parent = new DetailAstImpl();
256 
257         parent.setFirstChild(instance);
258 
259         instance.addPreviousSibling(previousSibling);
260 
261         assertWithMessage("unexpected result")
262             .that(instance.getPreviousSibling())
263             .isEqualTo(previousSibling);
264         assertWithMessage("unexpected result")
265             .that(parent.getFirstChild())
266             .isEqualTo(previousSibling);
267 
268         final DetailAST newPreviousSibling = new DetailAstImpl();
269 
270         instance.addPreviousSibling(newPreviousSibling);
271 
272         assertWithMessage("unexpected result")
273             .that(instance.getPreviousSibling())
274             .isEqualTo(newPreviousSibling);
275         assertWithMessage("unexpected result")
276             .that(newPreviousSibling.getPreviousSibling())
277             .isEqualTo(previousSibling);
278         assertWithMessage("unexpected result")
279             .that(previousSibling.getNextSibling())
280             .isEqualTo(newPreviousSibling);
281         assertWithMessage("unexpected result")
282             .that(parent.getFirstChild())
283             .isEqualTo(previousSibling);
284 
285         final DetailAstImpl secondNewPreviousSibling = new DetailAstImpl();
286         instance.addPreviousSibling(secondNewPreviousSibling);
287         assertWithMessage("unexpected result")
288                 .that(secondNewPreviousSibling.getPreviousSibling())
289                 .isEqualTo(newPreviousSibling);
290         assertWithMessage("unexpected result")
291                 .that(secondNewPreviousSibling.getNextSibling())
292                 .isEqualTo(instance);
293         assertWithMessage("unexpected result")
294                 .that(newPreviousSibling.getNextSibling())
295                 .isEqualTo(secondNewPreviousSibling);
296         assertWithMessage("unexpected result")
297                 .that(secondNewPreviousSibling.getPreviousSibling().getPreviousSibling())
298                 .isEqualTo(previousSibling);
299         assertWithMessage("unexpected result")
300                 .that(instance.getPreviousSibling().getPreviousSibling().getPreviousSibling())
301                 .isEqualTo(previousSibling);
302     }
303 
304     @Test
305     public void testAddPreviousSiblingNullParent() {
306         final DetailAstImpl child = new DetailAstImpl();
307         final DetailAST newSibling = new DetailAstImpl();
308 
309         child.addPreviousSibling(newSibling);
310 
311         assertWithMessage("Invalid child token")
312             .that(newSibling.getNextSibling())
313             .isEqualTo(child);
314         assertWithMessage("Invalid child token")
315             .that(child.getPreviousSibling())
316             .isEqualTo(newSibling);
317     }
318 
319     @Test
320     public void testInsertSiblingBetween() throws Exception {
321         final DetailAstImpl root = new DetailAstImpl();
322         final DetailAstImpl firstLevelA = new DetailAstImpl();
323         final DetailAST firstLevelB = new DetailAstImpl();
324         final DetailAST firstLevelC = new DetailAstImpl();
325 
326         assertWithMessage("Invalid child count")
327             .that(root.getChildCount())
328             .isEqualTo(0);
329 
330         root.setFirstChild(firstLevelA);
331         invokeSetParentMethod(firstLevelA, root);
332 
333         assertWithMessage("Invalid child count")
334             .that(root.getChildCount())
335             .isEqualTo(1);
336 
337         firstLevelA.addNextSibling(firstLevelB);
338         invokeSetParentMethod(firstLevelB, root);
339 
340         assertWithMessage("Invalid next sibling")
341             .that(firstLevelA.getNextSibling())
342             .isEqualTo(firstLevelB);
343 
344         firstLevelA.addNextSibling(firstLevelC);
345         invokeSetParentMethod(firstLevelC, root);
346 
347         assertWithMessage("Invalid next sibling")
348             .that(firstLevelA.getNextSibling())
349             .isEqualTo(firstLevelC);
350     }
351 
352     @Test
353     public void testBranchContains() {
354         final DetailAstImpl root = createToken(null, TokenTypes.CLASS_DEF);
355         final DetailAstImpl modifiers = createToken(root, TokenTypes.MODIFIERS);
356         createToken(modifiers, TokenTypes.LITERAL_PUBLIC);
357 
358         assertWithMessage("invalid result")
359                 .that(root.branchContains(TokenTypes.LITERAL_PUBLIC))
360                 .isTrue();
361         assertWithMessage("invalid result")
362                 .that(root.branchContains(TokenTypes.OBJBLOCK))
363                 .isFalse();
364     }
365 
366     private static DetailAstImpl createToken(DetailAstImpl root, int type) {
367         final DetailAstImpl result = new DetailAstImpl();
368         result.setType(type);
369         if (root != null) {
370             root.addChild(result);
371         }
372         return result;
373     }
374 
375     @Test
376     public void testClearBranchTokenTypes() throws Exception {
377         final DetailAstImpl parent = new DetailAstImpl();
378         final DetailAstImpl child = new DetailAstImpl();
379         parent.setFirstChild(child);
380 
381         final List<Consumer<DetailAstImpl>> clearBranchTokenTypesMethods = Arrays.asList(
382                 child::setFirstChild,
383                 child::setNextSibling,
384                 child::addPreviousSibling,
385                 child::addNextSibling,
386                 child::addChild,
387             ast -> {
388                 try {
389                     TestUtil.invokeVoidMethod(child, "setParent", ast);
390                 }
391                 // -@cs[IllegalCatch] Cannot avoid catching it.
392                 catch (Exception exception) {
393                     throw new IllegalStateException(exception);
394                 }
395             }
396         );
397 
398         for (Consumer<DetailAstImpl> method : clearBranchTokenTypesMethods) {
399             final BitSet branchTokenTypes = TestUtil.invokeMethod(parent,
400                     "getBranchTokenTypes", BitSet.class);
401             method.accept(null);
402             final BitSet branchTokenTypes2 = TestUtil.invokeMethod(parent,
403                     "getBranchTokenTypes", BitSet.class);
404             assertWithMessage("Branch token types are not equal")
405                 .that(branchTokenTypes)
406                 .isEqualTo(branchTokenTypes2);
407             assertWithMessage("Branch token types should not be the same")
408                 .that(branchTokenTypes)
409                 .isNotSameInstanceAs(branchTokenTypes2);
410         }
411     }
412 
413     @Test
414     public void testCacheBranchTokenTypes() {
415         final DetailAST root = new DetailAstImpl();
416         final BitSet bitSet = new BitSet();
417         bitSet.set(999);
418 
419         TestUtil.setInternalState(root, "branchTokenTypes", bitSet);
420         assertWithMessage("Branch tokens has changed")
421                 .that(root.branchContains(999))
422                 .isTrue();
423     }
424 
425     @Test
426     public void testClearChildCountCache() {
427         final DetailAstImpl parent = new DetailAstImpl();
428         final DetailAstImpl child = new DetailAstImpl();
429         parent.setFirstChild(child);
430 
431         final List<Consumer<DetailAstImpl>> clearChildCountCacheMethods = Arrays.asList(
432                 child::setNextSibling,
433                 child::addPreviousSibling,
434                 child::addNextSibling
435         );
436 
437         for (Consumer<DetailAstImpl> method : clearChildCountCacheMethods) {
438             final int startCount = parent.getChildCount();
439             method.accept(null);
440             final int intermediateCount = TestUtil.getInternalState(parent, "childCount",
441                     Integer.class);
442             final int finishCount = parent.getChildCount();
443             assertWithMessage("Child count has changed")
444                 .that(finishCount)
445                 .isEqualTo(startCount);
446             assertWithMessage("Invalid child count")
447                 .that(intermediateCount)
448                 .isEqualTo(Integer.MIN_VALUE);
449         }
450 
451         final int startCount = child.getChildCount();
452         child.addChild(null);
453         final int intermediateCount = TestUtil.getInternalState(child, "childCount", Integer.class);
454         final int finishCount = child.getChildCount();
455         assertWithMessage("Child count has changed")
456             .that(finishCount)
457             .isEqualTo(startCount);
458         assertWithMessage("Invalid child count")
459             .that(intermediateCount)
460             .isEqualTo(Integer.MIN_VALUE);
461     }
462 
463     @Test
464     public void testCacheGetChildCount() {
465         final DetailAST root = new DetailAstImpl();
466 
467         TestUtil.setInternalState(root, "childCount", 999);
468         assertWithMessage("Child count has changed")
469             .that(root.getChildCount())
470             .isEqualTo(999);
471     }
472 
473     @Test
474     public void testAddNextSibling() {
475         final DetailAstImpl parent = new DetailAstImpl();
476         final DetailAstImpl child = new DetailAstImpl();
477         final DetailAstImpl sibling = new DetailAstImpl();
478         final DetailAstImpl newSibling = new DetailAstImpl();
479         final DetailAST newNextSibling = new DetailAstImpl();
480 
481         parent.setFirstChild(child);
482         child.setNextSibling(sibling);
483         child.addNextSibling(newSibling);
484         newSibling.addNextSibling(newNextSibling);
485 
486         assertWithMessage("Invalid previous sibling")
487             .that(newNextSibling.getPreviousSibling())
488             .isEqualTo(newSibling);
489         assertWithMessage("Invalid next sibling")
490             .that(newNextSibling.getNextSibling())
491             .isEqualTo(sibling);
492         assertWithMessage("Invalid next sibling")
493             .that(sibling.getNextSibling())
494             .isNull();
495         assertWithMessage("Invalid node")
496             .that(sibling.getPreviousSibling().getPreviousSibling())
497             .isEqualTo(newSibling);
498         assertWithMessage("Invalid node")
499             .that(newNextSibling.getPreviousSibling().getPreviousSibling())
500             .isEqualTo(child);
501         assertWithMessage("Invalid parent")
502             .that(newSibling.getParent())
503             .isEqualTo(parent);
504         assertWithMessage("Invalid next sibling")
505             .that(newSibling.getNextSibling())
506             .isEqualTo(newNextSibling);
507         assertWithMessage("Invalid child")
508             .that(child.getNextSibling())
509             .isEqualTo(newSibling);
510     }
511 
512     @Test
513     public void testAddNextSibling2() {
514         final DetailAstImpl parent = new DetailAstImpl();
515         final DetailAstImpl child = new DetailAstImpl();
516         parent.setFirstChild(child);
517         final DetailAstImpl siblingOfChild = new DetailAstImpl();
518         child.addNextSibling(siblingOfChild);
519 
520         assertWithMessage("Previous Sibling should be child")
521             .that(siblingOfChild.getPreviousSibling())
522             .isEqualTo(child);
523 
524         final DetailAST nullChild = null;
525         siblingOfChild.addNextSibling(nullChild);
526         assertWithMessage("Expected to be null")
527             .that(siblingOfChild.getNextSibling())
528             .isNull();
529         assertWithMessage("Child count should be 2")
530             .that(parent.getChildCount())
531             .isEqualTo(2);
532     }
533 
534     @Test
535     public void testAddNextSibling3() {
536         final DetailAstImpl parent = new DetailAstImpl();
537         final DetailAstImpl child = new DetailAstImpl();
538         final DetailAstImpl sibling = new DetailAstImpl();
539 
540         parent.setFirstChild(child);
541         child.setNextSibling(sibling);
542         child.addNextSibling(null);
543 
544         assertWithMessage("Invalid next sibling")
545                 .that(child.getNextSibling())
546                 .isEqualTo(sibling);
547     }
548 
549     @Test
550     public void testAddNextSibling4() {
551         final DetailAstImpl parent = new DetailAstImpl();
552         parent.setText("Parent");
553         final DetailAstImpl child = new DetailAstImpl();
554         child.setText("Child");
555         final DetailAstImpl sibling = new DetailAstImpl();
556         sibling.setText("Sibling");
557         parent.setFirstChild(child);
558         child.addNextSibling(sibling);
559 
560         assertWithMessage("Invalid next sibling")
561                 .that(child.getNextSibling())
562                 .isEqualTo(sibling);
563     }
564 
565     @Test
566     public void testAddNextSiblingNullParent() {
567         final DetailAstImpl child = new DetailAstImpl();
568         final DetailAstImpl newSibling = new DetailAstImpl();
569         final DetailAstImpl oldParent = new DetailAstImpl();
570         oldParent.addChild(newSibling);
571         child.addNextSibling(newSibling);
572 
573         assertWithMessage("Invalid parent")
574             .that(newSibling.getParent())
575             .isEqualTo(oldParent);
576         assertWithMessage("Invalid next sibling")
577             .that(newSibling.getNextSibling())
578             .isNull();
579         assertWithMessage("Invalid parent")
580             .that(child.getNextSibling())
581             .isSameInstanceAs(newSibling);
582     }
583 
584     @Test
585     public void testGetLineNo() {
586         final DetailAstImpl root1 = new DetailAstImpl();
587         root1.setLineNo(1);
588         assertWithMessage("Invalid line number")
589             .that(root1.getLineNo())
590             .isEqualTo(1);
591 
592         final DetailAstImpl root2 = new DetailAstImpl();
593         final DetailAstImpl firstChild = new DetailAstImpl();
594         firstChild.setLineNo(2);
595         root2.setFirstChild(firstChild);
596         assertWithMessage("Invalid line number")
597             .that(root2.getLineNo())
598             .isEqualTo(2);
599 
600         final DetailAstImpl root3 = new DetailAstImpl();
601         final DetailAstImpl nextSibling = new DetailAstImpl();
602         nextSibling.setLineNo(3);
603         root3.setNextSibling(nextSibling);
604         assertWithMessage("Invalid line number")
605             .that(root3.getLineNo())
606             .isEqualTo(3);
607 
608         final DetailAstImpl root4 = new DetailAstImpl();
609         final DetailAstImpl comment = new DetailAstImpl();
610         comment.setType(TokenTypes.SINGLE_LINE_COMMENT);
611         comment.setLineNo(3);
612         root4.setFirstChild(comment);
613         assertWithMessage("Invalid line number")
614             .that(root4.getLineNo())
615             .isEqualTo(Integer.MIN_VALUE);
616     }
617 
618     @Test
619     public void testGetColumnNo() {
620         final DetailAstImpl root1 = new DetailAstImpl();
621         root1.setColumnNo(1);
622         assertWithMessage("Invalid column number")
623             .that(root1.getColumnNo())
624             .isEqualTo(1);
625 
626         final DetailAstImpl root2 = new DetailAstImpl();
627         final DetailAstImpl firstChild = new DetailAstImpl();
628         firstChild.setColumnNo(2);
629         root2.setFirstChild(firstChild);
630         assertWithMessage("Invalid column number")
631             .that(root2.getColumnNo())
632             .isEqualTo(2);
633 
634         final DetailAstImpl root3 = new DetailAstImpl();
635         final DetailAstImpl nextSibling = new DetailAstImpl();
636         nextSibling.setColumnNo(3);
637         root3.setNextSibling(nextSibling);
638         assertWithMessage("Invalid column number")
639             .that(root3.getColumnNo())
640             .isEqualTo(3);
641 
642         final DetailAstImpl root4 = new DetailAstImpl();
643         final DetailAstImpl comment = new DetailAstImpl();
644         comment.setType(TokenTypes.SINGLE_LINE_COMMENT);
645         comment.setColumnNo(3);
646         root4.setFirstChild(comment);
647         assertWithMessage("Invalid column number")
648             .that(root4.getColumnNo())
649             .isEqualTo(Integer.MIN_VALUE);
650     }
651 
652     @Test
653     public void testFindFirstToken() {
654         final DetailAstImpl root = new DetailAstImpl();
655         final DetailAstImpl firstChild = new DetailAstImpl();
656         firstChild.setType(TokenTypes.IDENT);
657         final DetailAstImpl secondChild = new DetailAstImpl();
658         secondChild.setType(TokenTypes.EXPR);
659         final DetailAstImpl thirdChild = new DetailAstImpl();
660         thirdChild.setType(TokenTypes.IDENT);
661 
662         root.addChild(firstChild);
663         root.addChild(secondChild);
664         root.addChild(thirdChild);
665 
666         assertWithMessage("Invalid result")
667             .that(firstChild.findFirstToken(TokenTypes.IDENT))
668             .isNull();
669         final DetailAST ident = root.findFirstToken(TokenTypes.IDENT);
670         assertWithMessage("Invalid result")
671             .that(ident)
672             .isEqualTo(firstChild);
673         final DetailAST expr = root.findFirstToken(TokenTypes.EXPR);
674         assertWithMessage("Invalid result")
675             .that(expr)
676             .isEqualTo(secondChild);
677         assertWithMessage("Invalid result")
678             .that(root.findFirstToken(0))
679             .isNull();
680     }
681 
682     @Test
683     public void testManyComments() throws Exception {
684         final File file = new File(temporaryFolder, "InputDetailASTManyComments.java");
685 
686         try (Writer bw = Files.newBufferedWriter(file.toPath())) {
687             bw.write(
688                     """
689                     /*
690                     com.puppycrawl.tools.checkstyle.checks.TodoCommentCheck
691                     format = (default)TODO\\:
692 
693                     */
694                     """);
695             bw.write("class C {\n");
696             for (int i = 0; i <= 30000; i++) {
697                 bw.write("// " + i + "\n");
698             }
699             bw.write("}\n");
700         }
701 
702         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
703         verifyWithInlineConfigParser(file.getAbsolutePath(), expected);
704     }
705 
706     @Test
707     public void testTreeStructure() throws Exception {
708         final List<File> files = getAllFiles(
709                 new File("src/test/resources/com/puppycrawl/tools/checkstyle"));
710 
711         for (File file : files) {
712             final String fileName = file.getCanonicalPath();
713             final DetailAST rootAST = JavaParser.parseFile(new File(fileName),
714                     JavaParser.Options.WITHOUT_COMMENTS);
715 
716             assertWithMessage("file must return a root node: %s", fileName)
717                 .that(rootAST)
718                 .isNotNull();
719 
720             assertWithMessage("tree is valid")
721                     .that(checkTree(fileName, rootAST))
722                     .isTrue();
723         }
724     }
725 
726     @Test
727     public void testToString() {
728         final DetailAstImpl ast = new DetailAstImpl();
729         ast.setText("text");
730         ast.setColumnNo(1);
731         ast.setLineNo(1);
732         assertWithMessage("Invalid text")
733             .that(ast.toString())
734             .isEqualTo("text[1x1]");
735     }
736 
737     @Test
738     public void testRemoveChildren() {
739         final DetailAstImpl parent = new DetailAstImpl();
740         final DetailAstImpl child1 = new DetailAstImpl();
741         parent.setFirstChild(child1);
742         final DetailAstImpl child2 = new DetailAstImpl();
743         child1.setNextSibling(child2);
744 
745         parent.removeChildren();
746 
747         assertWithMessage("")
748                 .that(parent.getChildCount())
749                 .isEqualTo(0);
750     }
751 
752     @Test
753     public void testAddChild() {
754         final DetailAstImpl grandParent = new DetailAstImpl();
755         grandParent.setText("grandparent");
756         final DetailAstImpl parent = new DetailAstImpl();
757         parent.setText("parent");
758         grandParent.setFirstChild(parent);
759 
760         final DetailAstImpl child = new DetailAstImpl();
761         child.setText("child");
762         parent.setFirstChild(child);
763 
764         final DetailAstImpl secondChild = new DetailAstImpl();
765         secondChild.setText("SecondChild");
766         parent.addChild(secondChild);
767 
768         assertWithMessage("Invalid previous sibling")
769                 .that(secondChild.getPreviousSibling())
770                 .isEqualTo(child);
771     }
772 
773     private static List<File> getAllFiles(File dir) {
774         final List<File> result = new ArrayList<>();
775 
776         dir.listFiles(file -> {
777             if (file.isDirectory()) {
778                 result.addAll(getAllFiles(file));
779             }
780             else if (file.getName().endsWith(".java")
781                     && !NO_ROOT_FILES.contains(file.getName())) {
782                 result.add(file);
783             }
784             return false;
785         });
786 
787         return result;
788     }
789 
790     private static boolean checkTree(final String filename, final DetailAST root) {
791         DetailAST curNode = root;
792         DetailAST parent = null;
793         DetailAST prev = null;
794         while (curNode != null) {
795             checkNode(curNode, parent, prev, filename, root);
796             DetailAST toVisit = curNode.getFirstChild();
797             if (toVisit == null) {
798                 while (curNode != null && toVisit == null) {
799                     toVisit = curNode.getNextSibling();
800                     if (toVisit == null) {
801                         curNode = curNode.getParent();
802                         if (curNode != null) {
803                             parent = curNode.getParent();
804                         }
805                     }
806                     else {
807                         prev = curNode;
808                         curNode = toVisit;
809                     }
810                 }
811             }
812             else {
813                 parent = curNode;
814                 curNode = toVisit;
815                 prev = null;
816             }
817         }
818 
819         return true;
820     }
821 
822     private static void checkNode(final DetailAST node,
823                                   final DetailAST parent,
824                                   final DetailAST prev,
825                                   final String filename,
826                                   final DetailAST root) {
827         final Object[] params = {
828             node, parent, prev, filename, root,
829         };
830         final MessageFormat badParentFormatter = new MessageFormat(
831                 "Bad parent node={0} parent={1} filename={3} root={4}", Locale.ROOT);
832         final String badParentMsg = badParentFormatter.format(params);
833         assertWithMessage(badParentMsg)
834             .that(node.getParent())
835             .isEqualTo(parent);
836         final MessageFormat badPrevFormatter = new MessageFormat(
837                 "Bad prev node={0} prev={2} parent={1} filename={3} root={4}", Locale.ROOT);
838         final String badPrevMsg = badPrevFormatter.format(params);
839         assertWithMessage(badPrevMsg)
840             .that(node.getPreviousSibling())
841             .isEqualTo(prev);
842     }
843 
844 }