IDEA-89175 Completing an annotation on an @interface class should always suggest meta-annotations

This commit is contained in:
peter
2012-07-24 12:29:58 +02:00
parent d901cd9d5d
commit 8e87fb2551
5 changed files with 220 additions and 201 deletions
@@ -30,6 +30,9 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import static com.intellij.patterns.PsiJavaPatterns.psiClass;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
/**
* @author peter
*/
@@ -37,17 +40,24 @@ public class JavaNoVariantsDelegator extends CompletionContributor {
@Override
public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet result) {
final boolean empty = containsOnlyPackages(result.runRemainingContributors(parameters, true));
final boolean empty = containsOnlyPackages(result.runRemainingContributors(parameters, true)) ||
suggestMetaAnnotations(parameters);
if (!empty && parameters.getInvocationCount() == 0) {
result.restartCompletionWhenNothingMatches();
}
if (empty) {
delegate(parameters, result);
delegate(parameters, JavaCompletionSorting.addJavaSorting(parameters, result));
}
}
private static boolean suggestMetaAnnotations(CompletionParameters parameters) {
PsiElement position = parameters.getPosition();
return psiElement().withParents(PsiJavaCodeReferenceElement.class, PsiAnnotation.class, PsiModifierList.class, PsiClass.class).accepts( position) &&
psiElement().withSuperParent(4, psiClass().isAnnotationType()).accepts(position);
}
public static boolean containsOnlyPackages(LinkedHashSet<CompletionResult> results) {
for (CompletionResult result : results) {
if (!(CompletionUtil.getTargetElement(result.getLookupElement()) instanceof PsiPackage)) {
@@ -152,7 +162,7 @@ public class JavaNoVariantsDelegator extends CompletionContributor {
final ClassByNameMerger merger = new ClassByNameMerger(parameters.getInvocationCount() == 0, result);
JavaClassNameCompletionContributor.addAllClasses(parameters,
true, JavaCompletionSorting.addJavaSorting(parameters, result).getPrefixMatcher(), new Consumer<LookupElement>() {
true, result.getPrefixMatcher(), new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
JavaPsiClassReferenceElement classElement = element.as(JavaPsiClassReferenceElement.CLASS_CONDITION_KEY);
@@ -1,12 +1,2 @@
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Z<caret>
@interface Foo {
}
@Target({ElementType.ANNOTATION_TYPE})
@interface ZMetaAnno {}
@Target({ElementType.LOCAL_VARIABLE})
@interface ZLocalAnno {}
@T<caret>
@interface Foo { }
@@ -343,7 +343,16 @@ public class NormalCompletionOrderingTest extends CompletionSortingTestCase {
}
public void testPreferApplicableAnnotations() throws Throwable {
checkPreferredItems 0, 'ZMetaAnno', 'ZLocalAnno'
myFixture.addClass '''
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE})
@interface TMetaAnno {}
@Target({ElementType.LOCAL_VARIABLE})
@interface TLocalAnno {}'''
checkPreferredItems 0, 'TMetaAnno', 'Target', 'TreeSelectionMode', 'TLocalAnno'
}
public void testJComponentAddNewWithStats() throws Throwable {
@@ -1,176 +1,188 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.lang.completion;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.psi.*;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @author peter
*/
public class GroovyNoVariantsDelegator extends CompletionContributor {
@Override
public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet result) {
final boolean empty = JavaNoVariantsDelegator.containsOnlyPackages(result.runRemainingContributors(parameters, true));
if (!empty && parameters.getInvocationCount() == 0) {
result.restartCompletionWhenNothingMatches();
}
if (empty) {
delegate(parameters, result);
}
}
private static void delegate(CompletionParameters parameters, CompletionResultSet result) {
if (parameters.getCompletionType() == CompletionType.BASIC) {
if (parameters.getInvocationCount() <= 1 &&
JavaCompletionContributor.mayStartClassName(result) &&
GroovyCompletionContributor.isClassNamePossible(parameters.getPosition()) &&
!MapArgumentCompletionProvider.isMapKeyCompletion(parameters)) {
suggestNonImportedClasses(parameters, result);
}
suggestChainedCalls(parameters, result);
}
}
private static void suggestNonImportedClasses(CompletionParameters parameters, CompletionResultSet result) {
final ClassByNameMerger merger = new ClassByNameMerger(parameters.getInvocationCount() == 0, result);
GroovyCompletionContributor.addAllClasses(parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
JavaPsiClassReferenceElement classElement =
element.as(JavaPsiClassReferenceElement.CLASS_CONDITION_KEY);
if (classElement != null) {
classElement.setAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
}
merger.consume(classElement);
}
}, new InheritorsHolder(parameters.getPosition(), result), result.getPrefixMatcher());
merger.finishedClassProcessing();
}
private static void suggestChainedCalls(CompletionParameters parameters, CompletionResultSet result) {
PsiElement position = parameters.getPosition();
PsiElement parent = position.getParent();
if (!(parent instanceof GrReferenceElement)) {
return;
}
PsiElement qualifier = ((GrReferenceElement)parent).getQualifier();
if (!(qualifier instanceof GrReferenceElement) ||
((GrReferenceElement)qualifier).getQualifier() != null) {
return;
}
PsiElement target = ((GrReferenceElement)qualifier).resolve();
if (target != null && !(target instanceof PsiPackage)) {
return;
}
String fullPrefix = position.getContainingFile().getText().substring(parent.getTextRange().getStartOffset(), parameters.getOffset());
final CompletionResultSet qualifiedCollector = result.withPrefixMatcher(fullPrefix);
InheritorsHolder inheritors = new InheritorsHolder(position, result);
for (final LookupElement base : suggestQualifierItems(parameters, (GrReferenceElement)qualifier, inheritors)) {
final PsiType type = JavaCompletionUtil.getLookupElementType(base);
if (type != null && !PsiType.VOID.equals(type)) {
GrReferenceElement ref = createMockReference(position, type, base);
PsiElement refName = ref.getReferenceNameElement();
assert refName != null;
CompletionParameters newParams = parameters.withPosition(refName, refName.getTextRange().getStartOffset());
GroovyCompletionContributor.completeReference(newParams, ref, inheritors, result.getPrefixMatcher(), new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
qualifiedCollector.addElement(new JavaChainLookupElement(base, element) {
@Override
protected boolean shouldParenthesizeQualifier(PsiFile file, int startOffset, int endOffset) {
return false;
}
});
}
});
}
}
}
private static GrReferenceElement createMockReference(final PsiElement place, @NotNull PsiType qualifierType, LookupElement qualifierItem) {
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(place.getProject());
if (qualifierItem.getObject() instanceof PsiClass) {
return factory
.createReferenceExpressionFromText(((PsiClass)qualifierItem.getObject()).getQualifiedName() + ".xxx", place);
}
return factory.createReferenceExpressionFromText("xxx.xxx",
JavaCompletionUtil
.createContextWithXxxVariable(place, qualifierType));
}
private static Set<LookupElement> suggestQualifierItems(CompletionParameters _parameters,
GrReferenceElement qualifier,
InheritorsHolder inheritors) {
CompletionParameters parameters =
_parameters.withPosition(qualifier.getReferenceNameElement(), qualifier.getTextRange().getEndOffset());
String referenceName = qualifier.getReferenceName();
if (referenceName == null) {
return Collections.emptySet();
}
final PrefixMatcher qMatcher = new CamelHumpMatcher(referenceName);
final Set<LookupElement> variants = new LinkedHashSet<LookupElement>();
GroovyCompletionContributor.completeReference(parameters, qualifier, inheritors, qMatcher, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
if (qMatcher.prefixMatches(element)) {
variants.add(element);
}
}
});
for (PsiClass aClass : PsiShortNamesCache.getInstance(qualifier.getProject()).getClassesByName(referenceName, qualifier.getResolveScope())) {
variants.add(GroovyCompletionUtil.createClassLookupItem(aClass));
}
if (variants.isEmpty()) {
GroovyCompletionContributor.addAllClasses(parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
if (qMatcher.prefixMatches(element)) {
variants.add(element);
}
}
}, inheritors, qMatcher);
}
return variants;
}
}
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.lang.completion;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.psi.*;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnnotationTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
/**
* @author peter
*/
public class GroovyNoVariantsDelegator extends CompletionContributor {
private static boolean suggestMetaAnnotations(CompletionParameters parameters) {
PsiElement position = parameters.getPosition();
return psiElement().withParents(GrCodeReferenceElement.class, GrAnnotation.class, GrModifierList.class, GrAnnotationTypeDefinition.class).accepts(position);
}
@Override
public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet result) {
final boolean empty = JavaNoVariantsDelegator.containsOnlyPackages(result.runRemainingContributors(parameters, true)) ||
suggestMetaAnnotations(parameters);
if (!empty && parameters.getInvocationCount() == 0) {
result.restartCompletionWhenNothingMatches();
}
if (empty) {
delegate(parameters, result);
}
}
private static void delegate(CompletionParameters parameters, CompletionResultSet result) {
if (parameters.getCompletionType() == CompletionType.BASIC) {
if (parameters.getInvocationCount() <= 1 &&
JavaCompletionContributor.mayStartClassName(result) &&
GroovyCompletionContributor.isClassNamePossible(parameters.getPosition()) &&
!MapArgumentCompletionProvider.isMapKeyCompletion(parameters)) {
suggestNonImportedClasses(parameters, result);
}
suggestChainedCalls(parameters, result);
}
}
private static void suggestNonImportedClasses(CompletionParameters parameters, CompletionResultSet result) {
final ClassByNameMerger merger = new ClassByNameMerger(parameters.getInvocationCount() == 0, result);
GroovyCompletionContributor.addAllClasses(parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
JavaPsiClassReferenceElement classElement =
element.as(JavaPsiClassReferenceElement.CLASS_CONDITION_KEY);
if (classElement != null) {
classElement.setAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
}
merger.consume(classElement);
}
}, new InheritorsHolder(parameters.getPosition(), result), result.getPrefixMatcher());
merger.finishedClassProcessing();
}
private static void suggestChainedCalls(CompletionParameters parameters, CompletionResultSet result) {
PsiElement position = parameters.getPosition();
PsiElement parent = position.getParent();
if (!(parent instanceof GrReferenceElement)) {
return;
}
PsiElement qualifier = ((GrReferenceElement)parent).getQualifier();
if (!(qualifier instanceof GrReferenceElement) ||
((GrReferenceElement)qualifier).getQualifier() != null) {
return;
}
PsiElement target = ((GrReferenceElement)qualifier).resolve();
if (target != null && !(target instanceof PsiPackage)) {
return;
}
String fullPrefix = position.getContainingFile().getText().substring(parent.getTextRange().getStartOffset(), parameters.getOffset());
final CompletionResultSet qualifiedCollector = result.withPrefixMatcher(fullPrefix);
InheritorsHolder inheritors = new InheritorsHolder(position, result);
for (final LookupElement base : suggestQualifierItems(parameters, (GrReferenceElement)qualifier, inheritors)) {
final PsiType type = JavaCompletionUtil.getLookupElementType(base);
if (type != null && !PsiType.VOID.equals(type)) {
GrReferenceElement ref = createMockReference(position, type, base);
PsiElement refName = ref.getReferenceNameElement();
assert refName != null;
CompletionParameters newParams = parameters.withPosition(refName, refName.getTextRange().getStartOffset());
GroovyCompletionContributor.completeReference(newParams, ref, inheritors, result.getPrefixMatcher(), new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
qualifiedCollector.addElement(new JavaChainLookupElement(base, element) {
@Override
protected boolean shouldParenthesizeQualifier(PsiFile file, int startOffset, int endOffset) {
return false;
}
});
}
});
}
}
}
private static GrReferenceElement createMockReference(final PsiElement place, @NotNull PsiType qualifierType, LookupElement qualifierItem) {
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(place.getProject());
if (qualifierItem.getObject() instanceof PsiClass) {
return factory
.createReferenceExpressionFromText(((PsiClass)qualifierItem.getObject()).getQualifiedName() + ".xxx", place);
}
return factory.createReferenceExpressionFromText("xxx.xxx",
JavaCompletionUtil
.createContextWithXxxVariable(place, qualifierType));
}
private static Set<LookupElement> suggestQualifierItems(CompletionParameters _parameters,
GrReferenceElement qualifier,
InheritorsHolder inheritors) {
CompletionParameters parameters =
_parameters.withPosition(qualifier.getReferenceNameElement(), qualifier.getTextRange().getEndOffset());
String referenceName = qualifier.getReferenceName();
if (referenceName == null) {
return Collections.emptySet();
}
final PrefixMatcher qMatcher = new CamelHumpMatcher(referenceName);
final Set<LookupElement> variants = new LinkedHashSet<LookupElement>();
GroovyCompletionContributor.completeReference(parameters, qualifier, inheritors, qMatcher, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
if (qMatcher.prefixMatches(element)) {
variants.add(element);
}
}
});
for (PsiClass aClass : PsiShortNamesCache.getInstance(qualifier.getProject()).getClassesByName(referenceName, qualifier.getResolveScope())) {
variants.add(GroovyCompletionUtil.createClassLookupItem(aClass));
}
if (variants.isEmpty()) {
GroovyCompletionContributor.addAllClasses(parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
if (qMatcher.prefixMatches(element)) {
variants.add(element);
}
}
}, inheritors, qMatcher);
}
return variants;
}
}
@@ -1399,21 +1399,19 @@ def bar(){}''')
}
public void testPreferApplicableAnnotations() {
configure('''
myFixture.addClass '''
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Z<caret>
@interface Foo {
}
@Target({ElementType.ANNOTATION_TYPE})
@interface TMetaAnno {}
@Target(ElementType.ANNOTATION_TYPE)
@interface ZMetaAnno {}
@Target({ElementType.LOCAL_VARIABLE})
@interface TLocalAnno {}'''
@Target(ElementType.LOCAL_VARIABLE)
@interface ZLocalAnno {}''')
configure('@T<caret> @interface Foo {}')
myFixture.completeBasic()
myFixture.assertPreferredCompletionItems 0, 'ZMetaAnno', 'ZLocalAnno'
myFixture.assertPreferredCompletionItems 0, 'TMetaAnno', 'Target', 'TreeSelectionMode', 'TLocalAnno'
}
}