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