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