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 java.io.File;
23  import java.io.IOException;
24  import java.nio.charset.StandardCharsets;
25  import java.util.List;
26  import java.util.ListIterator;
27  import java.util.Locale;
28  
29  import org.antlr.v4.runtime.BaseErrorListener;
30  import org.antlr.v4.runtime.CharStream;
31  import org.antlr.v4.runtime.CharStreams;
32  import org.antlr.v4.runtime.CommonToken;
33  import org.antlr.v4.runtime.CommonTokenStream;
34  import org.antlr.v4.runtime.RecognitionException;
35  import org.antlr.v4.runtime.Recognizer;
36  import org.antlr.v4.runtime.Token;
37  
38  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
39  import com.puppycrawl.tools.checkstyle.api.DetailAST;
40  import com.puppycrawl.tools.checkstyle.api.FileContents;
41  import com.puppycrawl.tools.checkstyle.api.FileText;
42  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
43  import com.puppycrawl.tools.checkstyle.grammar.CompositeLexerContextCache;
44  import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageLexer;
45  import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser;
46  import com.puppycrawl.tools.checkstyle.utils.ParserUtil;
47  
48  /**
49   * Helper methods to parse java source files.
50   *
51   */
52  // -@cs[ClassDataAbstractionCoupling] No way to split up class usage.
53  public final class JavaParser {
54  
55      /**
56       * Enum to be used for test if comments should be used.
57       */
58      public enum Options {
59  
60          /**
61           * Comments nodes should be processed.
62           */
63          WITH_COMMENTS,
64  
65          /**
66           * Comments nodes should be ignored.
67           */
68          WITHOUT_COMMENTS,
69  
70      }
71  
72      /** Stop instances being created. **/
73      private JavaParser() {
74      }
75  
76      /**
77       * Static helper method to parses a Java source file.
78       *
79       * @param contents contains the contents of the file
80       * @return the root of the AST
81       * @throws CheckstyleException if the contents is not a valid Java source
82       */
83      public static DetailAST parse(FileContents contents)
84              throws CheckstyleException {
85          final String fullText = contents.getText().getFullText().toString();
86          final CharStream codePointCharStream = CharStreams.fromString(fullText);
87          final JavaLanguageLexer lexer = new JavaLanguageLexer(codePointCharStream, true);
88          final CompositeLexerContextCache contextCache = new CompositeLexerContextCache(lexer);
89          lexer.setCommentListener(contents);
90          lexer.setContextCache(contextCache);
91  
92          final CommonTokenStream tokenStream = new CommonTokenStream(lexer);
93          final JavaLanguageParser parser =
94                  new JavaLanguageParser(tokenStream, JavaLanguageParser.CLEAR_DFA_LIMIT);
95          parser.setErrorHandler(new CheckstyleParserErrorStrategy());
96          parser.removeErrorListeners();
97          parser.addErrorListener(new CheckstyleErrorListener());
98  
99          final JavaLanguageParser.CompilationUnitContext compilationUnit;
100         try {
101             compilationUnit = parser.compilationUnit();
102         }
103         catch (IllegalStateException ex) {
104             final String exceptionMsg = String.format(Locale.ROOT,
105                 "%s occurred while parsing file %s.",
106                 ex.getClass().getSimpleName(), contents.getFileName());
107             throw new CheckstyleException(exceptionMsg, ex);
108         }
109 
110         return new JavaAstVisitor(tokenStream).visit(compilationUnit);
111     }
112 
113     /**
114      * Parse a text and return the parse tree.
115      *
116      * @param text the text to parse
117      * @param options {@link Options} to control inclusion of comment nodes
118      * @return the root node of the parse tree
119      * @throws CheckstyleException if the text is not a valid Java source
120      */
121     public static DetailAST parseFileText(FileText text, Options options)
122             throws CheckstyleException {
123         final FileContents contents = new FileContents(text);
124         final DetailAST ast = parse(contents);
125         if (options == Options.WITH_COMMENTS) {
126             appendHiddenCommentNodes(ast);
127         }
128         return ast;
129     }
130 
131     /**
132      * Parses Java source file.
133      *
134      * @param file the file to parse
135      * @param options {@link Options} to control inclusion of comment nodes
136      * @return DetailAST tree
137      * @throws IOException if the file could not be read
138      * @throws CheckstyleException if the file is not a valid Java source file
139      */
140     public static DetailAST parseFile(File file, Options options)
141             throws IOException, CheckstyleException {
142         final FileText text = new FileText(file,
143             StandardCharsets.UTF_8.name());
144         return parseFileText(text, options);
145     }
146 
147     /**
148      * Appends comment nodes to existing AST.
149      * It traverses each node in AST, looks for hidden comment tokens
150      * and appends found comment tokens as nodes in AST.
151      *
152      * @param root of AST
153      * @return root of AST with comment nodes
154      */
155     public static DetailAST appendHiddenCommentNodes(DetailAST root) {
156         DetailAST curNode = root;
157         DetailAST lastNode = root;
158 
159         while (curNode != null) {
160             lastNode = curNode;
161 
162             final List<Token> hiddenBefore = ((DetailAstImpl) curNode).getHiddenBefore();
163             if (hiddenBefore != null) {
164                 DetailAST currentSibling = curNode;
165 
166                 final ListIterator<Token> reverseCommentsIterator =
167                         hiddenBefore.listIterator(hiddenBefore.size());
168 
169                 while (reverseCommentsIterator.hasPrevious()) {
170                     final DetailAST newCommentNode =
171                             createCommentAstFromToken((CommonToken)
172                                     reverseCommentsIterator.previous());
173                     ((DetailAstImpl) currentSibling).addPreviousSibling(newCommentNode);
174 
175                     currentSibling = newCommentNode;
176                 }
177             }
178 
179             DetailAST toVisit = curNode.getFirstChild();
180             while (curNode != null && toVisit == null) {
181                 toVisit = curNode.getNextSibling();
182                 curNode = curNode.getParent();
183             }
184             curNode = toVisit;
185         }
186         if (lastNode != null) {
187             final List<Token> hiddenAfter = ((DetailAstImpl) lastNode).getHiddenAfter();
188             if (hiddenAfter != null) {
189                 DetailAST currentSibling = lastNode;
190                 for (Token token : hiddenAfter) {
191                     final DetailAST newCommentNode =
192                             createCommentAstFromToken((CommonToken) token);
193 
194                     ((DetailAstImpl) currentSibling).addNextSibling(newCommentNode);
195 
196                     currentSibling = newCommentNode;
197                 }
198             }
199         }
200         return root;
201     }
202 
203     /**
204      * Create comment AST from token. Depending on token type
205      * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created.
206      *
207      * @param token to create the AST
208      * @return DetailAST of comment node
209      */
210     private static DetailAST createCommentAstFromToken(CommonToken token) {
211         final DetailAST commentAst;
212         if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
213             commentAst = createSlCommentNode(token);
214         }
215         else {
216             commentAst = ParserUtil.createBlockCommentNode(token);
217         }
218         return commentAst;
219     }
220 
221     /**
222      * Create single-line comment from token.
223      *
224      * @param token to create the AST
225      * @return DetailAST with SINGLE_LINE_COMMENT type
226      */
227     private static DetailAST createSlCommentNode(Token token) {
228         final DetailAstImpl slComment = new DetailAstImpl();
229         slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
230         slComment.setText("//");
231 
232         slComment.setColumnNo(token.getCharPositionInLine());
233         slComment.setLineNo(token.getLine());
234 
235         final DetailAstImpl slCommentContent = new DetailAstImpl();
236         slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
237 
238         // plus length of '//'
239         slCommentContent.setColumnNo(token.getCharPositionInLine() + 2);
240         slCommentContent.setLineNo(token.getLine());
241         slCommentContent.setText(token.getText());
242 
243         slComment.addChild(slCommentContent);
244         return slComment;
245     }
246 
247     /**
248      * Custom error listener to provide detailed exception message.
249      */
250     private static final class CheckstyleErrorListener extends BaseErrorListener {
251 
252         @Override
253         public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
254                                 int line, int charPositionInLine,
255                                 String msg, RecognitionException ex) {
256             final String message = line + ":" + charPositionInLine + ": " + msg;
257             throw new IllegalStateException(message, ex);
258         }
259     }
260 }