diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/findUsages/GroovyConstructorUsagesSearchHelper.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/findUsages/GroovyConstructorUsagesSearchHelper.java index 9a46dcfc2899..30c2c35a4c67 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/findUsages/GroovyConstructorUsagesSearchHelper.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/findUsages/GroovyConstructorUsagesSearchHelper.java @@ -18,19 +18,33 @@ package org.jetbrains.plugins.groovy.findUsages; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadActionProcessor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.NullableComputable; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.light.LightMemberReference; +import com.intellij.psi.search.DelegatingGlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.SearchScope; +import com.intellij.psi.search.searches.AnnotatedElementsSearch; import com.intellij.psi.search.searches.DirectClassInheritorsSearch; +import com.intellij.psi.search.searches.MethodReferencesSearch; import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.Processor; +import com.intellij.util.containers.ConcurrentHashSet; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.GroovyFileType; import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; +import org.jetbrains.plugins.groovy.gpp.GppTypeConverter; +import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap; @@ -38,11 +52,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocat import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrSafeCastExpression; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrTypeCastExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant; @@ -50,9 +62,14 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMe import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction; +import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.GroovyExpectedTypesProvider; import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + /** * @author Maxim.Medvedev * Date: May 2, 2009 3:48:53 PM @@ -61,11 +78,12 @@ public class GroovyConstructorUsagesSearchHelper { private GroovyConstructorUsagesSearchHelper() { } - public static boolean execute(final PsiMethod constructor, SearchScope searchScope, final Processor consumer) { + public static boolean execute(final PsiMethod constructor, final SearchScope searchScope, final Processor consumer) { if (!constructor.isConstructor()) return true; - if (searchScope instanceof GlobalSearchScope) { - searchScope = GlobalSearchScope.getScopeRestrictedByFileTypes((GlobalSearchScope)searchScope, GroovyFileType.GROOVY_FILE_TYPE); + SearchScope onlyGroovy = searchScope; + if (onlyGroovy instanceof GlobalSearchScope) { + onlyGroovy = GlobalSearchScope.getScopeRestrictedByFileTypes((GlobalSearchScope)onlyGroovy, GroovyFileType.GROOVY_FILE_TYPE); } final PsiClass clazz = ApplicationManager.getApplication().runReadAction(new NullableComputable() { @@ -97,10 +115,11 @@ public class GroovyConstructorUsagesSearchHelper { } - ReferencesSearch.search(clazz, searchScope, true).forEach(new ReadActionProcessor() { + final Set processedMethods = new ConcurrentHashSet(); + ReferencesSearch.search(clazz, searchScope, true).forEach(new Processor() { @Override - public boolean processInReadAction(PsiReference ref) { - return processClassReference(ref, constructor, consumer); + public boolean process(PsiReference ref) { + return processClassReference(ref, clazz, constructor, consumer, processedMethods, searchScope); } }); @@ -111,7 +130,7 @@ public class GroovyConstructorUsagesSearchHelper { } } //super : does not work now, need to invent a way for it to work without repository - if (!DirectClassInheritorsSearch.search(clazz, searchScope).forEach(new Processor() { + if (!DirectClassInheritorsSearch.search(clazz, onlyGroovy).forEach(new Processor() { public boolean process(PsiClass inheritor) { if (inheritor instanceof GrTypeDefinition) { if (!processConstructors(constructor, consumer, inheritor, false)) return false; @@ -125,12 +144,99 @@ public class GroovyConstructorUsagesSearchHelper { return true; } - private static boolean processClassReference(PsiReference ref, final PsiMethod constructor, final Processor consumer) { + @Nullable + private static PsiMethod getMethodToSearchForCallsWithLiteralArguments(PsiElement element, PsiClass clazz, Set processedMethods) { + final PsiParameter parameter = PsiTreeUtil.getParentOfType(element, PsiParameter.class); + if (parameter != null) { + final PsiMethod method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class); + if (method != null && processedMethods.add(method) && Arrays.asList(method.getParameterList().getParameters()).contains(parameter)) { + final PsiType parameterType = parameter.getType(); + if (parameterType instanceof PsiClassType) { + if (method.getManager().areElementsEquivalent(clazz, ((PsiClassType)parameterType).resolve())) { + return method; + } + } + } + } + return null; + } + + private static boolean processClassReference(final PsiReference ref, + final PsiClass clazz, + final PsiMethod constructor, + final Processor consumer, + final Set processedMethods, SearchScope scope) { final PsiElement element = ref.getElement(); - if (!(element instanceof GrCodeReferenceElement)) { - return true; + if (element instanceof GrCodeReferenceElement) { + if (!ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public Boolean compute() { + return processGroovyConstructorUsages((GrCodeReferenceElement)element, constructor, consumer, ref); + } + })) { + return false; + } + } + final PsiMethod method = ApplicationManager.getApplication().runReadAction(new Computable() { + public PsiMethod compute() { + return getMethodToSearchForCallsWithLiteralArguments(element, clazz, processedMethods); + } + }); + if (method != null) { + return MethodReferencesSearch.search(method, getGppScope(clazz.getProject()).intersectWith(scope), true).forEach(new ReadActionProcessor() { + @Override + public boolean processInReadAction(PsiReference psiReference) { + if (psiReference instanceof GrReferenceElement) { + final PsiElement parent = ((GrReferenceElement)psiReference).getParent(); + if (parent instanceof GrCall) { + final GrArgumentList argList = ((GrCall)parent).getArgumentList(); + if (argList != null) { + boolean checkedTypedContext = false; + + for (GrExpression argument : argList.getExpressionArguments()) { + if (argument instanceof GrListOrMap && !((GrListOrMap)argument).isMap()) { + if (!checkedTypedContext) { + if (!GppTypeConverter.hasTypedContext(parent)) { + return true; + } + checkedTypedContext = true; + } + + for (PsiType psiType : GroovyExpectedTypesProvider.getDefaultExpectedTypes(argument)) { + if (psiType instanceof PsiClassType && + clazz.getManager().areElementsEquivalent(clazz,((PsiClassType)psiType).resolve()) && + !checkListInstantiation(constructor, consumer, (GrListOrMap)argument, (PsiClassType)psiType)) { + return false; + } + } + } + } + } + } + } + return true; + } + }); + } + + return true; + } + + private static GlobalSearchScope getGppScope(final Project project) { + return CachedValuesManager.getManager(project).getCachedValue(project, new CachedValueProvider() { + @Override + public Result compute() { + return Result.create(calcGppScope(project), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, ProjectRootManager.getInstance(project)); + } + }); + } + + private static boolean processGroovyConstructorUsages(GrCodeReferenceElement element, + final PsiMethod constructor, + final Processor consumer, + PsiReference ref) { final PsiElement parent = element.getParent(); if (parent instanceof GrNewExpression) { final PsiMethod resolvedConstructor = ((GrNewExpression)parent).resolveConstructor(); @@ -167,13 +273,15 @@ public class GroovyConstructorUsagesSearchHelper { } else if (grandpa instanceof GrTypeCastExpression) { final GrTypeCastExpression cast = (GrTypeCastExpression)grandpa; - if (cast.getCastTypeElement() == typeElement && !checkListInstantiation(constructor, consumer, cast.getOperand(), typeElement)) { + if (cast.getCastTypeElement() == typeElement && + !checkListInstantiation(constructor, consumer, cast.getOperand(), typeElement)) { return false; } } else if (grandpa instanceof GrSafeCastExpression) { final GrSafeCastExpression cast = (GrSafeCastExpression)grandpa; - if (cast.getCastTypeElement() == typeElement && !checkListInstantiation(constructor, consumer, cast.getOperand(), typeElement)) { + if (cast.getCastTypeElement() == typeElement && + !checkListInstantiation(constructor, consumer, cast.getOperand(), typeElement)) { return false; } } @@ -181,6 +289,33 @@ public class GroovyConstructorUsagesSearchHelper { return true; } + private static GlobalSearchScope calcGppScope(Project project) { + final GlobalSearchScope allScope = GlobalSearchScope.allScope(project); + final GlobalSearchScope maximal = GlobalSearchScope.getScopeRestrictedByFileTypes(allScope, GroovyFileType.GROOVY_FILE_TYPE); + GlobalSearchScope gppExtensions = new DelegatingGlobalSearchScope(maximal) { + @Override + public boolean contains(VirtualFile file) { + return super.contains(file) && GppTypeConverter.isGppExtension(file.getExtension()); + } + }; + final PsiClass typed = JavaPsiFacade.getInstance(project).findClass(GppTypeConverter.GROOVY_LANG_TYPED, allScope); + if (typed != null) { + final Set files = new HashSet(); + AnnotatedElementsSearch.searchElements(typed, maximal, PsiModifierListOwner.class).forEach(new Processor() { + @Override + public boolean process(PsiModifierListOwner occurrence) { + ContainerUtil.addIfNotNull(occurrence.getContainingFile().getVirtualFile(), files); + return true; + } + }); + + GlobalSearchScope withTypedAnno = GlobalSearchScope.filesScope(project, files); + return withTypedAnno.union(gppExtensions); + } + + return gppExtensions; + } + private static boolean checkListInstantiation(PsiMethod constructor, Processor consumer, GrExpression expression, final GrTypeElement typeElement) { @@ -188,15 +323,24 @@ public class GroovyConstructorUsagesSearchHelper { final GrListOrMap list = (GrListOrMap)expression; if (!list.isMap()) { final PsiType expectedType = typeElement.getType(); - final PsiType listType = list.getType(); - if (listType instanceof GrTupleType && expectedType instanceof PsiClassType) { - final GroovyResolveResult[] candidates = PsiUtil.getConstructorCandidates((PsiClassType)expectedType, ((GrTupleType)listType).getComponentTypes(), list); - for (GroovyResolveResult candidate : candidates) { - if (constructor.getManager().areElementsEquivalent(candidate.getElement(), constructor)) { - if (!consumer.process(PsiReferenceBase.createSelfReference(list, TextRange.from(0, list.getTextLength()), constructor))) { - return false; - } - } + if (expectedType instanceof PsiClassType) { + return checkListInstantiation(constructor, consumer, list, (PsiClassType)expectedType); + } + } + } + return true; + } + + private static boolean checkListInstantiation(PsiMethod constructor, + Processor consumer, + GrListOrMap list, + PsiClassType expectedType) { + final PsiType listType = list.getType(); + if (listType instanceof GrTupleType) { + for (GroovyResolveResult candidate : PsiUtil.getConstructorCandidates(expectedType, ((GrTupleType)listType).getComponentTypes(), list)) { + if (constructor.getManager().areElementsEquivalent(candidate.getElement(), constructor)) { + if (!consumer.process(PsiReferenceBase.createSelfReference(list, TextRange.from(0, list.getTextLength()), constructor))) { + return false; } } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppTypeConverter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppTypeConverter.java index 3a4ea0680f77..ab44998ef803 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppTypeConverter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/gpp/GppTypeConverter.java @@ -19,26 +19,29 @@ import org.jetbrains.plugins.groovy.lang.psi.impl.types.GrClosureSignatureUtil; */ public class GppTypeConverter extends GrTypeConverter { + public static final String GROOVY_LANG_TYPED = "groovy.lang.Typed"; + public static boolean hasTypedContext(PsiElement context) { if (context == null) { return false; } - if (AnnotatedContextFilter.hasAnnotatedContext(context, "groovy.lang.Typed")) { + if (AnnotatedContextFilter.hasAnnotatedContext(context, GROOVY_LANG_TYPED)) { return true; } final VirtualFile vfile = context.getContainingFile().getOriginalFile().getVirtualFile(); - if (vfile != null) { - final String extension = vfile.getExtension(); - if ("gpp".equals(extension) || "grunit".equals(vfile.getExtension())) { - return true; - } + if (vfile != null && isGppExtension(vfile.getExtension())) { + return true; } return false; } + public static boolean isGppExtension(String extension) { + return "gpp".equals(extension) || "grunit".equals(extension); + } + @Override public Boolean isConvertible(@NotNull PsiType lType, @NotNull PsiType rType, @NotNull GroovyPsiElement context) { if (rType instanceof GrTupleType) { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/LiteralConstructorUsagesTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/LiteralConstructorUsagesTest.groovy index f6e6df14f708..00432a8cb4d0 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/LiteralConstructorUsagesTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/LiteralConstructorUsagesTest.groovy @@ -7,8 +7,12 @@ import com.intellij.psi.search.searches.ReferencesSearch * @author peter */ class LiteralConstructorUsagesTest extends LightCodeInsightFixtureTestCase { + @Override protected void setUp() { + super.setUp(); + myFixture.addClass("package groovy.lang; public @interface Typed {}"); + } - public void testListVariable() throws Exception { + public void testList_Variable() throws Exception { def foo = myFixture.addClass("""class Foo { Foo() {} } @@ -17,7 +21,7 @@ class LiteralConstructorUsagesTest extends LightCodeInsightFixtureTestCase { assertOneElement(ReferencesSearch.search(foo.constructors[0]).findAll()) } - public void testListReturnValue() throws Exception { + public void testList_ReturnValue() throws Exception { def foo = myFixture.addClass("""class Foo { Foo() {} } @@ -26,7 +30,7 @@ class LiteralConstructorUsagesTest extends LightCodeInsightFixtureTestCase { assertEquals(2, ReferencesSearch.search(foo.constructors[0]).findAll().size()) } - public void testListCast() throws Exception { + public void testList_Cast() throws Exception { def foo = myFixture.addClass("""class Foo { Foo() {} } @@ -35,13 +39,65 @@ class LiteralConstructorUsagesTest extends LightCodeInsightFixtureTestCase { assertOneElement(ReferencesSearch.search(foo.constructors[0]).findAll()) } - public void testListAsCast() throws Exception { + public void testList_AsCast() throws Exception { def foo = myFixture.addClass("""class Foo { Foo() {} } -""") +}""") myFixture.addFileToProject "a.groovy", "def x = [] as Foo" assertOneElement(ReferencesSearch.search(foo.constructors[0]).findAll()) } + public void testList_GppMethodCall() throws Exception { + //------------------------declarations + def foo = myFixture.addClass(""" + package z; + class Foo { + public Foo() {} + } + """) + + myFixture.addClass(""" + package z; + public class Bar { + public static void giveMeFoo(int a, Foo f) {} + } +""") + myFixture.addFileToProject("Decl.groovy", "static def giveMeFooAsWell(z.Foo f) {}") + + //----------------------usages + myFixture.addFileToProject "a.gpp", "z.Bar.giveMeFoo(2, []) //usage" + myFixture.addFileToProject "b.groovy", """ + @Typed package aa; + z.Bar.giveMeFoo(3, []) //usage + """ + myFixture.addFileToProject "c.groovy", """ + @Typed def someMethod() { + z.Bar.giveMeFoo 4, [] //usage + Decl.giveMeFooAsWell([]) //usage + } + z.Bar.giveMeFoo 5, [] //non-typed context + Decl.giveMeFooAsWell([]) + """ + myFixture.addFileToProject "invalid.gpp", "z.Bar.giveMeFoo 42, 239, []" + myFixture.addFileToProject "nonGpp.groovy", "z.Bar.giveMeFoo(6, [])" + assertEquals(4, ReferencesSearch.search(foo.constructors[0]).findAll().size()) + } + + public void testList_GppConstructorCallWithSeveralParameters() throws Exception { + def foo = myFixture.addClass(""" + class Foo { + Foo() {} + } + """) + + myFixture.addClass(""" + class Bar { + Bar(Foo f1, Foo f2, Foo f3) {} + } + """) + myFixture.addFileToProject "a.gpp", "new Bar([],[],[])" + assertEquals(3, ReferencesSearch.search(foo.constructors[0]).findAll().size()) + } + }