[groovy] support @SelfType (IDEA-141131)

This commit is contained in:
Daniil Ovchinnikov
2016-09-05 22:49:05 +03:00
parent a000ad207b
commit 12a960afc5
5 changed files with 167 additions and 4 deletions
@@ -384,3 +384,4 @@ anonymous.classes.cannot.be.created.from.traits=Anonymous classes cannot be crea
trait.method.cannot.be.protected=Trait methods are not allowed to be protected
traits.are.not.supported.in.groovy.0=Traits are not supported in Groovy {0}
non.static.classes.not.allowed=Non-static inner classes are not allowed in traits
selfType.class.does.not.inherit=@SelfType: Class ''{0}'' does not inherit ''{1}''
@@ -416,6 +416,18 @@ public class GroovyAnnotator extends GroovyElementVisitor {
checkAnnotationCollector(myHolder, typeDefinition);
checkSameNameMethodsWithDifferentAccessModifiers(myHolder, typeDefinition.getCodeMethods());
checkInheritorOfSelfTypes(myHolder, typeDefinition);
}
private static void checkInheritorOfSelfTypes(AnnotationHolder holder, GrTypeDefinition definition) {
if (!(definition instanceof GrClassDefinition)) return;
List<PsiClass> selfTypeClasses = GrTraitUtil.getSelfTypeClasses(definition);
for (PsiClass selfClass : selfTypeClasses) {
if (InheritanceUtil.isInheritorOrSelf(definition, selfClass, true)) continue;
String message = GroovyBundle.message("selfType.class.does.not.inherit", definition.getQualifiedName(), selfClass.getQualifiedName());
holder.createErrorAnnotation(GrHighlightUtil.getClassHeaderTextRange(definition), message);
break;
}
}
private static void checkSameNameMethodsWithDifferentAccessModifiers(AnnotationHolder holder, GrMethod[] methods) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2016 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.
@@ -16,11 +16,19 @@
package org.jetbrains.plugins.groovy.lang.psi.impl.statements.typedef;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTraitTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.stubs.GrTypeDefinitionStub;
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
import java.util.List;
/**
* Created by Max Medvedev on 09/04/14
@@ -54,4 +62,17 @@ public class GrTraitTypeDefinitionImpl extends GrTypeDefinitionImpl implements G
public void accept(GroovyElementVisitor visitor) {
visitor.visitTraitDefinition(this);
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@Nullable PsiElement lastParent,
@NotNull PsiElement place) {
if (!super.processDeclarations(processor, state, lastParent, place)) return false;
List<PsiClass> classes = GrTraitUtil.getSelfTypeClasses(this);
for (PsiClass clazz : classes) {
if (!clazz.processDeclarations(processor, state, lastParent, place)) return false;
}
return true;
}
}
@@ -27,6 +27,7 @@ import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.Contract;
@@ -34,15 +35,14 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightField;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder;
import org.jetbrains.plugins.groovy.lang.resolve.GroovyTraitFieldsFileIndex;
import org.jetbrains.plugins.groovy.lang.resolve.GroovyTraitFieldsFileIndex.TraitFieldDescriptor;
import org.jetbrains.plugins.groovy.lang.resolve.GroovyTraitMethodsFileIndex;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.*;
import static com.intellij.psi.PsiModifier.ABSTRACT;
import static org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierFlags.*;
@@ -71,6 +71,29 @@ public class GrTraitUtil {
return method.getModifierList().hasExplicitModifier(ABSTRACT) || isInterface(method.getContainingClass());
}
public static List<PsiClass> getSelfTypeClasses(@NotNull PsiClass trait) {
return CachedValuesManager.getCachedValue(trait, () -> {
List<PsiClass> result = ContainerUtil.newArrayList();
Queue<PsiClass> queue = new ArrayDeque<>();
Set<PsiClass> visited = ContainerUtil.newHashSet();
queue.offer(trait);
while (!queue.isEmpty()) {
PsiClass clazz = queue.poll();
if (!visited.add(clazz)) continue;
ContainerUtil.addAll(queue, clazz.getSupers());
if (isTrait(clazz)) {
PsiAnnotation annotation = AnnotationUtil.findAnnotation(clazz, "groovy.transform.SelfType");
if (annotation != null) {
result.addAll(
GrAnnotationUtil.getClassArrayValue(annotation, "value")
);
}
}
}
return CachedValueProvider.Result.create(result, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
});
}
@NotNull
public static String getTraitFieldPrefix(@NotNull PsiClass aClass) {
String qname = aClass.getQualifiedName();
@@ -0,0 +1,106 @@
/*
* Copyright 2000-2016 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.resolve
import com.intellij.psi.PsiElement
import com.intellij.testFramework.LightProjectDescriptor
import groovy.transform.CompileStatic
import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor
import org.jetbrains.plugins.groovy.LightGroovyTestCase
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
@CompileStatic
class SelfTypeSupportTest extends LightGroovyTestCase {
LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST
String defaultImports = '''\
import groovy.transform.SelfType
'''
void 'test resolve from within trait'() {
def resolved = resolveByText('''\
interface I { def foo() }
@SelfType(I)
trait T {
def bar() {
fo<caret>o()
}
}
''')
assert (resolved as GrMethod).containingClass.name == 'I'
}
void 'test resolve outside trait'() {
def resolved = resolveByText('''\
interface I { def foo() }
@SelfType(I)
trait T {}
def bar(T t) {
t.f<caret>oo()
}
''')
assert (resolved as GrMethod).containingClass.name == 'I'
}
void 'test resolve inside trait extending trait'() {
def resolved = resolveByText('''\
interface I { def foo() }
@SelfType(I)
trait T {}
trait T2 extends T {
def bar() {
f<caret>oo()
}
}
''')
assert (resolved as GrMethod).containingClass.name == 'I'
}
void 'test do not count @SelfType on interfaces in hierarchy'() {
assert resolveByText('''\
interface I { def foo() }
@SelfType(I)
interface II {}
trait T implements II {
def bar() {
fo<caret>o()
}
}
''') == null
}
void 'test highlighting'() {
testHighlighting '''\
interface I {}
@SelfType(I)
trait T {}
<error descr="@SelfType: Class 'A' does not inherit 'I'">class A implements T</error> {}
trait T2 extends T {}
<error descr="@SelfType: Class 'B' does not inherit 'I'">class B implements T2</error> {}
'''
}
PsiElement resolveByText(String text) {
fixture.configureByText '_.groovy', "$defaultImports$text"
file.findReferenceAt(editor.caretModel.offset).resolve()
}
void testHighlighting(String text) {
fixture.configureByText '_.groovy', "$defaultImports$text"
fixture.checkHighlighting()
}
}