[javadoc] Support inline code in link labels

Introduce the DOC_MARKDOWN_REFERENCE_LABEL container element to support complex labels (comment data + inline code).

#IDEA-364475 Fixed

GitOrigin-RevId: a04680040c38223b88c63fd8df19be1c9593923c
This commit is contained in:
Louis Vignier
2025-05-28 16:31:30 +02:00
committed by intellij-monorepo-bot
parent 9d5c82f458
commit 02149e6864
19 changed files with 181 additions and 17 deletions

View File

@@ -297,4 +297,5 @@ val BasicJavaElementTypeConverter: ElementTypeConverter = elementTypeConverterOf
JavaDocSyntaxElementType.DOC_MARKDOWN_CODE_BLOCK to BasicJavaDocElementType.BASIC_DOC_MARKDOWN_CODE_BLOCK,
JavaDocSyntaxElementType.DOC_MARKDOWN_REFERENCE_LINK to BasicJavaDocElementType.BASIC_DOC_MARKDOWN_REFERENCE_LINK,
JavaDocSyntaxElementType.DOC_MARKDOWN_REFERENCE_LABEL to BasicJavaDocElementType.BASIC_DOC_MARKDOWN_REFERENCE_LABEL,
)

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.source;
import com.intellij.psi.tree.IElementType;
@@ -39,6 +39,8 @@ public abstract class AbstractBasicJavaDocElementTypeFactory {
public final IElementType DOC_MARKDOWN_REFERENCE_LINK;
public final IElementType DOC_MARKDOWN_REFERENCE_LABEL;
public JavaDocElementTypeContainer(IElementType DOC_TAG,
IElementType DOC_COMMENT,
IElementType DOC_SNIPPET_TAG,
@@ -54,7 +56,8 @@ public abstract class AbstractBasicJavaDocElementTypeFactory {
IElementType DOC_TYPE_HOLDER,
IElementType DOC_PARAMETER_REF,
IElementType DOC_MARKDOWN_CODE_BLOCK,
IElementType DOC_MARKDOWN_REFERENCE_LINK) {
IElementType DOC_MARKDOWN_REFERENCE_LINK,
IElementType DOC_MARKDOWN_REFERENCE_LABEL) {
this.DOC_TAG = DOC_TAG;
this.DOC_COMMENT = DOC_COMMENT;
this.DOC_SNIPPET_TAG = DOC_SNIPPET_TAG;
@@ -71,6 +74,7 @@ public abstract class AbstractBasicJavaDocElementTypeFactory {
this.DOC_PARAMETER_REF = DOC_PARAMETER_REF;
this.DOC_MARKDOWN_CODE_BLOCK = DOC_MARKDOWN_CODE_BLOCK;
this.DOC_MARKDOWN_REFERENCE_LINK = DOC_MARKDOWN_REFERENCE_LINK;
this.DOC_MARKDOWN_REFERENCE_LABEL = DOC_MARKDOWN_REFERENCE_LABEL;
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.source;
import com.intellij.java.syntax.element.JavaDocSyntaxElementType;
@@ -50,6 +50,7 @@ public interface BasicJavaDocElementType {
IElementType BASIC_DOC_COMMENT = new IJavaDocElementType("DOC_COMMENT");
IElementType BASIC_DOC_MARKDOWN_CODE_BLOCK = new IJavaDocElementType("DOC_CODE_BLOCK");
IElementType BASIC_DOC_MARKDOWN_REFERENCE_LINK = new IJavaDocElementType("DOC_REFERENCE_LINK");
IElementType BASIC_DOC_MARKDOWN_REFERENCE_LABEL = new IJavaDocElementType("DOC_REFERENCE_LABEL");
ParentAwareTokenSet BASIC_ALL_JAVADOC_ELEMENTS = ParentAwareTokenSet.create(
BASIC_DOC_TAG, BASIC_DOC_INLINE_TAG, BASIC_DOC_METHOD_OR_FIELD_REF, BASIC_DOC_PARAMETER_REF, BASIC_DOC_TAG_VALUE_ELEMENT,

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.parser;
import com.intellij.pom.java.LanguageLevel;
@@ -220,6 +220,7 @@ public abstract class AbstractBasicJavadocParsingTest extends AbstractBasicJavaP
public void testReferenceLinkMarkdown09() { doTest(true); }
public void testReferenceLinkMarkdown10() { doTest(true); }
public void testReferenceLinkMarkdown11() { doTest(true); }
public void testReferenceLinkMarkdown12() { doTest(true); }
public void testMarkdownWithDocCommentBeforeMarkdown() {
setLanguageLevel(LanguageLevel.JDK_21);

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi;
import com.intellij.psi.javadoc.*;
@@ -253,6 +253,10 @@ public abstract class JavaElementVisitor extends PsiElementVisitor {
visitElement(block);
}
public void visitMarkdownReferenceLabel(@NotNull PsiMarkdownReferenceLabel label) {
visitElement(label);
}
public void visitMarkdownReferenceLink(@NotNull PsiMarkdownReferenceLink referenceLink) {
visitElement(referenceLink);
}

View File

@@ -0,0 +1,7 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.javadoc;
import com.intellij.psi.PsiElement;
/** Describes a markdown reference label */
public interface PsiMarkdownReferenceLabel extends PsiElement {}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.javadoc;
import com.intellij.psi.PsiElement;

View File

@@ -289,6 +289,7 @@ class JavaElementTypeConverterExtension : ElementTypeConverterFactory {
JavaDocSyntaxElementType.DOC_COMMENT to JavaDocElementType.DOC_COMMENT,
JavaDocSyntaxElementType.DOC_MARKDOWN_CODE_BLOCK to JavaDocElementType.DOC_MARKDOWN_CODE_BLOCK,
JavaDocSyntaxElementType.DOC_MARKDOWN_REFERENCE_LINK to JavaDocElementType.DOC_MARKDOWN_REFERENCE_LINK,
JavaDocSyntaxElementType.DOC_MARKDOWN_REFERENCE_LABEL to JavaDocElementType.DOC_MARKDOWN_REFERENCE_LABEL,
JavaDocSyntaxTokenType.DOC_COMMENT_START to JavaDocTokenType.DOC_COMMENT_START,
JavaDocSyntaxTokenType.DOC_COMMENT_END to JavaDocTokenType.DOC_COMMENT_END,

View File

@@ -0,0 +1,30 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.source.javadoc;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.javadoc.PsiMarkdownReferenceLabel;
import org.jetbrains.annotations.NotNull;
import static com.intellij.psi.impl.source.tree.JavaDocElementType.DOC_MARKDOWN_REFERENCE_LABEL;
public class PsiMarkdownReferenceLabelImpl extends CompositePsiElement implements PsiMarkdownReferenceLabel {
public PsiMarkdownReferenceLabelImpl() {
super(DOC_MARKDOWN_REFERENCE_LABEL);
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitMarkdownReferenceLabel(this);
return;
}
super.accept(visitor);
}
@Override
public String toString() {
return "PsiReferenceLabel:";
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.source.javadoc;
import com.intellij.psi.JavaElementVisitor;
@@ -32,7 +32,9 @@ public class PsiMarkdownReferenceLinkImpl extends CompositePsiElement implements
@Override
public @Nullable PsiElement getLabel() {
if (getChildren().length >= 1) return getChildren()[1];
if (getChildren().length >= 1) {
return getChildren()[1];
}
// malformed/incomplete link
return null;
}

View File

@@ -55,6 +55,7 @@ public interface JavaDocElementType {
IElementType DOC_SNIPPET_ATTRIBUTE_VALUE = new JavaDocParentProviderElementType("DOC_SNIPPET_ATTRIBUTE_VALUE", BasicJavaDocElementType.BASIC_DOC_SNIPPET_ATTRIBUTE_VALUE);
IElementType DOC_MARKDOWN_CODE_BLOCK = new JavaDocCompositeElementType("DOC_CODE_BLOCK", () -> new PsiMarkdownCodeBlockImpl(), BasicJavaDocElementType.BASIC_DOC_MARKDOWN_CODE_BLOCK);
IElementType DOC_MARKDOWN_REFERENCE_LINK = new JavaDocCompositeElementType("DOC_REFERENCE_LINK", () -> new PsiMarkdownReferenceLinkImpl(), BasicJavaDocElementType.BASIC_DOC_MARKDOWN_REFERENCE_LINK);
IElementType DOC_MARKDOWN_REFERENCE_LABEL = new JavaDocCompositeElementType("DOC_REFERENCE_LABEL", () -> new PsiMarkdownReferenceLabelImpl(), BasicJavaDocElementType.BASIC_DOC_MARKDOWN_REFERENCE_LABEL);
ILazyParseableElementType DOC_REFERENCE_HOLDER = new BasicJavaDocElementType.DocReferenceHolderElementType();
ILazyParseableElementType DOC_TYPE_HOLDER = new BasicJavaDocElementType.DocTypeHolderElementType();

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.source.tree;
import com.intellij.psi.impl.source.AbstractBasicJavaDocElementTypeFactory;
@@ -29,7 +29,8 @@ public class JavaDocElementTypeFactory extends AbstractBasicJavaDocElementTypeFa
JavaDocElementType.DOC_TYPE_HOLDER,
JavaDocElementType.DOC_PARAMETER_REF,
JavaDocElementType.DOC_MARKDOWN_CODE_BLOCK,
JavaDocElementType.DOC_MARKDOWN_REFERENCE_LINK
JavaDocElementType.DOC_MARKDOWN_REFERENCE_LINK,
JavaDocElementType.DOC_MARKDOWN_REFERENCE_LABEL
);
}

View File

@@ -2,7 +2,6 @@
package com.intellij.java.syntax.element
import com.intellij.platform.syntax.SyntaxElementType
import kotlin.jvm.JvmField
/**
* @see com.intellij.psi.impl.source.tree.JavaDocElementType
@@ -27,4 +26,5 @@ object JavaDocSyntaxElementType {
@JvmField val DOC_COMMENT: SyntaxElementType = SyntaxElementType("DOC_COMMENT")
@JvmField val DOC_MARKDOWN_CODE_BLOCK: SyntaxElementType = SyntaxElementType("DOC_CODE_BLOCK")
@JvmField val DOC_MARKDOWN_REFERENCE_LINK: SyntaxElementType = SyntaxElementType("DOC_REFERENCE_LINK")
@JvmField val DOC_MARKDOWN_REFERENCE_LABEL: SyntaxElementType = SyntaxElementType("DOC_REFERENCE_LABEL")
}

View File

@@ -267,15 +267,22 @@ class JavaDocParser(
tag.rollbackTo()
tag = builder.mark()
if (hasLabel) {
builder.advanceLexer()
val label = builder.mark()
// Label range already known, mark it as comment data
while (!builder.eof()) {
builder.advanceLexer()
if (builder.tokenType === JavaDocSyntaxTokenType.DOC_INLINE_CODE_FENCE) {
parseInlineCodeBlock()
continue
}
if (builder.currentOffset < endLabelOffset) {
builder.remapCurrentToken(JavaDocSyntaxTokenType.DOC_COMMENT_DATA)
builder.advanceLexer()
continue
}
break
}
label.done(JavaDocSyntaxElementType.DOC_MARKDOWN_REFERENCE_LABEL)
builder.advanceLexer()
}

View File

@@ -1,5 +1,6 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">MarkdownCodeBlock</span></pre></div><div class='content'><p>Single liner code block:
<code><span style="">Hello world</span></code></p><p>No tags are interpreted inside them
<code><span style="">{@link java.lang.String niceLink}</span></code></p><p>No markdown markup is interpreted inside them
<code><span style="">_Hello_ &lt;code&gt;</span></code></p><p>This is a broken inline link
`Start of broken link</p>end of broken link`</div><table class='sections'><p></table>
<code><span style="">_Hello_ &lt;code&gt;</span></code></p><p>Code span inside a link
<a href="psi_element://java.lang.String"><span style="color:#0000ff;">my text with <code>a code span</code>!</span></a></p><p>This is a broken inline code span
`Start of broken code span</p>end of broken code span`</div><table class='sections'><p></table>

View File

@@ -8,8 +8,11 @@
/// No markdown markup is interpreted inside them
/// `_Hello_ <code>`
///
/// This is a broken inline link
/// `Start of broken link
/// Code span inside a link
/// [my text with `a code span`!][java.lang.String]
///
/// end of broken link`
/// This is a broken inline code span
/// `Start of broken code span
///
/// end of broken code span`
class MarkdownCodeBlock {}

View File

@@ -0,0 +1,2 @@
/// [link with `inline code`!][java.lang.String]
class C {}

View File

@@ -0,0 +1,49 @@
PsiJavaFile:ReferenceLinkMarkdown12.java
PsiImportList
<empty list>
PsiClass:C
PsiDocComment
PsiDocToken:DOC_COMMENT_LEADING_ASTERISKS('///')
PsiWhiteSpace(' ')
PsiReferenceLink:
PsiDocToken:DOC_LBRACKET('[')
PsiReferenceLabel:
PsiDocToken:DOC_COMMENT_DATA('link with ')
PsiMarkdownCodeBlock:
PsiDocToken:DOC_INLINE_CODE_FENCE('`')
PsiDocToken:DOC_COMMENT_DATA('inline code')
PsiDocToken:DOC_INLINE_CODE_FENCE('`')
PsiDocToken:DOC_COMMENT_DATA('!')
PsiDocToken:DOC_RBRACKET(']')
PsiDocToken:DOC_LBRACKET('[')
PsiElement(DOC_REFERENCE_HOLDER)
PsiJavaCodeReferenceElement:java.lang.String
PsiJavaCodeReferenceElement:java.lang
PsiJavaCodeReferenceElement:java
PsiIdentifier:java('java')
PsiReferenceParameterList
<empty list>
PsiJavaToken:DOT('.')
PsiIdentifier:lang('lang')
PsiReferenceParameterList
<empty list>
PsiJavaToken:DOT('.')
PsiIdentifier:String('String')
PsiReferenceParameterList
<empty list>
PsiDocToken:DOC_RBRACKET(']')
PsiWhiteSpace('\n')
PsiModifierList:
<empty list>
PsiKeyword:class('class')
PsiWhiteSpace(' ')
PsiIdentifier:C('C')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiJavaToken:RBRACE('}')

View File

@@ -0,0 +1,49 @@
java.FILE
IMPORT_LIST
<empty list>
CLASS
DOC_COMMENT
DOC_COMMENT_LEADING_ASTERISKS
WHITE_SPACE
DOC_REFERENCE_LINK
DOC_LBRACKET
DOC_REFERENCE_LABEL
DOC_COMMENT_DATA
DOC_CODE_BLOCK
DOC_INLINE_CODE_FENCE
DOC_COMMENT_DATA
DOC_INLINE_CODE_FENCE
DOC_COMMENT_DATA
DOC_RBRACKET
DOC_LBRACKET
DOC_REFERENCE_HOLDER
JAVA_CODE_REFERENCE
JAVA_CODE_REFERENCE
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOT
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOT
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOC_RBRACKET
WHITE_SPACE
MODIFIER_LIST
<empty list>
CLASS_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
RBRACE