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