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.HashSet; 023import java.util.List; 024import java.util.Set; 025 026import org.antlr.v4.runtime.BufferedTokenStream; 027import org.antlr.v4.runtime.CommonTokenStream; 028import org.antlr.v4.runtime.ParserRuleContext; 029import org.antlr.v4.runtime.Token; 030import org.antlr.v4.runtime.tree.ParseTree; 031import org.antlr.v4.runtime.tree.TerminalNode; 032 033import com.puppycrawl.tools.checkstyle.api.DetailNode; 034import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes; 035import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl; 036import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocCommentsLexer; 037import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocCommentsParser; 038import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocCommentsParserBaseVisitor; 039import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 040 041/** 042 * Visitor class used to build Checkstyle's Javadoc AST from the parse tree 043 * produced by {@link JavadocCommentsParser}. Each overridden {@code visit...} 044 * method visits children of a parse tree node (subrules) or creates terminal 045 * nodes (tokens), and returns a {@link JavadocNodeImpl} subtree as the result. 046 * 047 * <p> 048 * The order of {@code visit...} methods in {@code JavaAstVisitor.java} and production rules in 049 * {@code JavaLanguageParser.g4} should be consistent to ease maintenance. 050 * </p> 051 * 052 * @see JavadocCommentsLexer 053 * @see JavadocCommentsParser 054 * @see JavadocNodeImpl 055 * @see JavaAstVisitor 056 */ 057public class JavadocCommentsAstVisitor extends JavadocCommentsParserBaseVisitor<JavadocNodeImpl> { 058 059 /** 060 * All Javadoc tag token types. 061 */ 062 private static final Set<Integer> JAVADOC_TAG_TYPES = Set.of( 063 JavadocCommentsLexer.CODE, 064 JavadocCommentsLexer.LINK, 065 JavadocCommentsLexer.LINKPLAIN, 066 JavadocCommentsLexer.VALUE, 067 JavadocCommentsLexer.INHERIT_DOC, 068 JavadocCommentsLexer.SUMMARY, 069 JavadocCommentsLexer.SYSTEM_PROPERTY, 070 JavadocCommentsLexer.INDEX, 071 JavadocCommentsLexer.RETURN, 072 JavadocCommentsLexer.LITERAL, 073 JavadocCommentsLexer.SNIPPET, 074 JavadocCommentsLexer.CUSTOM_NAME, 075 JavadocCommentsLexer.AUTHOR, 076 JavadocCommentsLexer.DEPRECATED, 077 JavadocCommentsLexer.PARAM, 078 JavadocCommentsLexer.THROWS, 079 JavadocCommentsLexer.EXCEPTION, 080 JavadocCommentsLexer.SINCE, 081 JavadocCommentsLexer.VERSION, 082 JavadocCommentsLexer.SEE, 083 JavadocCommentsLexer.LITERAL_HIDDEN, 084 JavadocCommentsLexer.USES, 085 JavadocCommentsLexer.PROVIDES, 086 JavadocCommentsLexer.SERIAL, 087 JavadocCommentsLexer.SERIAL_DATA, 088 JavadocCommentsLexer.SERIAL_FIELD 089 ); 090 091 /** 092 * Line number of the Block comment AST that is being parsed. 093 */ 094 private final int blockCommentLineNumber; 095 096 /** 097 * Javadoc Ident. 098 */ 099 private final int javadocColumnNumber; 100 101 /** 102 * Token stream to check for hidden tokens. 103 */ 104 private final BufferedTokenStream tokens; 105 106 /** 107 * A set of token indices used to track which tokens have already had their 108 * hidden tokens added to the AST. 109 */ 110 private final Set<Integer> processedTokenIndices = new HashSet<>(); 111 112 /** 113 * Accumulator for consecutive TEXT tokens. 114 * This is used to merge multiple TEXT tokens into a single node. 115 */ 116 private final TextAccumulator accumulator = new TextAccumulator(); 117 118 /** 119 * The first non-tight HTML tag encountered in the Javadoc comment, if any. 120 */ 121 private DetailNode firstNonTightHtmlTag; 122 123 /** 124 * Constructs a JavaAstVisitor with given token stream, line number, and column number. 125 * 126 * @param tokens the token stream to check for hidden tokens 127 * @param blockCommentLineNumber the line number of the block comment being parsed 128 * @param javadocColumnNumber the column number of the javadoc indent 129 */ 130 public JavadocCommentsAstVisitor(CommonTokenStream tokens, 131 int blockCommentLineNumber, int javadocColumnNumber) { 132 this.tokens = tokens; 133 this.blockCommentLineNumber = blockCommentLineNumber; 134 this.javadocColumnNumber = javadocColumnNumber; 135 } 136 137 @Override 138 public JavadocNodeImpl visitJavadoc(JavadocCommentsParser.JavadocContext ctx) { 139 return buildImaginaryNode(JavadocCommentsTokenTypes.JAVADOC_CONTENT, ctx); 140 } 141 142 @Override 143 public JavadocNodeImpl visitMainDescription(JavadocCommentsParser.MainDescriptionContext ctx) { 144 return flattenedTree(ctx); 145 } 146 147 @Override 148 public JavadocNodeImpl visitBlockTag(JavadocCommentsParser.BlockTagContext ctx) { 149 final JavadocNodeImpl blockTagNode = 150 createImaginary(JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG); 151 final ParseTree tag = ctx.getChild(0); 152 153 if (tag instanceof ParserRuleContext context) { 154 final Token tagName = (Token) context.getChild(1).getPayload(); 155 final int tokenType = tagName.getType(); 156 157 final JavadocNodeImpl specificTagNode = switch (tokenType) { 158 case JavadocCommentsLexer.AUTHOR -> 159 buildImaginaryNode(JavadocCommentsTokenTypes.AUTHOR_BLOCK_TAG, ctx); 160 case JavadocCommentsLexer.DEPRECATED -> 161 buildImaginaryNode(JavadocCommentsTokenTypes.DEPRECATED_BLOCK_TAG, ctx); 162 case JavadocCommentsLexer.RETURN -> 163 buildImaginaryNode(JavadocCommentsTokenTypes.RETURN_BLOCK_TAG, ctx); 164 case JavadocCommentsLexer.PARAM -> 165 buildImaginaryNode(JavadocCommentsTokenTypes.PARAM_BLOCK_TAG, ctx); 166 case JavadocCommentsLexer.THROWS -> 167 buildImaginaryNode(JavadocCommentsTokenTypes.THROWS_BLOCK_TAG, ctx); 168 case JavadocCommentsLexer.EXCEPTION -> 169 buildImaginaryNode(JavadocCommentsTokenTypes.EXCEPTION_BLOCK_TAG, ctx); 170 case JavadocCommentsLexer.SINCE -> 171 buildImaginaryNode(JavadocCommentsTokenTypes.SINCE_BLOCK_TAG, ctx); 172 case JavadocCommentsLexer.VERSION -> 173 buildImaginaryNode(JavadocCommentsTokenTypes.VERSION_BLOCK_TAG, ctx); 174 case JavadocCommentsLexer.SEE -> 175 buildImaginaryNode(JavadocCommentsTokenTypes.SEE_BLOCK_TAG, ctx); 176 case JavadocCommentsLexer.LITERAL_HIDDEN -> 177 buildImaginaryNode(JavadocCommentsTokenTypes.HIDDEN_BLOCK_TAG, ctx); 178 case JavadocCommentsLexer.USES -> 179 buildImaginaryNode(JavadocCommentsTokenTypes.USES_BLOCK_TAG, ctx); 180 case JavadocCommentsLexer.PROVIDES -> 181 buildImaginaryNode(JavadocCommentsTokenTypes.PROVIDES_BLOCK_TAG, ctx); 182 case JavadocCommentsLexer.SERIAL -> 183 buildImaginaryNode(JavadocCommentsTokenTypes.SERIAL_BLOCK_TAG, ctx); 184 case JavadocCommentsLexer.SERIAL_DATA -> 185 buildImaginaryNode(JavadocCommentsTokenTypes.SERIAL_DATA_BLOCK_TAG, ctx); 186 case JavadocCommentsLexer.SERIAL_FIELD -> 187 buildImaginaryNode(JavadocCommentsTokenTypes.SERIAL_FIELD_BLOCK_TAG, ctx); 188 default -> 189 buildImaginaryNode(JavadocCommentsTokenTypes.CUSTOM_BLOCK_TAG, ctx); 190 }; 191 blockTagNode.addChild(specificTagNode); 192 } 193 194 return blockTagNode; 195 } 196 197 @Override 198 public JavadocNodeImpl visitAuthorTag(JavadocCommentsParser.AuthorTagContext ctx) { 199 return flattenedTree(ctx); 200 } 201 202 @Override 203 public JavadocNodeImpl visitDeprecatedTag(JavadocCommentsParser.DeprecatedTagContext ctx) { 204 return flattenedTree(ctx); 205 } 206 207 @Override 208 public JavadocNodeImpl visitReturnTag(JavadocCommentsParser.ReturnTagContext ctx) { 209 return flattenedTree(ctx); 210 } 211 212 @Override 213 public JavadocNodeImpl visitParameterTag(JavadocCommentsParser.ParameterTagContext ctx) { 214 return flattenedTree(ctx); 215 } 216 217 @Override 218 public JavadocNodeImpl visitThrowsTag(JavadocCommentsParser.ThrowsTagContext ctx) { 219 return flattenedTree(ctx); 220 } 221 222 @Override 223 public JavadocNodeImpl visitExceptionTag(JavadocCommentsParser.ExceptionTagContext ctx) { 224 return flattenedTree(ctx); 225 } 226 227 @Override 228 public JavadocNodeImpl visitSinceTag(JavadocCommentsParser.SinceTagContext ctx) { 229 return flattenedTree(ctx); 230 } 231 232 @Override 233 public JavadocNodeImpl visitVersionTag(JavadocCommentsParser.VersionTagContext ctx) { 234 return flattenedTree(ctx); 235 } 236 237 @Override 238 public JavadocNodeImpl visitSeeTag(JavadocCommentsParser.SeeTagContext ctx) { 239 return flattenedTree(ctx); 240 } 241 242 @Override 243 public JavadocNodeImpl visitHiddenTag(JavadocCommentsParser.HiddenTagContext ctx) { 244 return flattenedTree(ctx); 245 } 246 247 @Override 248 public JavadocNodeImpl visitUsesTag(JavadocCommentsParser.UsesTagContext ctx) { 249 return flattenedTree(ctx); 250 } 251 252 @Override 253 public JavadocNodeImpl visitProvidesTag(JavadocCommentsParser.ProvidesTagContext ctx) { 254 return flattenedTree(ctx); 255 } 256 257 @Override 258 public JavadocNodeImpl visitSerialTag(JavadocCommentsParser.SerialTagContext ctx) { 259 return flattenedTree(ctx); 260 } 261 262 @Override 263 public JavadocNodeImpl visitSerialDataTag(JavadocCommentsParser.SerialDataTagContext ctx) { 264 return flattenedTree(ctx); 265 } 266 267 @Override 268 public JavadocNodeImpl visitSerialFieldTag(JavadocCommentsParser.SerialFieldTagContext ctx) { 269 return flattenedTree(ctx); 270 } 271 272 @Override 273 public JavadocNodeImpl visitCustomBlockTag(JavadocCommentsParser.CustomBlockTagContext ctx) { 274 return flattenedTree(ctx); 275 } 276 277 @Override 278 public JavadocNodeImpl visitInlineTag(JavadocCommentsParser.InlineTagContext ctx) { 279 final JavadocNodeImpl inlineTagNode = 280 createImaginary(JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG); 281 final ParseTree tagContent = ctx.inlineTagContent().getChild(0); 282 283 if (tagContent instanceof ParserRuleContext context) { 284 final Token tagName = (Token) context.getChild(0).getPayload(); 285 final int tokenType = tagName.getType(); 286 287 final JavadocNodeImpl specificTagNode = switch (tokenType) { 288 case JavadocCommentsLexer.CODE -> 289 buildImaginaryNode(JavadocCommentsTokenTypes.CODE_INLINE_TAG, ctx); 290 case JavadocCommentsLexer.LINK -> 291 buildImaginaryNode(JavadocCommentsTokenTypes.LINK_INLINE_TAG, ctx); 292 case JavadocCommentsLexer.LINKPLAIN -> 293 buildImaginaryNode(JavadocCommentsTokenTypes.LINKPLAIN_INLINE_TAG, ctx); 294 case JavadocCommentsLexer.VALUE -> 295 buildImaginaryNode(JavadocCommentsTokenTypes.VALUE_INLINE_TAG, ctx); 296 case JavadocCommentsLexer.INHERIT_DOC -> 297 buildImaginaryNode(JavadocCommentsTokenTypes.INHERIT_DOC_INLINE_TAG, ctx); 298 case JavadocCommentsLexer.SUMMARY -> 299 buildImaginaryNode(JavadocCommentsTokenTypes.SUMMARY_INLINE_TAG, ctx); 300 case JavadocCommentsLexer.SYSTEM_PROPERTY -> 301 buildImaginaryNode(JavadocCommentsTokenTypes.SYSTEM_PROPERTY_INLINE_TAG, ctx); 302 case JavadocCommentsLexer.INDEX -> 303 buildImaginaryNode(JavadocCommentsTokenTypes.INDEX_INLINE_TAG, ctx); 304 case JavadocCommentsLexer.RETURN -> 305 buildImaginaryNode(JavadocCommentsTokenTypes.RETURN_INLINE_TAG, ctx); 306 case JavadocCommentsLexer.LITERAL -> 307 buildImaginaryNode(JavadocCommentsTokenTypes.LITERAL_INLINE_TAG, ctx); 308 case JavadocCommentsLexer.SNIPPET -> 309 buildImaginaryNode(JavadocCommentsTokenTypes.SNIPPET_INLINE_TAG, ctx); 310 default -> buildImaginaryNode(JavadocCommentsTokenTypes.CUSTOM_INLINE_TAG, ctx); 311 }; 312 inlineTagNode.addChild(specificTagNode); 313 } 314 315 return inlineTagNode; 316 } 317 318 @Override 319 public JavadocNodeImpl visitInlineTagContent( 320 JavadocCommentsParser.InlineTagContentContext ctx) { 321 return flattenedTree(ctx); 322 } 323 324 @Override 325 public JavadocNodeImpl visitCodeInlineTag(JavadocCommentsParser.CodeInlineTagContext ctx) { 326 return flattenedTree(ctx); 327 } 328 329 @Override 330 public JavadocNodeImpl visitLinkPlainInlineTag( 331 JavadocCommentsParser.LinkPlainInlineTagContext ctx) { 332 return flattenedTree(ctx); 333 } 334 335 @Override 336 public JavadocNodeImpl visitLinkInlineTag(JavadocCommentsParser.LinkInlineTagContext ctx) { 337 return flattenedTree(ctx); 338 } 339 340 @Override 341 public JavadocNodeImpl visitValueInlineTag(JavadocCommentsParser.ValueInlineTagContext ctx) { 342 return flattenedTree(ctx); 343 } 344 345 @Override 346 public JavadocNodeImpl visitInheritDocInlineTag( 347 JavadocCommentsParser.InheritDocInlineTagContext ctx) { 348 return flattenedTree(ctx); 349 } 350 351 @Override 352 public JavadocNodeImpl visitSummaryInlineTag( 353 JavadocCommentsParser.SummaryInlineTagContext ctx) { 354 return flattenedTree(ctx); 355 } 356 357 @Override 358 public JavadocNodeImpl visitSystemPropertyInlineTag( 359 JavadocCommentsParser.SystemPropertyInlineTagContext ctx) { 360 return flattenedTree(ctx); 361 } 362 363 @Override 364 public JavadocNodeImpl visitIndexInlineTag(JavadocCommentsParser.IndexInlineTagContext ctx) { 365 return flattenedTree(ctx); 366 } 367 368 @Override 369 public JavadocNodeImpl visitReturnInlineTag(JavadocCommentsParser.ReturnInlineTagContext ctx) { 370 return flattenedTree(ctx); 371 } 372 373 @Override 374 public JavadocNodeImpl visitLiteralInlineTag( 375 JavadocCommentsParser.LiteralInlineTagContext ctx) { 376 return flattenedTree(ctx); 377 } 378 379 @Override 380 public JavadocNodeImpl visitSnippetInlineTag( 381 JavadocCommentsParser.SnippetInlineTagContext ctx) { 382 final JavadocNodeImpl dummyRoot = new JavadocNodeImpl(); 383 if (!ctx.snippetAttributes.isEmpty()) { 384 final JavadocNodeImpl snippetAttributes = 385 createImaginary(JavadocCommentsTokenTypes.SNIPPET_ATTRIBUTES); 386 ctx.snippetAttributes.forEach(snippetAttributeContext -> { 387 final JavadocNodeImpl snippetAttribute = visit(snippetAttributeContext); 388 snippetAttributes.addChild(snippetAttribute); 389 }); 390 dummyRoot.addChild(snippetAttributes); 391 } 392 if (ctx.COLON() != null) { 393 dummyRoot.addChild(create((Token) ctx.COLON().getPayload())); 394 } 395 if (ctx.snippetBody() != null) { 396 dummyRoot.addChild(visit(ctx.snippetBody())); 397 } 398 return dummyRoot.getFirstChild(); 399 } 400 401 @Override 402 public JavadocNodeImpl visitCustomInlineTag(JavadocCommentsParser.CustomInlineTagContext ctx) { 403 return flattenedTree(ctx); 404 } 405 406 @Override 407 public JavadocNodeImpl visitReference(JavadocCommentsParser.ReferenceContext ctx) { 408 return buildImaginaryNode(JavadocCommentsTokenTypes.REFERENCE, ctx); 409 } 410 411 @Override 412 public JavadocNodeImpl visitTypeName(JavadocCommentsParser.TypeNameContext ctx) { 413 return flattenedTree(ctx); 414 415 } 416 417 @Override 418 public JavadocNodeImpl visitQualifiedName(JavadocCommentsParser.QualifiedNameContext ctx) { 419 return flattenedTree(ctx); 420 } 421 422 @Override 423 public JavadocNodeImpl visitTypeArguments(JavadocCommentsParser.TypeArgumentsContext ctx) { 424 return buildImaginaryNode(JavadocCommentsTokenTypes.TYPE_ARGUMENTS, ctx); 425 } 426 427 @Override 428 public JavadocNodeImpl visitTypeArgument(JavadocCommentsParser.TypeArgumentContext ctx) { 429 return buildImaginaryNode(JavadocCommentsTokenTypes.TYPE_ARGUMENT, ctx); 430 } 431 432 @Override 433 public JavadocNodeImpl visitMemberReference(JavadocCommentsParser.MemberReferenceContext ctx) { 434 return buildImaginaryNode(JavadocCommentsTokenTypes.MEMBER_REFERENCE, ctx); 435 } 436 437 @Override 438 public JavadocNodeImpl visitParameterTypeList( 439 JavadocCommentsParser.ParameterTypeListContext ctx) { 440 return buildImaginaryNode(JavadocCommentsTokenTypes.PARAMETER_TYPE_LIST, ctx); 441 } 442 443 @Override 444 public JavadocNodeImpl visitDescription(JavadocCommentsParser.DescriptionContext ctx) { 445 return buildImaginaryNode(JavadocCommentsTokenTypes.DESCRIPTION, ctx); 446 } 447 448 @Override 449 public JavadocNodeImpl visitSnippetAttribute( 450 JavadocCommentsParser.SnippetAttributeContext ctx) { 451 return buildImaginaryNode(JavadocCommentsTokenTypes.SNIPPET_ATTRIBUTE, ctx); 452 } 453 454 @Override 455 public JavadocNodeImpl visitSnippetBody(JavadocCommentsParser.SnippetBodyContext ctx) { 456 return buildImaginaryNode(JavadocCommentsTokenTypes.SNIPPET_BODY, ctx); 457 } 458 459 @Override 460 public JavadocNodeImpl visitHtmlElement(JavadocCommentsParser.HtmlElementContext ctx) { 461 return buildImaginaryNode(JavadocCommentsTokenTypes.HTML_ELEMENT, ctx); 462 } 463 464 @Override 465 public JavadocNodeImpl visitVoidElement(JavadocCommentsParser.VoidElementContext ctx) { 466 return buildImaginaryNode(JavadocCommentsTokenTypes.VOID_ELEMENT, ctx); 467 } 468 469 @Override 470 public JavadocNodeImpl visitTightElement(JavadocCommentsParser.TightElementContext ctx) { 471 return flattenedTree(ctx); 472 } 473 474 @Override 475 public JavadocNodeImpl visitNonTightElement(JavadocCommentsParser.NonTightElementContext ctx) { 476 if (firstNonTightHtmlTag == null) { 477 final ParseTree htmlTagStart = ctx.getChild(0); 478 final ParseTree tagNameToken = htmlTagStart.getChild(1); 479 firstNonTightHtmlTag = create((Token) tagNameToken.getPayload()); 480 } 481 return flattenedTree(ctx); 482 } 483 484 @Override 485 public JavadocNodeImpl visitSelfClosingElement( 486 JavadocCommentsParser.SelfClosingElementContext ctx) { 487 final JavadocNodeImpl javadocNode = 488 createImaginary(JavadocCommentsTokenTypes.VOID_ELEMENT); 489 javadocNode.addChild(create((Token) ctx.TAG_OPEN().getPayload())); 490 javadocNode.addChild(create((Token) ctx.TAG_NAME().getPayload())); 491 if (!ctx.htmlAttributes.isEmpty()) { 492 final JavadocNodeImpl htmlAttributes = 493 createImaginary(JavadocCommentsTokenTypes.HTML_ATTRIBUTES); 494 ctx.htmlAttributes.forEach(htmlAttributeContext -> { 495 final JavadocNodeImpl htmlAttribute = visit(htmlAttributeContext); 496 htmlAttributes.addChild(htmlAttribute); 497 }); 498 javadocNode.addChild(htmlAttributes); 499 } 500 501 javadocNode.addChild(create((Token) ctx.TAG_SLASH_CLOSE().getPayload())); 502 return javadocNode; 503 } 504 505 @Override 506 public JavadocNodeImpl visitHtmlTagStart(JavadocCommentsParser.HtmlTagStartContext ctx) { 507 final JavadocNodeImpl javadocNode = 508 createImaginary(JavadocCommentsTokenTypes.HTML_TAG_START); 509 javadocNode.addChild(create((Token) ctx.TAG_OPEN().getPayload())); 510 javadocNode.addChild(create((Token) ctx.TAG_NAME().getPayload())); 511 if (!ctx.htmlAttributes.isEmpty()) { 512 final JavadocNodeImpl htmlAttributes = 513 createImaginary(JavadocCommentsTokenTypes.HTML_ATTRIBUTES); 514 ctx.htmlAttributes.forEach(htmlAttributeContext -> { 515 final JavadocNodeImpl htmlAttribute = visit(htmlAttributeContext); 516 htmlAttributes.addChild(htmlAttribute); 517 }); 518 javadocNode.addChild(htmlAttributes); 519 } 520 521 final Token tagClose = (Token) ctx.TAG_CLOSE().getPayload(); 522 addHiddenTokensToTheLeft(tagClose, javadocNode); 523 javadocNode.addChild(create(tagClose)); 524 return javadocNode; 525 } 526 527 @Override 528 public JavadocNodeImpl visitHtmlTagEnd(JavadocCommentsParser.HtmlTagEndContext ctx) { 529 return buildImaginaryNode(JavadocCommentsTokenTypes.HTML_TAG_END, ctx); 530 } 531 532 @Override 533 public JavadocNodeImpl visitHtmlAttribute(JavadocCommentsParser.HtmlAttributeContext ctx) { 534 return buildImaginaryNode(JavadocCommentsTokenTypes.HTML_ATTRIBUTE, ctx); 535 } 536 537 @Override 538 public JavadocNodeImpl visitHtmlContent(JavadocCommentsParser.HtmlContentContext ctx) { 539 return buildImaginaryNode(JavadocCommentsTokenTypes.HTML_CONTENT, ctx); 540 } 541 542 @Override 543 public JavadocNodeImpl visitNonTightHtmlContent( 544 JavadocCommentsParser.NonTightHtmlContentContext ctx) { 545 return buildImaginaryNode(JavadocCommentsTokenTypes.HTML_CONTENT, ctx); 546 } 547 548 @Override 549 public JavadocNodeImpl visitHtmlComment(JavadocCommentsParser.HtmlCommentContext ctx) { 550 return buildImaginaryNode(JavadocCommentsTokenTypes.HTML_COMMENT, ctx); 551 } 552 553 @Override 554 public JavadocNodeImpl visitHtmlCommentContent( 555 JavadocCommentsParser.HtmlCommentContentContext ctx) { 556 return buildImaginaryNode(JavadocCommentsTokenTypes.HTML_COMMENT_CONTENT, ctx); 557 } 558 559 /** 560 * Creates an imaginary JavadocNodeImpl of the given token type and 561 * processes all children of the given ParserRuleContext. 562 * 563 * @param tokenType the token type of this JavadocNodeImpl 564 * @param ctx the ParserRuleContext whose children are to be processed 565 * @return new JavadocNodeImpl of given type with processed children 566 */ 567 private JavadocNodeImpl buildImaginaryNode(int tokenType, ParserRuleContext ctx) { 568 final JavadocNodeImpl javadocNode = createImaginary(tokenType); 569 processChildren(javadocNode, ctx.children); 570 return javadocNode; 571 } 572 573 /** 574 * Builds the AST for a particular node, then returns a "flattened" tree 575 * of siblings. 576 * 577 * @param ctx the ParserRuleContext to base tree on 578 * @return flattened DetailAstImpl 579 */ 580 private JavadocNodeImpl flattenedTree(ParserRuleContext ctx) { 581 final JavadocNodeImpl dummyNode = new JavadocNodeImpl(); 582 processChildren(dummyNode, ctx.children); 583 return dummyNode.getFirstChild(); 584 } 585 586 /** 587 * Adds all the children from the given ParseTree or ParserRuleContext 588 * list to the parent JavadocNodeImpl. 589 * 590 * @param parent the JavadocNodeImpl to add children to 591 * @param children the list of children to add 592 */ 593 private void processChildren(JavadocNodeImpl parent, List<? extends ParseTree> children) { 594 for (ParseTree child : children) { 595 if (child instanceof TerminalNode terminalNode) { 596 final Token token = (Token) terminalNode.getPayload(); 597 598 // Add hidden tokens before this token 599 addHiddenTokensToTheLeft(token, parent); 600 601 if (isTextToken(token)) { 602 accumulator.append(token); 603 } 604 else if (token.getType() != Token.EOF) { 605 accumulator.flushTo(parent); 606 parent.addChild(create(token)); 607 } 608 } 609 else { 610 accumulator.flushTo(parent); 611 final Token token = ((ParserRuleContext) child).getStart(); 612 addHiddenTokensToTheLeft(token, parent); 613 parent.addChild(visit(child)); 614 } 615 } 616 617 accumulator.flushTo(parent); 618 } 619 620 /** 621 * Checks whether a token is a Javadoc text token. 622 * 623 * @param token the token to check 624 * @return true if the token is a text token, false otherwise 625 */ 626 private static boolean isTextToken(Token token) { 627 return token.getType() == JavadocCommentsTokenTypes.TEXT; 628 } 629 630 /** 631 * Adds hidden tokens to the left of the given token to the parent node. 632 * Ensures text accumulation is flushed before adding hidden tokens. 633 * Hidden tokens are only added once per unique token index. 634 * 635 * @param token the token whose hidden tokens should be added 636 * @param parent the parent node to which hidden tokens are added 637 */ 638 private void addHiddenTokensToTheLeft(Token token, JavadocNodeImpl parent) { 639 final boolean alreadyProcessed = !processedTokenIndices.add(token.getTokenIndex()); 640 641 if (!alreadyProcessed) { 642 final int tokenIndex = token.getTokenIndex(); 643 final List<Token> hiddenTokens = tokens.getHiddenTokensToLeft(tokenIndex); 644 if (hiddenTokens != null) { 645 accumulator.flushTo(parent); 646 for (Token hiddenToken : hiddenTokens) { 647 parent.addChild(create(hiddenToken)); 648 } 649 } 650 } 651 } 652 653 /** 654 * Creates a JavadocNodeImpl from the given token. 655 * 656 * @param token the token to create the JavadocNodeImpl from 657 * @return a new JavadocNodeImpl initialized with the token 658 */ 659 private JavadocNodeImpl create(Token token) { 660 final JavadocNodeImpl node = new JavadocNodeImpl(); 661 node.initialize(token); 662 663 // adjust line number to the position of the block comment 664 node.setLineNumber(node.getLineNumber() + blockCommentLineNumber); 665 666 // adjust first line to indent of /** 667 if (node.getLineNumber() == blockCommentLineNumber) { 668 node.setColumnNumber(node.getColumnNumber() + javadocColumnNumber); 669 } 670 671 if (isJavadocTag(token.getType())) { 672 node.setType(JavadocCommentsTokenTypes.TAG_NAME); 673 } 674 675 if (token.getType() == JavadocCommentsLexer.WS) { 676 node.setType(JavadocCommentsTokenTypes.TEXT); 677 } 678 679 return node; 680 } 681 682 /** 683 * Checks if the given token type is a Javadoc tag. 684 * 685 * @param type the token type to check 686 * @return true if the token type is a Javadoc tag, false otherwise 687 */ 688 private static boolean isJavadocTag(int type) { 689 return JAVADOC_TAG_TYPES.contains(type); 690 } 691 692 /** 693 * Create a JavadocNodeImpl from a given token and token type. This method 694 * should be used for imaginary nodes only, i.e. 'JAVADOC_INLINE_TAG -> JAVADOC_INLINE_TAG', 695 * where the text on the RHS matches the text on the LHS. 696 * 697 * @param tokenType the token type of this JavadocNodeImpl 698 * @return new JavadocNodeImpl of given type 699 */ 700 private JavadocNodeImpl createImaginary(int tokenType) { 701 final JavadocNodeImpl node = new JavadocNodeImpl(); 702 node.setType(tokenType); 703 node.setText(JavadocUtil.getTokenName(tokenType)); 704 705 if (tokenType == JavadocCommentsTokenTypes.JAVADOC_CONTENT) { 706 node.setLineNumber(blockCommentLineNumber); 707 node.setColumnNumber(javadocColumnNumber); 708 } 709 710 return node; 711 } 712 713 /** 714 * Returns the first non-tight HTML tag encountered in the Javadoc comment, if any. 715 * 716 * @return the first non-tight HTML tag, or null if none was found 717 */ 718 public DetailNode getFirstNonTightHtmlTag() { 719 return firstNonTightHtmlTag; 720 } 721 722 /** 723 * A small utility to accumulate consecutive TEXT tokens into one node, 724 * preserving the starting token for accurate location metadata. 725 */ 726 private final class TextAccumulator { 727 /** 728 * Buffer to accumulate TEXT token texts. 729 * 730 * @noinspection StringBufferField 731 * @noinspectionreason StringBufferField - We want to reuse the same buffer to avoid 732 */ 733 private final StringBuilder buffer = new StringBuilder(256); 734 735 /** 736 * The first token in the accumulation, used for line/column info. 737 */ 738 private Token startToken; 739 740 /** 741 * Appends a TEXT token's text to the buffer and tracks the first token. 742 * 743 * @param token the token to accumulate 744 */ 745 public void append(Token token) { 746 if (buffer.isEmpty()) { 747 startToken = token; 748 } 749 buffer.append(token.getText()); 750 } 751 752 /** 753 * Flushes the accumulated buffer into a single {@link JavadocNodeImpl} node 754 * and adds it to the given parent. Clears the buffer after flushing. 755 * 756 * @param parent the parent node to add the new node to 757 */ 758 public void flushTo(JavadocNodeImpl parent) { 759 if (!buffer.isEmpty()) { 760 final JavadocNodeImpl startNode = create(startToken); 761 startNode.setText(buffer.toString()); 762 parent.addChild(startNode); 763 buffer.setLength(0); 764 } 765 } 766 } 767}