diff --git a/ChangeLog.md b/ChangeLog.md index a118f13f8f6..94d702ed83e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -198,6 +198,7 @@ These artifacts include extensions for the types available in the latter JDKs, s - [`KT-13928`](https://youtrack.jetbrains.com/issue/KT-13928) Move Inner Class to Upper Level: Fix replacement of outer class instances used in inner class constructor calls - [`KT-12556`](https://youtrack.jetbrains.com/issue/KT-12556) Allow using whitespaces and other symbols in "Generate -> Test Function" dialog - [`KT-14122`](https://youtrack.jetbrains.com/issue/KT-14122) Generate 'toString()': Permit for data classes +- [`KT-12398`](https://youtrack.jetbrains.com/issue/KT-12398) Call Hierarchy: Show Kotlin usages of Java methods #### Intention actions, inspections and quickfixes diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt index 7e4f78b0d6d..10bfab811e9 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt @@ -45,7 +45,7 @@ fun KtFile.findFacadeClass(): KtLightClass? { .firstOrNull { it is KtLightClassForFacade && this in it.files } as? KtLightClass } -fun KtDeclaration.toLightElements(): List = +fun KtElement.toLightElements(): List = when (this) { is KtClassOrObject -> toLightClass().singletonOrEmptyList() is KtNamedFunction, @@ -60,6 +60,7 @@ fun KtDeclaration.toLightElements(): List = elements } is KtTypeParameter -> toPsiTypeParameters() + is KtFile -> findFacadeClass().singletonOrEmptyList() else -> listOf() } diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index f24d1950ea1..ebaa1848b84 100755 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -544,6 +544,7 @@ fun main(args: Array) { model("hierarchy/class/super", extension = null, recursive = false, testMethod = "doSuperClassHierarchyTest") model("hierarchy/class/sub", extension = null, recursive = false, testMethod = "doSubClassHierarchyTest") model("hierarchy/calls/callers", extension = null, recursive = false, testMethod = "doCallerHierarchyTest") + model("hierarchy/calls/callersJava", extension = null, recursive = false, testMethod = "doCallerJavaHierarchyTest") model("hierarchy/calls/callees", extension = null, recursive = false, testMethod = "doCalleeHierarchyTest") model("hierarchy/overrides", extension = null, recursive = false, testMethod = "doOverrideHierarchyTest") } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 850c2d10964..75490f4b8de 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -596,6 +596,7 @@ + diff --git a/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallReferenceProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallReferenceProcessor.kt new file mode 100644 index 00000000000..3e8fe61b086 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallReferenceProcessor.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-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.kotlin.idea.hierarchy.calls + +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor +import com.intellij.ide.hierarchy.call.CallReferenceProcessor +import com.intellij.ide.hierarchy.call.JavaCallHierarchyData +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import org.jetbrains.kotlin.idea.references.KtReference + +class KotlinCallReferenceProcessor : CallReferenceProcessor { + override fun process(reference: PsiReference, data: JavaCallHierarchyData): Boolean { + if (reference !is KtReference) return true + val nodeDescriptor = data.nodeDescriptor as? HierarchyNodeDescriptor ?: return true + @Suppress("UNCHECKED_CAST") + return !KotlinCallerMethodsTreeStructure.defaultQueryProcessor( + if (nodeDescriptor is KotlinCallHierarchyNodeDescriptor) nodeDescriptor.javaDelegate else nodeDescriptor, + data.resultMap as MutableMap, + false, + true + ).process(reference) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallTreeStructure.java b/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallTreeStructure.java index d5cd56bfad9..ead24c204fc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallTreeStructure.java +++ b/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallTreeStructure.java @@ -26,10 +26,12 @@ import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiReference; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.HashMap; +import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.asJava.LightClassUtil; +import org.jetbrains.kotlin.asJava.LightClassUtilsKt; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; @@ -41,7 +43,7 @@ public abstract class KotlinCallTreeStructure extends HierarchyTreeStructure { protected final String scopeType; public KotlinCallTreeStructure(@NotNull Project project, PsiElement element, String scopeType) { - super(project, createNodeDescriptor(project, element, null, false)); + super(project, createNodeDescriptor(project, element, null, false, false)); this.scopeType = scopeType; } @@ -51,13 +53,19 @@ public abstract class KotlinCallTreeStructure extends HierarchyTreeStructure { : null; } - protected static HierarchyNodeDescriptor createNodeDescriptor( - Project project, PsiElement element, HierarchyNodeDescriptor parent, boolean navigateToReference + @Nullable + private static HierarchyNodeDescriptor createNodeDescriptor( + Project project, PsiElement element, HierarchyNodeDescriptor parent, boolean navigateToReference, boolean wrapAsLightElements ) { + PsiElement nodeElement = element; + if (wrapAsLightElements && element instanceof KtElement) { + nodeElement = CollectionsKt.firstOrNull(LightClassUtilsKt.toLightElements((KtElement) element)); + } + if (nodeElement == null) return null; boolean root = (parent == null); - return element instanceof KtElement - ? new KotlinCallHierarchyNodeDescriptor(project, parent, element, root, navigateToReference) - : new CallHierarchyNodeDescriptor(project, parent, element, root, navigateToReference); + return nodeElement instanceof KtElement + ? new KotlinCallHierarchyNodeDescriptor(project, parent, nodeElement, root, navigateToReference) + : new CallHierarchyNodeDescriptor(project, parent, nodeElement, root, navigateToReference); } protected static PsiElement getTargetElement(HierarchyNodeDescriptor descriptor) { @@ -131,20 +139,22 @@ public abstract class KotlinCallTreeStructure extends HierarchyTreeStructure { if (basePsiClass != null && !isInScope(basePsiClass, callee, scopeType)) continue; - addNodeDescriptorForElement(ref, callee, declarationToDescriptorMap, descriptor); + addNodeDescriptorForElement(ref, callee, declarationToDescriptorMap, descriptor, false); } return declarationToDescriptorMap.values().toArray(new Object[declarationToDescriptorMap.size()]); } - protected final void addNodeDescriptorForElement( + protected static void addNodeDescriptorForElement( PsiReference reference, PsiElement element, Map declarationToDescriptorMap, - HierarchyNodeDescriptor descriptor + HierarchyNodeDescriptor descriptor, + boolean wrapAsLightElements ) { HierarchyNodeDescriptor d = declarationToDescriptorMap.get(element); if (d == null) { - d = createNodeDescriptor(myProject, element, descriptor, true); + d = createNodeDescriptor(element.getProject(), element, descriptor, true, wrapAsLightElements); + if (d == null) return; declarationToDescriptorMap.put(element, d); } else if (d instanceof CallHierarchyNodeDescriptor) { diff --git a/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallerMethodsTreeStructure.java b/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallerMethodsTreeStructure.java index fe3f87e1dc3..51df812261b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallerMethodsTreeStructure.java +++ b/idea/src/org/jetbrains/kotlin/idea/hierarchy/calls/KotlinCallerMethodsTreeStructure.java @@ -127,7 +127,7 @@ public class KotlinCallerMethodsTreeStructure extends KotlinCallTreeStructure { return (javaCallers != null) ? ArrayUtil.mergeArrays(javaCallers, callers) : callers; } - private void processPsiMethodCallers( + private static void processPsiMethodCallers( Iterable lightMethods, HierarchyNodeDescriptor descriptor, Map methodToDescriptorMap, @@ -161,10 +161,10 @@ public class KotlinCallerMethodsTreeStructure extends KotlinCallTreeStructure { for (PsiMethod superMethod: methodsToFind) { ContainerUtil.addAll(references, MethodReferencesSearch.search(superMethod, searchScope, true)); } - ContainerUtil.process(references, defaultQueryProcessor(descriptor, methodToDescriptorMap, kotlinOnly)); + ContainerUtil.process(references, defaultQueryProcessor(descriptor, methodToDescriptorMap, kotlinOnly, false)); } - private void processKtClassOrObjectCallers( + private static void processKtClassOrObjectCallers( final KtClassOrObject classOrObject, HierarchyNodeDescriptor descriptor, Map methodToDescriptorMap, @@ -177,20 +177,21 @@ public class KotlinCallerMethodsTreeStructure extends KotlinCallTreeStructure { return UtilsKt.isConstructorUsage(reference, classOrObject); } }, - defaultQueryProcessor(descriptor, methodToDescriptorMap, false) + defaultQueryProcessor(descriptor, methodToDescriptorMap, false, false) ); ReferencesSearch.search(classOrObject, searchScope, false).forEach(processor); } - private Processor defaultQueryProcessor( + static Processor defaultQueryProcessor( final HierarchyNodeDescriptor descriptor, final Map methodToDescriptorMap, - boolean kotlinOnly + boolean kotlinOnly, + final boolean wrapAsLightElements ) { return new CalleeReferenceProcessor(kotlinOnly) { @Override protected void onAccept(@NotNull PsiReference ref, @NotNull PsiElement element) { - addNodeDescriptorForElement(ref, element, methodToDescriptorMap, descriptor); + addNodeDescriptorForElement(ref, element, methodToDescriptorMap, descriptor, wrapAsLightElements); } }; } diff --git a/idea/testData/hierarchy/calls/callersJava/javaMethod/JavaMethod_verification.xml b/idea/testData/hierarchy/calls/callersJava/javaMethod/JavaMethod_verification.xml new file mode 100644 index 00000000000..bc89a4b1c3c --- /dev/null +++ b/idea/testData/hierarchy/calls/callersJava/javaMethod/JavaMethod_verification.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/idea/testData/hierarchy/calls/callersJava/javaMethod/main0.java b/idea/testData/hierarchy/calls/callersJava/javaMethod/main0.java new file mode 100644 index 00000000000..95d8c1763dc --- /dev/null +++ b/idea/testData/hierarchy/calls/callersJava/javaMethod/main0.java @@ -0,0 +1,5 @@ +public class J { + public int foo() { + return 1; + } +} \ No newline at end of file diff --git a/idea/testData/hierarchy/calls/callersJava/javaMethod/main1.kt b/idea/testData/hierarchy/calls/callersJava/javaMethod/main1.kt new file mode 100644 index 00000000000..e7bdd548cf3 --- /dev/null +++ b/idea/testData/hierarchy/calls/callersJava/javaMethod/main1.kt @@ -0,0 +1,11 @@ +class A { + val x = J().foo() + + init { + J().foo() + } +} + +val y = J().foo() + +fun test(j: J) = j.foo() \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/hierarchy/AbstractHierarchyTest.java b/idea/tests/org/jetbrains/kotlin/idea/hierarchy/AbstractHierarchyTest.java index e725adc6dbc..7d87a290d86 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/hierarchy/AbstractHierarchyTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/hierarchy/AbstractHierarchyTest.java @@ -18,19 +18,18 @@ package org.jetbrains.kotlin.idea.hierarchy; import com.intellij.ide.hierarchy.*; import com.intellij.ide.hierarchy.actions.BrowseHierarchyActionBase; +import com.intellij.ide.hierarchy.call.CallerMethodsTreeStructure; import com.intellij.ide.hierarchy.type.SubtypesHierarchyTreeStructure; import com.intellij.ide.hierarchy.type.SupertypesHierarchyTreeStructure; import com.intellij.ide.hierarchy.type.TypeHierarchyTreeStructure; import com.intellij.lang.LanguageExtension; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; +import com.intellij.psi.*; import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException; import com.intellij.testFramework.MapDataContext; import com.intellij.util.ArrayUtil; @@ -76,6 +75,11 @@ public abstract class AbstractHierarchyTest extends KotlinHierarchyViewTestBase doHierarchyTest(getCallerHierarchyStructure(), getFilesToConfigure()); } + protected void doCallerJavaHierarchyTest(@NotNull String folderName) throws Exception { + this.folderName = folderName; + doHierarchyTest(getCallerJavaHierarchyStructure(), getFilesToConfigure()); + } + protected void doCalleeHierarchyTest(@NotNull String folderName) throws Exception { this.folderName = folderName; doHierarchyTest(getCalleeHierarchyStructure(), getFilesToConfigure()); @@ -137,6 +141,19 @@ public abstract class AbstractHierarchyTest extends KotlinHierarchyViewTestBase }; } + private Computable getCallerJavaHierarchyStructure() { + return new Computable() { + @Override + public HierarchyTreeStructure compute() { + return new CallerMethodsTreeStructure( + getProject(), + (PsiMethod) getElementAtCaret(LanguageCallHierarchy.INSTANCE), + HierarchyBrowserBaseEx.SCOPE_PROJECT + ); + } + }; + } + private Computable getCalleeHierarchyStructure() { return new Computable() { @Override @@ -171,9 +188,13 @@ public abstract class AbstractHierarchyTest extends KotlinHierarchyViewTestBase } private DataContext getDataContext() { + Editor editor = getEditor(); + PsiFile psiFile = getFile(); + MapDataContext context = new MapDataContext(); context.put(CommonDataKeys.PROJECT, getProject()); - context.put(CommonDataKeys.EDITOR, getEditor()); + context.put(CommonDataKeys.EDITOR, editor); + context.put(CommonDataKeys.PSI_ELEMENT, psiFile.findElementAt(editor.getCaretModel().getOffset())); return context; } diff --git a/idea/tests/org/jetbrains/kotlin/idea/hierarchy/HierarchyTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/hierarchy/HierarchyTestGenerated.java index dbc22aecb85..0aa9370b017 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/hierarchy/HierarchyTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/hierarchy/HierarchyTestGenerated.java @@ -413,6 +413,21 @@ public class HierarchyTestGenerated extends AbstractHierarchyTest { } } + @TestMetadata("idea/testData/hierarchy/calls/callersJava") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CallersJava extends AbstractHierarchyTest { + public void testAllFilesPresentInCallersJava() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/hierarchy/calls/callersJava"), Pattern.compile("^([^\\.]+)$"), false); + } + + @TestMetadata("javaMethod") + public void testJavaMethod() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/hierarchy/calls/callersJava/javaMethod/"); + doCallerJavaHierarchyTest(fileName); + } + } + @TestMetadata("idea/testData/hierarchy/calls/callees") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)