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