001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.util.ArrayDeque; 023import java.util.Deque; 024import java.util.List; 025 026import org.antlr.v4.runtime.ANTLRInputStream; 027import org.antlr.v4.runtime.BailErrorStrategy; 028import org.antlr.v4.runtime.BaseErrorListener; 029import org.antlr.v4.runtime.BufferedTokenStream; 030import org.antlr.v4.runtime.CommonToken; 031import org.antlr.v4.runtime.CommonTokenStream; 032import org.antlr.v4.runtime.FailedPredicateException; 033import org.antlr.v4.runtime.InputMismatchException; 034import org.antlr.v4.runtime.NoViableAltException; 035import org.antlr.v4.runtime.Parser; 036import org.antlr.v4.runtime.ParserRuleContext; 037import org.antlr.v4.runtime.RecognitionException; 038import org.antlr.v4.runtime.Recognizer; 039import org.antlr.v4.runtime.Token; 040import org.antlr.v4.runtime.misc.Interval; 041import org.antlr.v4.runtime.misc.ParseCancellationException; 042import org.antlr.v4.runtime.tree.ParseTree; 043import org.antlr.v4.runtime.tree.TerminalNode; 044 045import com.google.common.base.CaseFormat; 046import com.puppycrawl.tools.checkstyle.api.DetailAST; 047import com.puppycrawl.tools.checkstyle.api.DetailNode; 048import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 049import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl; 050import com.puppycrawl.tools.checkstyle.grammars.javadoc.JavadocLexer; 051import com.puppycrawl.tools.checkstyle.grammars.javadoc.JavadocParser; 052import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 053 054/** 055 * Used for parsing Javadoc comment as DetailNode tree. 056 * @author bizmailov 057 * 058 */ 059public class JavadocDetailNodeParser { 060 061 /** 062 * Message key of error message. Missed close HTML tag breaks structure 063 * of parse tree, so parser stops parsing and generates such error 064 * message. This case is special because parser prints error like 065 * {@code "no viable alternative at input 'b \n *\n'"} and it is not 066 * clear that error is about missed close HTML tag. 067 */ 068 public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = "javadoc.missed.html.close"; 069 070 /** 071 * Message key of error message. 072 */ 073 public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG = 074 "javadoc.wrong.singleton.html.tag"; 075 076 /** 077 * Parse error while rule recognition. 078 */ 079 public static final String MSG_JAVADOC_PARSE_RULE_ERROR = "javadoc.parse.rule.error"; 080 081 /** 082 * Message property key for the Unclosed HTML message. 083 */ 084 public static final String MSG_UNCLOSED_HTML_TAG = "javadoc.unclosedHtml"; 085 086 /** Symbols with which javadoc starts. */ 087 private static final String JAVADOC_START = "/**"; 088 089 /** 090 * Line number of the Block comment AST that is being parsed. 091 */ 092 private int blockCommentLineNumber; 093 094 /** 095 * Custom error listener. 096 */ 097 private DescriptiveErrorListener errorListener; 098 099 /** 100 * Parses Javadoc comment as DetailNode tree. 101 * @param javadocCommentAst 102 * DetailAST of Javadoc comment 103 * @return DetailNode tree of Javadoc comment 104 */ 105 public ParseStatus parseJavadocAsDetailNode(DetailAST javadocCommentAst) { 106 blockCommentLineNumber = javadocCommentAst.getLineNo(); 107 108 final String javadocComment = JavadocUtils.getJavadocCommentContent(javadocCommentAst); 109 110 // Use a new error listener each time to be able to use 111 // one check instance for multiple files to be checked 112 // without getting side effects. 113 errorListener = new DescriptiveErrorListener(); 114 115 // Log messages should have line number in scope of file, 116 // not in scope of Javadoc comment. 117 // Offset is line number of beginning of Javadoc comment. 118 errorListener.setOffset(javadocCommentAst.getLineNo() - 1); 119 120 final ParseStatus result = new ParseStatus(); 121 122 try { 123 final JavadocParser javadocParser = createJavadocParser(javadocComment); 124 125 final ParseTree javadocParseTree = javadocParser.javadoc(); 126 127 final DetailNode tree = convertParseTreeToDetailNode(javadocParseTree); 128 // adjust first line to indent of /** 129 adjustFirstLineToJavadocIndent(tree, 130 javadocCommentAst.getColumnNo() 131 + JAVADOC_START.length()); 132 result.setTree(tree); 133 result.firstNonTightHtmlTag = getFirstNonTightHtmlTag(javadocParser); 134 } 135 catch (ParseCancellationException | IllegalArgumentException ex) { 136 ParseErrorMessage parseErrorMessage = null; 137 138 if (ex.getCause() instanceof FailedPredicateException 139 || ex.getCause() instanceof NoViableAltException) { 140 final RecognitionException recognitionEx = (RecognitionException) ex.getCause(); 141 if (recognitionEx.getCtx() instanceof JavadocParser.HtmlTagContext) { 142 final Token htmlTagNameStart = getMissedHtmlTag(recognitionEx); 143 parseErrorMessage = new ParseErrorMessage( 144 errorListener.offset + htmlTagNameStart.getLine(), 145 MSG_JAVADOC_MISSED_HTML_CLOSE, 146 htmlTagNameStart.getCharPositionInLine(), 147 htmlTagNameStart.getText()); 148 } 149 } 150 151 if (parseErrorMessage == null) { 152 // If syntax error occurs then message is printed by error listener 153 // and parser throws this runtime exception to stop parsing. 154 // Just stop processing current Javadoc comment. 155 parseErrorMessage = errorListener.getErrorMessage(); 156 } 157 158 result.setParseErrorMessage(parseErrorMessage); 159 } 160 161 return result; 162 } 163 164 /** 165 * Parses block comment content as javadoc comment. 166 * @param blockComment 167 * block comment content. 168 * @return parse tree 169 * @noinspection deprecation 170 */ 171 private JavadocParser createJavadocParser(String blockComment) { 172 final ANTLRInputStream input = new ANTLRInputStream(blockComment); 173 174 final JavadocLexer lexer = new JavadocLexer(input); 175 176 final CommonTokenStream tokens = new CommonTokenStream(lexer); 177 178 final JavadocParser parser = new JavadocParser(tokens); 179 180 // remove default error listeners 181 parser.removeErrorListeners(); 182 183 // add custom error listener that logs syntax errors 184 parser.addErrorListener(errorListener); 185 186 // JavadocParserErrorStrategy stops parsing on first parse error encountered unlike the 187 // DefaultErrorStrategy used by ANTLR which rather attempts error recovery. 188 parser.setErrorHandler(new JavadocParserErrorStrategy()); 189 190 return parser; 191 } 192 193 /** 194 * Converts ParseTree (that is generated by ANTLRv4) to DetailNode tree. 195 * 196 * @param parseTreeNode root node of ParseTree 197 * @return root of DetailNode tree 198 * @noinspection SuspiciousArrayCast 199 */ 200 private DetailNode convertParseTreeToDetailNode(ParseTree parseTreeNode) { 201 final JavadocNodeImpl rootJavadocNode = createRootJavadocNode(parseTreeNode); 202 203 JavadocNodeImpl currentJavadocParent = rootJavadocNode; 204 ParseTree parseTreeParent = parseTreeNode; 205 206 while (currentJavadocParent != null) { 207 // remove unnecessary children tokens 208 if (currentJavadocParent.getType() == JavadocTokenTypes.TEXT) { 209 currentJavadocParent 210 .setChildren((DetailNode[]) JavadocNodeImpl.EMPTY_DETAIL_NODE_ARRAY); 211 } 212 213 final JavadocNodeImpl[] children = 214 (JavadocNodeImpl[]) currentJavadocParent.getChildren(); 215 216 insertChildrenNodes(children, parseTreeParent); 217 218 if (children.length > 0) { 219 currentJavadocParent = children[0]; 220 parseTreeParent = parseTreeParent.getChild(0); 221 } 222 else { 223 JavadocNodeImpl nextJavadocSibling = (JavadocNodeImpl) JavadocUtils 224 .getNextSibling(currentJavadocParent); 225 226 ParseTree nextParseTreeSibling = getNextSibling(parseTreeParent); 227 228 if (nextJavadocSibling == null) { 229 JavadocNodeImpl tempJavadocParent = 230 (JavadocNodeImpl) currentJavadocParent.getParent(); 231 232 ParseTree tempParseTreeParent = parseTreeParent.getParent(); 233 234 while (nextJavadocSibling == null && tempJavadocParent != null) { 235 nextJavadocSibling = (JavadocNodeImpl) JavadocUtils 236 .getNextSibling(tempJavadocParent); 237 238 nextParseTreeSibling = getNextSibling(tempParseTreeParent); 239 240 tempJavadocParent = (JavadocNodeImpl) tempJavadocParent.getParent(); 241 tempParseTreeParent = tempParseTreeParent.getParent(); 242 } 243 } 244 currentJavadocParent = nextJavadocSibling; 245 parseTreeParent = nextParseTreeSibling; 246 } 247 } 248 249 return rootJavadocNode; 250 } 251 252 /** 253 * Creates child nodes for each node from 'nodes' array. 254 * @param parseTreeParent original ParseTree parent node 255 * @param nodes array of JavadocNodeImpl nodes 256 */ 257 private void insertChildrenNodes(final JavadocNodeImpl[] nodes, ParseTree parseTreeParent) { 258 for (int i = 0; i < nodes.length; i++) { 259 final JavadocNodeImpl currentJavadocNode = nodes[i]; 260 final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i); 261 final JavadocNodeImpl[] subChildren = 262 createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild); 263 currentJavadocNode.setChildren((DetailNode[]) subChildren); 264 } 265 } 266 267 /** 268 * Creates children Javadoc nodes base on ParseTree node's children. 269 * @param parentJavadocNode node that will be parent for created children 270 * @param parseTreeNode original ParseTree node 271 * @return array of Javadoc nodes 272 */ 273 private JavadocNodeImpl[] 274 createChildrenNodes(JavadocNodeImpl parentJavadocNode, ParseTree parseTreeNode) { 275 final JavadocNodeImpl[] children = 276 new JavadocNodeImpl[parseTreeNode.getChildCount()]; 277 278 for (int j = 0; j < children.length; j++) { 279 final JavadocNodeImpl child = 280 createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j); 281 282 children[j] = child; 283 } 284 return children; 285 } 286 287 /** 288 * Creates root JavadocNodeImpl node base on ParseTree root node. 289 * @param parseTreeNode ParseTree root node 290 * @return root Javadoc node 291 */ 292 private JavadocNodeImpl createRootJavadocNode(ParseTree parseTreeNode) { 293 final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1); 294 295 final int childCount = parseTreeNode.getChildCount(); 296 final DetailNode[] children = rootJavadocNode.getChildren(); 297 298 for (int i = 0; i < childCount; i++) { 299 final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i), 300 rootJavadocNode, i); 301 children[i] = child; 302 } 303 rootJavadocNode.setChildren(children); 304 return rootJavadocNode; 305 } 306 307 /** 308 * Creates JavadocNodeImpl node on base of ParseTree node. 309 * 310 * @param parseTree ParseTree node 311 * @param parent DetailNode that will be parent of new node 312 * @param index child index that has new node 313 * @return JavadocNodeImpl node on base of ParseTree node. 314 */ 315 private JavadocNodeImpl createJavadocNode(ParseTree parseTree, DetailNode parent, int index) { 316 final JavadocNodeImpl node = new JavadocNodeImpl(); 317 if (parseTree.getChildCount() == 0 318 || "Text".equals(getNodeClassNameWithoutContext(parseTree))) { 319 node.setText(parseTree.getText()); 320 } 321 else { 322 node.setText(getFormattedNodeClassNameWithoutContext(parseTree)); 323 } 324 node.setColumnNumber(getColumn(parseTree)); 325 node.setLineNumber(getLine(parseTree) + blockCommentLineNumber); 326 node.setIndex(index); 327 node.setType(getTokenType(parseTree)); 328 node.setParent(parent); 329 node.setChildren((DetailNode[]) new JavadocNodeImpl[parseTree.getChildCount()]); 330 return node; 331 } 332 333 /** 334 * Adjust first line nodes to javadoc indent. 335 * @param tree DetailNode tree root 336 * @param javadocColumnNumber javadoc indent 337 */ 338 private void adjustFirstLineToJavadocIndent(DetailNode tree, int javadocColumnNumber) { 339 if (tree.getLineNumber() == blockCommentLineNumber) { 340 ((JavadocNodeImpl) tree).setColumnNumber(tree.getColumnNumber() + javadocColumnNumber); 341 final DetailNode[] children = tree.getChildren(); 342 for (DetailNode child : children) { 343 adjustFirstLineToJavadocIndent(child, javadocColumnNumber); 344 } 345 } 346 } 347 348 /** 349 * Gets line number from ParseTree node. 350 * @param tree 351 * ParseTree node 352 * @return line number 353 */ 354 private static int getLine(ParseTree tree) { 355 final int line; 356 if (tree instanceof TerminalNode) { 357 line = ((TerminalNode) tree).getSymbol().getLine() - 1; 358 } 359 else { 360 final ParserRuleContext rule = (ParserRuleContext) tree; 361 line = rule.start.getLine() - 1; 362 } 363 return line; 364 } 365 366 /** 367 * Gets column number from ParseTree node. 368 * @param tree 369 * ParseTree node 370 * @return column number 371 */ 372 private static int getColumn(ParseTree tree) { 373 final int column; 374 if (tree instanceof TerminalNode) { 375 column = ((TerminalNode) tree).getSymbol().getCharPositionInLine(); 376 } 377 else { 378 final ParserRuleContext rule = (ParserRuleContext) tree; 379 column = rule.start.getCharPositionInLine(); 380 } 381 return column; 382 } 383 384 /** 385 * Gets next sibling of ParseTree node. 386 * @param node ParseTree node 387 * @return next sibling of ParseTree node. 388 */ 389 private static ParseTree getNextSibling(ParseTree node) { 390 ParseTree nextSibling = null; 391 392 if (node.getParent() != null) { 393 final ParseTree parent = node.getParent(); 394 int index = 0; 395 while (true) { 396 final ParseTree currentNode = parent.getChild(index); 397 if (currentNode.equals(node)) { 398 nextSibling = parent.getChild(index + 1); 399 break; 400 } 401 index++; 402 } 403 } 404 return nextSibling; 405 } 406 407 /** 408 * Gets token type of ParseTree node from JavadocTokenTypes class. 409 * @param node ParseTree node. 410 * @return token type from JavadocTokenTypes 411 */ 412 private static int getTokenType(ParseTree node) { 413 final int tokenType; 414 415 if (node.getChildCount() == 0) { 416 tokenType = ((TerminalNode) node).getSymbol().getType(); 417 } 418 else { 419 final String className = getNodeClassNameWithoutContext(node); 420 final String typeName = 421 CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, className); 422 tokenType = JavadocUtils.getTokenId(typeName); 423 } 424 425 return tokenType; 426 } 427 428 /** 429 * Gets class name of ParseTree node and removes 'Context' postfix at the 430 * end and formats it. 431 * @param node {@code ParseTree} node whose class name is to be formatted and returned 432 * @return uppercased class name without the word 'Context' and with appropriately 433 * inserted underscores 434 */ 435 private static String getFormattedNodeClassNameWithoutContext(ParseTree node) { 436 final String classNameWithoutContext = getNodeClassNameWithoutContext(node); 437 return CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, classNameWithoutContext); 438 } 439 440 /** 441 * Gets class name of ParseTree node and removes 'Context' postfix at the 442 * end. 443 * @param node 444 * ParseTree node. 445 * @return class name without 'Context' 446 */ 447 private static String getNodeClassNameWithoutContext(ParseTree node) { 448 final String className = node.getClass().getSimpleName(); 449 // remove 'Context' at the end 450 final int contextLength = 7; 451 return className.substring(0, className.length() - contextLength); 452 } 453 454 /** 455 * Method to get the missed HTML tag to generate more informative error message for the user. 456 * This method doesn't concern itself with 457 * <a href="https://www.w3.org/TR/html51/syntax.html#void-elements">void elements</a> 458 * since it is forbidden to close them. 459 * Missed HTML tags for the following tags will <i>not</i> generate an error message from ANTLR: 460 * {@code 461 * <p> 462 * <li> 463 * <tr> 464 * <td> 465 * <th> 466 * <body> 467 * <colgroup> 468 * <dd> 469 * <dt> 470 * <head> 471 * <html> 472 * <option> 473 * <tbody> 474 * <thead> 475 * <tfoot> 476 * } 477 * @param exception {@code NoViableAltException} object catched while parsing javadoc 478 * @return returns appropriate {@link Token} if a HTML close tag is missed; 479 * null otherwise 480 */ 481 private static Token getMissedHtmlTag(RecognitionException exception) { 482 Token htmlTagNameStart = null; 483 final Interval sourceInterval = exception.getCtx().getSourceInterval(); 484 final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream()) 485 .getTokens(sourceInterval.a, sourceInterval.b); 486 final Deque<Token> stack = new ArrayDeque<>(); 487 for (int i = 0; i < tokenList.size(); i++) { 488 final Token token = tokenList.get(i); 489 if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME 490 && tokenList.get(i - 1).getType() == JavadocTokenTypes.START) { 491 stack.push(token); 492 } 493 else if (token.getType() == JavadocTokenTypes.HTML_TAG_NAME && !stack.isEmpty()) { 494 if (stack.peek().getText().equals(token.getText())) { 495 stack.pop(); 496 } 497 else { 498 htmlTagNameStart = stack.pop(); 499 } 500 } 501 } 502 if (htmlTagNameStart == null) { 503 htmlTagNameStart = stack.pop(); 504 } 505 return htmlTagNameStart; 506 } 507 508 /** 509 * This method is used to get the first non-tight HTML tag encountered while parsing javadoc. 510 * This shall eventually be reflected by the {@link ParseStatus} object returned by 511 * {@link #parseJavadocAsDetailNode(DetailAST)} method via the instance member 512 * {@link ParseStatus#firstNonTightHtmlTag}, and checks not supposed to process non-tight HTML 513 * or the ones which are supposed to log violation for non-tight javadocs can utilize that. 514 * 515 * @param javadocParser The ANTLR recognizer instance which has been used to parse the javadoc 516 * @return First non-tight HTML tag if one exists; null otherwise 517 */ 518 private Token getFirstNonTightHtmlTag(JavadocParser javadocParser) { 519 final CommonToken offendingToken; 520 final ParserRuleContext nonTightTagStartContext = javadocParser.nonTightTagStartContext; 521 if (nonTightTagStartContext == null) { 522 offendingToken = null; 523 } 524 else { 525 final Token token = ((TerminalNode) nonTightTagStartContext.getChild(1)) 526 .getSymbol(); 527 offendingToken = new CommonToken(token); 528 offendingToken.setLine(offendingToken.getLine() + errorListener.offset); 529 } 530 return offendingToken; 531 } 532 533 /** 534 * Custom error listener for JavadocParser that prints user readable errors. 535 */ 536 private static class DescriptiveErrorListener extends BaseErrorListener { 537 538 /** 539 * Offset is line number of beginning of the Javadoc comment. Log 540 * messages should have line number in scope of file, not in scope of 541 * Javadoc comment. 542 */ 543 private int offset; 544 545 /** 546 * Error message that appeared while parsing. 547 */ 548 private ParseErrorMessage errorMessage; 549 550 /** 551 * Getter for error message during parsing. 552 * @return Error message during parsing. 553 */ 554 private ParseErrorMessage getErrorMessage() { 555 return errorMessage; 556 } 557 558 /** 559 * Sets offset. Offset is line number of beginning of the Javadoc 560 * comment. Log messages should have line number in scope of file, not 561 * in scope of Javadoc comment. 562 * @param offset 563 * offset line number 564 */ 565 public void setOffset(int offset) { 566 this.offset = offset; 567 } 568 569 /** 570 * Logs parser errors in Checkstyle manner. Parser can generate error 571 * messages. There is special error that parser can generate. It is 572 * missed close HTML tag. This case is special because parser prints 573 * error like {@code "no viable alternative at input 'b \n *\n'"} and it 574 * is not clear that error is about missed close HTML tag. Other error 575 * messages are not special and logged simply as "Parse Error...". 576 * 577 * <p>{@inheritDoc} 578 */ 579 @Override 580 public void syntaxError( 581 Recognizer<?, ?> recognizer, Object offendingSymbol, 582 int line, int charPositionInLine, 583 String msg, RecognitionException ex) { 584 final int lineNumber = offset + line; 585 586 if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) { 587 errorMessage = new ParseErrorMessage(lineNumber, 588 MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine, 589 ((Token) offendingSymbol).getText()); 590 591 throw new IllegalArgumentException(msg); 592 } 593 else { 594 final int ruleIndex = ex.getCtx().getRuleIndex(); 595 final String ruleName = recognizer.getRuleNames()[ruleIndex]; 596 final String upperCaseRuleName = CaseFormat.UPPER_CAMEL.to( 597 CaseFormat.UPPER_UNDERSCORE, ruleName); 598 599 errorMessage = new ParseErrorMessage(lineNumber, 600 MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName); 601 } 602 } 603 604 } 605 606 /** 607 * Contains result of parsing javadoc comment: DetailNode tree and parse 608 * error message. 609 */ 610 public static class ParseStatus { 611 612 /** 613 * DetailNode tree (is null if parsing fails). 614 */ 615 private DetailNode tree; 616 617 /** 618 * Parse error message (is null if parsing is successful). 619 */ 620 private ParseErrorMessage parseErrorMessage; 621 622 /** 623 * Stores the first non-tight HTML tag encountered while parsing javadoc. 624 * 625 * @see <a 626 * href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules"> 627 * Tight HTML rules</a> 628 */ 629 private Token firstNonTightHtmlTag; 630 631 /** 632 * Getter for DetailNode tree. 633 * @return DetailNode tree if parsing was successful, null otherwise. 634 */ 635 public DetailNode getTree() { 636 return tree; 637 } 638 639 /** 640 * Sets DetailNode tree. 641 * @param tree DetailNode tree. 642 */ 643 public void setTree(DetailNode tree) { 644 this.tree = tree; 645 } 646 647 /** 648 * Getter for error message during parsing. 649 * @return Error message if parsing was unsuccessful, null otherwise. 650 */ 651 public ParseErrorMessage getParseErrorMessage() { 652 return parseErrorMessage; 653 } 654 655 /** 656 * Sets parse error message. 657 * @param parseErrorMessage Parse error message. 658 */ 659 public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) { 660 this.parseErrorMessage = parseErrorMessage; 661 } 662 663 /** 664 * This method is used to check if the javadoc parsed has non-tight HTML tags. 665 * 666 * @return returns true if the javadoc has at least one non-tight HTML tag; false otherwise 667 * @see <a 668 * href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules"> 669 * Tight HTML rules</a> 670 */ 671 public boolean isNonTight() { 672 return firstNonTightHtmlTag != null; 673 } 674 675 /** 676 * Getter for {@link #firstNonTightHtmlTag}. 677 * 678 * @return the first non-tight HTML tag that is encountered while parsing Javadoc, 679 * if one exists 680 */ 681 public Token getFirstNonTightHtmlTag() { 682 return firstNonTightHtmlTag; 683 } 684 685 } 686 687 /** 688 * Contains information about parse error message. 689 */ 690 public static class ParseErrorMessage { 691 692 /** 693 * Line number where parse error occurred. 694 */ 695 private final int lineNumber; 696 697 /** 698 * Key for error message. 699 */ 700 private final String messageKey; 701 702 /** 703 * Error message arguments. 704 */ 705 private final Object[] messageArguments; 706 707 /** 708 * Initializes parse error message. 709 * 710 * @param lineNumber line number 711 * @param messageKey message key 712 * @param messageArguments message arguments 713 */ 714 ParseErrorMessage(int lineNumber, String messageKey, Object... messageArguments) { 715 this.lineNumber = lineNumber; 716 this.messageKey = messageKey; 717 this.messageArguments = messageArguments.clone(); 718 } 719 720 /** 721 * Getter for line number where parse error occurred. 722 * @return Line number where parse error occurred. 723 */ 724 public int getLineNumber() { 725 return lineNumber; 726 } 727 728 /** 729 * Getter for key for error message. 730 * @return Key for error message. 731 */ 732 public String getMessageKey() { 733 return messageKey; 734 } 735 736 /** 737 * Getter for error message arguments. 738 * @return Array of error message arguments. 739 */ 740 public Object[] getMessageArguments() { 741 return messageArguments.clone(); 742 } 743 744 } 745 746 /** 747 * The DefaultErrorStrategy used by ANTLR attempts to recover from parse errors 748 * which might result in a performance overhead. Also, a parse error indicate 749 * that javadoc doesn't follow checkstyle Javadoc grammar and the user should be made aware 750 * of it. 751 * <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/BailErrorStrategy.html"> 752 * BailErrorStrategy</a> is used to make ANTLR generated parser bail out on the first error 753 * in parser and not attempt any recovery methods but it doesn't report error to the 754 * listeners. This class is to ensure proper error reporting. 755 * 756 * @see DescriptiveErrorListener 757 * @see <a href="http://www.antlr.org/api/Java/org/antlr/v4/runtime/ANTLRErrorStrategy.html"> 758 * ANTLRErrorStrategy</a> 759 */ 760 private static class JavadocParserErrorStrategy extends BailErrorStrategy { 761 762 @Override 763 public Token recoverInline(Parser recognizer) { 764 reportError(recognizer, new InputMismatchException(recognizer)); 765 return super.recoverInline(recognizer); 766 } 767 768 } 769 770}