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