Call Hierarchy: Show Kotlin usages of Java methods

#KT-12398 Fixed
This commit is contained in:
Alexey Sedunov
2016-09-30 21:00:51 +03:00
parent 5bb035282f
commit 3f6e60fb51
12 changed files with 133 additions and 23 deletions
+1
View File
@@ -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
@@ -45,7 +45,7 @@ fun KtFile.findFacadeClass(): KtLightClass? {
.firstOrNull { it is KtLightClassForFacade && this in it.files } as? KtLightClass
}
fun KtDeclaration.toLightElements(): List<PsiNamedElement> =
fun KtElement.toLightElements(): List<PsiNamedElement> =
when (this) {
is KtClassOrObject -> toLightClass().singletonOrEmptyList()
is KtNamedFunction,
@@ -60,6 +60,7 @@ fun KtDeclaration.toLightElements(): List<PsiNamedElement> =
elements
}
is KtTypeParameter -> toPsiTypeParameters()
is KtFile -> findFacadeClass().singletonOrEmptyList()
else -> listOf()
}
@@ -544,6 +544,7 @@ fun main(args: Array<String>) {
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")
}
+1
View File
@@ -596,6 +596,7 @@
<callHierarchyProvider
language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallHierarchyProvider" />
<hierarchy.referenceProcessor implementation="org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallReferenceProcessor"/>
<methodHierarchyProvider
language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideHierarchyProvider" />
@@ -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<PsiElement, HierarchyNodeDescriptor>,
false,
true
).process(reference)
}
}
@@ -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<PsiElement, HierarchyNodeDescriptor> 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) {
@@ -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<PsiMethod> lightMethods,
HierarchyNodeDescriptor descriptor,
Map<PsiElement, HierarchyNodeDescriptor> 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<PsiElement, HierarchyNodeDescriptor> 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<PsiReference> defaultQueryProcessor(
static Processor<PsiReference> defaultQueryProcessor(
final HierarchyNodeDescriptor descriptor,
final Map<PsiElement, HierarchyNodeDescriptor> 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);
}
};
}
@@ -0,0 +1,5 @@
<node text="J.foo() ()" base="true">
<node text="A(2 usages) ()"/>
<node text="Main1Kt.test(J) ()"/>
<node text="Main1Kt ()"/>
</node>
@@ -0,0 +1,5 @@
public class J {
public int <caret>foo() {
return 1;
}
}
@@ -0,0 +1,11 @@
class A {
val x = J().foo()
init {
J().foo()
}
}
val y = J().foo()
fun test(j: J) = j.foo()
@@ -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<HierarchyTreeStructure> getCallerJavaHierarchyStructure() {
return new Computable<HierarchyTreeStructure>() {
@Override
public HierarchyTreeStructure compute() {
return new CallerMethodsTreeStructure(
getProject(),
(PsiMethod) getElementAtCaret(LanguageCallHierarchy.INSTANCE),
HierarchyBrowserBaseEx.SCOPE_PROJECT
);
}
};
}
private Computable<HierarchyTreeStructure> getCalleeHierarchyStructure() {
return new Computable<HierarchyTreeStructure>() {
@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;
}
@@ -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)