Call Hierarchy: Refactoring

Do not rely on light methods to search usages as some elements
don't have them (e.g. local functions)

 #KT-9288 Fixed
 #KT-14428 Fixed
This commit is contained in:
Alexey Sedunov
2017-06-15 17:22:18 +03:00
parent 0c2ea5d799
commit 10941ae732
24 changed files with 566 additions and 743 deletions
@@ -36,3 +36,18 @@ fun KtDeclaration.processAllExactUsages(
options()
)
}
fun KtDeclaration.processAllUsages(
options: FindUsagesOptions,
processor: (UsageInfo) -> Unit
) {
val findUsagesHandler = KotlinFindUsagesHandlerFactory(project).createFindUsagesHandler(this, false, false)
findUsagesHandler.processElementUsages(
this,
{
processor(it)
true
},
options
)
}
@@ -20,24 +20,9 @@ import com.intellij.codeInsight.TargetElementUtil
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
fun isCallHierarchyElement(e: PsiElement): Boolean {
return e is PsiMethod ||
e is PsiClass ||
e is KtFile ||
e is KtNamedFunction ||
e is KtSecondaryConstructor ||
e is KtObjectDeclaration ||
e is KtClass && !e.isInterface() ||
e is KtProperty
}
fun getCurrentElement(dataContext: DataContext, project: Project): PsiElement? {
val editor = CommonDataKeys.EDITOR.getData(dataContext)
@@ -49,5 +34,3 @@ fun getCurrentElement(dataContext: DataContext, project: Project): PsiElement? {
return CommonDataKeys.PSI_ELEMENT.getData(dataContext)
}
fun getCallHierarchyElement(element: PsiElement) = element.parentsWithSelf.firstOrNull(::isCallHierarchyElement)
@@ -0,0 +1,91 @@
/*
* Copyright 2010-2017 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.openapi.application.ReadActionProcessor;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightMemberReference;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.references.KtReference;
import org.jetbrains.kotlin.psi.KtElement;
import org.jetbrains.kotlin.psi.KtImportDirective;
import org.jetbrains.kotlin.psi.KtNamedDeclaration;
import org.jetbrains.kotlin.psi.KtProperty;
public abstract class CalleeReferenceProcessor extends ReadActionProcessor<PsiReference> {
private final boolean kotlinOnly;
public CalleeReferenceProcessor(boolean only) {
kotlinOnly = only;
}
@Override
public boolean processInReadAction(PsiReference ref) {
// copied from Java
if (!(ref instanceof PsiReferenceExpression || ref instanceof KtReference)) {
if (!(ref instanceof PsiElement)) {
return true;
}
PsiElement parent = ((PsiElement) ref).getParent();
if (parent instanceof PsiNewExpression) {
if (((PsiNewExpression) parent).getClassReference() != ref) {
return true;
}
}
else if (parent instanceof PsiAnonymousClass) {
if (((PsiAnonymousClass) parent).getBaseClassReference() != ref) {
return true;
}
}
else if (ref instanceof LightMemberReference) {
PsiElement refTarget = ref.resolve();
// Accept implicit superclass constructor reference in Java code
if (!(refTarget instanceof PsiMethod && ((PsiMethod) refTarget).isConstructor())) return true;
}
else {
return true;
}
}
PsiElement refElement = ref.getElement();
if (PsiTreeUtil.getParentOfType(refElement, KtImportDirective.class, true) != null) return true;
PsiElement element = (refElement instanceof KtElement)
? CallHierarchyUtilsKt.getCallHierarchyElement(refElement)
: PsiTreeUtil.getParentOfType(refElement, PsiMethod.class, false);
if (kotlinOnly && !(element instanceof KtNamedDeclaration)) return true;
// If reference belongs to property initializer, show enclosing declaration instead
if (element instanceof KtProperty) {
KtProperty property = (KtProperty) element;
if (PsiTreeUtil.isAncestor(property.getInitializer(), refElement, false)) {
element = CallHierarchyUtilsKt.getCallHierarchyElement(element.getParent());
}
}
if (element != null) {
onAccept(ref, element);
}
return true;
}
protected abstract void onAccept(@NotNull PsiReference ref, @NotNull PsiElement element);
}
@@ -36,7 +36,7 @@ public abstract class CalleeReferenceVisitorBase extends KtTreeVisitorVoid {
this.deepTraversal = deepTraversal;
}
protected abstract void processDeclaration(KtSimpleNameExpression reference, PsiElement declaration);
protected abstract void processDeclaration(@NotNull KtSimpleNameExpression reference, @NotNull PsiElement declaration);
@Override
public void visitKtElement(@NotNull KtElement element) {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -25,20 +25,20 @@ import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.ui.PopupHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.hierarchy.HierarchyUtilsKt;
import org.jetbrains.kotlin.psi.KtElement;
import javax.swing.*;
import java.util.Comparator;
import java.util.Map;
public class KotlinCallHierarchyBrowser extends CallHierarchyBrowserBase {
public KotlinCallHierarchyBrowser(@NotNull Project project, @NotNull PsiElement element) {
super(project, element);
public KotlinCallHierarchyBrowser(@NotNull PsiElement element) {
super(element.getProject(), element);
}
@Override
@@ -63,7 +63,7 @@ public class KotlinCallHierarchyBrowser extends CallHierarchyBrowserBase {
private static PsiElement getTargetElement(@NotNull HierarchyNodeDescriptor descriptor) {
if (descriptor instanceof KotlinCallHierarchyNodeDescriptor) {
return ((KotlinCallHierarchyNodeDescriptor) descriptor).getTargetElement();
return descriptor.getPsiElement();
}
return null;
}
@@ -76,17 +76,19 @@ public class KotlinCallHierarchyBrowser extends CallHierarchyBrowserBase {
@Override
protected boolean isApplicableElement(@NotNull PsiElement element) {
if (element instanceof PsiClass) return false; // PsiClass is not allowed at the hierarchy root
return HierarchyUtilsKt.isCallHierarchyElement(element);
return CallHierarchyUtilsKt.isCallHierarchyElement(element);
}
@Override
protected HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String typeName, @NotNull PsiElement psiElement) {
if (!(psiElement instanceof KtElement)) return null;
if (typeName.equals(CALLER_TYPE)) {
return new KotlinCallerMethodsTreeStructure(myProject, psiElement, getCurrentScopeType());
return new KotlinCallerTreeStructure((KtElement) psiElement, getCurrentScopeType());
}
if (typeName.equals(CALLEE_TYPE)) {
return new KotlinCalleeMethodsTreeStructure(myProject, psiElement, getCurrentScopeType());
return new KotlinCalleeTreeStructure((KtElement) psiElement, getCurrentScopeType());
}
return null;
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -19,24 +19,22 @@ package org.jetbrains.kotlin.idea.hierarchy.calls;
import com.intellij.icons.AllIcons;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.JavaHierarchyUtil;
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.util.CompositeAppearance;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.LayeredIcon;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
@@ -49,34 +47,29 @@ import java.util.HashSet;
import java.util.Set;
public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor implements Navigatable {
private int usageCount = 0;
private final Set<PsiReference> references = new HashSet<PsiReference>();
private int usageCount = 1;
private final Set<PsiReference> references = new HashSet<>();
private final CallHierarchyNodeDescriptor javaDelegate;
public KotlinCallHierarchyNodeDescriptor(@NotNull Project project,
HierarchyNodeDescriptor parentDescriptor,
@NotNull PsiElement element,
public KotlinCallHierarchyNodeDescriptor(
@Nullable HierarchyNodeDescriptor parentDescriptor,
@NotNull KtElement element,
boolean isBase,
boolean navigateToReference) {
super(project, parentDescriptor, element, isBase);
super(element.getProject(), parentDescriptor, element, isBase);
this.javaDelegate = new CallHierarchyNodeDescriptor(myProject, null, element, isBase, navigateToReference);
}
public final CallHierarchyNodeDescriptor getJavaDelegate() {
return javaDelegate;
public final void incrementUsageCount() {
usageCount++;
javaDelegate.incrementUsageCount();
}
public final void addReference(PsiReference reference) {
if (references.add(reference)) {
usageCount++;
}
references.add(reference);
javaDelegate.addReference(reference);
}
public final PsiElement getTargetElement(){
return getPsiElement();
}
@Override
public final boolean isValid(){
//noinspection ConstantConditions
@@ -96,7 +89,7 @@ public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i
boolean changes = super.update();
PsiElement targetElement = getTargetElement();
PsiElement targetElement = getPsiElement();
String elementText = renderElement(targetElement);
if (elementText == null) {
@@ -122,16 +115,7 @@ public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i
mainTextAttributes = new TextAttributes(myColor, null, null, null, Font.PLAIN);
}
String packageName = null;
if (targetElement instanceof KtElement) {
packageName = KtPsiUtil.getPackageName((KtElement) targetElement);
}
else {
PsiClass enclosingClass = PsiTreeUtil.getParentOfType(targetElement, PsiClass.class, false);
if (enclosingClass != null) {
packageName = JavaHierarchyUtil.getPackageName(enclosingClass);
}
}
String packageName = KtPsiUtil.getPackageName((KtElement) targetElement);
myHighlightedText.getEnding().addText(elementText, mainTextAttributes);
@@ -188,7 +172,7 @@ public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i
}
}
}
else if (element instanceof KtNamedFunction || element instanceof KtSecondaryConstructor) {
else if (element instanceof KtNamedFunction || element instanceof KtConstructor) {
elementText = renderNamedFunction((FunctionDescriptor) descriptor);
}
else if (element instanceof KtProperty) {
@@ -219,12 +203,7 @@ public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i
String name = descriptorForName.getName().asString();
String paramTypes = StringUtil.join(
descriptor.getValueParameters(),
new Function<ValueParameterDescriptor, String>() {
@Override
public String fun(ValueParameterDescriptor descriptor) {
return DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(descriptor.getType());
}
},
descriptor1 -> DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(descriptor1.getType()),
", "
);
return name + "(" + paramTypes + ")";
@@ -1,75 +0,0 @@
/*
* Copyright 2010-2015 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.codeInsight.TargetElementUtilBase;
import com.intellij.ide.hierarchy.CallHierarchyBrowserBase;
import com.intellij.ide.hierarchy.HierarchyBrowser;
import com.intellij.ide.hierarchy.HierarchyProvider;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinLanguage;
import org.jetbrains.kotlin.idea.hierarchy.HierarchyUtilsKt;
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil;
import org.jetbrains.kotlin.psi.KtFile;
public class KotlinCallHierarchyProvider implements HierarchyProvider {
@Override
public PsiElement getTarget(@NotNull DataContext dataContext) {
Project project = CommonDataKeys.PROJECT.getData(dataContext);
if (project == null) return null;
PsiElement element = getCurrentElement(dataContext, project);
if (element == null) return null;
element = HierarchyUtilsKt.getCallHierarchyElement(element);
if (element instanceof KtFile || element == null || element.getLanguage() != KotlinLanguage.INSTANCE) return null;
return element;
}
private static PsiElement getCurrentElement(DataContext dataContext, Project project) {
Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
if (editor != null) {
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null) return null;
if (!ProjectRootsUtil.isInProjectOrLibSource(file)) return null;
return TargetElementUtilBase.findTargetElement(editor, TargetElementUtilBase.getInstance().getAllAccepted());
}
return CommonDataKeys.PSI_ELEMENT.getData(dataContext);
}
@NotNull
@Override
public HierarchyBrowser createHierarchyBrowser(PsiElement target) {
return new KotlinCallHierarchyBrowser(target.getProject(), target);
}
@Override
public void browserActivated(@NotNull HierarchyBrowser hierarchyBrowser) {
((KotlinCallHierarchyBrowser) hierarchyBrowser).changeView(CallHierarchyBrowserBase.CALLER_TYPE);
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2017 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.CallHierarchyBrowserBase
import com.intellij.ide.hierarchy.HierarchyBrowser
import com.intellij.ide.hierarchy.HierarchyProvider
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.refactoring.psiElement
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
class KotlinCallHierarchyProvider : HierarchyProvider {
override fun getTarget(dataContext: DataContext): KtElement? {
val element = dataContext.psiElement?.let { getCallHierarchyElement(it) }
if (element is KtFile) return null
return element
}
override fun createHierarchyBrowser(target: PsiElement) = KotlinCallHierarchyBrowser(target)
override fun browserActivated(hierarchyBrowser: HierarchyBrowser) {
(hierarchyBrowser as KotlinCallHierarchyBrowser).changeView(CallHierarchyBrowserBase.CALLER_TYPE)
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
* Copyright 2010-2017 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.
@@ -19,20 +19,15 @@ 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.ide.util.treeView.NodeDescriptor
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
val nodeDescriptor = data.nodeDescriptor as? HierarchyNodeDescriptor ?: return false
@Suppress("UNCHECKED_CAST")
return !KotlinCallerMethodsTreeStructure.defaultQueryProcessor(
if (nodeDescriptor is KotlinCallHierarchyNodeDescriptor) nodeDescriptor.javaDelegate else nodeDescriptor,
data.resultMap as MutableMap<PsiElement, HierarchyNodeDescriptor>,
false,
true
).process(reference)
KotlinCallerTreeStructure.processReference(reference, reference.element, nodeDescriptor, data.resultMap as MutableMap<PsiElement, NodeDescriptor<*>>, true)
return true
}
}
@@ -1,176 +0,0 @@
/*
* Copyright 2010-2015 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.HierarchyTreeStructure;
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
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;
import java.util.Map;
import static org.jetbrains.kotlin.asJava.LightClassUtilsKt.toLightClass;
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, false));
this.scopeType = scopeType;
}
protected static KtElement getEnclosingElementForLocalDeclaration(PsiElement element) {
return element instanceof KtNamedDeclaration
? KtPsiUtil.getEnclosingElementForLocalDeclaration((KtNamedDeclaration) element)
: null;
}
@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 nodeElement instanceof KtElement
? new KotlinCallHierarchyNodeDescriptor(project, parent, nodeElement, root, navigateToReference)
: new CallHierarchyNodeDescriptor(project, parent, nodeElement, root, navigateToReference);
}
protected static PsiElement getTargetElement(HierarchyNodeDescriptor descriptor) {
return descriptor instanceof CallHierarchyNodeDescriptor
? ((CallHierarchyNodeDescriptor) descriptor).getEnclosingElement()
: ((KotlinCallHierarchyNodeDescriptor)descriptor).getTargetElement();
}
private static final Function1<PsiElement, Boolean> IS_NON_LOCAL_DECLARATION = new Function1<PsiElement, Boolean>() {
@Override
public Boolean invoke(@javax.annotation.Nullable PsiElement input) {
return input instanceof PsiMethod
|| ((input instanceof KtNamedFunction || input instanceof KtClassOrObject || input instanceof KtProperty)
&& !KtPsiUtil.isLocal((KtNamedDeclaration) input));
}
};
@Nullable
protected static PsiMethod getRepresentativePsiMethod(PsiElement element) {
while (true) {
element = PsiUtilsKt.getParentOfTypesAndPredicate(element, false, ArrayUtil.EMPTY_CLASS_ARRAY, IS_NON_LOCAL_DECLARATION);
if (element == null) return null;
PsiMethod method = getRepresentativePsiMethodForNonLocalDeclaration(element);
if (method != null) return method;
element = element.getParent();
}
}
private static PsiMethod getRepresentativePsiMethodForNonLocalDeclaration(PsiElement element) {
if (element instanceof PsiMethod) {
return (PsiMethod) element;
}
if (element instanceof KtNamedFunction || element instanceof KtSecondaryConstructor) {
return LightClassUtil.INSTANCE.getLightClassMethod((KtFunction) element);
}
if (element instanceof KtProperty) {
LightClassUtil.PropertyAccessorsPsiMethods propertyMethods =
LightClassUtil.INSTANCE.getLightClassPropertyMethods((KtProperty) element);
return (propertyMethods.getGetter() != null) ? propertyMethods.getGetter() : propertyMethods.getSetter();
}
if (element instanceof KtClassOrObject) {
PsiClass psiClass = toLightClass((KtClassOrObject) element);
if (psiClass == null) return null;
PsiMethod[] constructors = psiClass.getConstructors();
if (constructors.length > 0) return constructors[0];
}
return null;
}
protected static CallHierarchyNodeDescriptor getJavaNodeDescriptor(HierarchyNodeDescriptor originalDescriptor) {
if (originalDescriptor instanceof CallHierarchyNodeDescriptor) return (CallHierarchyNodeDescriptor) originalDescriptor;
assert originalDescriptor instanceof KotlinCallHierarchyNodeDescriptor;
return ((KotlinCallHierarchyNodeDescriptor) originalDescriptor).getJavaDelegate();
}
protected Object[] collectNodeDescriptors(
HierarchyNodeDescriptor descriptor, Map<PsiReference, PsiElement> referencesToCalleeElements, PsiClass basePsiClass
) {
HashMap<PsiElement, HierarchyNodeDescriptor> declarationToDescriptorMap = new HashMap<PsiElement, HierarchyNodeDescriptor>();
for (Map.Entry<PsiReference, PsiElement> refToCallee : referencesToCalleeElements.entrySet()) {
PsiReference ref = refToCallee.getKey();
PsiElement callee = refToCallee.getValue();
if (basePsiClass != null && !isInScope(basePsiClass, callee, scopeType)) continue;
addNodeDescriptorForElement(ref, callee, declarationToDescriptorMap, descriptor, false);
}
return declarationToDescriptorMap.values().toArray(new Object[declarationToDescriptorMap.size()]);
}
protected static void addNodeDescriptorForElement(
PsiReference reference,
PsiElement element,
Map<PsiElement, HierarchyNodeDescriptor> declarationToDescriptorMap,
HierarchyNodeDescriptor descriptor,
boolean wrapAsLightElements
) {
HierarchyNodeDescriptor d = declarationToDescriptorMap.get(element);
if (d == null) {
d = createNodeDescriptor(element.getProject(), element, descriptor, true, wrapAsLightElements);
if (d == null) return;
declarationToDescriptorMap.put(element, d);
}
else if (d instanceof CallHierarchyNodeDescriptor) {
((CallHierarchyNodeDescriptor) d).incrementUsageCount();
}
if (d instanceof CallHierarchyNodeDescriptor) {
((CallHierarchyNodeDescriptor) d).addReference(reference);
}
else if (d instanceof KotlinCallHierarchyNodeDescriptor) {
((KotlinCallHierarchyNodeDescriptor) d).addReference(reference);
}
}
@Override
public boolean isAlwaysShowPlus() {
return true;
}
}
@@ -1,140 +0,0 @@
/*
* 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.CallHierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.call.CalleeMethodsTreeStructure;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.asJava.elements.KtLightMethod;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.idea.references.ReferenceUtilKt;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class KotlinCalleeMethodsTreeStructure extends KotlinCallTreeStructure {
private final CalleeMethodsTreeStructure javaTreeStructure;
private final PsiClass representativePsiClass;
public KotlinCalleeMethodsTreeStructure(@NotNull Project project, @NotNull PsiElement element, String scopeType) {
super(project, element, scopeType);
PsiMethod representativePsiMethod = getRepresentativePsiMethod(element);
assert representativePsiMethod != null;
this.representativePsiClass = representativePsiMethod.getContainingClass();
this.javaTreeStructure = new CalleeMethodsTreeStructure(project, representativePsiMethod, scopeType);
}
private static Map<PsiReference, PsiElement> getReferencesToCalleeElements(@NotNull KtElement rootElement) {
List<KtElement> elementsToAnalyze = new ArrayList<KtElement>();
if (rootElement instanceof KtNamedFunction) {
elementsToAnalyze.add(((KtNamedFunction) rootElement).getBodyExpression());
} else if (rootElement instanceof KtProperty) {
for (KtPropertyAccessor accessor : ((KtProperty) rootElement).getAccessors()) {
KtExpression body = accessor.getBodyExpression();
if (body != null) {
elementsToAnalyze.add(body);
}
}
} else {
KtClassOrObject classOrObject = (KtClassOrObject) rootElement;
for (KtSuperTypeListEntry specifier : classOrObject.getSuperTypeListEntries()) {
if (specifier instanceof KtCallElement) {
elementsToAnalyze.add(specifier);
}
}
KtClassBody body = classOrObject.getBody();
if (body != null) {
for (KtAnonymousInitializer initializer : body.getAnonymousInitializers()) {
KtExpression initializerBody = initializer.getBody();
if (initializerBody != null) {
elementsToAnalyze.add(initializerBody);
}
}
for (KtProperty property : body.getProperties()) {
KtExpression initializer = property.getInitializer();
if (initializer != null) {
elementsToAnalyze.add(initializer);
}
}
}
}
final Map<PsiReference, PsiElement> referencesToCalleeElements = new HashMap<PsiReference, PsiElement>();
for (KtElement element : elementsToAnalyze) {
element.accept(
new CalleeReferenceVisitorBase(ResolutionUtils.analyze(element, BodyResolveMode.FULL), false) {
@Override
protected void processDeclaration(KtSimpleNameExpression reference, PsiElement declaration) {
referencesToCalleeElements.put(ReferenceUtilKt.getMainReference(reference), declaration);
}
}
);
}
return referencesToCalleeElements;
}
@NotNull
@Override
protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) {
PsiElement targetElement = getTargetElement(descriptor);
// Kotlin class constructor invoked from Java code
if (targetElement instanceof PsiMethod) {
PsiMethod psiMethod = (PsiMethod) targetElement;
if (psiMethod.isConstructor()) {
PsiClass psiClass = psiMethod.getContainingClass();
PsiElement navigationElement = psiClass != null ? psiClass.getNavigationElement() : null;
if (navigationElement instanceof KtClass) {
return buildChildrenByKotlinTarget(descriptor, (KtElement) navigationElement);
}
}
}
// Kotlin function or property invoked from Java code
if (targetElement instanceof KtLightMethod) {
return buildChildrenByKotlinTarget(descriptor, ((KtLightMethod) targetElement).getKotlinOrigin());
}
if (targetElement instanceof KtElement) {
return buildChildrenByKotlinTarget(descriptor, (KtElement) targetElement);
}
CallHierarchyNodeDescriptor javaDescriptor = descriptor instanceof CallHierarchyNodeDescriptor
? (CallHierarchyNodeDescriptor) descriptor
: ((KotlinCallHierarchyNodeDescriptor)descriptor).getJavaDelegate();
return javaTreeStructure.getChildElements(javaDescriptor);
}
private Object[] buildChildrenByKotlinTarget(HierarchyNodeDescriptor descriptor, KtElement targetElement) {
Map<PsiReference, PsiElement> referencesToCalleeElements = getReferencesToCalleeElements(targetElement);
return collectNodeDescriptors(descriptor, referencesToCalleeElements, representativePsiClass);
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2017 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.HierarchyTreeStructure
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor
import com.intellij.ide.hierarchy.call.CalleeMethodsTreeStructure
import com.intellij.ide.util.treeView.NodeDescriptor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.util.ArrayUtil
import com.intellij.util.containers.HashMap
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchOverriders
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
class KotlinCalleeTreeStructure(
element: KtElement,
private val scopeType: String
) : HierarchyTreeStructure(element.project,
KotlinCallHierarchyNodeDescriptor(null, element, true, false)) {
private fun KtElement.getCalleeSearchScope(): List<KtElement> {
return when (this) {
is KtNamedFunction, is KtFunctionLiteral, is KtPropertyAccessor -> listOf((this as KtDeclarationWithBody).bodyExpression)
is KtProperty -> accessors.map { it.bodyExpression }
is KtClassOrObject -> {
superTypeListEntries.filterIsInstance<KtCallElement>() +
getAnonymousInitializers().map { it.body } +
declarations.filterIsInstance<KtProperty>().map { it.initializer }
}
else -> emptyList()
}.filterNotNull()
}
override fun buildChildren(nodeDescriptor: HierarchyNodeDescriptor): Array<Any> {
if (nodeDescriptor is CallHierarchyNodeDescriptor) {
val psiMethod = nodeDescriptor.enclosingElement as? PsiMethod ?: return ArrayUtil.EMPTY_OBJECT_ARRAY
return CalleeMethodsTreeStructure(myProject, psiMethod, scopeType).getChildElements(nodeDescriptor)
}
val element = nodeDescriptor.psiElement as? KtElement ?: return ArrayUtil.EMPTY_OBJECT_ARRAY
val result = LinkedHashSet<HierarchyNodeDescriptor>()
val baseClass = (element as? KtDeclaration)?.containingClassOrObject
val calleeToDescriptorMap = HashMap<PsiElement, NodeDescriptor<*>>()
element.getCalleeSearchScope().forEach {
it.accept(
object : CalleeReferenceVisitorBase(it.analyze(), false) {
override fun processDeclaration(reference: KtSimpleNameExpression, declaration: PsiElement) {
if (!isInScope(baseClass, declaration, scopeType)) return
result += (getOrCreateNodeDescriptor(nodeDescriptor, declaration, null, false, calleeToDescriptorMap, false) ?: return)
}
}
)
}
for (it in HierarchySearchRequest(element, element.useScope).searchOverriders()) {
val overrider = it.unwrapped as? KtElement ?: continue
if (!isInScope(baseClass, overrider, scopeType)) continue
result += KotlinCallHierarchyNodeDescriptor(nodeDescriptor, overrider, false, false)
}
return result.toArray()
}
}
@@ -1,259 +0,0 @@
/*
* Copyright 2010-2015 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.google.common.collect.Maps;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.call.CallerMethodsTreeStructure;
import com.intellij.openapi.application.ReadActionProcessor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightMemberReference;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.FilteringProcessor;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.asJava.LightClassUtil;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.idea.hierarchy.HierarchyUtilsKt;
import org.jetbrains.kotlin.idea.references.KtReference;
import org.jetbrains.kotlin.idea.references.ReferenceUtilKt;
import org.jetbrains.kotlin.idea.search.usagesSearch.UtilsKt;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import java.util.*;
public class KotlinCallerMethodsTreeStructure extends KotlinCallTreeStructure {
private final CallerMethodsTreeStructure javaTreeStructure;
private final PsiClass basePsiClass;
public KotlinCallerMethodsTreeStructure(@NotNull Project project, @NotNull PsiElement element, String scopeType) {
super(project, element, scopeType);
PsiMethod basePsiMethod = getRepresentativePsiMethod(element);
assert basePsiMethod != null : "Can't generate light method: " + element.getText();
basePsiClass = basePsiMethod.getContainingClass();
javaTreeStructure = new CallerMethodsTreeStructure(project, basePsiMethod, scopeType);
}
@NotNull
@Override
protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) {
final PsiElement element = getTargetElement(descriptor);
KtElement codeBlockForLocalDeclaration = getEnclosingElementForLocalDeclaration(element);
if (codeBlockForLocalDeclaration != null) {
BindingContext bindingContext = ResolutionUtils.analyze((KtElement) element, BodyResolveMode.FULL);
final Map<PsiReference, PsiElement> referencesToElements = new HashMap<PsiReference, PsiElement>();
codeBlockForLocalDeclaration.accept(new CalleeReferenceVisitorBase(bindingContext, true) {
@Override
protected void processDeclaration(KtSimpleNameExpression reference, PsiElement declaration) {
if (!declaration.equals(element)) return;
//noinspection unchecked
PsiElement container = PsiTreeUtil.getParentOfType(
reference,
KtNamedFunction.class, KtPropertyAccessor.class, KtClassOrObject.class
);
if (container instanceof KtPropertyAccessor) {
container = PsiTreeUtil.getParentOfType(container, KtProperty.class);
}
if (container != null) {
referencesToElements.put(ReferenceUtilKt.getMainReference(reference), container);
}
}
});
return collectNodeDescriptors(descriptor, referencesToElements, null);
}
SearchScope searchScope = getSearchScope(scopeType, basePsiClass);
Map<PsiElement, HierarchyNodeDescriptor> methodToDescriptorMap = Maps.newHashMap();
Object[] javaCallers = null;
if (element instanceof PsiMethod) {
javaCallers = javaTreeStructure.getChildElements(getJavaNodeDescriptor(descriptor));
processPsiMethodCallers(
Collections.singleton((PsiMethod) element), descriptor, methodToDescriptorMap, searchScope, true
);
}
if (element instanceof KtNamedFunction || element instanceof KtSecondaryConstructor) {
Collection<PsiMethod> lightMethods = LightClassUtil.INSTANCE.getLightClassMethods((KtFunction) element);
processPsiMethodCallers(lightMethods, descriptor, methodToDescriptorMap, searchScope, false);
}
if (element instanceof KtProperty) {
LightClassUtil.PropertyAccessorsPsiMethods propertyMethods =
LightClassUtil.INSTANCE.getLightClassPropertyMethods((KtProperty) element);
processPsiMethodCallers(propertyMethods, descriptor, methodToDescriptorMap, searchScope, false);
}
if (element instanceof KtClassOrObject) {
KtPrimaryConstructor constructor = ((KtClassOrObject) element).getPrimaryConstructor();
if (constructor != null) {
PsiMethod lightMethod = LightClassUtil.INSTANCE.getLightClassMethod(constructor);
processPsiMethodCallers(Collections.singleton(lightMethod), descriptor, methodToDescriptorMap, searchScope, false);
}
else {
processKtClassOrObjectCallers((KtClassOrObject) element, descriptor, methodToDescriptorMap, searchScope);
}
}
Object[] callers = methodToDescriptorMap.values().toArray(new Object[methodToDescriptorMap.size()]);
return (javaCallers != null) ? ArrayUtil.mergeArrays(javaCallers, callers) : callers;
}
private static void processPsiMethodCallers(
Iterable<PsiMethod> lightMethods,
HierarchyNodeDescriptor descriptor,
Map<PsiElement, HierarchyNodeDescriptor> methodToDescriptorMap,
SearchScope searchScope,
boolean kotlinOnly
) {
Set<PsiMethod> methodsToFind = new HashSet<PsiMethod>();
for (PsiMethod lightMethod : lightMethods) {
if (lightMethod == null) continue;
PsiMethod[] superMethods = lightMethod.findDeepestSuperMethods();
methodsToFind.add(lightMethod);
ContainerUtil.addAll(methodsToFind, superMethods);
}
if (methodsToFind.isEmpty()) return;
Set<PsiReference> references = ContainerUtil.newTroveSet(
new TObjectHashingStrategy<PsiReference>() {
@Override
public int computeHashCode(PsiReference object) {
return object.getElement().hashCode();
}
@Override
public boolean equals(PsiReference o1, PsiReference o2) {
return o1.getElement().equals(o2.getElement());
}
}
);
for (PsiMethod superMethod: methodsToFind) {
ContainerUtil.addAll(references, MethodReferencesSearch.search(superMethod, searchScope, true));
}
ContainerUtil.process(references, defaultQueryProcessor(descriptor, methodToDescriptorMap, kotlinOnly, false));
}
private static void processKtClassOrObjectCallers(
final KtClassOrObject classOrObject,
HierarchyNodeDescriptor descriptor,
Map<PsiElement, HierarchyNodeDescriptor> methodToDescriptorMap,
SearchScope searchScope
) {
Processor<PsiReference> processor = new FilteringProcessor<PsiReference>(
new Condition<PsiReference>() {
@Override
public boolean value(PsiReference reference) {
return UtilsKt.isConstructorUsage(reference, classOrObject);
}
},
defaultQueryProcessor(descriptor, methodToDescriptorMap, false, false)
);
ReferencesSearch.search(classOrObject, searchScope, false).forEach(processor);
}
static Processor<PsiReference> defaultQueryProcessor(
final HierarchyNodeDescriptor descriptor,
final Map<PsiElement, HierarchyNodeDescriptor> methodToDescriptorMap,
boolean kotlinOnly,
final boolean wrapAsLightElements
) {
return new CalleeReferenceProcessor(kotlinOnly) {
@Override
protected void onAccept(@NotNull PsiReference ref, @NotNull PsiElement element) {
addNodeDescriptorForElement(ref, element, methodToDescriptorMap, descriptor, wrapAsLightElements);
}
};
}
public static abstract class CalleeReferenceProcessor extends ReadActionProcessor<PsiReference> {
private final boolean kotlinOnly;
public CalleeReferenceProcessor(boolean only) {
kotlinOnly = only;
}
@Override
public boolean processInReadAction(PsiReference ref) {
// copied from Java
if (!(ref instanceof PsiReferenceExpression || ref instanceof KtReference)) {
if (!(ref instanceof PsiElement)) {
return true;
}
PsiElement parent = ((PsiElement) ref).getParent();
if (parent instanceof PsiNewExpression) {
if (((PsiNewExpression) parent).getClassReference() != ref) {
return true;
}
}
else if (parent instanceof PsiAnonymousClass) {
if (((PsiAnonymousClass) parent).getBaseClassReference() != ref) {
return true;
}
}
else if (ref instanceof LightMemberReference) {
PsiElement refTarget = ref.resolve();
// Accept implicit superclass constructor reference in Java code
if (!(refTarget instanceof PsiMethod && ((PsiMethod) refTarget).isConstructor())) return true;
}
else {
return true;
}
}
PsiElement refElement = ref.getElement();
if (PsiTreeUtil.getParentOfType(refElement, KtImportDirective.class, true) != null) return true;
PsiElement element = HierarchyUtilsKt.getCallHierarchyElement(refElement);
if (kotlinOnly && !(element instanceof KtNamedDeclaration)) return true;
// If reference belongs to property initializer, show enclosing declaration instead
if (element instanceof KtProperty) {
KtProperty property = (KtProperty) element;
if (PsiTreeUtil.isAncestor(property.getInitializer(), refElement, false)) {
element = HierarchyUtilsKt.getCallHierarchyElement(element.getParent());
}
}
if (element != null) {
onAccept(ref, element);
}
return true;
}
protected abstract void onAccept(@NotNull PsiReference ref, @NotNull PsiElement element);
}
}
@@ -0,0 +1,147 @@
/*
* Copyright 2010-2017 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.google.common.collect.Maps
import com.intellij.find.findUsages.JavaFindUsagesOptions
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor
import com.intellij.ide.hierarchy.HierarchyTreeStructure
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor
import com.intellij.ide.hierarchy.call.CallerMethodsTreeStructure
import com.intellij.ide.util.treeView.NodeDescriptor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiReference
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.findUsages.KotlinClassFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
import org.jetbrains.kotlin.idea.findUsages.processAllUsages
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
class KotlinCallerTreeStructure(
element: KtElement,
private val scopeType: String
) : HierarchyTreeStructure(element.project,
KotlinCallHierarchyNodeDescriptor(null, element, true, false)) {
companion object {
internal fun processReference(
reference: PsiReference?,
refElement: PsiElement,
nodeDescriptor: HierarchyNodeDescriptor,
callerToDescriptorMap: MutableMap<PsiElement, NodeDescriptor<*>>,
isJavaMap: Boolean
) {
var callerElement: PsiElement? = when (refElement) {
is KtElement -> getCallHierarchyElement(refElement)
else -> {
val psiMember = refElement.getNonStrictParentOfType<PsiMember>()
psiMember as? PsiMethod ?: psiMember?.containingClass
}
}
if (callerElement is KtProperty) {
if (PsiTreeUtil.isAncestor(callerElement.initializer, refElement, false)) {
callerElement = getCallHierarchyElement(callerElement.parent)
}
}
if (callerElement == null) return
getOrCreateNodeDescriptor(nodeDescriptor, callerElement, reference, true, callerToDescriptorMap, isJavaMap)
}
}
private fun buildChildren(
element: PsiElement,
nodeDescriptor: HierarchyNodeDescriptor,
callerToDescriptorMap: MutableMap<PsiElement, NodeDescriptor<*>>
): Collection<Any> {
if (nodeDescriptor is CallHierarchyNodeDescriptor) {
val psiMethod = nodeDescriptor.enclosingElement as? PsiMethod ?: return emptyList()
return CallerMethodsTreeStructure(myProject, psiMethod, scopeType).getChildElements(nodeDescriptor).toList()
}
if (element !is KtDeclaration) return emptyList()
val baseClass = (element as? KtDeclaration)?.containingClassOrObject
val searchScope = getSearchScope(scopeType, baseClass)
val findOptions: JavaFindUsagesOptions = when (element) {
is KtNamedFunction, is KtConstructor<*> -> KotlinFunctionFindUsagesOptions(myProject)
is KtProperty -> KotlinPropertyFindUsagesOptions(myProject)
is KtPropertyAccessor -> KotlinPropertyFindUsagesOptions(myProject).apply {
isReadAccess = element.isGetter
isWriteAccess = element.isSetter
}
is KtClass -> KotlinClassFindUsagesOptions(myProject).apply {
isUsages = false
}
else -> return emptyList()
}
findOptions.isSkipImportStatements = true
findOptions.searchScope = searchScope
findOptions.isSearchForTextOccurrences = false
val elementToSearch = when (element) {
is KtPropertyAccessor -> element.property
else -> element
}
// If reference belongs to property initializer, show enclosing declaration instead
elementToSearch.processAllUsages(findOptions) {
processReference(it.reference, it.element ?: return@processAllUsages, nodeDescriptor, callerToDescriptorMap, false)
}
return callerToDescriptorMap.values
}
override fun buildChildren(nodeDescriptor: HierarchyNodeDescriptor): Array<Any> {
val element = nodeDescriptor.psiElement as? KtDeclaration ?: return ArrayUtil.EMPTY_OBJECT_ARRAY
val callerToDescriptorMap = Maps.newHashMap<PsiElement, NodeDescriptor<*>>()
val descriptor = element.resolveToDescriptor()
if (descriptor is CallableMemberDescriptor) {
return descriptor.getDeepestSuperDeclarations().flatMap { rootDescriptor ->
val rootElement = DescriptorToSourceUtilsIde.getAnyDeclaration(myProject, rootDescriptor)
?: return@flatMap emptyList<Any>()
val rootNodeDescriptor = when (rootElement) {
is KtElement -> nodeDescriptor
is PsiMethod -> CallHierarchyNodeDescriptor(
myProject,
nodeDescriptor.parentDescriptor as HierarchyNodeDescriptor?,
rootElement,
nodeDescriptor.parentDescriptor == null,
false
)
else -> return@flatMap emptyList<Any>()
}
buildChildren(rootElement, rootNodeDescriptor, callerToDescriptorMap)
}.toTypedArray()
}
else {
return buildChildren(element, nodeDescriptor, callerToDescriptorMap).toTypedArray()
}
}
override fun isAlwaysShowPlus() = true
}
@@ -0,0 +1,86 @@
/*
* Copyright 2010-2017 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.CallHierarchyNodeDescriptor
import com.intellij.ide.util.treeView.NodeDescriptor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiReference
import org.jetbrains.kotlin.asJava.toLightElements
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
fun isCallHierarchyElement(e: PsiElement): Boolean {
return (e is KtNamedFunction && e.name != null) ||
e is KtSecondaryConstructor ||
(e is KtProperty && !e.isLocal) ||
e is KtObjectDeclaration ||
(e is KtClass && !e.isInterface()) ||
e is KtFile
}
fun getCallHierarchyElement(element: PsiElement) = element.parentsWithSelf.firstOrNull(::isCallHierarchyElement) as? KtElement
private fun NodeDescriptor<*>.incrementUsageCount() {
when (this) {
is KotlinCallHierarchyNodeDescriptor -> incrementUsageCount()
is CallHierarchyNodeDescriptor -> incrementUsageCount()
}
}
private fun NodeDescriptor<*>.addReference(reference: PsiReference) {
when (this) {
is KotlinCallHierarchyNodeDescriptor -> addReference(reference)
is CallHierarchyNodeDescriptor -> addReference(reference)
}
}
internal fun getOrCreateNodeDescriptor(
parent: HierarchyNodeDescriptor,
originalElement: PsiElement,
reference: PsiReference?,
navigateToReference: Boolean,
elementToDescriptorMap: MutableMap<PsiElement, NodeDescriptor<*>>,
isJavaMap: Boolean
): HierarchyNodeDescriptor? {
val element = (if (isJavaMap && originalElement is KtElement) originalElement.toLightElements().firstOrNull() else originalElement)
?: return null
val existingDescriptor = elementToDescriptorMap[element] as? HierarchyNodeDescriptor
val result = if (existingDescriptor != null) {
existingDescriptor.incrementUsageCount()
existingDescriptor
}
else {
val newDescriptor: HierarchyNodeDescriptor = when (element) {
is KtElement -> KotlinCallHierarchyNodeDescriptor(parent, element, false, navigateToReference)
is PsiMember -> CallHierarchyNodeDescriptor(element.project, parent, element, false, navigateToReference)
else -> return null
}
elementToDescriptorMap[element] = newDescriptor
newDescriptor
}
if (reference != null) {
result.addReference(reference)
}
return result
}
@@ -33,9 +33,9 @@ class KotlinOverrideTreeStructure(project: Project, val element: PsiElement) : H
setBaseElement(javaTreeStructures.first().baseDescriptor!!)
}
override fun buildChildren(descriptor: HierarchyNodeDescriptor): Array<Any> {
override fun buildChildren(nodeDescriptor: HierarchyNodeDescriptor): Array<Any> {
fun buildChildrenByTreeStructure(javaTreeStructure: MethodHierarchyTreeStructure): Array<Any> {
return javaTreeStructure.getChildElements(descriptor as MethodHierarchyNodeDescriptor) ?: ArrayUtil.EMPTY_OBJECT_ARRAY
return javaTreeStructure.getChildElements(nodeDescriptor as MethodHierarchyNodeDescriptor) ?: ArrayUtil.EMPTY_OBJECT_ARRAY
}
return javaTreeStructures
@@ -40,13 +40,12 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.hierarchy.calls.CalleeReferenceProcessor
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallHierarchyNodeDescriptor
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallerMethodsTreeStructure
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import java.util.HashSet
import java.util.LinkedHashSet
import java.util.*
class KotlinCallerChooser(
declaration: PsiElement,
@@ -110,7 +109,7 @@ class KotlinMethodNode(
val callers = LinkedHashSet<PsiElement>()
val processor = object: KotlinCallerMethodsTreeStructure.CalleeReferenceProcessor(false) {
val processor = object: CalleeReferenceProcessor(false) {
override fun onAccept(ref: PsiReference, element: PsiElement) {
if ((element is KtFunction || element is KtClass || element is PsiMethod) && element !in myCalled) {
callers.add(element)
@@ -20,9 +20,13 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
val DataContext.project: Project
get() = CommonDataKeys.PROJECT.getData(this)!!
val DataContext.hostEditor: Editor?
get() = CommonDataKeys.HOST_EDITOR.getData(this)
get() = CommonDataKeys.HOST_EDITOR.getData(this)
val DataContext.psiElement : PsiElement?
get() = CommonDataKeys.PSI_ELEMENT.getData(this)
@@ -0,0 +1,5 @@
<node text="foo() ()" base="true">
<node text="bar() ()">
<node text="baz() ()"/>
</node>
</node>
@@ -0,0 +1,11 @@
fun <caret>foo() = 1
fun bar() {
val x = fun () {
val y = foo()
}
}
fun baz() {
bar()
}
@@ -0,0 +1,5 @@
<node text="foo() ()" base="true">
<node text="bar() ()">
<node text="baz() ()"/>
</node>
</node>
@@ -0,0 +1,11 @@
fun <caret>foo() = 1
fun bar() {
val x = {
val y = foo()
}
}
fun baz() {
bar()
}
@@ -26,6 +26,7 @@ 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.fileEditor.impl.text.TextEditorPsiDataProvider;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.io.FileUtil;
@@ -36,10 +37,11 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinHierarchyViewTestBase;
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCalleeMethodsTreeStructure;
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallerMethodsTreeStructure;
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCalleeTreeStructure;
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallerTreeStructure;
import org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideTreeStructure;
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase;
import org.jetbrains.kotlin.psi.KtElement;
import java.io.File;
import java.util.ArrayList;
@@ -132,9 +134,8 @@ public abstract class AbstractHierarchyTest extends KotlinHierarchyViewTestBase
return new Computable<HierarchyTreeStructure>() {
@Override
public HierarchyTreeStructure compute() {
return new KotlinCallerMethodsTreeStructure(
getProject(),
getElementAtCaret(LanguageCallHierarchy.INSTANCE),
return new KotlinCallerTreeStructure(
(KtElement) getElementAtCaret(LanguageCallHierarchy.INSTANCE),
HierarchyBrowserBaseEx.SCOPE_PROJECT
);
}
@@ -158,9 +159,8 @@ public abstract class AbstractHierarchyTest extends KotlinHierarchyViewTestBase
return new Computable<HierarchyTreeStructure>() {
@Override
public HierarchyTreeStructure compute() {
return new KotlinCalleeMethodsTreeStructure(
getProject(),
getElementAtCaret(LanguageCallHierarchy.INSTANCE),
return new KotlinCalleeTreeStructure(
(KtElement) getElementAtCaret(LanguageCallHierarchy.INSTANCE),
HierarchyBrowserBaseEx.SCOPE_PROJECT
);
}
@@ -189,12 +189,16 @@ 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, editor);
context.put(CommonDataKeys.PSI_ELEMENT, psiFile.findElementAt(editor.getCaretModel().getOffset()));
PsiElement targetElement = (PsiElement) new TextEditorPsiDataProvider().getData(
CommonDataKeys.PSI_ELEMENT.getName(),
editor,
editor.getCaretModel().getCurrentCaret()
);
context.put(CommonDataKeys.PSI_ELEMENT, targetElement);
return context;
}
@@ -323,6 +323,18 @@ public class HierarchyTestGenerated extends AbstractHierarchyTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/hierarchy/calls/callers"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, false);
}
@TestMetadata("callInsideAnonymousFun")
public void testCallInsideAnonymousFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/hierarchy/calls/callers/callInsideAnonymousFun/");
doCallerHierarchyTest(fileName);
}
@TestMetadata("callInsideLambda")
public void testCallInsideLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/hierarchy/calls/callers/callInsideLambda/");
doCallerHierarchyTest(fileName);
}
@TestMetadata("kotlinClass")
public void testKotlinClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/hierarchy/calls/callers/kotlinClass/");