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.invokeMethod(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.invokeMethod(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, "getBranchTokenTypes");
397             method.accept(null);
398             final BitSet branchTokenTypes2 = TestUtil.invokeMethod(parent, "getBranchTokenTypes");
399             assertWithMessage("Branch token types are not equal")
400                 .that(branchTokenTypes)
401                 .isEqualTo(branchTokenTypes2);
402             assertWithMessage("Branch token types should not be the same")
403                 .that(branchTokenTypes)
404                 .isNotSameInstanceAs(branchTokenTypes2);
405         }
406     }
407 
408     @Test
409     public void testCacheBranchTokenTypes() {
410         final DetailAST root = new DetailAstImpl();
411         final BitSet bitSet = new BitSet();
412         bitSet.set(999);
413 
414         TestUtil.setInternalState(root, "branchTokenTypes", bitSet);
415         assertWithMessage("Branch tokens has changed")
416                 .that(root.branchContains(999))
417                 .isTrue();
418     }
419 
420     @Test
421     public void testClearChildCountCache() {
422         final DetailAstImpl parent = new DetailAstImpl();
423         final DetailAstImpl child = new DetailAstImpl();
424         parent.setFirstChild(child);
425 
426         final List<Consumer<DetailAstImpl>> clearChildCountCacheMethods = Arrays.asList(
427                 child::setNextSibling,
428                 child::addPreviousSibling,
429                 child::addNextSibling
430         );
431 
432         for (Consumer<DetailAstImpl> method : clearChildCountCacheMethods) {
433             final int startCount = parent.getChildCount();
434             method.accept(null);
435             final int intermediateCount = TestUtil.getInternalState(parent, "childCount",
436                     Integer.class);
437             final int finishCount = parent.getChildCount();
438             assertWithMessage("Child count has changed")
439                 .that(finishCount)
440                 .isEqualTo(startCount);
441             assertWithMessage("Invalid child count")
442                 .that(intermediateCount)
443                 .isEqualTo(Integer.MIN_VALUE);
444         }
445 
446         final int startCount = child.getChildCount();
447         child.addChild(null);
448         final int intermediateCount = TestUtil.getInternalState(child, "childCount", Integer.class);
449         final int finishCount = child.getChildCount();
450         assertWithMessage("Child count has changed")
451             .that(finishCount)
452             .isEqualTo(startCount);
453         assertWithMessage("Invalid child count")
454             .that(intermediateCount)
455             .isEqualTo(Integer.MIN_VALUE);
456     }
457 
458     @Test
459     public void testCacheGetChildCount() {
460         final DetailAST root = new DetailAstImpl();
461 
462         TestUtil.setInternalState(root, "childCount", 999);
463         assertWithMessage("Child count has changed")
464             .that(root.getChildCount())
465             .isEqualTo(999);
466     }
467 
468     @Test
469     public void testAddNextSibling() {
470         final DetailAstImpl parent = new DetailAstImpl();
471         final DetailAstImpl child = new DetailAstImpl();
472         final DetailAstImpl sibling = new DetailAstImpl();
473         final DetailAstImpl newSibling = new DetailAstImpl();
474         final DetailAST newNextSibling = new DetailAstImpl();
475 
476         parent.setFirstChild(child);
477         child.setNextSibling(sibling);
478         child.addNextSibling(newSibling);
479         newSibling.addNextSibling(newNextSibling);
480 
481         assertWithMessage("Invalid previous sibling")
482             .that(newNextSibling.getPreviousSibling())
483             .isEqualTo(newSibling);
484         assertWithMessage("Invalid next sibling")
485             .that(newNextSibling.getNextSibling())
486             .isEqualTo(sibling);
487         assertWithMessage("Invalid next sibling")
488             .that(sibling.getNextSibling())
489             .isNull();
490         assertWithMessage("Invalid node")
491             .that(sibling.getPreviousSibling().getPreviousSibling())
492             .isEqualTo(newSibling);
493         assertWithMessage("Invalid node")
494             .that(newNextSibling.getPreviousSibling().getPreviousSibling())
495             .isEqualTo(child);
496         assertWithMessage("Invalid parent")
497             .that(newSibling.getParent())
498             .isEqualTo(parent);
499         assertWithMessage("Invalid next sibling")
500             .that(newSibling.getNextSibling())
501             .isEqualTo(newNextSibling);
502         assertWithMessage("Invalid child")
503             .that(child.getNextSibling())
504             .isEqualTo(newSibling);
505     }
506 
507     @Test
508     public void testAddNextSibling2() {
509         final DetailAstImpl parent = new DetailAstImpl();
510         final DetailAstImpl child = new DetailAstImpl();
511         parent.setFirstChild(child);
512         final DetailAstImpl siblingOfChild = new DetailAstImpl();
513         child.addNextSibling(siblingOfChild);
514 
515         assertWithMessage("Previous Sibling should be child")
516             .that(siblingOfChild.getPreviousSibling())
517             .isEqualTo(child);
518 
519         final DetailAST nullChild = null;
520         siblingOfChild.addNextSibling(nullChild);
521         assertWithMessage("Expected to be null")
522             .that(siblingOfChild.getNextSibling())
523             .isNull();
524         assertWithMessage("Child count should be 2")
525             .that(parent.getChildCount())
526             .isEqualTo(2);
527     }
528 
529     @Test
530     public void testAddNextSibling3() {
531         final DetailAstImpl parent = new DetailAstImpl();
532         final DetailAstImpl child = new DetailAstImpl();
533         final DetailAstImpl sibling = new DetailAstImpl();
534 
535         parent.setFirstChild(child);
536         child.setNextSibling(sibling);
537         child.addNextSibling(null);
538 
539         assertWithMessage("Invalid next sibling")
540                 .that(child.getNextSibling())
541                 .isEqualTo(sibling);
542     }
543 
544     @Test
545     public void testAddNextSibling4() {
546         final DetailAstImpl parent = new DetailAstImpl();
547         parent.setText("Parent");
548         final DetailAstImpl child = new DetailAstImpl();
549         child.setText("Child");
550         final DetailAstImpl sibling = new DetailAstImpl();
551         sibling.setText("Sibling");
552         parent.setFirstChild(child);
553         child.addNextSibling(sibling);
554 
555         assertWithMessage("Invalid next sibling")
556                 .that(child.getNextSibling())
557                 .isEqualTo(sibling);
558     }
559 
560     @Test
561     public void testAddNextSiblingNullParent() {
562         final DetailAstImpl child = new DetailAstImpl();
563         final DetailAstImpl newSibling = new DetailAstImpl();
564         final DetailAstImpl oldParent = new DetailAstImpl();
565         oldParent.addChild(newSibling);
566         child.addNextSibling(newSibling);
567 
568         assertWithMessage("Invalid parent")
569             .that(newSibling.getParent())
570             .isEqualTo(oldParent);
571         assertWithMessage("Invalid next sibling")
572             .that(newSibling.getNextSibling())
573             .isNull();
574         assertWithMessage("Invalid parent")
575             .that(child.getNextSibling())
576             .isSameInstanceAs(newSibling);
577     }
578 
579     @Test
580     public void testGetLineNo() {
581         final DetailAstImpl root1 = new DetailAstImpl();
582         root1.setLineNo(1);
583         assertWithMessage("Invalid line number")
584             .that(root1.getLineNo())
585             .isEqualTo(1);
586 
587         final DetailAstImpl root2 = new DetailAstImpl();
588         final DetailAstImpl firstChild = new DetailAstImpl();
589         firstChild.setLineNo(2);
590         root2.setFirstChild(firstChild);
591         assertWithMessage("Invalid line number")
592             .that(root2.getLineNo())
593             .isEqualTo(2);
594 
595         final DetailAstImpl root3 = new DetailAstImpl();
596         final DetailAstImpl nextSibling = new DetailAstImpl();
597         nextSibling.setLineNo(3);
598         root3.setNextSibling(nextSibling);
599         assertWithMessage("Invalid line number")
600             .that(root3.getLineNo())
601             .isEqualTo(3);
602 
603         final DetailAstImpl root4 = new DetailAstImpl();
604         final DetailAstImpl comment = new DetailAstImpl();
605         comment.setType(TokenTypes.SINGLE_LINE_COMMENT);
606         comment.setLineNo(3);
607         root4.setFirstChild(comment);
608         assertWithMessage("Invalid line number")
609             .that(root4.getLineNo())
610             .isEqualTo(Integer.MIN_VALUE);
611     }
612 
613     @Test
614     public void testGetColumnNo() {
615         final DetailAstImpl root1 = new DetailAstImpl();
616         root1.setColumnNo(1);
617         assertWithMessage("Invalid column number")
618             .that(root1.getColumnNo())
619             .isEqualTo(1);
620 
621         final DetailAstImpl root2 = new DetailAstImpl();
622         final DetailAstImpl firstChild = new DetailAstImpl();
623         firstChild.setColumnNo(2);
624         root2.setFirstChild(firstChild);
625         assertWithMessage("Invalid column number")
626             .that(root2.getColumnNo())
627             .isEqualTo(2);
628 
629         final DetailAstImpl root3 = new DetailAstImpl();
630         final DetailAstImpl nextSibling = new DetailAstImpl();
631         nextSibling.setColumnNo(3);
632         root3.setNextSibling(nextSibling);
633         assertWithMessage("Invalid column number")
634             .that(root3.getColumnNo())
635             .isEqualTo(3);
636 
637         final DetailAstImpl root4 = new DetailAstImpl();
638         final DetailAstImpl comment = new DetailAstImpl();
639         comment.setType(TokenTypes.SINGLE_LINE_COMMENT);
640         comment.setColumnNo(3);
641         root4.setFirstChild(comment);
642         assertWithMessage("Invalid column number")
643             .that(root4.getColumnNo())
644             .isEqualTo(Integer.MIN_VALUE);
645     }
646 
647     @Test
648     public void testFindFirstToken() {
649         final DetailAstImpl root = new DetailAstImpl();
650         final DetailAstImpl firstChild = new DetailAstImpl();
651         firstChild.setType(TokenTypes.IDENT);
652         final DetailAstImpl secondChild = new DetailAstImpl();
653         secondChild.setType(TokenTypes.EXPR);
654         final DetailAstImpl thirdChild = new DetailAstImpl();
655         thirdChild.setType(TokenTypes.IDENT);
656 
657         root.addChild(firstChild);
658         root.addChild(secondChild);
659         root.addChild(thirdChild);
660 
661         assertWithMessage("Invalid result")
662             .that(firstChild.findFirstToken(TokenTypes.IDENT))
663             .isNull();
664         final DetailAST ident = root.findFirstToken(TokenTypes.IDENT);
665         assertWithMessage("Invalid result")
666             .that(ident)
667             .isEqualTo(firstChild);
668         final DetailAST expr = root.findFirstToken(TokenTypes.EXPR);
669         assertWithMessage("Invalid result")
670             .that(expr)
671             .isEqualTo(secondChild);
672         assertWithMessage("Invalid result")
673             .that(root.findFirstToken(0))
674             .isNull();
675     }
676 
677     @Test
678     public void testManyComments() throws Exception {
679         final File file = new File(temporaryFolder, "InputDetailASTManyComments.java");
680 
681         try (Writer bw = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
682             bw.write("/*\ncom.puppycrawl.tools.checkstyle.checks.TodoCommentCheck\n\n\n\n*/\n");
683             bw.write("class C {\n");
684             for (int i = 0; i <= 30000; i++) {
685                 bw.write("// " + i + "\n");
686             }
687             bw.write("}\n");
688         }
689 
690         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
691         verifyWithInlineConfigParser(file.getAbsolutePath(), expected);
692     }
693 
694     @Test
695     public void testTreeStructure() throws Exception {
696         final List<File> files = getAllFiles(
697                 new File("src/test/resources/com/puppycrawl/tools/checkstyle"));
698 
699         for (File file : files) {
700             final String fileName = file.getCanonicalPath();
701             final DetailAST rootAST = JavaParser.parseFile(new File(fileName),
702                     JavaParser.Options.WITHOUT_COMMENTS);
703 
704             assertWithMessage("file must return a root node: " + fileName)
705                 .that(rootAST)
706                 .isNotNull();
707 
708             assertWithMessage("tree is valid")
709                     .that(checkTree(fileName, rootAST))
710                     .isTrue();
711         }
712     }
713 
714     @Test
715     public void testToString() {
716         final DetailAstImpl ast = new DetailAstImpl();
717         ast.setText("text");
718         ast.setColumnNo(1);
719         ast.setLineNo(1);
720         assertWithMessage("Invalid text")
721             .that(ast.toString())
722             .isEqualTo("text[1x1]");
723     }
724 
725     @Test
726     public void testRemoveChildren() {
727         final DetailAstImpl parent = new DetailAstImpl();
728         final DetailAstImpl child1 = new DetailAstImpl();
729         parent.setFirstChild(child1);
730         final DetailAstImpl child2 = new DetailAstImpl();
731         child1.setNextSibling(child2);
732 
733         parent.removeChildren();
734 
735         assertWithMessage("")
736                 .that(parent.getChildCount())
737                 .isEqualTo(0);
738     }
739 
740     @Test
741     public void testAddChild() {
742         final DetailAstImpl grandParent = new DetailAstImpl();
743         grandParent.setText("grandparent");
744         final DetailAstImpl parent = new DetailAstImpl();
745         parent.setText("parent");
746         grandParent.setFirstChild(parent);
747 
748         final DetailAstImpl child = new DetailAstImpl();
749         child.setText("child");
750         parent.setFirstChild(child);
751 
752         final DetailAstImpl secondChild = new DetailAstImpl();
753         secondChild.setText("SecondChild");
754         parent.addChild(secondChild);
755 
756         assertWithMessage("Invalid previous sibling")
757                 .that(secondChild.getPreviousSibling())
758                 .isEqualTo(child);
759     }
760 
761     private static List<File> getAllFiles(File dir) {
762         final List<File> result = new ArrayList<>();
763 
764         dir.listFiles(file -> {
765             if (file.isDirectory()) {
766                 result.addAll(getAllFiles(file));
767             }
768             else if (file.getName().endsWith(".java")
769                     && !NO_ROOT_FILES.contains(file.getName())) {
770                 result.add(file);
771             }
772             return false;
773         });
774 
775         return result;
776     }
777 
778     private static boolean checkTree(final String filename, final DetailAST root) {
779         DetailAST curNode = root;
780         DetailAST parent = null;
781         DetailAST prev = null;
782         while (curNode != null) {
783             checkNode(curNode, parent, prev, filename, root);
784             DetailAST toVisit = curNode.getFirstChild();
785             if (toVisit == null) {
786                 while (curNode != null && toVisit == null) {
787                     toVisit = curNode.getNextSibling();
788                     if (toVisit == null) {
789                         curNode = curNode.getParent();
790                         if (curNode != null) {
791                             parent = curNode.getParent();
792                         }
793                     }
794                     else {
795                         prev = curNode;
796                         curNode = toVisit;
797                     }
798                 }
799             }
800             else {
801                 parent = curNode;
802                 curNode = toVisit;
803                 prev = null;
804             }
805         }
806 
807         return true;
808     }
809 
810     private static void checkNode(final DetailAST node,
811                                   final DetailAST parent,
812                                   final DetailAST prev,
813                                   final String filename,
814                                   final DetailAST root) {
815         final Object[] params = {
816             node, parent, prev, filename, root,
817         };
818         final MessageFormat badParentFormatter = new MessageFormat(
819                 "Bad parent node={0} parent={1} filename={3} root={4}", Locale.ROOT);
820         final String badParentMsg = badParentFormatter.format(params);
821         assertWithMessage(badParentMsg)
822             .that(node.getParent())
823             .isEqualTo(parent);
824         final MessageFormat badPrevFormatter = new MessageFormat(
825                 "Bad prev node={0} prev={2} parent={1} filename={3} root={4}", Locale.ROOT);
826         final String badPrevMsg = badPrevFormatter.format(params);
827         assertWithMessage(badPrevMsg)
828             .that(node.getPreviousSibling())
829             .isEqualTo(prev);
830     }
831 
832 }