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