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 exc) {
129 ParseErrorMessage parseErrorMessage = null;
130
131 if (exc.getCause() instanceof FailedPredicateException
132 || exc.getCause() instanceof NoViableAltException) {
133 final RecognitionException recognitionEx = (RecognitionException) exc.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 final Interval sourceInterval = exception.getCtx().getSourceInterval();
488 final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream())
489 .getTokens(sourceInterval.a, sourceInterval.b);
490 final Deque<Token> stack = new ArrayDeque<>();
491 int prevTokenType = JavadocTokenTypes.EOF;
492 for (final Token token : tokenList) {
493 final int tokenType = token.getType();
494 if (tokenType == JavadocTokenTypes.HTML_TAG_NAME
495 && prevTokenType == JavadocTokenTypes.START) {
496 stack.push(token);
497 }
498 else if (tokenType == JavadocTokenTypes.HTML_TAG_NAME
499 && stack.peek().getText().equals(token.getText())) {
500 stack.pop();
501 }
502 prevTokenType = tokenType;
503 }
504 return stack.pop();
505 }
506
507 /**
508 * This method is used to get the first non-tight HTML tag encountered while parsing javadoc.
509 * This shall eventually be reflected by the {@link ParseStatus} object returned by
510 * {@link #parseJavadocAsDetailNode(DetailAST)} method via the instance member
511 * {@link ParseStatus#firstNonTightHtmlTag}, and checks not supposed to process non-tight HTML
512 * or the ones which are supposed to log violation for non-tight javadocs can utilize that.
513 *
514 * @param javadocParser The ANTLR recognizer instance which has been used to parse the javadoc
515 * @param javadocLineOffset The line number of beginning of the Javadoc comment
516 * @return First non-tight HTML tag if one exists; null otherwise
517 */
518 private static Token getFirstNonTightHtmlTag(JavadocParser javadocParser,
519 int javadocLineOffset) {
520 final CommonToken offendingToken;
521 final ParserRuleContext nonTightTagStartContext = javadocParser.nonTightTagStartContext;
522 if (nonTightTagStartContext == null) {
523 offendingToken = null;
524 }
525 else {
526 final Token token = ((TerminalNode) nonTightTagStartContext.getChild(1))
527 .getSymbol();
528 offendingToken = new CommonToken(token);
529 offendingToken.setLine(offendingToken.getLine() + javadocLineOffset);
530 }
531 return offendingToken;
532 }
533
534 /**
535 * Converts the given {@code text} from camel case to all upper case with
536 * underscores separating each word.
537 *
538 * @param text The string to convert.
539 * @return The result of the conversion.
540 */
541 private static String convertUpperCamelToUpperUnderscore(String text) {
542 final StringBuilder result = new StringBuilder(20);
543 boolean first = true;
544 for (char letter : text.toCharArray()) {
545 if (!first && Character.isUpperCase(letter)) {
546 result.append('_');
547 }
548 result.append(Character.toUpperCase(letter));
549 first = false;
550 }
551 return result.toString();
552 }
553
554 /**
555 * Custom error listener for JavadocParser that prints user readable errors.
556 */
557 private static final class DescriptiveErrorListener extends BaseErrorListener {
558
559 /**
560 * Offset is line number of beginning of the Javadoc comment. Log
561 * messages should have line number in scope of file, not in scope of
562 * Javadoc comment.
563 */
564 private int offset;
565
566 /**
567 * Error message that appeared while parsing.
568 */
569 private ParseErrorMessage errorMessage;
570
571 /**
572 * Getter for error message during parsing.
573 *
574 * @return Error message during parsing.
575 */
576 private ParseErrorMessage getErrorMessage() {
577 return errorMessage;
578 }
579
580 /**
581 * Sets offset. Offset is line number of beginning of the Javadoc
582 * comment. Log messages should have line number in scope of file, not
583 * in scope of Javadoc comment.
584 *
585 * @param offset
586 * offset line number
587 */
588 public void setOffset(int offset) {
589 this.offset = offset;
590 }
591
592 /**
593 * Logs parser errors in Checkstyle manner. Parser can generate error
594 * messages. There is special error that parser can generate. It is
595 * missed close HTML tag. This case is special because parser prints
596 * error like {@code "no viable alternative at input 'b \n *\n'"} and it
597 * is not clear that error is about missed close HTML tag. Other error
598 * messages are not special and logged simply as "Parse Error...".
599 *
600 * <p>{@inheritDoc}
601 */
602 @Override
603 public void syntaxError(
604 Recognizer<?, ?> recognizer, Object offendingSymbol,
605 int line, int charPositionInLine,
606 String msg, RecognitionException ex) {
607 final int lineNumber = offset + line;
608
609 if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) {
610 errorMessage = new ParseErrorMessage(lineNumber,
611 MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine,
612 ((Token) offendingSymbol).getText());
613
614 throw new IllegalArgumentException(msg);
615 }
616
617 final int ruleIndex = ex.getCtx().getRuleIndex();
618 final String ruleName = recognizer.getRuleNames()[ruleIndex];
619 final String upperCaseRuleName = convertUpperCamelToUpperUnderscore(ruleName);
620
621 errorMessage = new ParseErrorMessage(lineNumber,
622 MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName);
623
624 }
625
626 }
627
628 /**
629 * Contains result of parsing javadoc comment: DetailNode tree and parse
630 * error message.
631 */
632 public static class ParseStatus {
633
634 /**
635 * DetailNode tree (is null if parsing fails).
636 */
637 private DetailNode tree;
638
639 /**
640 * Parse error message (is null if parsing is successful).
641 */
642 private ParseErrorMessage parseErrorMessage;
643
644 /**
645 * Stores the first non-tight HTML tag encountered while parsing javadoc.
646 *
647 * @see <a
648 * href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
649 * Tight HTML rules</a>
650 */
651 private Token firstNonTightHtmlTag;
652
653 /**
654 * Getter for DetailNode tree.
655 *
656 * @return DetailNode tree if parsing was successful, null otherwise.
657 */
658 public DetailNode getTree() {
659 return tree;
660 }
661
662 /**
663 * Sets DetailNode tree.
664 *
665 * @param tree DetailNode tree.
666 */
667 public void setTree(DetailNode tree) {
668 this.tree = tree;
669 }
670
671 /**
672 * Getter for error message during parsing.
673 *
674 * @return Error message if parsing was unsuccessful, null otherwise.
675 */
676 public ParseErrorMessage getParseErrorMessage() {
677 return parseErrorMessage;
678 }
679
680 /**
681 * Sets parse error message.
682 *
683 * @param parseErrorMessage Parse error message.
684 */
685 public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
686 this.parseErrorMessage = parseErrorMessage;
687 }
688
689 /**
690 * This method is used to check if the javadoc parsed has non-tight HTML tags.
691 *
692 * @return returns true if the javadoc has at least one non-tight HTML tag; false otherwise
693 * @see <a
694 * href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
695 * Tight HTML rules</a>
696 */
697 public boolean isNonTight() {
698 return firstNonTightHtmlTag != null;
699 }
700
701 /**
702 * Getter for the first non-tight HTML tag encountered while parsing javadoc.
703 *
704 * @return the first non-tight HTML tag that is encountered while parsing Javadoc,
705 * if one exists
706 * @see <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
707 * Tight HTML rules</a>
708 */
709 public Token getFirstNonTightHtmlTag() {
710 return firstNonTightHtmlTag;
711 }
712
713 }
714
715 /**
716 * Contains information about parse error message.
717 */
718 public static class ParseErrorMessage {
719
720 /**
721 * Line number where parse error occurred.
722 */
723 private final int lineNumber;
724
725 /**
726 * Key for error message.
727 */
728 private final String messageKey;
729
730 /**
731 * Error message arguments.
732 */
733 private final Object[] messageArguments;
734
735 /**
736 * Initializes parse error message.
737 *
738 * @param lineNumber line number
739 * @param messageKey message key
740 * @param messageArguments message arguments
741 */
742 /* package */ ParseErrorMessage(int lineNumber, String messageKey,
743 Object... messageArguments) {
744 this.lineNumber = lineNumber;
745 this.messageKey = messageKey;
746 this.messageArguments = messageArguments.clone();
747 }
748
749 /**
750 * Getter for line number where parse error occurred.
751 *
752 * @return Line number where parse error occurred.
753 */
754 public int getLineNumber() {
755 return lineNumber;
756 }
757
758 /**
759 * Getter for key for error message.
760 *
761 * @return Key for error message.
762 */
763 public String getMessageKey() {
764 return messageKey;
765 }
766
767 /**
768 * Getter for error message arguments.
769 *
770 * @return Array of error message arguments.
771 */
772 public Object[] getMessageArguments() {
773 return messageArguments.clone();
774 }
775
776 }
777}