001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 089 final CommonTokenStream tokenStream = new CommonTokenStream(lexer); 090 final JavaLanguageParser parser = 091 new JavaLanguageParser(tokenStream, JavaLanguageParser.CLEAR_DFA_LIMIT); 092 parser.setErrorHandler(new CheckstyleParserErrorStrategy()); 093 parser.removeErrorListeners(); 094 parser.addErrorListener(new CheckstyleErrorListener()); 095 096 final JavaLanguageParser.CompilationUnitContext compilationUnit; 097 try { 098 compilationUnit = parser.compilationUnit(); 099 } 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}