001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle; 021 022import java.io.File; 023import java.io.IOException; 024import java.nio.charset.StandardCharsets; 025import java.util.List; 026import java.util.ListIterator; 027import java.util.Locale; 028 029import org.antlr.v4.runtime.BaseErrorListener; 030import org.antlr.v4.runtime.CharStream; 031import org.antlr.v4.runtime.CharStreams; 032import org.antlr.v4.runtime.CommonToken; 033import org.antlr.v4.runtime.CommonTokenStream; 034import org.antlr.v4.runtime.RecognitionException; 035import org.antlr.v4.runtime.Recognizer; 036import org.antlr.v4.runtime.Token; 037 038import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 039import com.puppycrawl.tools.checkstyle.api.DetailAST; 040import com.puppycrawl.tools.checkstyle.api.FileContents; 041import com.puppycrawl.tools.checkstyle.api.FileText; 042import com.puppycrawl.tools.checkstyle.api.TokenTypes; 043import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageLexer; 044import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser; 045import com.puppycrawl.tools.checkstyle.utils.ParserUtil; 046 047/** 048 * Helper methods to parse java source files. 049 * 050 */ 051// -@cs[ClassDataAbstractionCoupling] No way to split up class usage. 052public final class JavaParser { 053 054 /** 055 * Enum to be used for test if comments should be used. 056 */ 057 public enum Options { 058 059 /** 060 * Comments nodes should be processed. 061 */ 062 WITH_COMMENTS, 063 064 /** 065 * Comments nodes should be ignored. 066 */ 067 WITHOUT_COMMENTS, 068 069 } 070 071 /** Stop instances being created. **/ 072 private JavaParser() { 073 } 074 075 /** 076 * Static helper method to parses a Java source file. 077 * 078 * @param contents contains the contents of the file 079 * @return the root of the AST 080 * @throws CheckstyleException if the contents is not a valid Java source 081 */ 082 public static DetailAST parse(FileContents contents) 083 throws CheckstyleException { 084 final String fullText = contents.getText().getFullText().toString(); 085 final CharStream codePointCharStream = CharStreams.fromString(fullText); 086 final JavaLanguageLexer lexer = new JavaLanguageLexer(codePointCharStream, true); 087 lexer.setCommentListener(contents); 088 lexer.removeErrorListeners(); 089 090 final CommonTokenStream tokenStream = new CommonTokenStream(lexer); 091 final JavaLanguageParser parser = 092 new JavaLanguageParser(tokenStream, JavaLanguageParser.CLEAR_DFA_LIMIT); 093 parser.setErrorHandler(new CheckstyleParserErrorStrategy()); 094 parser.removeErrorListeners(); 095 parser.addErrorListener(new CheckstyleErrorListener()); 096 097 final JavaLanguageParser.CompilationUnitContext compilationUnit; 098 try { 099 compilationUnit = parser.compilationUnit(); 100 } 101 catch (IllegalStateException ex) { 102 final String exceptionMsg = String.format(Locale.ROOT, 103 "%s occurred while parsing file %s.", 104 ex.getClass().getSimpleName(), contents.getFileName()); 105 throw new CheckstyleException(exceptionMsg, ex); 106 } 107 108 return new JavaAstVisitor(tokenStream).visit(compilationUnit); 109 } 110 111 /** 112 * Parse a text and return the parse tree. 113 * 114 * @param text the text to parse 115 * @param options {@link Options} to control inclusion of comment nodes 116 * @return the root node of the parse tree 117 * @throws CheckstyleException if the text is not a valid Java source 118 */ 119 public static DetailAST parseFileText(FileText text, Options options) 120 throws CheckstyleException { 121 final FileContents contents = new FileContents(text); 122 final DetailAST ast = parse(contents); 123 if (options == Options.WITH_COMMENTS) { 124 appendHiddenCommentNodes(ast); 125 } 126 return ast; 127 } 128 129 /** 130 * Parses Java source file. 131 * 132 * @param file the file to parse 133 * @param options {@link Options} to control inclusion of comment nodes 134 * @return DetailAST tree 135 * @throws IOException if the file could not be read 136 * @throws CheckstyleException if the file is not a valid Java source file 137 */ 138 public static DetailAST parseFile(File file, Options options) 139 throws IOException, CheckstyleException { 140 final FileText text = new FileText(file.getAbsoluteFile(), 141 StandardCharsets.UTF_8.name()); 142 return parseFileText(text, options); 143 } 144 145 /** 146 * Appends comment nodes to existing AST. 147 * It traverses each node in AST, looks for hidden comment tokens 148 * and appends found comment tokens as nodes in AST. 149 * 150 * @param root of AST 151 * @return root of AST with comment nodes 152 */ 153 public static DetailAST appendHiddenCommentNodes(DetailAST root) { 154 DetailAST curNode = root; 155 DetailAST lastNode = root; 156 157 while (curNode != null) { 158 lastNode = curNode; 159 160 final List<Token> hiddenBefore = ((DetailAstImpl) curNode).getHiddenBefore(); 161 if (hiddenBefore != null) { 162 DetailAST currentSibling = curNode; 163 164 final ListIterator<Token> reverseCommentsIterator = 165 hiddenBefore.listIterator(hiddenBefore.size()); 166 167 while (reverseCommentsIterator.hasPrevious()) { 168 final DetailAST newCommentNode = 169 createCommentAstFromToken((CommonToken) 170 reverseCommentsIterator.previous()); 171 ((DetailAstImpl) currentSibling).addPreviousSibling(newCommentNode); 172 173 currentSibling = newCommentNode; 174 } 175 } 176 177 DetailAST toVisit = curNode.getFirstChild(); 178 while (curNode != null && toVisit == null) { 179 toVisit = curNode.getNextSibling(); 180 curNode = curNode.getParent(); 181 } 182 curNode = toVisit; 183 } 184 if (lastNode != null) { 185 final List<Token> hiddenAfter = ((DetailAstImpl) lastNode).getHiddenAfter(); 186 if (hiddenAfter != null) { 187 DetailAST currentSibling = lastNode; 188 for (Token token : hiddenAfter) { 189 final DetailAST newCommentNode = 190 createCommentAstFromToken((CommonToken) token); 191 192 ((DetailAstImpl) currentSibling).addNextSibling(newCommentNode); 193 194 currentSibling = newCommentNode; 195 } 196 } 197 } 198 return root; 199 } 200 201 /** 202 * Create comment AST from token. Depending on token type 203 * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created. 204 * 205 * @param token to create the AST 206 * @return DetailAST of comment node 207 */ 208 private static DetailAST createCommentAstFromToken(CommonToken token) { 209 final DetailAST commentAst; 210 if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 211 commentAst = createSlCommentNode(token); 212 } 213 else { 214 commentAst = ParserUtil.createBlockCommentNode(token); 215 } 216 return commentAst; 217 } 218 219 /** 220 * Create single-line comment from token. 221 * 222 * @param token to create the AST 223 * @return DetailAST with SINGLE_LINE_COMMENT type 224 */ 225 private static DetailAST createSlCommentNode(Token token) { 226 final DetailAstImpl slComment = new DetailAstImpl(); 227 slComment.setType(TokenTypes.SINGLE_LINE_COMMENT); 228 slComment.setText("//"); 229 230 slComment.setColumnNo(token.getCharPositionInLine()); 231 slComment.setLineNo(token.getLine()); 232 233 final DetailAstImpl slCommentContent = new DetailAstImpl(); 234 slCommentContent.setType(TokenTypes.COMMENT_CONTENT); 235 236 // plus length of '//' 237 slCommentContent.setColumnNo(token.getCharPositionInLine() + 2); 238 slCommentContent.setLineNo(token.getLine()); 239 slCommentContent.setText(token.getText()); 240 241 slComment.addChild(slCommentContent); 242 return slComment; 243 } 244 245 /** 246 * Custom error listener to provide detailed exception message. 247 */ 248 private static final class CheckstyleErrorListener extends BaseErrorListener { 249 250 @Override 251 public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, 252 int line, int charPositionInLine, 253 String msg, RecognitionException ex) { 254 final String message = line + ":" + charPositionInLine + ": " + msg; 255 throw new IllegalStateException(message, ex); 256 } 257 } 258}