mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
IDEA-162456 Auto-completion of generic class parameters inside javadoc comment
GitOrigin-RevId: abdb5b0b1cb155fb156a9feb6f05b82be4dced69
This commit is contained in:
committed by
intellij-monorepo-bot
parent
fa66a98490
commit
ac1c2422ab
@@ -82,7 +82,15 @@ public class JavaDocCompletionContributor extends CompletionContributor {
|
||||
boolean onlyConstants = !isArg && tag != null && tag.getName().equals(VALUE_TAG);
|
||||
|
||||
final PsiReference ref = position.getContainingFile().findReferenceAt(parameters.getOffset());
|
||||
if (ref instanceof PsiJavaReference) {
|
||||
PsiElement refElement = ref == null ? null : ref.getElement();
|
||||
if (refElement instanceof PsiDocParamRef) {
|
||||
result = result.withPrefixMatcher(
|
||||
refElement.getText().substring(0, parameters.getOffset() - refElement.getTextRange().getStartOffset()));
|
||||
for (PsiNamedElement param : getParametersToSuggest(PsiTreeUtil.getParentOfType(position, PsiDocComment.class))) {
|
||||
result.addElement(PrioritizedLookupElement.withPriority(LookupElementBuilder.create(param, nameForParamTag(param)), param instanceof PsiTypeParameter ? 0 : 1));
|
||||
}
|
||||
}
|
||||
else if (ref instanceof PsiJavaReference) {
|
||||
result = JavaCompletionSorting.addJavaSorting(parameters, result);
|
||||
result.stopHere();
|
||||
|
||||
@@ -340,14 +348,8 @@ public class JavaDocCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
|
||||
if ("param".equals(tagName)) {
|
||||
PsiMethod psiMethod = PsiTreeUtil.getParentOfType(comment, PsiMethod.class);
|
||||
if (psiMethod != null) {
|
||||
PsiDocTag[] tags = comment.getTags();
|
||||
for (PsiParameter param : psiMethod.getParameterList().getParameters()) {
|
||||
if (!JavadocHighlightUtil.hasTagForParameter(tags, param)) {
|
||||
result.add(tagName + " " + param.getName());
|
||||
}
|
||||
}
|
||||
for (PsiNamedElement parameter : getParametersToSuggest(comment)) {
|
||||
result.add(tagName + " " + nameForParamTag(parameter));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -367,6 +369,17 @@ public class JavaDocCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<PsiNamedElement> getParametersToSuggest(PsiDocComment comment) {
|
||||
List<PsiNamedElement> allParams = PsiDocParamRef.getAllParameters(comment);
|
||||
PsiDocTag[] tags = comment.getTags();
|
||||
return ContainerUtil.filter(allParams, param -> !JavadocHighlightUtil.hasTagForParameter(tags, param));
|
||||
}
|
||||
|
||||
private static String nameForParamTag(PsiNamedElement param) {
|
||||
String name = param.getName();
|
||||
return param instanceof PsiTypeParameter ? "<" + name + ">" : name;
|
||||
}
|
||||
|
||||
private static class InlineInsertHandler implements InsertHandler<LookupElement> {
|
||||
@Override
|
||||
public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) {
|
||||
|
||||
@@ -17,9 +17,11 @@ package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.patterns.PlatformPatterns;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
|
||||
import com.intellij.psi.javadoc.PsiDocTag;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -34,7 +36,7 @@ public class JavadocCompletionConfidence extends CompletionConfidence {
|
||||
@Override
|
||||
public ThreeState shouldSkipAutopopup(@NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) {
|
||||
if (psiElement().inside(PsiDocTag.class).accepts(contextElement)) {
|
||||
if (findJavaReference(psiFile, offset - 1) != null) {
|
||||
if (hasKnownReference(psiFile, offset - 1)) {
|
||||
return ThreeState.NO;
|
||||
}
|
||||
if (PlatformPatterns.psiElement(JavaDocTokenType.DOC_TAG_NAME).accepts(contextElement)) {
|
||||
@@ -47,17 +49,14 @@ public class JavadocCompletionConfidence extends CompletionConfidence {
|
||||
return super.shouldSkipAutopopup(contextElement, psiFile, offset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiJavaReference findJavaReference(final PsiFile file, final int offset) {
|
||||
private static boolean hasKnownReference(final PsiFile file, final int offset) {
|
||||
PsiReference reference = file.findReferenceAt(offset);
|
||||
if (reference instanceof PsiMultiReference) {
|
||||
for (final PsiReference psiReference : ((PsiMultiReference)reference).getReferences()) {
|
||||
if (psiReference instanceof PsiJavaReference) {
|
||||
return (PsiJavaReference)psiReference;
|
||||
}
|
||||
}
|
||||
}
|
||||
return reference instanceof PsiJavaReference ? (PsiJavaReference)reference : null;
|
||||
return reference instanceof PsiMultiReference
|
||||
? ContainerUtil.exists(((PsiMultiReference)reference).getReferences(), JavadocCompletionConfidence::isKnownReference)
|
||||
: isKnownReference(reference);
|
||||
}
|
||||
|
||||
private static boolean isKnownReference(@Nullable PsiReference reference) {
|
||||
return reference instanceof PsiJavaReference || reference != null && reference.getElement() instanceof PsiDocParamRef;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +19,20 @@ import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.tree.*;
|
||||
import com.intellij.psi.infos.CandidateInfo;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.javadoc.PsiDocTag;
|
||||
import com.intellij.psi.javadoc.PsiDocTagValue;
|
||||
import com.intellij.psi.javadoc.PsiDocToken;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.CharTable;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author mike
|
||||
@@ -50,39 +49,18 @@ public class PsiDocParamRef extends CompositePsiElement implements PsiDocTagValu
|
||||
final PsiJavaDocumentedElement owner = comment.getOwner();
|
||||
if (!(owner instanceof PsiMethod) &&
|
||||
!(owner instanceof PsiClass)) return null;
|
||||
final ASTNode valueToken = findChildByType(JavaDocTokenType.DOC_TAG_VALUE_TOKEN);
|
||||
ASTNode valueToken = getValueToken();
|
||||
if (valueToken == null) return null;
|
||||
final String name = valueToken.getText();
|
||||
PsiElement reference = null;
|
||||
final PsiElement firstChild = getFirstChild();
|
||||
if (firstChild instanceof PsiDocToken && ((PsiDocToken)firstChild).getTokenType().equals(JavaDocTokenType.DOC_TAG_VALUE_LT)) {
|
||||
final PsiTypeParameter[] typeParameters = ((PsiTypeParameterListOwner)owner).getTypeParameters();
|
||||
for (PsiTypeParameter typeParameter : typeParameters) {
|
||||
if (typeParameter.getName().equals(name)) {
|
||||
reference = typeParameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (owner instanceof PsiMethod) {
|
||||
final PsiParameter[] parameters = ((PsiMethod)owner).getParameterList().getParameters();
|
||||
for (PsiParameter parameter : parameters) {
|
||||
if (parameter.getName().equals(name)) {
|
||||
reference = parameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean isTypeParamRef = isTypeParamRef();
|
||||
PsiElement target = ContainerUtil.find(getAllParameters(comment),
|
||||
param -> isTypeParamRef == param instanceof PsiTypeParameter && name.equals(param.getName()));
|
||||
|
||||
final PsiElement resultReference = reference;
|
||||
return new PsiJavaReference() {
|
||||
TextRange range = TextRange.from(valueToken.getPsi().getStartOffsetInParent(), valueToken.getTextLength());
|
||||
return new PsiReferenceBase<PsiElement>(this, range) {
|
||||
@Override
|
||||
public PsiElement resolve() {
|
||||
return resultReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getCanonicalText() {
|
||||
return valueToken.getText();
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,90 +77,36 @@ public class PsiDocParamRef extends CompositePsiElement implements PsiDocTagValu
|
||||
if(!(element instanceof PsiParameter)) {
|
||||
throw new IncorrectOperationException("Unsupported operation");
|
||||
}
|
||||
return handleElementRename(((PsiParameter) element).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReferenceTo(@NotNull PsiElement element) {
|
||||
if (!(element instanceof PsiNamedElement)) return false;
|
||||
PsiNamedElement namedElement = (PsiNamedElement)element;
|
||||
if (!getCanonicalText().equals(namedElement.getName())) return false;
|
||||
return getManager().areElementsEquivalent(resolve(), element);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiElement[] getVariants() {
|
||||
final PsiElement firstChild = getFirstChild();
|
||||
|
||||
Set<String> usedNames = new HashSet<>();
|
||||
for (PsiDocTag tag : comment.getTags()) {
|
||||
if (tag.getName().equals("param")) {
|
||||
PsiDocTagValue valueElement = tag.getValueElement();
|
||||
if (valueElement != null) {
|
||||
usedNames.add(valueElement.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PsiNamedElement[] result = PsiNamedElement.EMPTY_ARRAY;
|
||||
if (firstChild instanceof PsiDocToken && ((PsiDocToken)firstChild).getTokenType().equals(JavaDocTokenType.DOC_TAG_VALUE_LT)) {
|
||||
result = ((PsiTypeParameterListOwner)owner).getTypeParameters();
|
||||
} else if (owner instanceof PsiMethod) {
|
||||
result = ((PsiMethod)owner).getParameterList().getParameters();
|
||||
}
|
||||
List<PsiElement> filtered = new ArrayList<>();
|
||||
for (PsiNamedElement namedElement : result) {
|
||||
if (!usedNames.contains(namedElement.getName())) {
|
||||
filtered.add(namedElement);
|
||||
}
|
||||
}
|
||||
return filtered.toArray(PsiElement.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSoft(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public TextRange getRangeInElement() {
|
||||
final int startOffsetInParent = valueToken.getPsi().getStartOffsetInParent();
|
||||
return new TextRange(startOffsetInParent, startOffsetInParent + valueToken.getTextLength());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElement getElement() {
|
||||
return PsiDocParamRef.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processVariants(@NotNull PsiScopeProcessor processor) {
|
||||
for (final PsiElement element : getVariants()) {
|
||||
if (!processor.execute(element, ResolveState.initial())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public JavaResolveResult advancedResolve(boolean incompleteCode) {
|
||||
return resultReference == null ? JavaResolveResult.EMPTY : new CandidateInfo(resultReference, PsiSubstitutor.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public JavaResolveResult[] multiResolve(boolean incompleteCode) {
|
||||
return resultReference == null
|
||||
? JavaResolveResult.EMPTY_ARRAY
|
||||
: new JavaResolveResult[]{new CandidateInfo(resultReference, PsiSubstitutor.EMPTY)};
|
||||
return handleElementRename(Objects.requireNonNull(((PsiParameter)element).getName()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<PsiNamedElement> getAllParameters(@NotNull PsiDocComment comment) {
|
||||
List<PsiNamedElement> allParams = new ArrayList<>();
|
||||
PsiJavaDocumentedElement owner = comment.getOwner();
|
||||
if (owner instanceof PsiMethod) {
|
||||
Collections.addAll(allParams, ((PsiMethod)owner).getParameterList().getParameters());
|
||||
}
|
||||
if (owner instanceof PsiMethod || owner instanceof PsiClass) {
|
||||
PsiTypeParameterList tpl = ((PsiTypeParameterListOwner)owner).getTypeParameterList();
|
||||
if (tpl != null) {
|
||||
Collections.addAll(allParams, tpl.getTypeParameters());
|
||||
}
|
||||
}
|
||||
return allParams;
|
||||
}
|
||||
|
||||
public boolean isTypeParamRef() {
|
||||
return PsiUtilCore.getElementType(getFirstChild()) == JavaDocTokenType.DOC_TAG_VALUE_LT;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ASTNode getValueToken() {
|
||||
return findChildByType(JavaDocTokenType.DOC_TAG_VALUE_TOKEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(@NotNull PsiElementVisitor visitor) {
|
||||
if (visitor instanceof JavaElementVisitor) {
|
||||
|
||||
@@ -3,5 +3,5 @@ class C{
|
||||
/**
|
||||
* @param <caret>
|
||||
*/
|
||||
private int foo(int a, char b, String c) {}
|
||||
private <A, B> int foo(int a, char b, String c) {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class C{
|
||||
|
||||
/**
|
||||
* @param <<caret>
|
||||
*/
|
||||
private <A, B> int foo(int a, char b, String c) {}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ class JavadocCompletionTest extends LightFixtureCompletionTestCase {
|
||||
|
||||
void testParamValueCompletion() {
|
||||
configureByFile("ParamValue0.java")
|
||||
assertStringItems("a", "b", "c")
|
||||
assertStringItems("a", "b", "c", "<A>", "<B>")
|
||||
}
|
||||
|
||||
void testParamValueWithPrefixCompletion() {
|
||||
@@ -90,6 +90,11 @@ class JavadocCompletionTest extends LightFixtureCompletionTestCase {
|
||||
assertStringItems("a1", "a2", "a3")
|
||||
}
|
||||
|
||||
void testTypeParamValueWithPrefix() {
|
||||
configureByTestName()
|
||||
assertStringItems("<A>", "<B>")
|
||||
}
|
||||
|
||||
void testDescribedParameters() {
|
||||
configureByFile("ParamValue2.java")
|
||||
assertStringItems("a2", "a3")
|
||||
@@ -246,6 +251,20 @@ class Foo {
|
||||
myFixture.assertPreferredCompletionItems 0, 'param', 'param param2'
|
||||
}
|
||||
|
||||
void "test suggest type param names"() {
|
||||
myFixture.configureByText "a.java", '''
|
||||
/**
|
||||
* @par<caret>
|
||||
*/
|
||||
class Foo<T,V>{}
|
||||
'''
|
||||
myFixture.completeBasic()
|
||||
myFixture.assertPreferredCompletionItems 0, 'param', 'param <T>', 'param <V>'
|
||||
myFixture.type('\n <T>\n@para')
|
||||
myFixture.completeBasic()
|
||||
myFixture.assertPreferredCompletionItems 0, 'param', 'param <V>'
|
||||
}
|
||||
|
||||
void "test fqns in package info"() {
|
||||
myFixture.configureByText "package-info.java", '''
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user