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