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