mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
mixin support for Groovy refactored
This commit is contained in:
@@ -20,12 +20,3 @@ contributor(ctype:hasAnnotation("groovy.lang.Category")) {
|
||||
}
|
||||
}
|
||||
|
||||
contributor(ctype:hasAnnotation("groovy.lang.Mixin")) {
|
||||
def annotations = psiClass?.getAnnotations("groovy.lang.Mixin")
|
||||
for (a in annotations) {
|
||||
def refs = a.findAttributeValue("value")
|
||||
for (c in refs?.asList()) {
|
||||
delegatesTo c?.bind()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@
|
||||
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.noncode.GrCollectionTypeMembersProvider"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.noncode.LoggingContributor"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.groovy.spock.SpockMemberContributor"/>
|
||||
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.MixinMemberContributor"/>
|
||||
|
||||
<astTransformContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.DelegatedMethodsContributor"/>
|
||||
<astTransformContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.AutoExternalizeContributor"/>
|
||||
|
||||
+2
-1
@@ -46,7 +46,8 @@ public final class GroovyCommonClassNames {
|
||||
@NonNls public static final String GROOVY_TRANSFORM_AUTO_EXTERNALIZE = "groovy.transform.AutoExternalize";
|
||||
@NonNls public static final String GROOVY_TRANSFORM_AUTO_CLONE = "groovy.transform.AutoClone";
|
||||
@NonNls public static final String GROOVY_LANG_CATEGORY = "groovy.lang.Category";
|
||||
public static final String GROOVY_LANG_USE = "groovy.lang.Use";
|
||||
@NonNls public static final String GROOVY_LANG_USE = "groovy.lang.Use";
|
||||
@NonNls public static final String GROOVY_LANG_MIXIN = "groovy.lang.Mixin";
|
||||
|
||||
|
||||
private GroovyCommonClassNames() {
|
||||
|
||||
+9
-10
@@ -75,9 +75,9 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
|
||||
}
|
||||
|
||||
public static GdkMethodHolder retrieveMethodMap(final Project project,
|
||||
final GlobalSearchScope scope,
|
||||
final boolean isStatic,
|
||||
@NotNull final PsiClass categoryClass) {
|
||||
final GlobalSearchScope scope,
|
||||
final boolean isStatic,
|
||||
@NotNull final PsiClass categoryClass) {
|
||||
return CachedValuesManager.getManager(project)
|
||||
.getCachedValue(categoryClass, METHOD_KEY, new CachedValueProvider<GdkMethodHolder>() {
|
||||
@Override
|
||||
@@ -86,7 +86,8 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
|
||||
|
||||
final ProjectRootManager rootManager = ProjectRootManager.getInstance(project);
|
||||
final VirtualFile vfile = categoryClass.getContainingFile().getVirtualFile();
|
||||
if (vfile != null && (rootManager.getFileIndex().isInLibraryClasses(vfile) || rootManager.getFileIndex().isInLibrarySource(vfile))) {
|
||||
if (vfile != null &&
|
||||
(rootManager.getFileIndex().isInLibraryClasses(vfile) || rootManager.getFileIndex().isInLibrarySource(vfile))) {
|
||||
return Result.create(result, rootManager);
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
||||
private static class GdkMethodHolder {
|
||||
private final MultiMap<String, PsiMethod> originalMethodsByName;
|
||||
private final NotNullLazyValue<MultiMap<String, PsiMethod>> originalMethodByType;
|
||||
@@ -110,7 +111,7 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
|
||||
for (PsiMethod m : categoryClass.getMethods()) {
|
||||
final PsiParameter[] params = m.getParameterList().getParameters();
|
||||
if (params.length == 0) continue;
|
||||
|
||||
|
||||
byName.putValue(m.getName(), m);
|
||||
}
|
||||
this.originalMethodsByName = byName;
|
||||
@@ -121,7 +122,6 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
|
||||
MultiMap<String, PsiMethod> map = new MultiMap<String, PsiMethod>();
|
||||
for (PsiMethod method : originalMethodsByName.values()) {
|
||||
map.putValue(getCategoryTargetType(method).getCanonicalText(), method);
|
||||
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
|
||||
if (name != null) {
|
||||
for (PsiMethod method : originalMethodsByName.get(name)) {
|
||||
if (getCategoryTargetType(method).isAssignableFrom(psiType)) {
|
||||
if (!processor.execute(new GrGdkMethodImpl(method, myStatic), state)) {
|
||||
if (!processor.execute(GrGdkMethodImpl.createGdkMethod(method, myStatic), state)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -153,14 +153,13 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
|
||||
|
||||
for (String superType : ResolveUtil.getAllSuperTypes(psiType, descriptor.getProject()).keySet()) {
|
||||
for (PsiMethod method : originalMethodByType.getValue().get(superType)) {
|
||||
if (!processor.execute(new GrGdkMethodImpl(method, myStatic), state)) {
|
||||
if (!processor.execute(GrGdkMethodImpl.createGdkMethod(method, myStatic), state)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.resolve;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.scope.DelegatingScopeProcessor;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArrayInitializer;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationMemberValue;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrGdkMethodImpl;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Max Medvedev
|
||||
*/
|
||||
public class MixinMemberContributor extends NonCodeMembersContributor {
|
||||
@Override
|
||||
public void processDynamicElements(@NotNull final PsiType qualifierType,
|
||||
PsiScopeProcessor processor,
|
||||
GroovyPsiElement place,
|
||||
ResolveState state) {
|
||||
if (!(qualifierType instanceof PsiClassType)) return;
|
||||
if (isInAnnotation(place)) return;
|
||||
final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)qualifierType).resolveGenerics();
|
||||
final PsiClass aClass = resolveResult.getElement();
|
||||
if (aClass == null) return;
|
||||
|
||||
final PsiModifierList modifierList = aClass.getModifierList();
|
||||
if (modifierList == null) return;
|
||||
|
||||
final PsiAnnotation annotation = modifierList.findAnnotation(GroovyCommonClassNames.GROOVY_LANG_MIXIN);
|
||||
if (annotation == null) return;
|
||||
|
||||
final PsiAnnotationMemberValue value = annotation.findAttributeValue("value");
|
||||
|
||||
List<PsiClass> mixins = new ArrayList<PsiClass>();
|
||||
if (value instanceof GrAnnotationArrayInitializer) {
|
||||
final GrAnnotationMemberValue[] initializers = ((GrAnnotationArrayInitializer)value).getInitializers();
|
||||
for (GrAnnotationMemberValue initializer : initializers) {
|
||||
addMixin(initializer, mixins);
|
||||
}
|
||||
}
|
||||
else if (value instanceof GrExpression) {
|
||||
addMixin((GrExpression)value, mixins);
|
||||
}
|
||||
|
||||
for (PsiClass mixin : mixins) {
|
||||
if (!mixin.processDeclarations(new DelegatingScopeProcessor(processor) {
|
||||
@Override
|
||||
public boolean execute(PsiElement element, ResolveState state) {
|
||||
if (isCategoryMethod(element, qualifierType)) {
|
||||
return super.execute(GrGdkMethodImpl.createGdkMethod((PsiMethod)element, false), state);
|
||||
}
|
||||
else {
|
||||
return super.execute(element, state);
|
||||
}
|
||||
}
|
||||
}, state, null, place)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCategoryMethod(PsiElement element, PsiType qualifierType) {
|
||||
if (!(element instanceof PsiMethod)) return false;
|
||||
if (!((PsiMethod)element).hasModifierProperty(PsiModifier.STATIC)) return false;
|
||||
|
||||
final PsiParameter[] parameters = ((PsiMethod)element).getParameterList().getParameters();
|
||||
if (parameters.length == 0) return false;
|
||||
|
||||
final PsiParameter selfParam = parameters[0];
|
||||
final PsiType selfType = selfParam.getType();
|
||||
|
||||
return TypesUtil.isAssignable(selfType, qualifierType, element.getManager(), element.getResolveScope());
|
||||
}
|
||||
|
||||
private static boolean isInAnnotation(GroovyPsiElement place) {
|
||||
return place.getParent() instanceof GrAnnotation || place.getParent() instanceof GrAnnotationArrayInitializer;
|
||||
}
|
||||
|
||||
private static void addMixin(GrAnnotationMemberValue value, List<PsiClass> mixins) {
|
||||
if (value instanceof GrReferenceExpression) {
|
||||
final PsiElement resolved = ((GrReferenceExpression)value).resolve();
|
||||
if (resolved instanceof PsiClass) {
|
||||
mixins.add((PsiClass)resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
|
||||
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
|
||||
@@ -75,12 +76,21 @@ public class ResolveUtil {
|
||||
private ResolveUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param place - place to start tree walk up
|
||||
* @param processor
|
||||
* @param processNonCodeMethods. this parameter tells us if we need non code members. But non code members are started to process only after we walk up any code block or script
|
||||
* @return
|
||||
*/
|
||||
public static boolean treeWalkUp(@NotNull GroovyPsiElement place, PsiScopeProcessor processor, boolean processNonCodeMethods) {
|
||||
PsiElement lastParent = null;
|
||||
PsiElement run = place;
|
||||
|
||||
final Project project = place.getProject();
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
|
||||
boolean doProcessNonCodeMembers = false;
|
||||
|
||||
while (run != null) {
|
||||
if (!run.processDeclarations(processor, ResolveState.initial(), lastParent, place)) return false;
|
||||
@@ -89,18 +99,24 @@ public class ResolveUtil {
|
||||
if (superClass != null && !superClass.processDeclarations(processor, ResolveState.initial(), null, place)) return false;
|
||||
}
|
||||
if (processNonCodeMethods) {
|
||||
if (run instanceof GrTypeDefinition) {
|
||||
if (!processNonCodeMembers(factory.createType(((GrTypeDefinition)run)), processor, place)) return false;
|
||||
if (!doProcessNonCodeMembers) {
|
||||
if (run instanceof GrCodeBlock) doProcessNonCodeMembers = true;
|
||||
else if (run instanceof GrStatement && run.getContext() instanceof GroovyFile) doProcessNonCodeMembers = true;
|
||||
}
|
||||
else if ((run instanceof GroovyFileBase) && ((GroovyFileBase)run).isScript()) {
|
||||
final PsiClass psiClass = ((GroovyFileBase)run).getScriptClass();
|
||||
if (psiClass != null) {
|
||||
if (!processNonCodeMembers(factory.createType(psiClass), processor, place)) return false;
|
||||
if (doProcessNonCodeMembers) {
|
||||
if (run instanceof GrTypeDefinition) {
|
||||
if (!processNonCodeMembers(factory.createType(((GrTypeDefinition)run)), processor, place)) return false;
|
||||
}
|
||||
else if ((run instanceof GroovyFileBase) && ((GroovyFileBase)run).isScript()) {
|
||||
final PsiClass psiClass = ((GroovyFileBase)run).getScriptClass();
|
||||
if (psiClass != null) {
|
||||
if (!processNonCodeMembers(factory.createType(psiClass), processor, place)) return false;
|
||||
}
|
||||
}
|
||||
else if (run instanceof GrClosableBlock) {
|
||||
if (!GdkMethodUtil.categoryIteration((GrClosableBlock)run, processor)) return false;
|
||||
if (!GdkMethodUtil.withIteration((GrClosableBlock)run, processor, place)) return false;
|
||||
}
|
||||
}
|
||||
else if (run instanceof GrClosableBlock) {
|
||||
if (!GdkMethodUtil.categoryIteration((GrClosableBlock)run, processor)) return false;
|
||||
if (!GdkMethodUtil.withIteration((GrClosableBlock)run, processor, place)) return false;
|
||||
}
|
||||
}
|
||||
lastParent = run;
|
||||
|
||||
+39
@@ -757,4 +757,43 @@ class Foo {
|
||||
assertInstanceOf(resolved, GrReflectedMethod)
|
||||
assertTrue(resolved.modifierList.hasModifierProperty(PsiModifier.STATIC))
|
||||
}
|
||||
|
||||
void testMixinAndCategory() {
|
||||
def ref = configureByText("""
|
||||
@Category(B)
|
||||
class A {
|
||||
def foo() {print getName()}
|
||||
}
|
||||
|
||||
@Mixin(A)
|
||||
class B {
|
||||
def getName('B');
|
||||
}
|
||||
|
||||
print new B().f<caret>oo()
|
||||
""")
|
||||
|
||||
def resolved = ref.resolve()
|
||||
assertInstanceOf(resolved, GrGdkMethod)
|
||||
assertInstanceOf(resolved.staticMethod, GrReflectedMethod)
|
||||
}
|
||||
|
||||
void testOnlyMixin() {
|
||||
def ref = configureByText("""
|
||||
class A {
|
||||
def foo() {print getName()}
|
||||
}
|
||||
|
||||
@Mixin(A)
|
||||
class B {
|
||||
def getName('B');
|
||||
}
|
||||
|
||||
print new B().f<caret>oo()
|
||||
""")
|
||||
|
||||
def resolved = ref.resolve()
|
||||
assertInstanceOf(resolved, GrMethod)
|
||||
assertTrue(resolved.isPhysical())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user