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