001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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 if (((DetailAstImpl) curNode).getHiddenBefore() != null) { 161 DetailAST currentSibling = curNode; 162 163 final List<Token> commentsList = ((DetailAstImpl) curNode).getHiddenBefore(); 164 final ListIterator<Token> reverseCommentsIterator = 165 commentsList.listIterator(commentsList.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 && ((DetailAstImpl) lastNode).getHiddenAfter() != null) { 185 DetailAST currentSibling = lastNode; 186 for (Token token: ((DetailAstImpl) lastNode).getHiddenAfter()) { 187 final DetailAST newCommentNode = 188 createCommentAstFromToken((CommonToken) token); 189 190 ((DetailAstImpl) currentSibling).addNextSibling(newCommentNode); 191 192 currentSibling = newCommentNode; 193 } 194 } 195 return root; 196 } 197 198 /** 199 * Create comment AST from token. Depending on token type 200 * SINGLE_LINE_COMMENT or BLOCK_COMMENT_BEGIN is created. 201 * 202 * @param token to create the AST 203 * @return DetailAST of comment node 204 */ 205 private static DetailAST createCommentAstFromToken(CommonToken token) { 206 final DetailAST commentAst; 207 if (token.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 208 commentAst = createSlCommentNode(token); 209 } 210 else { 211 commentAst = ParserUtil.createBlockCommentNode(token); 212 } 213 return commentAst; 214 } 215 216 /** 217 * Create single-line comment from token. 218 * 219 * @param token to create the AST 220 * @return DetailAST with SINGLE_LINE_COMMENT type 221 */ 222 private static DetailAST createSlCommentNode(CommonToken token) { 223 final DetailAstImpl slComment = new DetailAstImpl(); 224 slComment.setType(TokenTypes.SINGLE_LINE_COMMENT); 225 slComment.setText("//"); 226 227 slComment.setColumnNo(token.getCharPositionInLine()); 228 slComment.setLineNo(token.getLine()); 229 230 final DetailAstImpl slCommentContent = new DetailAstImpl(); 231 slCommentContent.setType(TokenTypes.COMMENT_CONTENT); 232 233 // plus length of '//' 234 slCommentContent.setColumnNo(token.getCharPositionInLine() + 2); 235 slCommentContent.setLineNo(token.getLine()); 236 slCommentContent.setText(token.getText()); 237 238 slComment.addChild(slCommentContent); 239 return slComment; 240 } 241 242 /** 243 * Custom error listener to provide detailed exception message. 244 */ 245 private static class CheckstyleErrorListener extends BaseErrorListener { 246 247 @Override 248 public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, 249 int line, int charPositionInLine, 250 String msg, RecognitionException ex) { 251 final String message = line + ":" + charPositionInLine + ": " + msg; 252 throw new IllegalStateException(message, ex); 253 } 254 } 255}