View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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 java.io.File;
23  import java.io.Reader;
24  import java.io.StringReader;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.HashSet;
28  import java.util.Locale;
29  import java.util.Set;
30  import java.util.SortedSet;
31  import java.util.TreeSet;
32  
33  import antlr.CommonHiddenStreamToken;
34  import antlr.RecognitionException;
35  import antlr.Token;
36  import antlr.TokenStreamException;
37  import antlr.TokenStreamHiddenTokenFilter;
38  import antlr.TokenStreamRecognitionException;
39  import com.google.common.collect.HashMultimap;
40  import com.google.common.collect.Multimap;
41  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
42  import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
43  import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
44  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
45  import com.puppycrawl.tools.checkstyle.api.Configuration;
46  import com.puppycrawl.tools.checkstyle.api.Context;
47  import com.puppycrawl.tools.checkstyle.api.DetailAST;
48  import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
49  import com.puppycrawl.tools.checkstyle.api.FileContents;
50  import com.puppycrawl.tools.checkstyle.api.FileText;
51  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
52  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
53  import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaLexer;
54  import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaRecognizer;
55  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
56  import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
57  
58  /**
59   * Responsible for walking an abstract syntax tree and notifying interested
60   * checks at each each node.
61   *
62   * @author Oliver Burn
63   */
64  // -@cs[ClassFanOutComplexity] To resolve issue 4714, new classes were imported. Number of
65  // classes current class relies on currently is 27, which is above threshold 25.
66  // see https://github.com/checkstyle/checkstyle/issues/4714.
67  public final class TreeWalker extends AbstractFileSetCheck implements ExternalResourceHolder {
68  
69      /** Default distance between tab stops. */
70      private static final int DEFAULT_TAB_WIDTH = 8;
71  
72      /** Maps from token name to ordinary checks. */
73      private final Multimap<String, AbstractCheck> tokenToOrdinaryChecks =
74          HashMultimap.create();
75  
76      /** Maps from token name to comment checks. */
77      private final Multimap<String, AbstractCheck> tokenToCommentChecks =
78              HashMultimap.create();
79  
80      /** Registered ordinary checks, that don't use comment nodes. */
81      private final Set<AbstractCheck> ordinaryChecks = new HashSet<>();
82  
83      /** Registered comment checks. */
84      private final Set<AbstractCheck> commentChecks = new HashSet<>();
85  
86      /** The ast filters. */
87      private final Set<TreeWalkerFilter> filters = new HashSet<>();
88  
89      /** The sorted set of messages. */
90      private final SortedSet<LocalizedMessage> messages = new TreeSet<>();
91  
92      /** The distance between tab stops. */
93      private int tabWidth = DEFAULT_TAB_WIDTH;
94  
95      /** Class loader to resolve classes with. **/
96      private ClassLoader classLoader;
97  
98      /** Context of child components. */
99      private Context childContext;
100 
101     /** A factory for creating submodules (i.e. the Checks) */
102     private ModuleFactory moduleFactory;
103 
104     /**
105      * Creates a new {@code TreeWalker} instance.
106      */
107     public TreeWalker() {
108         setFileExtensions("java");
109     }
110 
111     /**
112      * Sets tab width.
113      * @param tabWidth the distance between tab stops
114      */
115     public void setTabWidth(int tabWidth) {
116         this.tabWidth = tabWidth;
117     }
118 
119     /**
120      * Sets cache file.
121      * @deprecated Use {@link Checker#setCacheFile} instead. It does not do anything now. We just
122      *             keep the setter for transition period to the same option in Checker. The
123      *             method will be completely removed in Checkstyle 8.0. See
124      *             <a href="https://github.com/checkstyle/checkstyle/issues/2883">issue#2883</a>
125      * @param fileName the cache file
126      */
127     @Deprecated
128     public void setCacheFile(String fileName) {
129         // Deprecated
130     }
131 
132     /**
133      * Sets classLoader to load class.
134      * @param classLoader class loader to resolve classes with.
135      */
136     public void setClassLoader(ClassLoader classLoader) {
137         this.classLoader = classLoader;
138     }
139 
140     /**
141      * Sets the module factory for creating child modules (Checks).
142      * @param moduleFactory the factory
143      */
144     public void setModuleFactory(ModuleFactory moduleFactory) {
145         this.moduleFactory = moduleFactory;
146     }
147 
148     @Override
149     public void finishLocalSetup() {
150         final DefaultContext checkContext = new DefaultContext();
151         checkContext.add("classLoader", classLoader);
152         checkContext.add("severity", getSeverity());
153         checkContext.add("tabWidth", String.valueOf(tabWidth));
154 
155         childContext = checkContext;
156     }
157 
158     /**
159      * {@inheritDoc} Creates child module.
160      * @noinspection ChainOfInstanceofChecks
161      */
162     @Override
163     public void setupChild(Configuration childConf)
164             throws CheckstyleException {
165         final String name = childConf.getName();
166         final Object module = moduleFactory.createModule(name);
167         if (module instanceof AutomaticBean) {
168             final AutomaticBean bean = (AutomaticBean) module;
169             bean.contextualize(childContext);
170             bean.configure(childConf);
171         }
172         if (module instanceof AbstractCheck) {
173             final AbstractCheck check = (AbstractCheck) module;
174             check.init();
175             registerCheck(check);
176         }
177         else if (module instanceof TreeWalkerFilter) {
178             final TreeWalkerFilter filter = (TreeWalkerFilter) module;
179             filters.add(filter);
180         }
181         else {
182             throw new CheckstyleException(
183                 "TreeWalker is not allowed as a parent of " + name
184                         + " Please review 'Parent Module' section for this Check in web"
185                         + " documentation if Check is standard.");
186         }
187     }
188 
189     @Override
190     protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
191         // check if already checked and passed the file
192         if (CommonUtils.matchesFileExtension(file, getFileExtensions())) {
193             final String msg = "%s occurred during the analysis of file %s.";
194             final String fileName = file.getPath();
195 
196             try {
197                 if (!ordinaryChecks.isEmpty()
198                         || !commentChecks.isEmpty()) {
199                     final FileContents contents = new FileContents(fileText);
200                     final DetailAST rootAST = parse(contents);
201 
202                     if (!ordinaryChecks.isEmpty()) {
203                         walk(rootAST, contents, AstState.ORDINARY);
204                     }
205                     if (!commentChecks.isEmpty()) {
206                         final DetailAST astWithComments = appendHiddenCommentNodes(rootAST);
207 
208                         walk(astWithComments, contents, AstState.WITH_COMMENTS);
209                     }
210                     if (filters.isEmpty()) {
211                         addMessages(messages);
212                     }
213                     else {
214                         final SortedSet<LocalizedMessage> filteredMessages =
215                             getFilteredMessages(fileName, contents, rootAST);
216                         addMessages(filteredMessages);
217                     }
218                     messages.clear();
219                 }
220             }
221             catch (final TokenStreamRecognitionException tre) {
222                 final String exceptionMsg = String.format(Locale.ROOT, msg,
223                         "TokenStreamRecognitionException", fileName);
224                 throw new CheckstyleException(exceptionMsg, tre);
225             }
226             catch (RecognitionException | TokenStreamException ex) {
227                 final String exceptionMsg = String.format(Locale.ROOT, msg,
228                         ex.getClass().getSimpleName(), fileName);
229                 throw new CheckstyleException(exceptionMsg, ex);
230             }
231         }
232     }
233 
234     /**
235      * Returns filtered set of {@link LocalizedMessage}.
236      * @param fileName path to the file
237      * @param fileContents the contents of the file
238      * @param rootAST root AST element {@link DetailAST} of the file
239      * @return filtered set of messages
240      */
241     private SortedSet<LocalizedMessage> getFilteredMessages(
242             String fileName, FileContents fileContents, DetailAST rootAST) {
243         final SortedSet<LocalizedMessage> result = new TreeSet<>(messages);
244         for (LocalizedMessage element : messages) {
245             final TreeWalkerAuditEvent event =
246                     new TreeWalkerAuditEvent(fileContents, fileName, element, rootAST);
247             for (TreeWalkerFilter filter : filters) {
248                 if (!filter.accept(event)) {
249                     result.remove(element);
250                     break;
251                 }
252             }
253         }
254         return result;
255     }
256 
257     /**
258      * Register a check for a given configuration.
259      * @param check the check to register
260      * @throws CheckstyleException if an error occurs
261      */
262     private void registerCheck(AbstractCheck check)
263             throws CheckstyleException {
264         validateDefaultTokens(check);
265         final int[] tokens;
266         final Set<String> checkTokens = check.getTokenNames();
267         if (checkTokens.isEmpty()) {
268             tokens = check.getDefaultTokens();
269         }
270         else {
271             tokens = check.getRequiredTokens();
272 
273             //register configured tokens
274             final int[] acceptableTokens = check.getAcceptableTokens();
275             Arrays.sort(acceptableTokens);
276             for (String token : checkTokens) {
277                 final int tokenId = TokenUtils.getTokenId(token);
278                 if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) {
279                     registerCheck(token, check);
280                 }
281                 else {
282                     final String message = String.format(Locale.ROOT, "Token \"%s\" was "
283                             + "not found in Acceptable tokens list in check %s",
284                             token, check.getClass().getName());
285                     throw new CheckstyleException(message);
286                 }
287             }
288         }
289         for (int element : tokens) {
290             registerCheck(element, check);
291         }
292         if (check.isCommentNodesRequired()) {
293             commentChecks.add(check);
294         }
295         else {
296             ordinaryChecks.add(check);
297         }
298     }
299 
300     /**
301      * Register a check for a specified token id.
302      * @param tokenId the id of the token
303      * @param check the check to register
304      * @throws CheckstyleException if Check is misconfigured
305      */
306     private void registerCheck(int tokenId, AbstractCheck check) throws CheckstyleException {
307         registerCheck(TokenUtils.getTokenName(tokenId), check);
308     }
309 
310     /**
311      * Register a check for a specified token name.
312      * @param token the name of the token
313      * @param check the check to register
314      * @throws CheckstyleException if Check is misconfigured
315      */
316     private void registerCheck(String token, AbstractCheck check) throws CheckstyleException {
317         if (check.isCommentNodesRequired()) {
318             tokenToCommentChecks.put(token, check);
319         }
320         else if (TokenUtils.isCommentType(token)) {
321             final String message = String.format(Locale.ROOT, "Check '%s' waits for comment type "
322                     + "token ('%s') and should override 'isCommentNodesRequired()' "
323                     + "method to return 'true'", check.getClass().getName(), token);
324             throw new CheckstyleException(message);
325         }
326         else {
327             tokenToOrdinaryChecks.put(token, check);
328         }
329     }
330 
331     /**
332      * Validates that check's required tokens are subset of default tokens.
333      * @param check to validate
334      * @throws CheckstyleException when validation of default tokens fails
335      */
336     private static void validateDefaultTokens(AbstractCheck check) throws CheckstyleException {
337         if (check.getRequiredTokens().length != 0) {
338             final int[] defaultTokens = check.getDefaultTokens();
339             Arrays.sort(defaultTokens);
340             for (final int token : check.getRequiredTokens()) {
341                 if (Arrays.binarySearch(defaultTokens, token) < 0) {
342                     final String message = String.format(Locale.ROOT, "Token \"%s\" from required "
343                             + "tokens was not found in default tokens list in check %s",
344                             token, check.getClass().getName());
345                     throw new CheckstyleException(message);
346                 }
347             }
348         }
349     }
350 
351     /**
352      * Initiates the walk of an AST.
353      * @param ast the root AST
354      * @param contents the contents of the file the AST was generated from.
355      * @param astState state of AST.
356      */
357     private void walk(DetailAST ast, FileContents contents,
358             AstState astState) {
359         notifyBegin(ast, contents, astState);
360 
361         // empty files are not flagged by javac, will yield ast == null
362         if (ast != null) {
363             processIter(ast, astState);
364         }
365         notifyEnd(ast, astState);
366     }
367 
368     /**
369      * Notify checks that we are about to begin walking a tree.
370      * @param rootAST the root of the tree.
371      * @param contents the contents of the file the AST was generated from.
372      * @param astState state of AST.
373      */
374     private void notifyBegin(DetailAST rootAST, FileContents contents,
375             AstState astState) {
376         final Set<AbstractCheck> checks;
377 
378         if (astState == AstState.WITH_COMMENTS) {
379             checks = commentChecks;
380         }
381         else {
382             checks = ordinaryChecks;
383         }
384 
385         for (AbstractCheck check : checks) {
386             check.setFileContents(contents);
387             check.clearMessages();
388             check.beginTree(rootAST);
389         }
390     }
391 
392     /**
393      * Notify checks that we have finished walking a tree.
394      * @param rootAST the root of the tree.
395      * @param astState state of AST.
396      */
397     private void notifyEnd(DetailAST rootAST, AstState astState) {
398         final Set<AbstractCheck> checks;
399 
400         if (astState == AstState.WITH_COMMENTS) {
401             checks = commentChecks;
402         }
403         else {
404             checks = ordinaryChecks;
405         }
406 
407         for (AbstractCheck check : checks) {
408             check.finishTree(rootAST);
409             messages.addAll(check.getMessages());
410         }
411     }
412 
413     /**
414      * Notify checks that visiting a node.
415      * @param ast the node to notify for.
416      * @param astState state of AST.
417      */
418     private void notifyVisit(DetailAST ast, AstState astState) {
419         final Collection<AbstractCheck> visitors = getListOfChecks(ast, astState);
420 
421         if (visitors != null) {
422             for (AbstractCheck check : visitors) {
423                 check.visitToken(ast);
424             }
425         }
426     }
427 
428     /**
429      * Notify checks that leaving a node.
430      * @param ast
431      *        the node to notify for
432      * @param astState state of AST.
433      */
434     private void notifyLeave(DetailAST ast, AstState astState) {
435         final Collection<AbstractCheck> visitors = getListOfChecks(ast, astState);
436 
437         if (visitors != null) {
438             for (AbstractCheck check : visitors) {
439                 check.leaveToken(ast);
440             }
441         }
442     }
443 
444     /**
445      * Method returns list of checks.
446      *
447      * @param ast
448      *            the node to notify for
449      * @param astState
450      *            state of AST.
451      * @return list of visitors
452      */
453     private Collection<AbstractCheck> getListOfChecks(DetailAST ast, AstState astState) {
454         Collection<AbstractCheck> visitors = null;
455         final String tokenType = TokenUtils.getTokenName(ast.getType());
456 
457         if (astState == AstState.WITH_COMMENTS) {
458             if (tokenToCommentChecks.containsKey(tokenType)) {
459                 visitors = tokenToCommentChecks.get(tokenType);
460             }
461         }
462         else {
463             if (tokenToOrdinaryChecks.containsKey(tokenType)) {
464                 visitors = tokenToOrdinaryChecks.get(tokenType);
465             }
466         }
467         return visitors;
468     }
469 
470     /**
471      * Static helper method to parses a Java source file.
472      *
473      * @param contents
474      *                contains the contents of the file
475      * @return the root of the AST
476      * @throws TokenStreamException
477      *                 if lexing failed
478      * @throws RecognitionException
479      *                 if parsing failed
480      */
481     public static DetailAST parse(FileContents contents)
482             throws RecognitionException, TokenStreamException {
483         final String fullText = contents.getText().getFullText().toString();
484         final Reader reader = new StringReader(fullText);
485         final GeneratedJavaLexer lexer = new GeneratedJavaLexer(reader);
486         lexer.setCommentListener(contents);
487         lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
488 
489         final TokenStreamHiddenTokenFilter filter =
490                 new TokenStreamHiddenTokenFilter(lexer);
491         filter.hide(TokenTypes.SINGLE_LINE_COMMENT);
492         filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN);
493 
494         final GeneratedJavaRecognizer parser =
495             new GeneratedJavaRecognizer(filter);
496         parser.setFilename(contents.getFileName());
497         parser.setASTNodeClass(DetailAST.class.getName());
498         parser.compilationUnit();
499 
500         return (DetailAST) parser.getAST();
501     }
502 
503     /**
504      * Parses Java source file. Result AST contains comment nodes.
505      * @param contents source file content
506      * @return DetailAST tree
507      * @throws RecognitionException if parser failed
508      * @throws TokenStreamException if lexer failed
509      */
510     public static DetailAST parseWithComments(FileContents contents)
511             throws RecognitionException, TokenStreamException {
512         return appendHiddenCommentNodes(parse(contents));
513     }
514 
515     @Override
516     public void destroy() {
517         ordinaryChecks.forEach(AbstractCheck::destroy);
518         commentChecks.forEach(AbstractCheck::destroy);
519         super.destroy();
520     }
521 
522     @Override
523     public Set<String> getExternalResourceLocations() {
524         final Set<String> ordinaryChecksResources =
525                 getExternalResourceLocationsOfChecks(ordinaryChecks);
526         final Set<String> commentChecksResources =
527                 getExternalResourceLocationsOfChecks(commentChecks);
528         final Set<String> filtersResources =
529                 getExternalResourceLocationsOfFilters();
530         final int resultListSize = commentChecksResources.size()
531                 + ordinaryChecksResources.size()
532                 + filtersResources.size();
533         final Set<String> resourceLocations = new HashSet<>(resultListSize);
534         resourceLocations.addAll(ordinaryChecksResources);
535         resourceLocations.addAll(commentChecksResources);
536         resourceLocations.addAll(filtersResources);
537         return resourceLocations;
538     }
539 
540     /**
541      * Returns a set of external configuration resource locations which are used by the filters set.
542      * @return a set of external configuration resource locations which are used by the filters set.
543      */
544     private Set<String> getExternalResourceLocationsOfFilters() {
545         final Set<String> externalConfigurationResources = new HashSet<>();
546         filters.stream().filter(filter -> filter instanceof ExternalResourceHolder)
547                 .forEach(filter -> {
548                     final Set<String> checkExternalResources =
549                         ((ExternalResourceHolder) filter).getExternalResourceLocations();
550                     externalConfigurationResources.addAll(checkExternalResources);
551                 });
552         return externalConfigurationResources;
553     }
554 
555     /**
556      * Returns a set of external configuration resource locations which are used by the checks set.
557      * @param checks a set of checks.
558      * @return a set of external configuration resource locations which are used by the checks set.
559      */
560     private static Set<String> getExternalResourceLocationsOfChecks(Set<AbstractCheck> checks) {
561         final Set<String> externalConfigurationResources = new HashSet<>();
562         checks.stream().filter(check -> check instanceof ExternalResourceHolder).forEach(check -> {
563             final Set<String> checkExternalResources =
564                 ((ExternalResourceHolder) check).getExternalResourceLocations();
565             externalConfigurationResources.addAll(checkExternalResources);
566         });
567         return externalConfigurationResources;
568     }
569 
570     /**
571      * Processes a node calling interested checks at each node.
572      * Uses iterative algorithm.
573      * @param root the root of tree for process
574      * @param astState state of AST.
575      */
576     private void processIter(DetailAST root, AstState astState) {
577         DetailAST curNode = root;
578         while (curNode != null) {
579             notifyVisit(curNode, astState);
580             DetailAST toVisit = curNode.getFirstChild();
581             while (curNode != null && toVisit == null) {
582                 notifyLeave(curNode, astState);
583                 toVisit = curNode.getNextSibling();
584                 if (toVisit == null) {
585                     curNode = curNode.getParent();
586                 }
587             }
588             curNode = toVisit;
589         }
590     }
591 
592     /**
593      * Appends comment nodes to existing AST.
594      * It traverses each node in AST, looks for hidden comment tokens
595      * and appends found comment tokens as nodes in AST.
596      * @param root
597      *        root of AST.
598      * @return root of AST with comment nodes.
599      */
600     private static DetailAST appendHiddenCommentNodes(DetailAST root) {
601         DetailAST result = root;
602         DetailAST curNode = root;
603         DetailAST lastNode = root;
604 
605         while (curNode != null) {
606             if (isPositionGreater(curNode, lastNode)) {
607                 lastNode = curNode;
608             }
609 
610             CommonHiddenStreamToken tokenBefore = curNode.getHiddenBefore();
611             DetailAST currentSibling = curNode;
612             while (tokenBefore != null) {
613                 final DetailAST newCommentNode =
614                          createCommentAstFromToken(tokenBefore);
615 
616                 currentSibling.addPreviousSibling(newCommentNode);
617 
618                 if (currentSibling == result) {
619                     result = newCommentNode;
620                 }
621 
622                 currentSibling = newCommentNode;
623                 tokenBefore = tokenBefore.getHiddenBefore();
624             }
625 
626             DetailAST toVisit = curNode.getFirstChild();
627             while (curNode != null && toVisit == null) {
628                 toVisit = curNode.getNextSibling();
629                 if (toVisit == null) {
630                     curNode = curNode.getParent();
631                 }
632             }
633             curNode = toVisit;
634         }
635         if (lastNode != null) {
636             CommonHiddenStreamToken tokenAfter = lastNode.getHiddenAfter();
637             DetailAST currentSibling = lastNode;
638             while (tokenAfter != null) {
639                 final DetailAST newCommentNode =
640                         createCommentAstFromToken(tokenAfter);
641 
642                 currentSibling.addNextSibling(newCommentNode);
643 
644                 currentSibling = newCommentNode;
645                 tokenAfter = tokenAfter.getHiddenAfter();
646             }
647         }
648         return result;
649     }
650 
651     /**
652      * Checks if position of first DetailAST is greater than position of
653      * second DetailAST. Position is line number and column number in source
654      * file.
655      * @param ast1
656      *        first DetailAST node.
657      * @param ast2
658      *        second DetailAST node.
659      * @return true if position of ast1 is greater than position of ast2.
660      */
661     private static boolean isPositionGreater(DetailAST ast1, DetailAST ast2) {
662         boolean isGreater = ast1.getLineNo() > ast2.getLineNo();
663         if (!isGreater && ast1.getLineNo() == ast2.getLineNo()) {
664             isGreater = ast1.getColumnNo() > ast2.getColumnNo();
665         }
666         return isGreater;
667     }
668 
669     /**
670      * Create comment AST from token. Depending on token type
671      * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created.
672      * @param token
673      *        Token object.
674      * @return DetailAST of comment node.
675      */
676     private static DetailAST createCommentAstFromToken(Token token) {
677         final DetailAST commentAst;
678         if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
679             commentAst = createSlCommentNode(token);
680         }
681         else {
682             commentAst = CommonUtils.createBlockCommentNode(token);
683         }
684         return commentAst;
685     }
686 
687     /**
688      * Create single-line comment from token.
689      * @param token
690      *        Token object.
691      * @return DetailAST with SINGLE_LINE_COMMENT type.
692      */
693     private static DetailAST createSlCommentNode(Token token) {
694         final DetailAST slComment = new DetailAST();
695         slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
696         slComment.setText("//");
697 
698         // column counting begins from 0
699         slComment.setColumnNo(token.getColumn() - 1);
700         slComment.setLineNo(token.getLine());
701 
702         final DetailAST slCommentContent = new DetailAST();
703         slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
704 
705         // column counting begins from 0
706         // plus length of '//'
707         slCommentContent.setColumnNo(token.getColumn() - 1 + 2);
708         slCommentContent.setLineNo(token.getLine());
709         slCommentContent.setText(token.getText());
710 
711         slComment.addChild(slCommentContent);
712         return slComment;
713     }
714 
715     /**
716      * State of AST.
717      * Indicates whether tree contains certain nodes.
718      */
719     private enum AstState {
720         /**
721          * Ordinary tree.
722          */
723         ORDINARY,
724 
725         /**
726          * AST contains comment nodes.
727          */
728         WITH_COMMENTS
729     }
730 }