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