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