IDEA-162456 Auto-completion of generic class parameters inside javadoc comment

GitOrigin-RevId: abdb5b0b1cb155fb156a9feb6f05b82be4dced69
This commit is contained in:
peter
2019-10-04 19:34:11 +02:00
committed by intellij-monorepo-bot
parent fa66a98490
commit ac1c2422ab
6 changed files with 98 additions and 136 deletions

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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) {}
}

View File

@@ -0,0 +1,7 @@
class C{
/**
* @param <<caret>
*/
private <A, B> int foo(int a, char b, String c) {}
}

View File

@@ -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", '''
/**