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