IDEA-381363 IDEA-375688 javadoc: handle bare method/class references

#Fixed IDEA-381363 IDEA-375688

GitOrigin-RevId: eb2cb1928026665b7626c77b9d730ddc70b769ea
This commit is contained in:
Mathias
2026-03-19 11:47:31 +00:00
committed by intellij-monorepo-bot
parent 8c0d077abe
commit 673d615897
28 changed files with 663 additions and 224 deletions
@@ -43,6 +43,7 @@ import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef;
import com.intellij.psi.javadoc.PsiDocFragmentName;
import com.intellij.psi.javadoc.PsiDocReferenceHolder;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
@@ -137,6 +138,15 @@ final class JavaNamesHighlightVisitor extends JavaElementVisitor implements High
}
}
@Override
public void visitDocReferenceHolder(PsiDocReferenceHolder refHolder) {
PsiElement resolved = refHolder.getReference().resolve();
if (resolved instanceof PsiMethod psiMethod) {
myHolder.add(HighlightNamesUtil.highlightMethodName(psiMethod, refHolder, false, myHolder.getColorsScheme()));
}
super.visitDocReferenceHolder(refHolder);
}
@Override
public void visitIdentifier(@NotNull PsiIdentifier identifier) {
@@ -61,6 +61,7 @@ public abstract class AbstractBasicJavadocParsingTest extends AbstractBasicJavaP
public void testLinkTag8() { doTest(true); }
public void testLinkTag9() { doTest(true); }
public void testLinkTag10() { doTest(true); }
public void testLinkTag11() { doTest(true); }
public void testParamTag0() { doTest(true); }
public void testParamTag1() { doTest(true); }
@@ -238,6 +239,7 @@ public abstract class AbstractBasicJavadocParsingTest extends AbstractBasicJavaP
public void testReferenceLinkMarkdown12() { doTest(true); }
public void testReferenceLinkMarkdown13() { doTest(true); }
public void testReferenceLinkMarkdown14() { doTest(true); }
public void testReferenceLinkMarkdown15() { doTest(true); }
public void testNestedTag0Markdown() { doTest(true); }
public void testNestedTag1Markdown() { doTest(true); }
@@ -103,7 +103,8 @@ public final class JavaSafeDeleteDelegateImpl implements JavaSafeDeleteDelegate
usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(element, parameter, true) {
@Override
public void deleteElement() throws IncorrectOperationException {
final PsiDocMethodOrFieldRef.MyReference javadocMethodReference = (PsiDocMethodOrFieldRef.MyReference)element.getReference();
final PsiDocMethodOrFieldRef.MethodOrFieldReference
javadocMethodReference = (PsiDocMethodOrFieldRef.MethodOrFieldReference)element.getReference();
if (javadocMethodReference != null) {
javadocMethodReference.bindToText(newText);
}
@@ -51,6 +51,7 @@ import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiResolveHelper;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
@@ -60,6 +61,7 @@ import com.intellij.psi.javadoc.JavadocManager;
import com.intellij.psi.javadoc.JavadocTagInfo;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocFragmentRef;
import com.intellij.psi.javadoc.PsiDocReferenceHolder;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.javadoc.PsiDocToken;
@@ -155,9 +157,28 @@ public final class JavaDocReferenceInspection extends LocalInspectionTool {
JavadocManager javadocManager = JavadocManager.getInstance(holder.getProject());
comment.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceElement(@NotNull PsiJavaCodeReferenceElement reference) {
visitRefElement(reference, context, isOnTheFly, holder);
public void visitTypeElement(@NotNull PsiTypeElement type) {
PsiJavaCodeReferenceElement ref = type.getInnermostComponentReferenceElement();
if (ref == null) return;
visitRefElement(ref, context, isOnTheFly, holder);
}
@Override
public void visitDocReferenceHolder(PsiDocReferenceHolder refHolder) {
PsiReference ref = refHolder.getReference();
if (ref == null) return;
PsiElement resolved = ref.resolve();
if (resolved == null) {
PsiElement element = refHolder.getFirstChild();
if (element instanceof PsiJavaCodeReferenceElement) {
// Since we don't know whether a method or a class is the intended element, treat it as a potentially missing class reference
visitRefElement((PsiJavaCodeReferenceElement)element, context, isOnTheFly, holder);
}
}
}
@Override
@@ -239,7 +260,7 @@ public final class JavaDocReferenceInspection extends LocalInspectionTool {
element, message, fix, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, isOnTheFly));
}
}
private void visitMarkdownReference(PsiMarkdownReferenceLink referenceLink, PsiElement context, ProblemsHolder holder, boolean isOnTheFly) {
PsiElement linkElement = referenceLink.getLinkElement();
if (linkElement == null) return;
@@ -247,6 +268,12 @@ public final class JavaDocReferenceInspection extends LocalInspectionTool {
if (reference == null) return;
PsiElement element = reference.resolve();
if (element == null && linkElement instanceof PsiDocReferenceHolder) {
linkElement = linkElement.getFirstChild();
reference = linkElement.getReference();
element = reference.resolve();
}
String linkText = linkElement.getText();
String message = element == null && reference instanceof PsiPolyVariantReference ?
getResolveErrorMessage(((PsiPolyVariantReference)reference).multiResolve(false), context, linkText) :
@@ -265,6 +292,12 @@ public final class JavaDocReferenceInspection extends LocalInspectionTool {
linkElement, reference.getRangeInElement(), message, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, isOnTheFly, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)));
}
private boolean visitDocRefHolder(PsiDocReferenceHolder refHolder, PsiElement context, ProblemsHolder holder, boolean isOnTheFly) {
return true;
}
private void visitRefInDocTag(PsiDocTag tag, JavadocManager manager, PsiElement context, ProblemsHolder holder, boolean isOnTheFly) {
PsiDocTagValue value = tag.getValueElement();
if (value == null) return;
@@ -2,6 +2,7 @@
package com.intellij.psi;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocReferenceHolder;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.javadoc.PsiDocToken;
@@ -122,6 +123,10 @@ public abstract class JavaElementVisitor extends PsiElementVisitor {
visitComment(comment);
}
public void visitDocReferenceHolder(PsiDocReferenceHolder refHolder) {
visitElement(refHolder);
}
public void visitDocTag(@NotNull PsiDocTag tag) {
visitElement(tag);
}
@@ -0,0 +1,11 @@
// Copyright 2000-2026 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;
/// Represent a reference from the Javadoc.
/// Usually a reference to a class through its children ([com.intellij.psi.PsiJavaReference]), or a bare reference to a method/field through itself.
///
/// @see PsiDocMethodOrFieldRef PsiDocMethodOrFieldRef for other methods/field references
public interface PsiDocReferenceHolder extends PsiElement {
}
@@ -138,24 +138,25 @@ public final class JavaDocUtil {
int poundIndex = refTextCorrected.indexOf('#');
if (poundIndex < 0) {
return findClassOrPackage(manager, context, useNavigationElement, facade, refTextCorrected);
PsiElement maybeClass = findClassOrPackage(manager, context, useNavigationElement, facade, refTextCorrected);
if (maybeClass != null) return maybeClass;
}
else {
int fragmentIndex = refTextCorrected.indexOf("##");
String classRef = refTextCorrected.substring(0, poundIndex).trim();
PsiClass aClass = classRef.isEmpty()
? PsiTreeUtil.getParentOfType(context, PsiClass.class, false)
: findClassFromRef(manager, facade, classRef, context);
while (aClass != null){
PsiElement reference = fragmentIndex >= 0
? findFragmentOwner(aClass, useNavigationElement, refTextCorrected, fragmentIndex, manager)
: findReference(aClass, context, useNavigationElement, refTextCorrected, poundIndex);
if (reference != null) return reference;
aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true);
}
return null;
int fragmentIndex = refTextCorrected.indexOf("##");
String classRef = refTextCorrected.substring(0, Math.max(poundIndex, 0)).trim();
PsiClass aClass = classRef.isEmpty()
? PsiTreeUtil.getParentOfType(context, PsiClass.class, false)
: findClassFromRef(manager, facade, classRef, context);
while (aClass != null) {
PsiElement reference = fragmentIndex >= 0
? findFragmentOwner(aClass, useNavigationElement, refTextCorrected, fragmentIndex, manager)
: findReference(aClass, context, useNavigationElement, refTextCorrected, poundIndex);
if (reference != null) return reference;
aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true);
}
return null;
}
private static @Nullable PsiElement findClassOrPackage(@NotNull PsiManager manager,
@@ -18,6 +18,7 @@ import com.intellij.psi.PsiField;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiJavaReference;
import com.intellij.psi.PsiKeyword;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiReference;
@@ -27,7 +28,6 @@ import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.PsiTypes;
import com.intellij.psi.ResolveState;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.PsiSuperMethodImplUtil;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
@@ -41,6 +41,7 @@ import com.intellij.psi.impl.source.tree.SharedImplUtil;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocReferenceHolder;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.scope.DelegatingScopeProcessor;
@@ -65,6 +66,9 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
/// PsiElement that is a _guarantee_ to reference either a **method** or a **field**.
///
/// @see PsiDocReferenceHolder PsiDocReferenceHolder for other ways to reference methods and fields
public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDocTagValue, Constants {
private static final List<String> SIGNATURE_TO_REPLACE = Arrays.asList("\\[", "\\]");
private static final List<String> SIGNATURE_REPLACEMENT = Arrays.asList("[", "]");
@@ -84,75 +88,8 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
}
@Override
public PsiReference getReference() {
final PsiClass scope = getScope();
final PsiElement element = getNameElement();
if (scope == null || element == null) return new MyReference(PsiElement.EMPTY_ARRAY);
PsiReference psiReference = getReferenceInScope(scope, element);
if (psiReference != null) return psiReference;
PsiClass classScope;
PsiClass containingClass = scope.getContainingClass();
while (containingClass != null) {
classScope = containingClass;
psiReference = getReferenceInScope(classScope, element);
if (psiReference != null) return psiReference;
containingClass = classScope.getContainingClass();
}
return new MyReference(PsiElement.EMPTY_ARRAY);
}
private @Nullable PsiReference getReferenceInScope(PsiClass scope, PsiElement element) {
final String name = element.getText();
final String[] signature = getSignature();
if (signature == null) {
PsiField var = scope.findFieldByName(name, true);
if (var != null) {
return new MyReference(new PsiElement[]{var});
}
}
final MethodSignature methodSignature;
if (signature != null) {
final List<PsiType> types = new ArrayList<>(signature.length);
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(element.getProject());
for (String s : signature) {
try {
types.add(elementFactory.createTypeFromText(s, element));
}
catch (IncorrectOperationException e) {
types.add(PsiTypes.nullType());
}
}
methodSignature = MethodSignatureUtil.createMethodSignature(name, types.toArray(PsiType.createArray(types.size())),
PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY,
name.equals(scope.getName()));
}
else {
methodSignature = null;
}
PsiMethod[] methods = findMethods(methodSignature, scope, name, getAllMethods(scope, this));
if (methods.length == 0) return null;
return new MyReference(methods) {
@Override
public void processVariants(@NotNull PsiScopeProcessor processor) {
super.processVariants(new DelegatingScopeProcessor(processor) {
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if (element instanceof PsiMethod && name.equals(((PsiMethod)element).getName())) {
return super.execute(element, state);
}
return true;
}
});
}
};
public @Nullable PsiReference getReference() {
return getReference(this);
}
@Override
@@ -162,59 +99,39 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
}
public @Nullable PsiElement getNameElement() {
final ASTNode name = findChildByType(DOC_TAG_VALUE_TOKEN);
return name != null ? SourceTreeToPsiMap.treeToPsiNotNull(name) : null;
return getNameElement(this);
}
public String @Nullable [] getSignature() {
PsiElement element = getNameElement();
if (element == null) return null;
element = element.getNextSibling();
while (element != null && !(element instanceof PsiDocTagValue)) {
element = element.getNextSibling();
}
if (element == null) return null;
List<String> types = new ArrayList<>();
for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child.getNode().getElementType() == DOC_TYPE_HOLDER) {
// JEP-467: Markdown comments have escaped brackets for array types
types.add(Strings.replace(child.getText(), SIGNATURE_TO_REPLACE, SIGNATURE_REPLACEMENT));
}
}
return ArrayUtilRt.toStringArray(types);
}
private @Nullable PsiClass getScope() {
return getScope(this);
return getSignature(this);
}
/**
* Returns the PsiClass targeted by the given reference element (e.g. {@code MyClass#…} or {@code MyClass##…}).
*/
public static @Nullable PsiClass getScope(CompositePsiElement ref) {
final TreeElement firstChildNode = ref.getFirstChildNode();
if (firstChildNode != null && firstChildNode.getElementType() == DOC_REFERENCE_HOLDER) {
final PsiElement firstChildPsi = SourceTreeToPsiMap.treeElementToPsi(firstChildNode.getFirstChildNode());
if (firstChildPsi instanceof PsiJavaCodeReferenceElement) {
PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)firstChildPsi;
final PsiElement referencedElement = referenceElement.resolve();
if (referencedElement instanceof PsiClass) return (PsiClass)referencedElement;
return null;
}
else if (firstChildPsi instanceof PsiKeyword) {
final PsiKeyword keyword = (PsiKeyword)firstChildPsi;
if (keyword.getTokenType().equals(THIS_KEYWORD)) {
return JavaResolveUtil.getContextClass(ref);
}
else if (keyword.getTokenType().equals(SUPER_KEYWORD)) {
final PsiClass contextClass = JavaResolveUtil.getContextClass(ref);
if (contextClass != null) return contextClass.getSuperClass();
public static @Nullable PsiClass getScope(PsiElement ref) {
if (ref instanceof TreeElement) {
final TreeElement firstChildNode = ((TreeElement)ref).getFirstChildNode();
if (firstChildNode != null && firstChildNode.getElementType() == DOC_REFERENCE_HOLDER) {
final PsiElement firstChildPsi = SourceTreeToPsiMap.treeElementToPsi(firstChildNode.getFirstChildNode());
if (firstChildPsi instanceof PsiJavaCodeReferenceElement) {
PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)firstChildPsi;
final PsiElement referencedElement = referenceElement.resolve();
if (referencedElement instanceof PsiClass) return (PsiClass)referencedElement;
return null;
}
else if (firstChildPsi instanceof PsiKeyword) {
final PsiKeyword keyword = (PsiKeyword)firstChildPsi;
if (keyword.getTokenType().equals(THIS_KEYWORD)) {
return JavaResolveUtil.getContextClass(ref);
}
else if (keyword.getTokenType().equals(SUPER_KEYWORD)) {
final PsiClass contextClass = JavaResolveUtil.getContextClass(ref);
if (contextClass != null) return contextClass.getSuperClass();
return null;
}
}
}
}
return JavaResolveUtil.getContextClass(ref);
@@ -273,10 +190,124 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
return result.toArray(PsiMethod.EMPTY_ARRAY);
}
public class MyReference implements PsiJavaReference {
/// Get the dog tag value name from the psi tree
private static @Nullable PsiElement getNameElement(PsiElement element) {
ASTNode name = element.getNode().findChildByType(DOC_TAG_VALUE_TOKEN);
if (name != null) {
return SourceTreeToPsiMap.treeToPsiNotNull(name);
}
name = element.getNode().findChildByType(JAVA_CODE_REFERENCE);
if (name != null) {
return SourceTreeToPsiMap.treeToPsiNotNull(name);
}
return null;
}
private static String @Nullable [] getSignature(PsiElement element) {
PsiElement nameElement = getNameElement(element);
if (nameElement == null) return null;
nameElement = nameElement.getNextSibling();
while (nameElement != null && !(nameElement instanceof PsiDocTagValue)) {
nameElement = nameElement.getNextSibling();
}
if (nameElement == null) return null;
List<String> types = new ArrayList<>();
for (PsiElement child = nameElement.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child.getNode().getElementType() == DOC_TYPE_HOLDER) {
// JEP-467: Markdown comments have escaped brackets for array types
types.add(Strings.replace(child.getText(), SIGNATURE_TO_REPLACE, SIGNATURE_REPLACEMENT));
}
}
return ArrayUtilRt.toStringArray(types);
}
/// Tries to get the reference in an ever larger scope
///
/// @return The reference to a method or a ref
public static @NotNull PsiDocMethodOrFieldRef.MethodOrFieldReference getReference(PsiElement element) {
final PsiClass scope = getScope(element);
final PsiElement nameElement = getNameElement(element);
if (scope == null || nameElement == null) return new MethodOrFieldReference(element, PsiElement.EMPTY_ARRAY);
MethodOrFieldReference psiReference = getReferenceInScope(element, scope, nameElement);
if (psiReference != null) return psiReference;
PsiClass classScope;
PsiClass containingClass = scope.getContainingClass();
while (containingClass != null) {
classScope = containingClass;
psiReference = getReferenceInScope(element, classScope, nameElement);
if (psiReference != null) return psiReference;
containingClass = classScope.getContainingClass();
}
return new MethodOrFieldReference(element, PsiElement.EMPTY_ARRAY);
}
/// @return The reference if found in the given scope
private static @Nullable PsiDocMethodOrFieldRef.MethodOrFieldReference getReferenceInScope(PsiElement referringElement,
PsiClass scope,
PsiElement element) {
final String name = element.getText();
final String[] signature = getSignature(referringElement);
if (signature == null) {
PsiField var = scope.findFieldByName(name, true);
if (var != null) {
return new MethodOrFieldReference(referringElement, new PsiElement[]{var});
}
}
final MethodSignature methodSignature;
if (signature != null) {
final List<PsiType> types = new ArrayList<>(signature.length);
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(element.getProject());
for (String s : signature) {
try {
types.add(elementFactory.createTypeFromText(s, element));
}
catch (IncorrectOperationException e) {
types.add(PsiTypes.nullType());
}
}
methodSignature = MethodSignatureUtil.createMethodSignature(name, types.toArray(PsiType.createArray(types.size())),
PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY,
name.equals(scope.getName()));
}
else {
methodSignature = null;
}
PsiMethod[] methods = findMethods(methodSignature, scope, name, getAllMethods(scope, referringElement));
if (methods.length == 0) return null;
return new MethodOrFieldReference(referringElement, methods) {
@Override
public void processVariants(@NotNull PsiScopeProcessor processor) {
super.processVariants(new DelegatingScopeProcessor(processor) {
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if (element instanceof PsiMethod && name.equals(((PsiMethod)element).getName())) {
return super.execute(element, state);
}
return true;
}
});
}
};
}
/// Reference to a Java element made from the Javadoc
public static class MethodOrFieldReference implements PsiJavaReference {
private final PsiElement myReferringElement;
private final PsiElement[] myReferredElements;
public MyReference(PsiElement[] referredElements) {
public MethodOrFieldReference(PsiElement element, PsiElement[] referredElements) {
myReferringElement = element;
myReferredElements = referredElements;
}
@@ -288,7 +319,7 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
@Override
public void processVariants(@NotNull PsiScopeProcessor processor) {
PsiClass scope = getScope();
PsiClass scope = getScope(myReferringElement);
while (scope != null) {
if (!scope.processDeclarations(new DelegatingScopeProcessor(processor) {
@Override
@@ -298,7 +329,7 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
}
return true;
}
}, ResolveState.initial(), null, PsiDocMethodOrFieldRef.this)) {
}, ResolveState.initial(), null, myReferringElement)) {
return;
}
scope = scope.getContainingClass();
@@ -330,26 +361,27 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
@Override
public @NotNull String getCanonicalText() {
final PsiElement nameElement = getNameElement();
final PsiElement nameElement = getNameElement(myReferringElement);
assert nameElement != null;
return nameElement.getText();
}
@Override
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
final PsiElement nameElement = getNameElement();
final PsiElement nameElement = getNameElement(myReferringElement);
assert nameElement != null;
final ASTNode treeElement = SourceTreeToPsiMap.psiToTreeNotNull(nameElement);
final CharTable charTableByTree = SharedImplUtil.findCharTableByTree(treeElement);
final LeafElement newToken = Factory.createSingleLeafElement(DOC_TAG_VALUE_TOKEN, newElementName, charTableByTree, getManager());
final LeafElement newToken =
Factory.createSingleLeafElement(DOC_TAG_VALUE_TOKEN, newElementName, charTableByTree, myReferringElement.getManager());
((CompositeElement)treeElement.getTreeParent()).replaceChildInternal(SourceTreeToPsiMap.psiToTreeNotNull(nameElement), newToken);
return SourceTreeToPsiMap.treeToPsiNotNull(newToken);
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
if (isReferenceTo(element)) return PsiDocMethodOrFieldRef.this;
final PsiElement nameElement = getNameElement();
if (isReferenceTo(element)) return myReferringElement;
final PsiElement nameElement = getNameElement(myReferringElement);
assert nameElement != null;
final String name = nameElement.getText();
final String newName;
@@ -360,7 +392,7 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
final PsiClass containingClass;
if (element instanceof PsiMethod) {
method = (PsiMethod)element;
hasSignature = getSignature() != null;
hasSignature = getSignature(myReferringElement) != null;
containingClass = method.getContainingClass();
newName = method.getName();
} else if (element instanceof PsiField) {
@@ -373,25 +405,25 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
throw new IncorrectOperationException();
}
final PsiElement child = getFirstChild();
final PsiElement child = myReferringElement.getFirstChild();
if (containingClass != null && child != null && child.getNode().getElementType() == JavaDocElementType.DOC_REFERENCE_HOLDER) {
PsiElement ref = child.getFirstChild();
if (ref instanceof PsiJavaCodeReferenceElement) {
((PsiJavaCodeReferenceElement)ref).bindToElement(containingClass);
}
}
else if (containingClass != null && PsiTreeUtil.getParentOfType(PsiDocMethodOrFieldRef.this, PsiClass.class) != containingClass) {
else if (containingClass != null && PsiTreeUtil.getParentOfType(myReferringElement, PsiClass.class) != containingClass) {
String qName = containingClass.getQualifiedName();
if (qName == null) qName = containingClass.getName(); // local class has no qualified name, but has a short name
if (qName == null) return PsiDocMethodOrFieldRef.this; // ref can't be fixed
if (qName == null) return myReferringElement; // ref can't be fixed
PsiDocComment fromText = JavaPsiFacade.getElementFactory(containingClass.getProject())
.createDocCommentFromText("/**{@link " + qName + "#" + newName + "}*/");
PsiDocMethodOrFieldRef methodOrFieldRefFromText = PsiTreeUtil.findChildOfType(fromText, PsiDocMethodOrFieldRef.class);
addAfter(Objects.requireNonNull(methodOrFieldRefFromText).getFirstChild(), null);
myReferringElement.addAfter(Objects.requireNonNull(methodOrFieldRefFromText).getFirstChild(), null);
}
if (hasSignature || !name.equals(newName)) {
String text = getText();
String text = myReferringElement.getText();
@NonNls StringBuffer newText = new StringBuffer();
newText.append("/** @see ");
@@ -416,21 +448,21 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
return bindToText(newText);
}
return PsiDocMethodOrFieldRef.this;
return myReferringElement;
}
public PsiElement bindToText(StringBuffer newText) {
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(getProject());
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myReferringElement.getProject());
PsiComment comment = elementFactory.createCommentFromText(newText.toString(), null);
PsiElement tag = PsiTreeUtil.getChildOfType(comment, PsiDocTag.class);
PsiElement ref = PsiTreeUtil.getChildOfType(tag, PsiDocMethodOrFieldRef.class);
assert ref != null : newText;
return replace(ref);
return myReferringElement.replace(ref);
}
@Override
public boolean isReferenceTo(@NotNull PsiElement element) {
PsiManagerEx manager = getManager();
PsiManager manager = myReferringElement.getManager();
for (PsiElement myReferredElement : myReferredElements) {
if (manager.areElementsEquivalent(element, myReferredElement)) return true;
}
@@ -439,20 +471,20 @@ public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDo
@Override
public @NotNull TextRange getRangeInElement() {
final ASTNode sharp = findChildByType(DOC_TAG_VALUE_SHARP_TOKEN);
if (sharp == null) return new TextRange(0, getTextLength());
final ASTNode sharp = myReferringElement.getNode().findChildByType(DOC_TAG_VALUE_SHARP_TOKEN);
if (sharp == null) return new TextRange(0, myReferringElement.getTextLength());
final PsiElement nextSibling = SourceTreeToPsiMap.treeToPsiNotNull(sharp).getNextSibling();
if (nextSibling != null) {
final int startOffset = nextSibling.getTextRange().getStartOffset() - getTextRange().getStartOffset();
int endOffset = nextSibling.getTextRange().getEndOffset() - getTextRange().getStartOffset();
final int startOffset = nextSibling.getTextRange().getStartOffset() - myReferringElement.getTextRange().getStartOffset();
int endOffset = nextSibling.getTextRange().getEndOffset() - myReferringElement.getTextRange().getStartOffset();
return new TextRange(startOffset, endOffset);
}
return new TextRange(getTextLength(), getTextLength());
return new TextRange(myReferringElement.getTextLength(), myReferringElement.getTextLength());
}
@Override
public @NotNull PsiElement getElement() {
return PsiDocMethodOrFieldRef.this;
return myReferringElement;
}
}
}
@@ -0,0 +1,41 @@
// Copyright 2000-2026 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.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.source.resolve.reference.impl.PsiDelegateReference;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.impl.source.tree.LazyParseablePsiElement;
import com.intellij.psi.javadoc.PsiDocReferenceHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
final public class PsiDocReferenceHolderImpl extends LazyParseablePsiElement implements PsiDocReferenceHolder {
public PsiDocReferenceHolderImpl(CharSequence text) {
super(JavaDocElementType.DOC_REFERENCE_HOLDER, text);
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitDocReferenceHolder(this);
}
super.accept(visitor);
}
@Override
public PsiReference getReference() {
return new PsiDelegateReference(PsiDocMethodOrFieldRef.getReference(this)) {
@Override
public @Nullable PsiElement resolve() {
PsiReference childRef = PsiDocReferenceHolderImpl.this.getFirstChild().getReference();
if (childRef != null && childRef.resolve() != null) {
return null;
}
return super.resolve();
}
};
}
}
@@ -20,6 +20,7 @@ import com.intellij.psi.impl.source.javadoc.PsiDocFragmentNameImpl;
import com.intellij.psi.impl.source.javadoc.PsiDocFragmentRefImpl;
import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
import com.intellij.psi.impl.source.javadoc.PsiDocReferenceHolderImpl;
import com.intellij.psi.impl.source.javadoc.PsiDocTagImpl;
import com.intellij.psi.impl.source.javadoc.PsiInlineDocTagImpl;
import com.intellij.psi.impl.source.javadoc.PsiMarkdownCodeBlockImpl;
@@ -139,6 +140,11 @@ public interface JavaDocElementType {
false,
LanguageLevel.JDK_1_3);
}
@Override
public ASTNode createNode(CharSequence text) {
return new PsiDocReferenceHolderImpl(text);
}
}
final class DocTypeHolderElementType extends JavaDocLazyElementType {
@@ -369,18 +369,31 @@ class JavaDocParser(
}
moduleMarker?.done(JavaDocSyntaxElementType.DOC_TAG_VALUE_ELEMENT)
val refStart = builder.mark()
var isMethodFieldOrRef = false
if (!referenceEnded && getTokenType() !== JavaDocSyntaxTokenType.DOC_SHARP && getTokenType() !== JavaDocSyntaxTokenType.DOC_DOUBLE_SHARP) {
builder.remapCurrentToken(JavaDocSyntaxElementType.DOC_REFERENCE_HOLDER)
builder.advanceLexer()
// Javadoc methods references may not have the # token if it is alone, rely on the existence of () to assign the proper type.
// In practice, () is not mandatory, and fields have the same issue but cannot be separated from class names on parsing
if (builder.lookAhead(1) == JavaDocSyntaxTokenType.DOC_LPAREN) {
isMethodFieldOrRef = true
}
else {
builder.remapCurrentToken(JavaDocSyntaxElementType.DOC_REFERENCE_HOLDER)
builder.advanceLexer()
}
}
if (!referenceEnded && getTokenType() === JavaDocSyntaxTokenType.DOC_SHARP) {
// Existing integration require this token for auto completion
builder.remapCurrentToken(JavaDocSyntaxTokenType.DOC_TAG_VALUE_SHARP_TOKEN)
if (!referenceEnded && !isMethodFieldOrRef) {
isMethodFieldOrRef = getTokenType() === JavaDocSyntaxTokenType.DOC_SHARP
if (isMethodFieldOrRef) {
// Existing integration require this token for auto completion
builder.remapCurrentToken(JavaDocSyntaxTokenType.DOC_TAG_VALUE_SHARP_TOKEN)
builder.advanceLexer()
}
}
if (!referenceEnded && isMethodFieldOrRef) {
// method/variable name
builder.advanceLexer()
builder.remapCurrentToken(JavaDocSyntaxTokenType.DOC_TAG_VALUE_TOKEN)
// A method only has parenthesis, comment data which may be the type, the optional argument name and commas
@@ -403,7 +416,7 @@ class JavaDocParser(
else if (type !== JavaDocSyntaxTokenType.DOC_COMMA) {
break
} else {
dataSinceComma = false;
dataSinceComma = false
}
builder.advanceLexer()
}
@@ -627,6 +640,10 @@ class JavaDocParser(
attribute.done(JavaDocSyntaxElementType.DOC_SNIPPET_ATTRIBUTE)
}
/**
* Parse the reference inside a tag (like `@link` and `@see`)
* @param allowBareFieldReference Whether bare references are **always** considered method/field refs
*/
private fun parseSeeTagValue(allowBareFieldReference: Boolean) {
val moduleMarker = parseModuleRef(builder.mark())
@@ -648,7 +665,9 @@ class JavaDocParser(
else if (getTokenType() === JavaDocSyntaxTokenType.DOC_TAG_VALUE_DOUBLE_SHARP_TOKEN) {
parseFragmentRef(refStart)
}
else if (allowBareFieldReference) {
// Javadoc methods references may not have the # token if it is alone, rely on the existence of () to assign the proper type.
// In practice, () is not mandatory, and fields have the same issue but cannot be separated from class names on parsing
else if (allowBareFieldReference || getTokenType() == JavaDocSyntaxTokenType.DOC_TAG_VALUE_LPAREN) {
refStart.rollbackTo()
builder.remapCurrentToken(JavaDocSyntaxTokenType.DOC_TAG_VALUE_TOKEN)
parseMethodRef(builder.mark())
@@ -0,0 +1,9 @@
/// [java.lang.String]
/// [foo]
/// {@link foo}
/// [<error descr="Cannot resolve symbol 'bar'">bar</error>]
/// {@link <error descr="Cannot resolve symbol 'bar'">bar</error>}
class Test {
public void foo(Object bar) {}
}
@@ -0,0 +1,4 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">Main</span></pre></div><div class='content'><p><a href="psi_element://java.lang.Object#equals(java.lang.Object)"><code><span style="color:#0000ff;">equals</span></code></a>
<a href="psi_element://java.lang.Object#equals(java.lang.Object)"><code><span style="color:#0000ff;">equals</span><span style="">(Object)</span></code></a>
<a href="psi_element://java.lang.Object#equals(java.lang.Object)"><code><span style="color:#0000ff;">equals</span></code></a>
<a href="psi_element://java.lang.Object#equals(java.lang.Object)"><code><span style="color:#0000ff;">equals</span><span style="">(Object)</span></code></a></p></div><table class='sections'><p></table>
@@ -0,0 +1,7 @@
/// [equals]
/// [equals(Object)]
/// {@link equals}
/// {@link equals(Object)}
class Ma<caret>in {
}
@@ -0,0 +1,5 @@
/**
* {@link equals}
* {@link equals(Object)}
*/
public class C {}
@@ -0,0 +1,56 @@
PsiJavaFile:LinkTag11.java
PsiImportList
<empty list>
PsiClass:C
PsiDocComment
PsiDocToken:DOC_COMMENT_START('/**')
PsiWhiteSpace('\n ')
PsiDocToken:DOC_COMMENT_LEADING_ASTERISKS('*')
PsiDocToken:DOC_COMMENT_DATA(' ')
PsiInlineDocTag:@link
PsiDocToken:DOC_INLINE_TAG_START('{')
PsiDocToken:DOC_TAG_NAME('@link')
PsiWhiteSpace(' ')
PsiElement(DOC_REFERENCE_HOLDER)
PsiJavaCodeReferenceElement:equals
PsiIdentifier:equals('equals')
PsiReferenceParameterList
<empty list>
PsiDocToken:DOC_INLINE_TAG_END('}')
PsiWhiteSpace('\n ')
PsiDocToken:DOC_COMMENT_LEADING_ASTERISKS('*')
PsiDocToken:DOC_COMMENT_DATA(' ')
PsiInlineDocTag:@link
PsiDocToken:DOC_INLINE_TAG_START('{')
PsiDocToken:DOC_TAG_NAME('@link')
PsiWhiteSpace(' ')
PsiElement(DOC_METHOD_OR_FIELD_REF)
PsiDocToken:DOC_TAG_VALUE_TOKEN('equals')
PsiDocToken:DOC_TAG_VALUE_LPAREN('(')
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiElement(DOC_TYPE_HOLDER)
PsiTypeElement:Object
PsiJavaCodeReferenceElement:Object
PsiIdentifier:Object('Object')
PsiReferenceParameterList
<empty list>
PsiDocToken:DOC_TAG_VALUE_RPAREN(')')
PsiDocToken:DOC_INLINE_TAG_END('}')
PsiWhiteSpace('\n ')
PsiDocToken:DOC_COMMENT_END('*/')
PsiWhiteSpace('\n')
PsiModifierList:public
PsiKeyword:public('public')
PsiWhiteSpace(' ')
PsiKeyword:class('class')
PsiWhiteSpace(' ')
PsiIdentifier:C('C')
PsiTypeParameterList
<empty list>
PsiReferenceList
<empty list>
PsiReferenceList
<empty list>
PsiWhiteSpace(' ')
PsiJavaToken:LBRACE('{')
PsiJavaToken:RBRACE('}')
@@ -0,0 +1,56 @@
java.FILE
IMPORT_LIST
<empty list>
CLASS
DOC_COMMENT
DOC_COMMENT_START
WHITE_SPACE
DOC_COMMENT_LEADING_ASTERISKS
DOC_COMMENT_DATA
DOC_INLINE_TAG
DOC_INLINE_TAG_START
DOC_TAG_NAME
WHITE_SPACE
DOC_REFERENCE_HOLDER
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOC_INLINE_TAG_END
WHITE_SPACE
DOC_COMMENT_LEADING_ASTERISKS
DOC_COMMENT_DATA
DOC_INLINE_TAG
DOC_INLINE_TAG_START
DOC_TAG_NAME
WHITE_SPACE
DOC_METHOD_OR_FIELD_REF
DOC_TAG_VALUE_TOKEN
DOC_TAG_VALUE_LPAREN
DOC_TAG_VALUE_ELEMENT
DOC_TYPE_HOLDER
TYPE
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOC_TAG_VALUE_RPAREN
DOC_INLINE_TAG_END
WHITE_SPACE
DOC_COMMENT_END
WHITE_SPACE
MODIFIER_LIST
PUBLIC_KEYWORD
WHITE_SPACE
CLASS_KEYWORD
WHITE_SPACE
IDENTIFIER
TYPE_PARAMETER_LIST
<empty list>
EXTENDS_LIST
<empty list>
IMPLEMENTS_LIST
<empty list>
WHITE_SPACE
LBRACE
RBRACE
@@ -0,0 +1,3 @@
/// [equals] method Link without the hash char and params
/// [equals(Object)] method Link without the hash char
class C {}
@@ -0,0 +1,49 @@
PsiJavaFile:ReferenceLinkMarkdown15.java
PsiImportList
<empty list>
PsiClass:C
PsiDocComment
PsiDocToken:DOC_COMMENT_LEADING_ASTERISKS('///')
PsiWhiteSpace(' ')
PsiReferenceLink:
PsiDocToken:DOC_LBRACKET('[')
PsiElement(DOC_REFERENCE_HOLDER)
PsiJavaCodeReferenceElement:equals
PsiIdentifier:equals('equals')
PsiReferenceParameterList
<empty list>
PsiDocToken:DOC_RBRACKET(']')
PsiDocToken:DOC_COMMENT_DATA(' method Link without the hash char and params')
PsiWhiteSpace('\n')
PsiDocToken:DOC_COMMENT_LEADING_ASTERISKS('///')
PsiDocToken:DOC_COMMENT_DATA(' ')
PsiReferenceLink:
PsiDocToken:DOC_LBRACKET('[')
PsiElement(DOC_METHOD_OR_FIELD_REF)
PsiDocToken:DOC_TAG_VALUE_TOKEN('equals')
PsiDocToken:DOC_LPAREN('(')
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiElement(DOC_TYPE_HOLDER)
PsiTypeElement:Object
PsiJavaCodeReferenceElement:Object
PsiIdentifier:Object('Object')
PsiReferenceParameterList
<empty list>
PsiDocToken:DOC_RPAREN(')')
PsiDocToken:DOC_RBRACKET(']')
PsiDocToken:DOC_COMMENT_DATA(' method Link without the hash char')
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('}')
@@ -0,0 +1,49 @@
java.FILE
IMPORT_LIST
<empty list>
CLASS
DOC_MARKDOWN_COMMENT
DOC_COMMENT_LEADING_ASTERISKS
WHITE_SPACE
DOC_REFERENCE_LINK
DOC_LBRACKET
DOC_REFERENCE_HOLDER
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOC_RBRACKET
DOC_COMMENT_DATA
WHITE_SPACE
DOC_COMMENT_LEADING_ASTERISKS
DOC_COMMENT_DATA
DOC_REFERENCE_LINK
DOC_LBRACKET
DOC_METHOD_OR_FIELD_REF
DOC_TAG_VALUE_TOKEN
DOC_LPAREN
DOC_TAG_VALUE_ELEMENT
DOC_TYPE_HOLDER
TYPE
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOC_RPAREN
DOC_RBRACKET
DOC_COMMENT_DATA
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
@@ -10,21 +10,18 @@ PsiJavaFile:SeeTag9.java
PsiDocTag:@see
PsiDocToken:DOC_TAG_NAME('@see')
PsiWhiteSpace(' ')
PsiElement(DOC_REFERENCE_HOLDER)
PsiJavaCodeReferenceElement:equals
PsiIdentifier:equals('equals')
PsiReferenceParameterList
<empty list>
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiElement(DOC_METHOD_OR_FIELD_REF)
PsiDocToken:DOC_TAG_VALUE_TOKEN('equals')
PsiDocToken:DOC_TAG_VALUE_LPAREN('(')
PsiElement(DOC_REFERENCE_HOLDER)
PsiKeyword:long('long')
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiDocToken:DOC_TAG_VALUE_COMMA(',')
PsiWhiteSpace(' ')
PsiElement(DOC_REFERENCE_HOLDER)
PsiKeyword:long('long')
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiElement(DOC_TYPE_HOLDER)
PsiTypeElement:long
PsiKeyword:long('long')
PsiDocToken:DOC_TAG_VALUE_COMMA(',')
PsiWhiteSpace(' ')
PsiElement(DOC_TYPE_HOLDER)
PsiTypeElement:long
PsiKeyword:long('long')
PsiDocToken:DOC_TAG_VALUE_RPAREN(')')
PsiWhiteSpace('\n ')
PsiDocToken:DOC_COMMENT_END('*/')
@@ -10,21 +10,18 @@ PsiJavaFile:SeeTag9Markdown.java
PsiDocTag:@see
PsiDocToken:DOC_TAG_NAME('@see')
PsiWhiteSpace(' ')
PsiElement(DOC_REFERENCE_HOLDER)
PsiJavaCodeReferenceElement:equals
PsiIdentifier:equals('equals')
PsiReferenceParameterList
<empty list>
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiElement(DOC_METHOD_OR_FIELD_REF)
PsiDocToken:DOC_TAG_VALUE_TOKEN('equals')
PsiDocToken:DOC_TAG_VALUE_LPAREN('(')
PsiElement(DOC_REFERENCE_HOLDER)
PsiKeyword:long('long')
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiDocToken:DOC_TAG_VALUE_COMMA(',')
PsiWhiteSpace(' ')
PsiElement(DOC_REFERENCE_HOLDER)
PsiKeyword:long('long')
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiElement(DOC_TAG_VALUE_ELEMENT)
PsiElement(DOC_TYPE_HOLDER)
PsiTypeElement:long
PsiKeyword:long('long')
PsiDocToken:DOC_TAG_VALUE_COMMA(',')
PsiWhiteSpace(' ')
PsiElement(DOC_TYPE_HOLDER)
PsiTypeElement:long
PsiKeyword:long('long')
PsiDocToken:DOC_TAG_VALUE_RPAREN(')')
PsiWhiteSpace('\n')
PsiDocToken:DOC_COMMENT_LEADING_ASTERISKS('///')
@@ -10,21 +10,18 @@ java.FILE
DOC_TAG
DOC_TAG_NAME
WHITE_SPACE
DOC_REFERENCE_HOLDER
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOC_TAG_VALUE_ELEMENT
DOC_METHOD_OR_FIELD_REF
DOC_TAG_VALUE_TOKEN
DOC_TAG_VALUE_LPAREN
DOC_REFERENCE_HOLDER
LONG_KEYWORD
DOC_TAG_VALUE_ELEMENT
DOC_TAG_VALUE_COMMA
WHITE_SPACE
DOC_REFERENCE_HOLDER
LONG_KEYWORD
DOC_TAG_VALUE_ELEMENT
DOC_TAG_VALUE_ELEMENT
DOC_TYPE_HOLDER
TYPE
LONG_KEYWORD
DOC_TAG_VALUE_COMMA
WHITE_SPACE
DOC_TYPE_HOLDER
TYPE
LONG_KEYWORD
DOC_TAG_VALUE_RPAREN
WHITE_SPACE
DOC_COMMENT_LEADING_ASTERISKS
@@ -10,21 +10,18 @@ java.FILE
DOC_TAG
DOC_TAG_NAME
WHITE_SPACE
DOC_REFERENCE_HOLDER
JAVA_CODE_REFERENCE
IDENTIFIER
REFERENCE_PARAMETER_LIST
<empty list>
DOC_TAG_VALUE_ELEMENT
DOC_METHOD_OR_FIELD_REF
DOC_TAG_VALUE_TOKEN
DOC_TAG_VALUE_LPAREN
DOC_REFERENCE_HOLDER
LONG_KEYWORD
DOC_TAG_VALUE_ELEMENT
DOC_TAG_VALUE_COMMA
WHITE_SPACE
DOC_REFERENCE_HOLDER
LONG_KEYWORD
DOC_TAG_VALUE_ELEMENT
DOC_TAG_VALUE_ELEMENT
DOC_TYPE_HOLDER
TYPE
LONG_KEYWORD
DOC_TAG_VALUE_COMMA
WHITE_SPACE
DOC_TYPE_HOLDER
TYPE
LONG_KEYWORD
DOC_TAG_VALUE_RPAREN
WHITE_SPACE
DOC_COMMENT_END
@@ -80,6 +80,7 @@ public class JavadocDeclarationHighlightingTest extends LightDaemonAnalyzerTestC
public void testLink0() { doTest(); }
public void testLink1() { doTest(); }
public void testLink2() { doTest(); }
public void testLink3() { doTest(); }
public void testLinkFromInnerClassToSelfMethod() { doTest(); }
public void testValueBadReference() { doTest(); }
public void testValueGoodReference() { doTest(); }
@@ -253,6 +253,7 @@ public class JavaDocInfoGeneratorTest extends JavaCodeInsightTestCase {
public void testPackageInfoMarkdown() { doTestPackageInfo(); }
public void testListInTags() { doTestMethod(); }
public void testParagraphInTagsMarkdown() { doTestMethod(); }
public void testBareMethodReferences() { doTestClass(); }
public void testPreTagInJavadocTag() { doTestClass();}
public void testLinkTagMalformed() { doTestClass(); }
public void testCursedCodeBlock() { doTestClass(); }
@@ -0,0 +1,47 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.navigation;
import com.intellij.refactoring.suggested.LightJavaCodeInsightFixtureTestCaseWithUtils;
public final class JavadocReferenceNavigationTest extends LightJavaCodeInsightFixtureTestCaseWithUtils {
private static final String BASIC_FUNCTION = "void function();";
private static final String BASIC_FUNCTION_ARGS = "void functionArgs(int beep, int boop);";
public void testTags() {
check("/// @see function<caret>", BASIC_FUNCTION);
check("/// @see #functionArgs<caret>(int, int)", BASIC_FUNCTION_ARGS);
check("/// {@link function<caret>()}", BASIC_FUNCTION);
check("/// {@link functionArgs<caret>(int, int)}", BASIC_FUNCTION_ARGS);
}
public void testMarkdownReferences() {
check("/// [function<caret>()]", BASIC_FUNCTION);
check("/// [functionArgs<caret>(int,int)]", BASIC_FUNCTION_ARGS);
check("/// [function<caret>()]", BASIC_FUNCTION);
check("/// [functionArgs<caret>(int,int)]", BASIC_FUNCTION_ARGS);
// Just ignore how the module is not taken into account here
check("/// [java.base/SeeTags#function<caret>()]", BASIC_FUNCTION);
}
/// Verify if the navigation brings you to the expected line
///
/// The caller is expected to bring a comment containing a `<caret>` tag
private void check(String comment, String expectedLine) {
myFixture.configureByText("SeeTags.java", """
%s
interface SeeTags {
%s
%s
}""".formatted(comment, BASIC_FUNCTION, BASIC_FUNCTION_ARGS));
navigateAndCheckLine(expectedLine);
}
private void navigateAndCheckLine(String expectedLine) {
myFixture.performEditorAction("GotoDeclaration");
final var selectionModel = myFixture.getEditor().getSelectionModel();
selectionModel.selectLineAtCaret();
assertEquals(expectedLine, selectionModel.getSelectedText().trim());
}
}
@@ -19,9 +19,9 @@ import com.intellij.psi.PsiElementFinder;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaReference;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiPolyVariantReference;import com.intellij.psi.PsiReference;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.testFramework.IndexingTestUtil;
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;import com.intellij.testFramework.IndexingTestUtil;
import com.intellij.testFramework.JavaResolveTestCase;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.util.containers.ContainerUtil;
@@ -68,6 +68,9 @@ public class ResolveClassInModulesWithDependenciesTest extends JavaResolveTestCa
configureDependency();
PsiReference ref = configure();
if (ref instanceof PsiMultiReference polyRef) {
ref = polyRef.getReferences()[0];
}
PsiElement target = ((PsiJavaReference)ref).advancedResolve(true).getElement();
assertNotNull(target);
assertNotSame(unrelated, ModuleUtilCore.findModuleForPsiElement(target));