001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.puppycrawl.tools.checkstyle.api.DetailAST; 046import com.puppycrawl.tools.checkstyle.api.DetailNode; 047import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 048import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl; 049import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocLexer; 050import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocParser; 051import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 052 053/** 054 * Used for parsing Javadoc comment as DetailNode tree. 055 * 056 */ 057public class JavadocDetailNodeParser { 058 059 /** 060 * Message key of error message. Missed close HTML tag breaks structure 061 * of parse tree, so parser stops parsing and generates such error 062 * message. This case is special because parser prints error like 063 * {@code "no viable alternative at input 'b \n *\n'"} and it is not 064 * clear that error is about missed close HTML tag. 065 */ 066 public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = "javadoc.missed.html.close"; 067 068 /** 069 * Message key of error message. 070 */ 071 public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG = 072 "javadoc.wrong.singleton.html.tag"; 073 074 /** 075 * Parse error while rule recognition. 076 */ 077 public static final String MSG_JAVADOC_PARSE_RULE_ERROR = "javadoc.parse.rule.error"; 078 079 /** 080 * Message property key for the Unclosed HTML message. 081 */ 082 public static final String MSG_UNCLOSED_HTML_TAG = "javadoc.unclosedHtml"; 083 084 /** Symbols with which javadoc starts. */ 085 private static final String JAVADOC_START = "/**"; 086 087 /** 088 * Line number of the Block comment AST that is being parsed. 089 */ 090 private int blockCommentLineNumber; 091 092 /** 093 * Custom error listener. 094 */ 095 private DescriptiveErrorListener errorListener; 096 097 /** 098 * Parses Javadoc comment as DetailNode tree. 099 * 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 * 166 * @param blockComment 167 * block comment content. 168 * @return parse tree 169 */ 170 private JavadocParser createJavadocParser(String blockComment) { 171 final JavadocLexer lexer = new JavadocLexer(CharStreams.fromString(blockComment)); 172 173 final CommonTokenStream tokens = new CommonTokenStream(lexer); 174 175 final JavadocParser parser = new JavadocParser(tokens); 176 177 // remove default error listeners 178 parser.removeErrorListeners(); 179 180 // add custom error listener that logs syntax errors 181 parser.addErrorListener(errorListener); 182 183 // JavadocParserErrorStrategy stops parsing on first parse error encountered unlike the 184 // DefaultErrorStrategy used by ANTLR which rather attempts error recovery. 185 parser.setErrorHandler(new JavadocParserErrorStrategy()); 186 187 return parser; 188 } 189 190 /** 191 * Converts ParseTree (that is generated by ANTLRv4) to DetailNode tree. 192 * 193 * @param parseTreeNode root node of ParseTree 194 * @return root of DetailNode tree 195 * @noinspection SuspiciousArrayCast 196 */ 197 private DetailNode convertParseTreeToDetailNode(ParseTree parseTreeNode) { 198 final JavadocNodeImpl rootJavadocNode = createRootJavadocNode(parseTreeNode); 199 200 JavadocNodeImpl currentJavadocParent = rootJavadocNode; 201 ParseTree parseTreeParent = parseTreeNode; 202 203 while (currentJavadocParent != null) { 204 // remove unnecessary children tokens 205 if (currentJavadocParent.getType() == JavadocTokenTypes.TEXT) { 206 currentJavadocParent.setChildren(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 * 250 * @param parseTreeParent original ParseTree parent node 251 * @param nodes array of JavadocNodeImpl nodes 252 */ 253 private void insertChildrenNodes(final JavadocNodeImpl[] nodes, ParseTree parseTreeParent) { 254 for (int i = 0; i < nodes.length; i++) { 255 final JavadocNodeImpl currentJavadocNode = nodes[i]; 256 final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i); 257 final JavadocNodeImpl[] subChildren = 258 createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild); 259 currentJavadocNode.setChildren(subChildren); 260 } 261 } 262 263 /** 264 * Creates children Javadoc nodes base on ParseTree node's children. 265 * 266 * @param parentJavadocNode node that will be parent for created children 267 * @param parseTreeNode original ParseTree node 268 * @return array of Javadoc nodes 269 */ 270 private JavadocNodeImpl[] 271 createChildrenNodes(JavadocNodeImpl parentJavadocNode, ParseTree parseTreeNode) { 272 final JavadocNodeImpl[] children = 273 new JavadocNodeImpl[parseTreeNode.getChildCount()]; 274 275 for (int j = 0; j < children.length; j++) { 276 final JavadocNodeImpl child = 277 createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j); 278 279 children[j] = child; 280 } 281 return children; 282 } 283 284 /** 285 * Creates root JavadocNodeImpl node base on ParseTree root node. 286 * 287 * @param parseTreeNode ParseTree root node 288 * @return root Javadoc node 289 */ 290 private JavadocNodeImpl createRootJavadocNode(ParseTree parseTreeNode) { 291 final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1); 292 293 final int childCount = parseTreeNode.getChildCount(); 294 final DetailNode[] children = rootJavadocNode.getChildren(); 295 296 for (int i = 0; i < childCount; i++) { 297 final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i), 298 rootJavadocNode, i); 299 children[i] = child; 300 } 301 rootJavadocNode.setChildren(children); 302 return rootJavadocNode; 303 } 304 305 /** 306 * Creates JavadocNodeImpl node on base of ParseTree node. 307 * 308 * @param parseTree ParseTree node 309 * @param parent DetailNode that will be parent of new node 310 * @param index child index that has new node 311 * @return JavadocNodeImpl node on base of ParseTree node. 312 */ 313 private JavadocNodeImpl createJavadocNode(ParseTree parseTree, DetailNode parent, int index) { 314 final JavadocNodeImpl node = new JavadocNodeImpl(); 315 if (parseTree.getChildCount() == 0 316 || "Text".equals(getNodeClassNameWithoutContext(parseTree))) { 317 node.setText(parseTree.getText()); 318 } 319 else { 320 node.setText(getFormattedNodeClassNameWithoutContext(parseTree)); 321 } 322 node.setColumnNumber(getColumn(parseTree)); 323 node.setLineNumber(getLine(parseTree) + blockCommentLineNumber); 324 node.setIndex(index); 325 node.setType(getTokenType(parseTree)); 326 node.setParent(parent); 327 node.setChildren(new JavadocNodeImpl[parseTree.getChildCount()]); 328 return node; 329 } 330 331 /** 332 * Adjust first line nodes to javadoc indent. 333 * 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 * 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 * 369 * @param tree 370 * ParseTree node 371 * @return column number 372 */ 373 private static int getColumn(ParseTree tree) { 374 final int column; 375 if (tree instanceof TerminalNode) { 376 column = ((TerminalNode) tree).getSymbol().getCharPositionInLine(); 377 } 378 else { 379 final ParserRuleContext rule = (ParserRuleContext) tree; 380 column = rule.start.getCharPositionInLine(); 381 } 382 return column; 383 } 384 385 /** 386 * Gets next sibling of ParseTree node. 387 * 388 * @param node ParseTree node 389 * @return next sibling of ParseTree node. 390 */ 391 private static ParseTree getNextSibling(ParseTree node) { 392 ParseTree nextSibling = null; 393 394 if (node.getParent() != null) { 395 final ParseTree parent = node.getParent(); 396 int index = 0; 397 while (true) { 398 final ParseTree currentNode = parent.getChild(index); 399 if (currentNode.equals(node)) { 400 nextSibling = parent.getChild(index + 1); 401 break; 402 } 403 index++; 404 } 405 } 406 return nextSibling; 407 } 408 409 /** 410 * Gets token type of ParseTree node from JavadocTokenTypes class. 411 * 412 * @param node ParseTree node. 413 * @return token type from JavadocTokenTypes 414 */ 415 private static int getTokenType(ParseTree node) { 416 final int tokenType; 417 418 if (node.getChildCount() == 0) { 419 tokenType = ((TerminalNode) node).getSymbol().getType(); 420 } 421 else { 422 final String className = getNodeClassNameWithoutContext(node); 423 tokenType = JavadocUtil.getTokenId(convertUpperCamelToUpperUnderscore(className)); 424 } 425 426 return tokenType; 427 } 428 429 /** 430 * Gets class name of ParseTree node and removes 'Context' postfix at the 431 * end and formats it. 432 * 433 * @param node {@code ParseTree} node whose class name is to be formatted and returned 434 * @return uppercased class name without the word 'Context' and with appropriately 435 * inserted underscores 436 */ 437 private static String getFormattedNodeClassNameWithoutContext(ParseTree node) { 438 final String classNameWithoutContext = getNodeClassNameWithoutContext(node); 439 return convertUpperCamelToUpperUnderscore(classNameWithoutContext); 440 } 441 442 /** 443 * Gets class name of ParseTree node and removes 'Context' postfix at the 444 * end. 445 * 446 * @param node 447 * ParseTree node. 448 * @return class name without 'Context' 449 */ 450 private static String getNodeClassNameWithoutContext(ParseTree node) { 451 final String className = node.getClass().getSimpleName(); 452 // remove 'Context' at the end 453 final int contextLength = 7; 454 return className.substring(0, className.length() - contextLength); 455 } 456 457 /** 458 * Method to get the missed HTML tag to generate more informative error message for the user. 459 * This method doesn't concern itself with 460 * <a href="https://www.w3.org/TR/html51/syntax.html#void-elements">void elements</a> 461 * since it is forbidden to close them. 462 * Missed HTML tags for the following tags will <i>not</i> generate an error message from ANTLR: 463 * {@code 464 * <p> 465 * <li> 466 * <tr> 467 * <td> 468 * <th> 469 * <body> 470 * <colgroup> 471 * <dd> 472 * <dt> 473 * <head> 474 * <html> 475 * <option> 476 * <tbody> 477 * <thead> 478 * <tfoot> 479 * } 480 * 481 * @param exception {@code NoViableAltException} object catched while parsing javadoc 482 * @return returns appropriate {@link Token} if a HTML close tag is missed; 483 * null otherwise 484 */ 485 private static Token getMissedHtmlTag(RecognitionException exception) { 486 Token htmlTagNameStart = null; 487 final Interval sourceInterval = exception.getCtx().getSourceInterval(); 488 final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream()) 489 .getTokens(sourceInterval.a, sourceInterval.b); 490 final Deque<Token> stack = new ArrayDeque<>(); 491 int prevTokenType = JavadocTokenTypes.EOF; 492 for (final Token token : tokenList) { 493 final int tokenType = token.getType(); 494 if (tokenType == JavadocTokenTypes.HTML_TAG_NAME 495 && prevTokenType == JavadocTokenTypes.START) { 496 stack.push(token); 497 } 498 else if (tokenType == JavadocTokenTypes.HTML_TAG_NAME && !stack.isEmpty()) { 499 if (stack.peek().getText().equals(token.getText())) { 500 stack.pop(); 501 } 502 else { 503 htmlTagNameStart = stack.pop(); 504 } 505 } 506 prevTokenType = tokenType; 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 * Converts the given {@code text} from camel case to all upper case with 541 * underscores separating each word. 542 * 543 * @param text The string to convert. 544 * @return The result of the conversion. 545 */ 546 private static String convertUpperCamelToUpperUnderscore(String text) { 547 final StringBuilder result = new StringBuilder(20); 548 boolean first = true; 549 for (char letter : text.toCharArray()) { 550 if (!first && Character.isUpperCase(letter)) { 551 result.append('_'); 552 } 553 result.append(Character.toUpperCase(letter)); 554 first = false; 555 } 556 return result.toString(); 557 } 558 559 /** 560 * Custom error listener for JavadocParser that prints user readable errors. 561 */ 562 private static class DescriptiveErrorListener extends BaseErrorListener { 563 564 /** 565 * Offset is line number of beginning of the Javadoc comment. Log 566 * messages should have line number in scope of file, not in scope of 567 * Javadoc comment. 568 */ 569 private int offset; 570 571 /** 572 * Error message that appeared while parsing. 573 */ 574 private ParseErrorMessage errorMessage; 575 576 /** 577 * Getter for error message during parsing. 578 * 579 * @return Error message during parsing. 580 */ 581 private ParseErrorMessage getErrorMessage() { 582 return errorMessage; 583 } 584 585 /** 586 * Sets offset. Offset is line number of beginning of the Javadoc 587 * comment. Log messages should have line number in scope of file, not 588 * in scope of Javadoc comment. 589 * 590 * @param offset 591 * offset line number 592 */ 593 public void setOffset(int offset) { 594 this.offset = offset; 595 } 596 597 /** 598 * Logs parser errors in Checkstyle manner. Parser can generate error 599 * messages. There is special error that parser can generate. It is 600 * missed close HTML tag. This case is special because parser prints 601 * error like {@code "no viable alternative at input 'b \n *\n'"} and it 602 * is not clear that error is about missed close HTML tag. Other error 603 * messages are not special and logged simply as "Parse Error...". 604 * 605 * <p>{@inheritDoc} 606 */ 607 @Override 608 public void syntaxError( 609 Recognizer<?, ?> recognizer, Object offendingSymbol, 610 int line, int charPositionInLine, 611 String msg, RecognitionException ex) { 612 final int lineNumber = offset + line; 613 614 if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) { 615 errorMessage = new ParseErrorMessage(lineNumber, 616 MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine, 617 ((Token) offendingSymbol).getText()); 618 619 throw new IllegalArgumentException(msg); 620 } 621 622 final int ruleIndex = ex.getCtx().getRuleIndex(); 623 final String ruleName = recognizer.getRuleNames()[ruleIndex]; 624 final String upperCaseRuleName = convertUpperCamelToUpperUnderscore(ruleName); 625 626 errorMessage = new ParseErrorMessage(lineNumber, 627 MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName); 628 629 } 630 631 } 632 633 /** 634 * Contains result of parsing javadoc comment: DetailNode tree and parse 635 * error message. 636 */ 637 public static class ParseStatus { 638 639 /** 640 * DetailNode tree (is null if parsing fails). 641 */ 642 private DetailNode tree; 643 644 /** 645 * Parse error message (is null if parsing is successful). 646 */ 647 private ParseErrorMessage parseErrorMessage; 648 649 /** 650 * Stores the first non-tight HTML tag encountered while parsing javadoc. 651 * 652 * @see <a 653 * href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules"> 654 * Tight HTML rules</a> 655 */ 656 private Token firstNonTightHtmlTag; 657 658 /** 659 * Getter for DetailNode tree. 660 * 661 * @return DetailNode tree if parsing was successful, null otherwise. 662 */ 663 public DetailNode getTree() { 664 return tree; 665 } 666 667 /** 668 * Sets DetailNode tree. 669 * 670 * @param tree DetailNode tree. 671 */ 672 public void setTree(DetailNode tree) { 673 this.tree = tree; 674 } 675 676 /** 677 * Getter for error message during parsing. 678 * 679 * @return Error message if parsing was unsuccessful, null otherwise. 680 */ 681 public ParseErrorMessage getParseErrorMessage() { 682 return parseErrorMessage; 683 } 684 685 /** 686 * Sets parse error message. 687 * 688 * @param parseErrorMessage Parse error message. 689 */ 690 public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) { 691 this.parseErrorMessage = parseErrorMessage; 692 } 693 694 /** 695 * This method is used to check if the javadoc parsed has non-tight HTML tags. 696 * 697 * @return returns true if the javadoc has at least one non-tight HTML tag; false otherwise 698 * @see <a 699 * href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules"> 700 * Tight HTML rules</a> 701 */ 702 public boolean isNonTight() { 703 return firstNonTightHtmlTag != null; 704 } 705 706 /** 707 * Getter for the first non-tight HTML tag encountered while parsing javadoc. 708 * 709 * @return the first non-tight HTML tag that is encountered while parsing Javadoc, 710 * if one exists 711 * @see <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules"> 712 * Tight HTML rules</a> 713 */ 714 public Token getFirstNonTightHtmlTag() { 715 return firstNonTightHtmlTag; 716 } 717 718 } 719 720 /** 721 * Contains information about parse error message. 722 */ 723 public static class ParseErrorMessage { 724 725 /** 726 * Line number where parse error occurred. 727 */ 728 private final int lineNumber; 729 730 /** 731 * Key for error message. 732 */ 733 private final String messageKey; 734 735 /** 736 * Error message arguments. 737 */ 738 private final Object[] messageArguments; 739 740 /** 741 * Initializes parse error message. 742 * 743 * @param lineNumber line number 744 * @param messageKey message key 745 * @param messageArguments message arguments 746 */ 747 /* package */ ParseErrorMessage(int lineNumber, String messageKey, 748 Object... messageArguments) { 749 this.lineNumber = lineNumber; 750 this.messageKey = messageKey; 751 this.messageArguments = messageArguments.clone(); 752 } 753 754 /** 755 * Getter for line number where parse error occurred. 756 * 757 * @return Line number where parse error occurred. 758 */ 759 public int getLineNumber() { 760 return lineNumber; 761 } 762 763 /** 764 * Getter for key for error message. 765 * 766 * @return Key for error message. 767 */ 768 public String getMessageKey() { 769 return messageKey; 770 } 771 772 /** 773 * Getter for error message arguments. 774 * 775 * @return Array of error message arguments. 776 */ 777 public Object[] getMessageArguments() { 778 return messageArguments.clone(); 779 } 780 781 } 782 783 /** 784 * The DefaultErrorStrategy used by ANTLR attempts to recover from parse errors 785 * which might result in a performance overhead. Also, a parse error indicate 786 * that javadoc doesn't follow checkstyle Javadoc grammar and the user should be made aware 787 * of it. 788 * <a href="https://www.antlr.org/api/Java/org/antlr/v4/runtime/BailErrorStrategy.html"> 789 * BailErrorStrategy</a> is used to make ANTLR generated parser bail out on the first error 790 * in parser and not attempt any recovery methods but it doesn't report error to the 791 * listeners. This class is to ensure proper error reporting. 792 * 793 * @see DescriptiveErrorListener 794 * @see <a href="https://www.antlr.org/api/Java/org/antlr/v4/runtime/ANTLRErrorStrategy.html"> 795 * ANTLRErrorStrategy</a> 796 */ 797 private static class JavadocParserErrorStrategy extends BailErrorStrategy { 798 799 @Override 800 public Token recoverInline(Parser recognizer) { 801 reportError(recognizer, new InputMismatchException(recognizer)); 802 return super.recoverInline(recognizer); 803 } 804 805 } 806 807}