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() 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.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil 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? { fun getCurrentElement(dataContext: DataContext, project: Project): PsiElement? {
val editor = CommonDataKeys.EDITOR.getData(dataContext) val editor = CommonDataKeys.EDITOR.getData(dataContext)
@@ -49,5 +34,3 @@ fun getCurrentElement(dataContext: DataContext, project: Project): PsiElement? {
return CommonDataKeys.PSI_ELEMENT.getData(dataContext) 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; this.deepTraversal = deepTraversal;
} }
protected abstract void processDeclaration(KtSimpleNameExpression reference, PsiElement declaration); protected abstract void processDeclaration(@NotNull KtSimpleNameExpression reference, @NotNull PsiElement declaration);
@Override @Override
public void visitKtElement(@NotNull KtElement element) { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.ui.PopupHandler; import com.intellij.ui.PopupHandler;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.hierarchy.HierarchyUtilsKt; import org.jetbrains.kotlin.idea.hierarchy.HierarchyUtilsKt;
import org.jetbrains.kotlin.psi.KtElement;
import javax.swing.*; import javax.swing.*;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map; import java.util.Map;
public class KotlinCallHierarchyBrowser extends CallHierarchyBrowserBase { public class KotlinCallHierarchyBrowser extends CallHierarchyBrowserBase {
public KotlinCallHierarchyBrowser(@NotNull Project project, @NotNull PsiElement element) { public KotlinCallHierarchyBrowser(@NotNull PsiElement element) {
super(project, element); super(element.getProject(), element);
} }
@Override @Override
@@ -63,7 +63,7 @@ public class KotlinCallHierarchyBrowser extends CallHierarchyBrowserBase {
private static PsiElement getTargetElement(@NotNull HierarchyNodeDescriptor descriptor) { private static PsiElement getTargetElement(@NotNull HierarchyNodeDescriptor descriptor) {
if (descriptor instanceof KotlinCallHierarchyNodeDescriptor) { if (descriptor instanceof KotlinCallHierarchyNodeDescriptor) {
return ((KotlinCallHierarchyNodeDescriptor) descriptor).getTargetElement(); return descriptor.getPsiElement();
} }
return null; return null;
} }
@@ -76,17 +76,19 @@ public class KotlinCallHierarchyBrowser extends CallHierarchyBrowserBase {
@Override @Override
protected boolean isApplicableElement(@NotNull PsiElement element) { protected boolean isApplicableElement(@NotNull PsiElement element) {
if (element instanceof PsiClass) return false; // PsiClass is not allowed at the hierarchy root if (element instanceof PsiClass) return false; // PsiClass is not allowed at the hierarchy root
return HierarchyUtilsKt.isCallHierarchyElement(element); return CallHierarchyUtilsKt.isCallHierarchyElement(element);
} }
@Override @Override
protected HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String typeName, @NotNull PsiElement psiElement) { protected HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String typeName, @NotNull PsiElement psiElement) {
if (!(psiElement instanceof KtElement)) return null;
if (typeName.equals(CALLER_TYPE)) { if (typeName.equals(CALLER_TYPE)) {
return new KotlinCallerMethodsTreeStructure(myProject, psiElement, getCurrentScopeType()); return new KotlinCallerTreeStructure((KtElement) psiElement, getCurrentScopeType());
} }
if (typeName.equals(CALLEE_TYPE)) { if (typeName.equals(CALLEE_TYPE)) {
return new KotlinCalleeMethodsTreeStructure(myProject, psiElement, getCurrentScopeType()); return new KotlinCalleeTreeStructure((KtElement) psiElement, getCurrentScopeType());
} }
return null; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.icons.AllIcons;
import com.intellij.ide.IdeBundle; import com.intellij.ide.IdeBundle;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.JavaHierarchyUtil;
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor; import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor;
import com.intellij.openapi.editor.markup.TextAttributes; 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.roots.ui.util.CompositeAppearance;
import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Iconable; import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.Navigatable; import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference; import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.LayeredIcon; import com.intellij.ui.LayeredIcon;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; 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.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer; import org.jetbrains.kotlin.renderer.DescriptorRenderer;
@@ -49,34 +47,29 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor implements Navigatable { public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor implements Navigatable {
private int usageCount = 0; private int usageCount = 1;
private final Set<PsiReference> references = new HashSet<PsiReference>(); private final Set<PsiReference> references = new HashSet<>();
private final CallHierarchyNodeDescriptor javaDelegate; private final CallHierarchyNodeDescriptor javaDelegate;
public KotlinCallHierarchyNodeDescriptor(@NotNull Project project, public KotlinCallHierarchyNodeDescriptor(
HierarchyNodeDescriptor parentDescriptor, @Nullable HierarchyNodeDescriptor parentDescriptor,
@NotNull PsiElement element, @NotNull KtElement element,
boolean isBase, boolean isBase,
boolean navigateToReference) { boolean navigateToReference) {
super(project, parentDescriptor, element, isBase); super(element.getProject(), parentDescriptor, element, isBase);
this.javaDelegate = new CallHierarchyNodeDescriptor(myProject, null, element, isBase, navigateToReference); this.javaDelegate = new CallHierarchyNodeDescriptor(myProject, null, element, isBase, navigateToReference);
} }
public final CallHierarchyNodeDescriptor getJavaDelegate() { public final void incrementUsageCount() {
return javaDelegate; usageCount++;
javaDelegate.incrementUsageCount();
} }
public final void addReference(PsiReference reference) { public final void addReference(PsiReference reference) {
if (references.add(reference)) { references.add(reference);
usageCount++;
}
javaDelegate.addReference(reference); javaDelegate.addReference(reference);
} }
public final PsiElement getTargetElement(){
return getPsiElement();
}
@Override @Override
public final boolean isValid(){ public final boolean isValid(){
//noinspection ConstantConditions //noinspection ConstantConditions
@@ -96,7 +89,7 @@ public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i
boolean changes = super.update(); boolean changes = super.update();
PsiElement targetElement = getTargetElement(); PsiElement targetElement = getPsiElement();
String elementText = renderElement(targetElement); String elementText = renderElement(targetElement);
if (elementText == null) { if (elementText == null) {
@@ -122,16 +115,7 @@ public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i
mainTextAttributes = new TextAttributes(myColor, null, null, null, Font.PLAIN); mainTextAttributes = new TextAttributes(myColor, null, null, null, Font.PLAIN);
} }
String packageName = null; String packageName = KtPsiUtil.getPackageName((KtElement) targetElement);
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);
}
}
myHighlightedText.getEnding().addText(elementText, mainTextAttributes); 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); elementText = renderNamedFunction((FunctionDescriptor) descriptor);
} }
else if (element instanceof KtProperty) { else if (element instanceof KtProperty) {
@@ -219,12 +203,7 @@ public class KotlinCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor i
String name = descriptorForName.getName().asString(); String name = descriptorForName.getName().asString();
String paramTypes = StringUtil.join( String paramTypes = StringUtil.join(
descriptor.getValueParameters(), descriptor.getValueParameters(),
new Function<ValueParameterDescriptor, String>() { descriptor1 -> DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(descriptor1.getType()),
@Override
public String fun(ValueParameterDescriptor descriptor) {
return DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(descriptor.getType());
}
},
", " ", "
); );
return name + "(" + paramTypes + ")"; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.HierarchyNodeDescriptor
import com.intellij.ide.hierarchy.call.CallReferenceProcessor import com.intellij.ide.hierarchy.call.CallReferenceProcessor
import com.intellij.ide.hierarchy.call.JavaCallHierarchyData import com.intellij.ide.hierarchy.call.JavaCallHierarchyData
import com.intellij.ide.util.treeView.NodeDescriptor
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference import com.intellij.psi.PsiReference
import org.jetbrains.kotlin.idea.references.KtReference
class KotlinCallReferenceProcessor : CallReferenceProcessor { class KotlinCallReferenceProcessor : CallReferenceProcessor {
override fun process(reference: PsiReference, data: JavaCallHierarchyData): Boolean { override fun process(reference: PsiReference, data: JavaCallHierarchyData): Boolean {
if (reference !is KtReference) return true val nodeDescriptor = data.nodeDescriptor as? HierarchyNodeDescriptor ?: return false
val nodeDescriptor = data.nodeDescriptor as? HierarchyNodeDescriptor ?: return true
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return !KotlinCallerMethodsTreeStructure.defaultQueryProcessor( KotlinCallerTreeStructure.processReference(reference, reference.element, nodeDescriptor, data.resultMap as MutableMap<PsiElement, NodeDescriptor<*>>, true)
if (nodeDescriptor is KotlinCallHierarchyNodeDescriptor) nodeDescriptor.javaDelegate else nodeDescriptor, return true
data.resultMap as MutableMap<PsiElement, HierarchyNodeDescriptor>,
false,
true
).process(reference)
} }
} }
@@ -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!!) setBaseElement(javaTreeStructures.first().baseDescriptor!!)
} }
override fun buildChildren(descriptor: HierarchyNodeDescriptor): Array<Any> { override fun buildChildren(nodeDescriptor: HierarchyNodeDescriptor): Array<Any> {
fun buildChildrenByTreeStructure(javaTreeStructure: MethodHierarchyTreeStructure): 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 return javaTreeStructures
@@ -40,13 +40,12 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor 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.KotlinCallHierarchyNodeDescriptor
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallerMethodsTreeStructure
import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import java.util.HashSet import java.util.*
import java.util.LinkedHashSet
class KotlinCallerChooser( class KotlinCallerChooser(
declaration: PsiElement, declaration: PsiElement,
@@ -110,7 +109,7 @@ class KotlinMethodNode(
val callers = LinkedHashSet<PsiElement>() val callers = LinkedHashSet<PsiElement>()
val processor = object: KotlinCallerMethodsTreeStructure.CalleeReferenceProcessor(false) { val processor = object: CalleeReferenceProcessor(false) {
override fun onAccept(ref: PsiReference, element: PsiElement) { override fun onAccept(ref: PsiReference, element: PsiElement) {
if ((element is KtFunction || element is KtClass || element is PsiMethod) && element !in myCalled) { if ((element is KtFunction || element is KtClass || element is PsiMethod) && element !in myCalled) {
callers.add(element) callers.add(element)
@@ -20,9 +20,13 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
val DataContext.project: Project val DataContext.project: Project
get() = CommonDataKeys.PROJECT.getData(this)!! get() = CommonDataKeys.PROJECT.getData(this)!!
val DataContext.hostEditor: Editor? 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.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.impl.text.TextEditorPsiDataProvider;
import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
@@ -36,10 +37,11 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor; import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinHierarchyViewTestBase; import org.jetbrains.kotlin.idea.KotlinHierarchyViewTestBase;
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCalleeMethodsTreeStructure; import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCalleeTreeStructure;
import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallerMethodsTreeStructure; import org.jetbrains.kotlin.idea.hierarchy.calls.KotlinCallerTreeStructure;
import org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideTreeStructure; import org.jetbrains.kotlin.idea.hierarchy.overrides.KotlinOverrideTreeStructure;
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase; import org.jetbrains.kotlin.idea.test.PluginTestCaseBase;
import org.jetbrains.kotlin.psi.KtElement;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -132,9 +134,8 @@ public abstract class AbstractHierarchyTest extends KotlinHierarchyViewTestBase
return new Computable<HierarchyTreeStructure>() { return new Computable<HierarchyTreeStructure>() {
@Override @Override
public HierarchyTreeStructure compute() { public HierarchyTreeStructure compute() {
return new KotlinCallerMethodsTreeStructure( return new KotlinCallerTreeStructure(
getProject(), (KtElement) getElementAtCaret(LanguageCallHierarchy.INSTANCE),
getElementAtCaret(LanguageCallHierarchy.INSTANCE),
HierarchyBrowserBaseEx.SCOPE_PROJECT HierarchyBrowserBaseEx.SCOPE_PROJECT
); );
} }
@@ -158,9 +159,8 @@ public abstract class AbstractHierarchyTest extends KotlinHierarchyViewTestBase
return new Computable<HierarchyTreeStructure>() { return new Computable<HierarchyTreeStructure>() {
@Override @Override
public HierarchyTreeStructure compute() { public HierarchyTreeStructure compute() {
return new KotlinCalleeMethodsTreeStructure( return new KotlinCalleeTreeStructure(
getProject(), (KtElement) getElementAtCaret(LanguageCallHierarchy.INSTANCE),
getElementAtCaret(LanguageCallHierarchy.INSTANCE),
HierarchyBrowserBaseEx.SCOPE_PROJECT HierarchyBrowserBaseEx.SCOPE_PROJECT
); );
} }
@@ -189,12 +189,16 @@ public abstract class AbstractHierarchyTest extends KotlinHierarchyViewTestBase
private DataContext getDataContext() { private DataContext getDataContext() {
Editor editor = getEditor(); Editor editor = getEditor();
PsiFile psiFile = getFile();
MapDataContext context = new MapDataContext(); MapDataContext context = new MapDataContext();
context.put(CommonDataKeys.PROJECT, getProject()); context.put(CommonDataKeys.PROJECT, getProject());
context.put(CommonDataKeys.EDITOR, editor); 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; 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); 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") @TestMetadata("kotlinClass")
public void testKotlinClass() throws Exception { public void testKotlinClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/hierarchy/calls/callers/kotlinClass/"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/hierarchy/calls/callers/kotlinClass/");