View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.lang.reflect.Method;
27  import java.nio.charset.StandardCharsets;
28  import java.nio.file.Files;
29  import java.text.MessageFormat;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.BitSet;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.Set;
36  import java.util.function.Consumer;
37  
38  import org.antlr.v4.runtime.CommonToken;
39  import org.junit.jupiter.api.Test;
40  import org.junit.jupiter.api.io.TempDir;
41  
42  import com.puppycrawl.tools.checkstyle.api.DetailAST;
43  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
44  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
45  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
46  
47  /**
48   * TestCase to check DetailAST.
49   */
50  public class DetailAstImplTest extends AbstractModuleTestSupport {
51  
52      // Ignores file which are not meant to have root node intentionally.
53      public static final Set<String> NO_ROOT_FILES = Set.of(
54                   // fails with unexpected character
55                   "InputGrammar.java",
56                   // comment only files, no root
57                   "InputPackageDeclarationWithCommentOnly.java",
58                   "InputSingleSpaceSeparatorEmpty.java",
59                   "InputNoCodeInFile1.java",
60                   "InputNoCodeInFile2.java",
61                   "InputNoCodeInFile3.java",
62                   "InputNoCodeInFile5.java"
63          );
64  
65      @TempDir
66      public File temporaryFolder;
67  
68      @Override
69      protected String getPackageLocation() {
70          return "com/puppycrawl/tools/checkstyle/api/detailast";
71      }
72  
73      private static Method getSetParentMethod() throws Exception {
74          final Class<DetailAstImpl> detailAstClass = DetailAstImpl.class;
75          final Method setParentMethod =
76              detailAstClass.getDeclaredMethod("setParent", DetailAstImpl.class);
77          setParentMethod.setAccessible(true);
78          return setParentMethod;
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         final Method setParentMethod = getSetParentMethod();
143         setParentMethod.invoke(firstLevelA, root);
144         firstLevelA.setFirstChild(secondLevelA);
145         firstLevelA.setNextSibling(firstLevelB);
146 
147         setParentMethod.invoke(firstLevelB, root);
148 
149         setParentMethod.invoke(secondLevelA, root);
150 
151         assertWithMessage("Invalid child count")
152             .that(secondLevelA.getChildCount())
153             .isEqualTo(0);
154         assertWithMessage("Invalid child count")
155             .that(firstLevelB.getChildCount())
156             .isEqualTo(0);
157         assertWithMessage("Invalid child count")
158             .that(firstLevelA.getChildCount())
159             .isEqualTo(1);
160         assertWithMessage("Invalid child count")
161             .that(root.getChildCount())
162             .isEqualTo(2);
163         assertWithMessage("Invalid child count")
164             .that(root.getNumberOfChildren())
165             .isEqualTo(2);
166 
167         assertWithMessage("Previous sibling should be null")
168             .that(root.getPreviousSibling())
169             .isNull();
170         assertWithMessage("Previous sibling should be null")
171             .that(firstLevelA.getPreviousSibling())
172             .isNull();
173         assertWithMessage("Previous sibling should be null")
174             .that(secondLevelA.getPreviousSibling())
175             .isNull();
176         assertWithMessage("Invalid previous sibling")
177             .that(firstLevelB.getPreviousSibling())
178             .isEqualTo(firstLevelA);
179     }
180 
181     @Test
182     public void testHasChildren() {
183         final DetailAstImpl root = new DetailAstImpl();
184         final DetailAstImpl child = new DetailAstImpl();
185         root.setFirstChild(child);
186 
187         assertWithMessage("Root node should have children")
188                 .that(root.hasChildren())
189                 .isTrue();
190         assertWithMessage("Child node should have no children")
191                 .that(child.hasChildren())
192                 .isFalse();
193     }
194 
195     @Test
196     public void testGetChildCountType() throws Exception {
197         final DetailAstImpl root = new DetailAstImpl();
198         final DetailAstImpl firstLevelA = new DetailAstImpl();
199         final DetailAstImpl firstLevelB = new DetailAstImpl();
200 
201         root.setFirstChild(firstLevelA);
202 
203         final Method setParentMethod = getSetParentMethod();
204         setParentMethod.invoke(firstLevelA, root);
205         firstLevelA.setNextSibling(firstLevelB);
206 
207         firstLevelA.setType(TokenTypes.IDENT);
208         firstLevelB.setType(TokenTypes.EXPR);
209 
210         setParentMethod.invoke(firstLevelB, root);
211 
212         final int childCountLevelB = firstLevelB.getChildCount(0);
213         assertWithMessage("Invalid child count")
214             .that(childCountLevelB)
215             .isEqualTo(0);
216         final int childCountLevelA = firstLevelA.getChildCount(TokenTypes.EXPR);
217         assertWithMessage("Invalid child count")
218             .that(childCountLevelA)
219             .isEqualTo(0);
220         final int identTypeCount = root.getChildCount(TokenTypes.IDENT);
221         assertWithMessage("Invalid child count")
222             .that(identTypeCount)
223             .isEqualTo(1);
224         final int exprTypeCount = root.getChildCount(TokenTypes.EXPR);
225         assertWithMessage("Invalid child count")
226             .that(exprTypeCount)
227             .isEqualTo(1);
228         final int invalidTypeCount = root.getChildCount(0);
229         assertWithMessage("Invalid child count")
230             .that(invalidTypeCount)
231             .isEqualTo(0);
232     }
233 
234     @Test
235     public void testSetSiblingNull() throws Exception {
236         final DetailAstImpl root = new DetailAstImpl();
237         final DetailAstImpl firstLevelA = new DetailAstImpl();
238 
239         root.setFirstChild(firstLevelA);
240 
241         assertWithMessage("Invalid child count")
242             .that(root.getChildCount())
243             .isEqualTo(1);
244 
245         getSetParentMethod().invoke(firstLevelA, root);
246         firstLevelA.addPreviousSibling(null);
247         firstLevelA.addNextSibling(null);
248 
249         assertWithMessage("Invalid child count")
250             .that(root.getChildCount())
251             .isEqualTo(1);
252     }
253 
254     @Test
255     public void testAddPreviousSibling() {
256         final DetailAST previousSibling = new DetailAstImpl();
257         final DetailAstImpl instance = new DetailAstImpl();
258         final DetailAstImpl parent = new DetailAstImpl();
259 
260         parent.setFirstChild(instance);
261 
262         instance.addPreviousSibling(previousSibling);
263 
264         assertWithMessage("unexpected result")
265             .that(instance.getPreviousSibling())
266             .isEqualTo(previousSibling);
267         assertWithMessage("unexpected result")
268             .that(parent.getFirstChild())
269             .isEqualTo(previousSibling);
270 
271         final DetailAST newPreviousSibling = new DetailAstImpl();
272 
273         instance.addPreviousSibling(newPreviousSibling);
274 
275         assertWithMessage("unexpected result")
276             .that(instance.getPreviousSibling())
277             .isEqualTo(newPreviousSibling);
278         assertWithMessage("unexpected result")
279             .that(newPreviousSibling.getPreviousSibling())
280             .isEqualTo(previousSibling);
281         assertWithMessage("unexpected result")
282             .that(previousSibling.getNextSibling())
283             .isEqualTo(newPreviousSibling);
284         assertWithMessage("unexpected result")
285             .that(parent.getFirstChild())
286             .isEqualTo(previousSibling);
287 
288         final DetailAstImpl secondNewPreviousSibling = new DetailAstImpl();
289         instance.addPreviousSibling(secondNewPreviousSibling);
290         assertWithMessage("unexpected result")
291                 .that(secondNewPreviousSibling.getPreviousSibling())
292                 .isEqualTo(newPreviousSibling);
293         assertWithMessage("unexpected result")
294                 .that(secondNewPreviousSibling.getNextSibling())
295                 .isEqualTo(instance);
296         assertWithMessage("unexpected result")
297                 .that(newPreviousSibling.getNextSibling())
298                 .isEqualTo(secondNewPreviousSibling);
299         assertWithMessage("unexpected result")
300                 .that(secondNewPreviousSibling.getPreviousSibling().getPreviousSibling())
301                 .isEqualTo(previousSibling);
302         assertWithMessage("unexpected result")
303                 .that(instance.getPreviousSibling().getPreviousSibling().getPreviousSibling())
304                 .isEqualTo(previousSibling);
305     }
306 
307     @Test
308     public void testAddPreviousSiblingNullParent() {
309         final DetailAstImpl child = new DetailAstImpl();
310         final DetailAST newSibling = new DetailAstImpl();
311 
312         child.addPreviousSibling(newSibling);
313 
314         assertWithMessage("Invalid child token")
315             .that(newSibling.getNextSibling())
316             .isEqualTo(child);
317         assertWithMessage("Invalid child token")
318             .that(child.getPreviousSibling())
319             .isEqualTo(newSibling);
320     }
321 
322     @Test
323     public void testInsertSiblingBetween() throws Exception {
324         final DetailAstImpl root = new DetailAstImpl();
325         final DetailAstImpl firstLevelA = new DetailAstImpl();
326         final DetailAST firstLevelB = new DetailAstImpl();
327         final DetailAST firstLevelC = new DetailAstImpl();
328 
329         assertWithMessage("Invalid child count")
330             .that(root.getChildCount())
331             .isEqualTo(0);
332 
333         root.setFirstChild(firstLevelA);
334         final Method setParentMethod = getSetParentMethod();
335         setParentMethod.invoke(firstLevelA, root);
336 
337         assertWithMessage("Invalid child count")
338             .that(root.getChildCount())
339             .isEqualTo(1);
340 
341         firstLevelA.addNextSibling(firstLevelB);
342         setParentMethod.invoke(firstLevelB, root);
343 
344         assertWithMessage("Invalid next sibling")
345             .that(firstLevelA.getNextSibling())
346             .isEqualTo(firstLevelB);
347 
348         firstLevelA.addNextSibling(firstLevelC);
349         setParentMethod.invoke(firstLevelC, root);
350 
351         assertWithMessage("Invalid next sibling")
352             .that(firstLevelA.getNextSibling())
353             .isEqualTo(firstLevelC);
354     }
355 
356     @Test
357     public void testBranchContains() {
358         final DetailAstImpl root = createToken(null, TokenTypes.CLASS_DEF);
359         final DetailAstImpl modifiers = createToken(root, TokenTypes.MODIFIERS);
360         createToken(modifiers, TokenTypes.LITERAL_PUBLIC);
361 
362         assertWithMessage("invalid result")
363                 .that(root.branchContains(TokenTypes.LITERAL_PUBLIC))
364                 .isTrue();
365         assertWithMessage("invalid result")
366                 .that(root.branchContains(TokenTypes.OBJBLOCK))
367                 .isFalse();
368     }
369 
370     private static DetailAstImpl createToken(DetailAstImpl root, int type) {
371         final DetailAstImpl result = new DetailAstImpl();
372         result.setType(type);
373         if (root != null) {
374             root.addChild(result);
375         }
376         return result;
377     }
378 
379     @Test
380     public void testClearBranchTokenTypes() throws Exception {
381         final DetailAstImpl parent = new DetailAstImpl();
382         final DetailAstImpl child = new DetailAstImpl();
383         parent.setFirstChild(child);
384 
385         final List<Consumer<DetailAstImpl>> clearBranchTokenTypesMethods = Arrays.asList(
386                 child::setFirstChild,
387                 child::setNextSibling,
388                 child::addPreviousSibling,
389                 child::addNextSibling,
390                 child::addChild,
391             ast -> {
392                 try {
393                     TestUtil.invokeMethod(child, "setParent", ast);
394                 }
395                 // -@cs[IllegalCatch] Cannot avoid catching it.
396                 catch (Exception exception) {
397                     throw new IllegalStateException(exception);
398                 }
399             }
400         );
401 
402         for (Consumer<DetailAstImpl> method : clearBranchTokenTypesMethods) {
403             final BitSet branchTokenTypes = TestUtil.invokeMethod(parent, "getBranchTokenTypes");
404             method.accept(null);
405             final BitSet branchTokenTypes2 = TestUtil.invokeMethod(parent, "getBranchTokenTypes");
406             assertWithMessage("Branch token types are not equal")
407                 .that(branchTokenTypes)
408                 .isEqualTo(branchTokenTypes2);
409             assertWithMessage("Branch token types should not be the same")
410                 .that(branchTokenTypes)
411                 .isNotSameInstanceAs(branchTokenTypes2);
412         }
413     }
414 
415     @Test
416     public void testCacheBranchTokenTypes() {
417         final DetailAST root = new DetailAstImpl();
418         final BitSet bitSet = new BitSet();
419         bitSet.set(999);
420 
421         TestUtil.setInternalState(root, "branchTokenTypes", bitSet);
422         assertWithMessage("Branch tokens has changed")
423                 .that(root.branchContains(999))
424                 .isTrue();
425     }
426 
427     @Test
428     public void testClearChildCountCache() {
429         final DetailAstImpl parent = new DetailAstImpl();
430         final DetailAstImpl child = new DetailAstImpl();
431         parent.setFirstChild(child);
432 
433         final List<Consumer<DetailAstImpl>> clearChildCountCacheMethods = Arrays.asList(
434                 child::setNextSibling,
435                 child::addPreviousSibling,
436                 child::addNextSibling
437         );
438 
439         for (Consumer<DetailAstImpl> method : clearChildCountCacheMethods) {
440             final int startCount = parent.getChildCount();
441             method.accept(null);
442             final int intermediateCount = TestUtil.getInternalState(parent, "childCount");
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");
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(), StandardCharsets.UTF_8)) {
688             bw.write("/*\ncom.puppycrawl.tools.checkstyle.checks.TodoCommentCheck\n\n\n\n*/\n");
689             bw.write("class C {\n");
690             for (int i = 0; i <= 30000; i++) {
691                 bw.write("// " + i + "\n");
692             }
693             bw.write("}\n");
694         }
695 
696         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
697         verifyWithInlineConfigParser(file.getAbsolutePath(), expected);
698     }
699 
700     @Test
701     public void testTreeStructure() throws Exception {
702         final List<File> files = getAllFiles(
703                 new File("src/test/resources/com/puppycrawl/tools/checkstyle"));
704 
705         for (File file : files) {
706             final String fileName = file.getCanonicalPath();
707             final DetailAST rootAST = JavaParser.parseFile(new File(fileName),
708                     JavaParser.Options.WITHOUT_COMMENTS);
709 
710             assertWithMessage("file must return a root node: " + fileName)
711                 .that(rootAST)
712                 .isNotNull();
713 
714             assertWithMessage("tree is valid")
715                     .that(checkTree(fileName, rootAST))
716                     .isTrue();
717         }
718     }
719 
720     @Test
721     public void testToString() {
722         final DetailAstImpl ast = new DetailAstImpl();
723         ast.setText("text");
724         ast.setColumnNo(1);
725         ast.setLineNo(1);
726         assertWithMessage("Invalid text")
727             .that(ast.toString())
728             .isEqualTo("text[1x1]");
729     }
730 
731     @Test
732     public void testRemoveChildren() {
733         final DetailAstImpl parent = new DetailAstImpl();
734         final DetailAstImpl child1 = new DetailAstImpl();
735         parent.setFirstChild(child1);
736         final DetailAstImpl child2 = new DetailAstImpl();
737         child1.setNextSibling(child2);
738 
739         parent.removeChildren();
740 
741         assertWithMessage("")
742                 .that(parent.getChildCount())
743                 .isEqualTo(0);
744     }
745 
746     @Test
747     public void testAddChild() {
748         final DetailAstImpl grandParent = new DetailAstImpl();
749         grandParent.setText("grandparent");
750         final DetailAstImpl parent = new DetailAstImpl();
751         parent.setText("parent");
752         grandParent.setFirstChild(parent);
753 
754         final DetailAstImpl child = new DetailAstImpl();
755         child.setText("child");
756         parent.setFirstChild(child);
757 
758         final DetailAstImpl secondChild = new DetailAstImpl();
759         secondChild.setText("SecondChild");
760         parent.addChild(secondChild);
761 
762         assertWithMessage("Invalid previous sibling")
763                 .that(secondChild.getPreviousSibling())
764                 .isEqualTo(child);
765     }
766 
767     private static List<File> getAllFiles(File dir) {
768         final List<File> result = new ArrayList<>();
769 
770         dir.listFiles(file -> {
771             if (file.isDirectory()) {
772                 result.addAll(getAllFiles(file));
773             }
774             else if (file.getName().endsWith(".java")
775                     && !NO_ROOT_FILES.contains(file.getName())) {
776                 result.add(file);
777             }
778             return false;
779         });
780 
781         return result;
782     }
783 
784     private static boolean checkTree(final String filename, final DetailAST root) {
785         DetailAST curNode = root;
786         DetailAST parent = null;
787         DetailAST prev = null;
788         while (curNode != null) {
789             checkNode(curNode, parent, prev, filename, root);
790             DetailAST toVisit = curNode.getFirstChild();
791             if (toVisit == null) {
792                 while (curNode != null && toVisit == null) {
793                     toVisit = curNode.getNextSibling();
794                     if (toVisit == null) {
795                         curNode = curNode.getParent();
796                         if (curNode != null) {
797                             parent = curNode.getParent();
798                         }
799                     }
800                     else {
801                         prev = curNode;
802                         curNode = toVisit;
803                     }
804                 }
805             }
806             else {
807                 parent = curNode;
808                 curNode = toVisit;
809                 prev = null;
810             }
811         }
812 
813         return true;
814     }
815 
816     private static void checkNode(final DetailAST node,
817                                   final DetailAST parent,
818                                   final DetailAST prev,
819                                   final String filename,
820                                   final DetailAST root) {
821         final Object[] params = {
822             node, parent, prev, filename, root,
823         };
824         final MessageFormat badParentFormatter = new MessageFormat(
825                 "Bad parent node={0} parent={1} filename={3} root={4}", Locale.ROOT);
826         final String badParentMsg = badParentFormatter.format(params);
827         assertWithMessage(badParentMsg)
828             .that(node.getParent())
829             .isEqualTo(parent);
830         final MessageFormat badPrevFormatter = new MessageFormat(
831                 "Bad prev node={0} prev={2} parent={1} filename={3} root={4}", Locale.ROOT);
832         final String badPrevMsg = badPrevFormatter.format(params);
833         assertWithMessage(badPrevMsg)
834             .that(node.getPreviousSibling())
835             .isEqualTo(prev);
836     }
837 
838 }