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