diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitor.java index 61b63d28896a..0b44ef51462e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitor.java @@ -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) { diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavadocParsingTest.java b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavadocParsingTest.java index 7ca00216a494..e13b720e8c9b 100644 --- a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavadocParsingTest.java +++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavadocParsingTest.java @@ -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); } diff --git a/java/java-impl-refactorings/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteDelegateImpl.java b/java/java-impl-refactorings/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteDelegateImpl.java index 816e56ca80a6..e9d1d1f9ce7f 100644 --- a/java/java-impl-refactorings/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteDelegateImpl.java +++ b/java/java-impl-refactorings/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteDelegateImpl.java @@ -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); } diff --git a/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavaDocReferenceInspection.java b/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavaDocReferenceInspection.java index 35b63c3c532b..954256ac19a8 100644 --- a/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavaDocReferenceInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavaDocReferenceInspection.java @@ -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; diff --git a/java/java-psi-api/src/com/intellij/psi/JavaElementVisitor.java b/java/java-psi-api/src/com/intellij/psi/JavaElementVisitor.java index cb11c16ac8dd..89c46b35bb9a 100644 --- a/java/java-psi-api/src/com/intellij/psi/JavaElementVisitor.java +++ b/java/java-psi-api/src/com/intellij/psi/JavaElementVisitor.java @@ -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); } diff --git a/java/java-psi-api/src/com/intellij/psi/javadoc/PsiDocReferenceHolder.java b/java/java-psi-api/src/com/intellij/psi/javadoc/PsiDocReferenceHolder.java new file mode 100644 index 000000000000..3ed3e32ecfb7 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/psi/javadoc/PsiDocReferenceHolder.java @@ -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 { +} diff --git a/java/java-psi-impl/src/com/intellij/codeInsight/javadoc/JavaDocUtil.java b/java/java-psi-impl/src/com/intellij/codeInsight/javadoc/JavaDocUtil.java index 508fe6c7b184..ec30d1402ad3 100644 --- a/java/java-psi-impl/src/com/intellij/codeInsight/javadoc/JavaDocUtil.java +++ b/java/java-psi-impl/src/com/intellij/codeInsight/javadoc/JavaDocUtil.java @@ -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, diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/javadoc/PsiDocMethodOrFieldRef.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/javadoc/PsiDocMethodOrFieldRef.java index 6fe4341bb929..0ab8a378e2b9 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/javadoc/PsiDocMethodOrFieldRef.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/javadoc/PsiDocMethodOrFieldRef.java @@ -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 SIGNATURE_TO_REPLACE = Arrays.asList("\\[", "\\]"); private static final List 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 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 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 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 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; } } } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/javadoc/PsiDocReferenceHolderImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/javadoc/PsiDocReferenceHolderImpl.java new file mode 100644 index 000000000000..044d61d62441 --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/javadoc/PsiDocReferenceHolderImpl.java @@ -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(); + } + }; + } +} diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/JavaDocElementType.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/JavaDocElementType.java index d91015f4f442..a426d106a794 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/JavaDocElementType.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/JavaDocElementType.java @@ -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 { diff --git a/java/java-syntax/src/com/intellij/java/syntax/parser/JavaDocParser.kt b/java/java-syntax/src/com/intellij/java/syntax/parser/JavaDocParser.kt index 57e1618809aa..061dc4debef3 100644 --- a/java/java-syntax/src/com/intellij/java/syntax/parser/JavaDocParser.kt +++ b/java/java-syntax/src/com/intellij/java/syntax/parser/JavaDocParser.kt @@ -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()) diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/javadocDeclaration/Link3.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/javadocDeclaration/Link3.java new file mode 100644 index 000000000000..e7eaec31e32a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/javadocDeclaration/Link3.java @@ -0,0 +1,9 @@ +/// [java.lang.String] +/// [foo] +/// {@link foo} +/// [bar] +/// {@link bar} +class Test { + public void foo(Object bar) {} + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/bareMethodReferences.html b/java/java-tests/testData/codeInsight/javadocIG/bareMethodReferences.html new file mode 100644 index 000000000000..c5dc7abcc237 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/bareMethodReferences.html @@ -0,0 +1,4 @@ +
class Main

\ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/bareMethodReferences.java b/java/java-tests/testData/codeInsight/javadocIG/bareMethodReferences.java new file mode 100644 index 000000000000..bd44acd46805 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/bareMethodReferences.java @@ -0,0 +1,7 @@ + +/// [equals] +/// [equals(Object)] +/// {@link equals} +/// {@link equals(Object)} +class Main { +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11.java b/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11.java new file mode 100644 index 000000000000..ecb849d8fc45 --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11.java @@ -0,0 +1,5 @@ +/** + * {@link equals} + * {@link equals(Object)} + */ +public class C {} \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11.txt b/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11.txt new file mode 100644 index 000000000000..3764105dfe9f --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11.txt @@ -0,0 +1,56 @@ +PsiJavaFile:LinkTag11.java + PsiImportList + + 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 + + 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 + + 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 + + PsiReferenceList + + PsiReferenceList + + PsiWhiteSpace(' ') + PsiJavaToken:LBRACE('{') + PsiJavaToken:RBRACE('}') \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11_node.txt b/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11_node.txt new file mode 100644 index 000000000000..fcb4635b1bca --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/LinkTag11_node.txt @@ -0,0 +1,56 @@ +java.FILE + IMPORT_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 + + 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 + + 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 + + EXTENDS_LIST + + IMPLEMENTS_LIST + + WHITE_SPACE + LBRACE + RBRACE \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15.java b/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15.java new file mode 100644 index 000000000000..a140198e680e --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15.java @@ -0,0 +1,3 @@ +/// [equals] method Link without the hash char and params +/// [equals(Object)] method Link without the hash char +class C {} \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15.txt b/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15.txt new file mode 100644 index 000000000000..da763c2f89fb --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15.txt @@ -0,0 +1,49 @@ +PsiJavaFile:ReferenceLinkMarkdown15.java + PsiImportList + + PsiClass:C + PsiDocComment + PsiDocToken:DOC_COMMENT_LEADING_ASTERISKS('///') + PsiWhiteSpace(' ') + PsiReferenceLink: + PsiDocToken:DOC_LBRACKET('[') + PsiElement(DOC_REFERENCE_HOLDER) + PsiJavaCodeReferenceElement:equals + PsiIdentifier:equals('equals') + PsiReferenceParameterList + + 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 + + PsiDocToken:DOC_RPAREN(')') + PsiDocToken:DOC_RBRACKET(']') + PsiDocToken:DOC_COMMENT_DATA(' method Link without the hash char') + PsiWhiteSpace('\n') + PsiModifierList: + + PsiKeyword:class('class') + PsiWhiteSpace(' ') + PsiIdentifier:C('C') + PsiTypeParameterList + + PsiReferenceList + + PsiReferenceList + + PsiWhiteSpace(' ') + PsiJavaToken:LBRACE('{') + PsiJavaToken:RBRACE('}') \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15_node.txt b/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15_node.txt new file mode 100644 index 000000000000..c7e4b20cb7a8 --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/ReferenceLinkMarkdown15_node.txt @@ -0,0 +1,49 @@ +java.FILE + IMPORT_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 + + 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 + + DOC_RPAREN + DOC_RBRACKET + DOC_COMMENT_DATA + WHITE_SPACE + MODIFIER_LIST + + CLASS_KEYWORD + WHITE_SPACE + IDENTIFIER + TYPE_PARAMETER_LIST + + EXTENDS_LIST + + IMPLEMENTS_LIST + + WHITE_SPACE + LBRACE + RBRACE \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9.txt b/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9.txt index 7b2b10a6a7ce..d04f93c5c4d9 100644 --- a/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9.txt +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9.txt @@ -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 - - 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('*/') diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9Markdown.txt b/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9Markdown.txt index 380bf9806fdd..8f416c04f1aa 100644 --- a/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9Markdown.txt +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9Markdown.txt @@ -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 - - 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('///') diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9Markdown_node.txt b/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9Markdown_node.txt index 558448e9b63c..65ded921b58e 100644 --- a/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9Markdown_node.txt +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9Markdown_node.txt @@ -10,21 +10,18 @@ java.FILE DOC_TAG DOC_TAG_NAME WHITE_SPACE - DOC_REFERENCE_HOLDER - JAVA_CODE_REFERENCE - IDENTIFIER - REFERENCE_PARAMETER_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 diff --git a/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9_node.txt b/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9_node.txt index 97c4f544882d..c1175d162c0e 100644 --- a/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9_node.txt +++ b/java/java-tests/testData/psi/parser-full/javadocParsing/SeeTag9_node.txt @@ -10,21 +10,18 @@ java.FILE DOC_TAG DOC_TAG_NAME WHITE_SPACE - DOC_REFERENCE_HOLDER - JAVA_CODE_REFERENCE - IDENTIFIER - REFERENCE_PARAMETER_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 diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/JavadocDeclarationHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/JavadocDeclarationHighlightingTest.java index 8fd153e20294..a2c6c663cc4e 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/JavadocDeclarationHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/JavadocDeclarationHighlightingTest.java @@ -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(); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavaDocInfoGeneratorTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavaDocInfoGeneratorTest.java index 9d6664f9b62d..2a2edbbab136 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavaDocInfoGeneratorTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavaDocInfoGeneratorTest.java @@ -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(); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavadocReferenceNavigationTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavadocReferenceNavigationTest.java new file mode 100644 index 000000000000..3aa22784c632 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavadocReferenceNavigationTest.java @@ -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", BASIC_FUNCTION); + check("/// @see #functionArgs(int, int)", BASIC_FUNCTION_ARGS); + + check("/// {@link function()}", BASIC_FUNCTION); + check("/// {@link functionArgs(int, int)}", BASIC_FUNCTION_ARGS); + } + + public void testMarkdownReferences() { + check("/// [function()]", BASIC_FUNCTION); + check("/// [functionArgs(int,int)]", BASIC_FUNCTION_ARGS); + check("/// [function()]", BASIC_FUNCTION); + check("/// [functionArgs(int,int)]", BASIC_FUNCTION_ARGS); + + // Just ignore how the module is not taken into account here + check("/// [java.base/SeeTags#function()]", BASIC_FUNCTION); + } + + /// Verify if the navigation brings you to the expected line + /// + /// The caller is expected to bring a comment containing a `` 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()); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveClassInModulesWithDependenciesTest.java b/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveClassInModulesWithDependenciesTest.java index 7bab75c0a229..e815c9d9e8b6 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveClassInModulesWithDependenciesTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveClassInModulesWithDependenciesTest.java @@ -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));