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