[kotlin] k2 call hierarchy: copy/paste implementation

KTIJ-29300

GitOrigin-RevId: 231ba561e739001057df55e569a1de683bd12e22
This commit is contained in:
Anna Kozlova
2024-06-11 09:29:51 +02:00
committed by intellij-monorepo-bot
parent 58bd4f5b72
commit 5aba74cafd
13 changed files with 1074 additions and 0 deletions

View File

@@ -59,5 +59,7 @@
<orderEntry type="library" name="kotlinc.analysis-api-k2" level="project" />
<orderEntry type="module" module-name="kotlin.formatter.minimal" />
<orderEntry type="module" module-name="kotlin.refactorings.k2" />
<orderEntry type="module" module-name="intellij.java" />
<orderEntry type="module" module-name="kotlin.searching.base" />
</component>
</module>

View File

@@ -232,6 +232,11 @@
<vcs.codeVisionLanguageContext language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.codeInsight.hints.KotlinVcsCodeVisionContext"/>
<callHierarchyProvider
language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.calls.KotlinCallHierarchyProvider"/>
<hierarchy.referenceProcessor implementation="org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.calls.KotlinCallReferenceProcessor"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<codeinsight.quickfix.registrar implementation="org.jetbrains.kotlin.idea.k2.codeinsight.quickFixes.createFromUsage.K2CreateFromUsageQuickFixesRegistrar"/>

View File

@@ -0,0 +1,47 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.calls
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtCallElement
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
abstract class CalleeReferenceVisitorBase protected constructor(private val deepTraversal: Boolean) : KtTreeVisitorVoid() {
protected abstract fun processDeclaration(reference: KtSimpleNameExpression, declaration: PsiElement)
override fun visitKtElement(element: KtElement) {
if (deepTraversal || !(element is KtClassOrObject || element is KtNamedFunction)) {
super.visitKtElement(element)
}
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val declaration = expression.mainReference.resolve()
if (declaration == null || (declaration.containingFile as? KtFile)?.isCompiled == true) return
if (declaration is KtProperty && !declaration.isLocal || isCallable(declaration, expression)) {
processDeclaration(expression, declaration)
}
}
companion object {
// Accept callees of KtCallElement which refer to Kotlin function, Kotlin class or Java method
private fun isCallable(declaration: PsiElement, reference: KtSimpleNameExpression): Boolean {
val callElement = PsiTreeUtil.getParentOfType(reference, KtCallElement::class.java)
if (callElement == null || !PsiTreeUtil.isAncestor(callElement.calleeExpression, reference, false)) return false
return declaration is KtClassOrObject
|| declaration is KtNamedFunction
|| declaration is PsiMethod
}
}
}

View File

@@ -0,0 +1,77 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.calls
import com.intellij.ide.hierarchy.CallHierarchyBrowserBase
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor
import com.intellij.ide.hierarchy.HierarchyTreeStructure
import com.intellij.ide.hierarchy.JavaHierarchyUtil
import com.intellij.ide.util.treeView.NodeDescriptor
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.ui.PopupHandler
import org.jetbrains.kotlin.psi.KtElement
import javax.swing.JTree
class KotlinCallHierarchyBrowser(element: PsiElement) :
CallHierarchyBrowserBase(element.project, element) {
override fun createTrees(trees: MutableMap<in String, in JTree>) {
val baseOnThisMethodAction = BaseOnThisMethodAction()
val tree1 = createTree(false)
PopupHandler.installPopupMenu(
tree1,
IdeActions.GROUP_CALL_HIERARCHY_POPUP,
ActionPlaces.CALL_HIERARCHY_VIEW_POPUP
)
baseOnThisMethodAction.registerCustomShortcutSet(
ActionManager.getInstance().getAction(IdeActions.ACTION_CALL_HIERARCHY).shortcutSet,
tree1
)
trees[getCalleeType()] = tree1
val tree2 = createTree(false)
PopupHandler.installPopupMenu(
tree2,
IdeActions.GROUP_CALL_HIERARCHY_POPUP,
ActionPlaces.CALL_HIERARCHY_VIEW_POPUP
)
baseOnThisMethodAction.registerCustomShortcutSet(
ActionManager.getInstance().getAction(IdeActions.ACTION_CALL_HIERARCHY).shortcutSet,
tree2
)
trees[getCallerType()] = tree2
}
override fun getElementFromDescriptor(descriptor: HierarchyNodeDescriptor): PsiElement? {
return getTargetElement(descriptor)
}
override fun isApplicableElement(element: PsiElement): Boolean {
return if (element is PsiClass) false else isCallHierarchyElement(element) // PsiClass is not allowed at the hierarchy root
}
override fun createHierarchyTreeStructure(
type: String,
psiElement: PsiElement
): HierarchyTreeStructure? {
if (psiElement !is KtElement) return null
return when (type) {
getCallerType() -> KotlinCallerTreeStructure(psiElement, currentScopeType)
getCalleeType() -> KotlinCalleeTreeStructure(psiElement, currentScopeType)
else -> null
}
}
override fun getComparator(): Comparator<NodeDescriptor<*>> {
return JavaHierarchyUtil.getComparator(myProject)
}
companion object {
private fun getTargetElement(descriptor: HierarchyNodeDescriptor): PsiElement? {
return (descriptor as? KotlinCallHierarchyNodeDescriptor)?.psiElement
}
}
}

View File

@@ -0,0 +1,214 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.calls
import com.intellij.icons.AllIcons
import com.intellij.ide.IdeBundle
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor
import com.intellij.ide.hierarchy.call.CallHierarchyNodeDescriptor
import com.intellij.openapi.editor.markup.TextAttributes
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.NlsSafe
import com.intellij.openapi.util.text.StringUtil
import com.intellij.pom.Navigatable
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.ui.LayeredIcon
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource
import org.jetbrains.kotlin.analysis.api.symbols.KaClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaConstructorSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaPackageSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaVariableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KaNamedSymbol
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.types.Variance
import java.awt.Font
class KotlinCallHierarchyNodeDescriptor(
parentDescriptor: HierarchyNodeDescriptor?,
element: KtElement,
isBase: Boolean,
navigateToReference: Boolean
) : HierarchyNodeDescriptor(element.project, parentDescriptor, element, isBase),
Navigatable {
private var usageCount = 1
private val references: MutableSet<PsiReference> = HashSet()
private val javaDelegate = CallHierarchyNodeDescriptor(myProject, null, element, isBase, navigateToReference)
fun incrementUsageCount() {
usageCount++
javaDelegate.incrementUsageCount()
}
fun addReference(reference: PsiReference) {
references.add(reference)
javaDelegate.addReference(reference)
}
override fun isValid(): Boolean {
val myElement = psiElement
return myElement != null && myElement.isValid
}
override fun update(): Boolean {
val oldText = myHighlightedText
val oldIcon = icon
val flags: Int = if (isMarkReadOnly) {
Iconable.ICON_FLAG_VISIBILITY or Iconable.ICON_FLAG_READ_STATUS
} else {
Iconable.ICON_FLAG_VISIBILITY
}
var changes = super.update()
val elementText = (psiElement as? KtElement)?.let { analyze(it) { renderElement(it) } }
if (elementText == null) {
val invalidPrefix = IdeBundle.message("node.hierarchy.invalid")
if (!myHighlightedText.text.startsWith(invalidPrefix)) {
myHighlightedText.beginning.addText(invalidPrefix, getInvalidPrefixAttributes())
}
return true
}
val targetElement = psiElement
val elementIcon = targetElement!!.getIcon(flags)
icon = if (changes && myIsBase) {
LayeredIcon(2).apply {
setIcon(elementIcon, 0)
setIcon(AllIcons.General.Modified, 1, -AllIcons.General.Modified.iconWidth / 2, 0)
}
} else {
elementIcon
}
val mainTextAttributes: TextAttributes? = if (myColor != null) {
TextAttributes(myColor, null, null, null, Font.PLAIN)
} else {
null
}
myHighlightedText = CompositeAppearance()
myHighlightedText.ending.addText(elementText, mainTextAttributes)
if (usageCount > 1) {
myHighlightedText.ending.addText(
IdeBundle.message("node.call.hierarchy.N.usages", usageCount),
getUsageCountPrefixAttributes(),
)
}
@NlsSafe
val packageName = KtPsiUtil.getPackageName(targetElement as KtElement) ?: ""
myHighlightedText.ending.addText(" ($packageName)", getPackageNameAttributes())
myName = myHighlightedText.text
if (!(Comparing.equal(myHighlightedText, oldText) && Comparing.equal(icon, oldIcon))) {
changes = true
}
return changes
}
override fun navigate(requestFocus: Boolean) {
javaDelegate.navigate(requestFocus)
}
override fun canNavigate(): Boolean {
return javaDelegate.canNavigate()
}
override fun canNavigateToSource(): Boolean {
return javaDelegate.canNavigateToSource()
}
companion object {
context(KaSession)
@NlsSafe
private fun renderElement(element: PsiElement?): String? {
when (element) {
is KtFile -> {
return element.name
}
!is KtNamedDeclaration -> {
return null
}
else -> {
var declarationSymbol = element.getSymbol()
val elementText: String?
when (element) {
is KtClassOrObject -> {
when {
element is KtObjectDeclaration && element.isCompanion() -> {
val containingDescriptor = declarationSymbol.getContainingSymbol()
if (containingDescriptor !is KaClassOrObjectSymbol) return null
declarationSymbol = containingDescriptor
elementText = renderClassOrObject(declarationSymbol)
}
element is KtEnumEntry -> {
elementText = element.name
}
else -> {
elementText = if (element.name != null) {
renderClassOrObject(declarationSymbol as KaClassOrObjectSymbol)
} else {
KotlinBundle.message("hierarchy.text.anonymous")
}
}
}
}
is KtNamedFunction, is KtConstructor<*> -> {
if (declarationSymbol !is KaFunctionLikeSymbol) return null
elementText = renderNamedFunction(declarationSymbol)
}
is KtProperty -> {
elementText = element.name
}
else -> return null
}
if (elementText == null) return null
var containerText: String? = null
var containerDescriptor = declarationSymbol.getContainingSymbol()
while (containerDescriptor != null) {
if (containerDescriptor is KaPackageSymbol) {
break
}
val name = (containerDescriptor as? KaNamedSymbol)?.name?.takeUnless { containerDescriptor is KaVariableSymbol }
if (name != null && !name.isSpecial) {
val identifier = name.identifier
containerText = if (containerText != null) "$identifier.$containerText" else identifier
}
containerDescriptor = containerDescriptor.getContainingSymbol()
}
return if (containerText != null) "$containerText.$elementText" else elementText
}
}
}
context(KaSession)
fun renderNamedFunction(symbol: KaFunctionLikeSymbol): String? {
val name = ((symbol as? KaFunctionSymbol)?.name ?: ((symbol as? KaConstructorSymbol)?.getContainingSymbol() as? KaClassOrObjectSymbol)?.name)?.asString() ?: return null
val paramTypes =
StringUtil.join(
symbol.valueParameters,
{
it.returnType.render(KaTypeRendererForSource.WITH_SHORT_NAMES, position = Variance.INVARIANT)
},
", "
)
return "$name($paramTypes)"
}
private fun renderClassOrObject(descriptor: KaClassOrObjectSymbol): String {
return descriptor.name?.asString() ?: KotlinBundle.message("hierarchy.text.anonymous")
}
}
}

View File

@@ -0,0 +1,26 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.k2.codeinsight.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.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.psi.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.getData(CommonDataKeys.PSI_ELEMENT)?.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.getCallerType())
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.k2.codeinsight.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
class KotlinCallReferenceProcessor : CallReferenceProcessor {
override fun process(reference: PsiReference, data: JavaCallHierarchyData): Boolean {
val nodeDescriptor = data.nodeDescriptor as? HierarchyNodeDescriptor ?: return false
@Suppress("UNCHECKED_CAST")
KotlinCallerTreeStructure.processReference(
reference,
reference.element,
nodeDescriptor,
data.resultMap as MutableMap<PsiElement, NodeDescriptor<*>>,
true
)
return true
}
}

View File

@@ -0,0 +1,76 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.k2.codeinsight.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.PsiMember
import com.intellij.psi.PsiMethod
import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesSupport
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> = 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.asSequence().filterIsInstance<KtProperty>().map { it.initializer }.toList()
}
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 as PsiMember, 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(false) {
override fun processDeclaration(reference: KtSimpleNameExpression, declaration: PsiElement) {
if (!isInScope(baseClass, declaration, scopeType)) return
result += (getOrCreateNodeDescriptor(
parent = nodeDescriptor, originalElement = declaration, reference = null,
navigateToReference = false,
elementToDescriptorMap = calleeToDescriptorMap,
isJavaMap = false
)
?: return)
}
}
)
}
for (it in KotlinFindUsagesSupport.searchOverriders(element, element.useScope)) {
val overrider = it.unwrapped as? KtElement ?: continue
if (!isInScope(baseClass, overrider, scopeType)) continue
result += KotlinCallHierarchyNodeDescriptor(nodeDescriptor, overrider, false, false)
}
return result.toArray()
}
}

View File

@@ -0,0 +1,140 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.calls
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.impl.source.resolve.JavaResolveUtil
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.ArrayUtil
import org.jetbrains.kotlin.idea.base.searching.usages.KotlinClassFindUsagesOptions
import org.jetbrains.kotlin.idea.base.searching.usages.KotlinFunctionFindUsagesOptions
import org.jetbrains.kotlin.idea.base.searching.usages.KotlinPropertyFindUsagesOptions
import org.jetbrains.kotlin.idea.base.searching.usages.processAllUsages
import org.jetbrains.kotlin.idea.search.KotlinSearchUsagesSupport
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.lexer.KtTokens
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(
parent = nodeDescriptor,
originalElement = callerElement,
reference = reference,
navigateToReference = true,
elementToDescriptorMap = callerToDescriptorMap,
isJavaMap = 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 as PsiMember, 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 KtEnumEntry -> KotlinClassFindUsagesOptions(myProject)
is KtClass, is KtObjectDeclaration -> 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) {
val refElement = it.element
val isInKDoc = PsiTreeUtil.getParentOfType(refElement, KDoc::class.java) != null
if (refElement != null && !JavaResolveUtil.isInJavaDoc(refElement) && !isInKDoc) {
processReference(it.reference, refElement, 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 = hashMapOf<PsiElement, NodeDescriptor<*>>()
if (element is KtCallableDeclaration && element.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
return KotlinSearchUsagesSupport.getInstance(element.project).findSuperMethodsNoWrapping(element, true)
.flatMap { rootElement ->
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
}

View File

@@ -0,0 +1,69 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.k2.codeinsight.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.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
}

View File

@@ -0,0 +1,122 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.hierarchy
import com.intellij.ide.hierarchy.HierarchyBrowserBaseEx
import com.intellij.ide.hierarchy.HierarchyProvider
import com.intellij.ide.hierarchy.HierarchyTreeStructure
import com.intellij.ide.hierarchy.LanguageCallHierarchy
import com.intellij.ide.hierarchy.LanguageMethodHierarchy
import com.intellij.ide.hierarchy.actions.BrowseHierarchyActionBase
import com.intellij.ide.hierarchy.call.CallerMethodsTreeStructure
import com.intellij.lang.LanguageExtension
import com.intellij.openapi.util.Computable
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.util.ArrayUtil
import junit.framework.ComparisonFailure
import junit.framework.TestCase
import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
import org.jetbrains.kotlin.idea.KotlinHierarchyViewTestBase
import org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.calls.KotlinCalleeTreeStructure
import org.jetbrains.kotlin.idea.k2.codeinsight.hierarchy.calls.KotlinCallerTreeStructure
import org.jetbrains.kotlin.idea.test.KotlinTestUtils
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.createTextEditorBasedDataContext
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtElement
import java.io.File
abstract class AbstractFirHierarchyTest : KotlinHierarchyViewTestBase() {
protected var folderName: String? = null
private fun doHierarchyTest(folderName: String, structure: () -> HierarchyTreeStructure) {
this.folderName = folderName
doHierarchyTest(structure, *filesToConfigure)
}
protected fun doCallerHierarchyTest(folderName: String) = doHierarchyTest(folderName) {
KotlinCallerTreeStructure(
(getElementAtCaret(LanguageCallHierarchy.INSTANCE) as KtElement),
HierarchyBrowserBaseEx.SCOPE_PROJECT
)
}
protected fun doCallerJavaHierarchyTest(folderName: String) = doHierarchyTest(folderName) {
CallerMethodsTreeStructure(
project,
getElementAtCaret(LanguageCallHierarchy.INSTANCE) as PsiMember,
HierarchyBrowserBaseEx.SCOPE_PROJECT
)
}
protected fun doCalleeHierarchyTest(folderName: String) = doHierarchyTest(folderName) {
KotlinCalleeTreeStructure(
(getElementAtCaret(LanguageCallHierarchy.INSTANCE) as KtElement),
HierarchyBrowserBaseEx.SCOPE_PROJECT
)
}
private fun getElementAtCaret(extension: LanguageExtension<HierarchyProvider>): PsiElement {
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
val dataContext = createTextEditorBasedDataContext(project, editor, editor.caretModel.currentCaret)
return BrowseHierarchyActionBase.findProvider(extension, file, file, dataContext)?.getTarget(dataContext)
?: throw RefactoringErrorHintException("Cannot apply action for element at caret")
}
protected val filesToConfigure: Array<String>
get() {
val folderName = folderName ?: error("folderName should be initialized")
val files: MutableList<String> = ArrayList(2)
FileUtil.processFilesRecursively(
File(folderName)
) { file ->
val fileName = file.name
if (fileName.endsWith(".kt") || fileName.endsWith(".java")) {
files.add(fileName)
}
true
}
files.sort()
return ArrayUtil.toStringArray(files)
}
@OptIn(KaAllowAnalysisOnEdt::class)
@Throws(Exception::class)
override fun doHierarchyTest(
treeStructureComputable: Computable<out HierarchyTreeStructure>,
vararg fileNames: String
) {
try {
allowAnalysisOnEdt {
super.doHierarchyTest(treeStructureComputable, *fileNames)
}
} catch (e: RefactoringErrorHintException) {
val file = File(folderName, "messages.txt")
if (file.exists()) {
val expectedMessage = FileUtil.loadFile(file, true)
TestCase.assertEquals(expectedMessage, e.localizedMessage)
} else {
TestCase.fail("Unexpected error: " + e.localizedMessage)
}
} catch (failure: ComparisonFailure) {
val actual = failure.actual
val verificationFile = File(testDataDirectory, getTestName(false) + "_verification.xml")
KotlinTestUtils.assertEqualsToFile(verificationFile, actual)
}
}
override fun getProjectDescriptor(): LightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.getInstance()
override val testDataDirectory: File
get() {
val testRoot = super.testDataDirectory
val testDir = KotlinTestUtils.getTestDataFileName(this.javaClass, name) ?: error("Test data file name is missing")
return File(testRoot, testDir)
}
}

View File

@@ -0,0 +1,263 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.hierarchy;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode;
import org.jetbrains.kotlin.idea.base.test.TestRoot;
import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.idea.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
/**
* This class is generated by {@link org.jetbrains.kotlin.testGenerator.generator.TestGenerator}.
* DO NOT MODIFY MANUALLY.
*/
@SuppressWarnings("all")
@TestRoot("code-insight/kotlin.code-insight.k2")
@TestDataPath("$CONTENT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public abstract class FirHierarchyTestGenerated extends AbstractFirHierarchyTest {
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../idea/tests/testData/hierarchy/calls/callers")
public static class Callers extends AbstractFirHierarchyTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doCallerHierarchyTest, this, testDataFilePath);
}
@TestMetadata("callInsideAnonymousFun")
public void testCallInsideAnonymousFun() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/callInsideAnonymousFun/");
}
@TestMetadata("callInsideLambda")
public void testCallInsideLambda() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/callInsideLambda/");
}
@TestMetadata("insideJavadoc")
public void testInsideJavadoc() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/insideJavadoc/");
}
@TestMetadata("insideKDoc")
public void testInsideKDoc() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/insideKDoc/");
}
@TestMetadata("kotlinClass")
public void testKotlinClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinClass/");
}
@TestMetadata("kotlinEnumClass")
public void testKotlinEnumClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinEnumClass/");
}
@TestMetadata("kotlinEnumEntry")
public void testKotlinEnumEntry() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinEnumEntry/");
}
@TestMetadata("kotlinFunction")
public void testKotlinFunction() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinFunction/");
}
@TestMetadata("kotlinFunctionNonCallUsages")
public void testKotlinFunctionNonCallUsages() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinFunctionNonCallUsages/");
}
@TestMetadata("kotlinInterface")
public void testKotlinInterface() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinInterface/");
}
@TestMetadata("kotlinLocalClass")
public void testKotlinLocalClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinLocalClass/");
}
@TestMetadata("kotlinLocalFunction")
public void testKotlinLocalFunction() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinLocalFunction/");
}
@TestMetadata("kotlinLocalFunctionWithNonLocalCallers")
public void testKotlinLocalFunctionWithNonLocalCallers() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinLocalFunctionWithNonLocalCallers/");
}
@TestMetadata("kotlinNestedClass")
public void testKotlinNestedClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinNestedClass/");
}
@TestMetadata("kotlinNestedInnerClass")
public void testKotlinNestedInnerClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinNestedInnerClass/");
}
@TestMetadata("kotlinObjectDeclaration")
public void testKotlinObjectDeclaration() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinObjectDeclaration/");
}
@TestMetadata("kotlinPackageFunction")
public void testKotlinPackageFunction() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinPackageFunction/");
}
@TestMetadata("kotlinPackageProperty")
public void testKotlinPackageProperty() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinPackageProperty/");
}
@TestMetadata("kotlinPrimaryConstructorImplicitCalls")
public void testKotlinPrimaryConstructorImplicitCalls() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinPrimaryConstructorImplicitCalls/");
}
@TestMetadata("kotlinPrivateClass")
public void testKotlinPrivateClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinPrivateClass/");
}
@TestMetadata("kotlinPrivateFunction")
public void testKotlinPrivateFunction() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinPrivateFunction/");
}
@TestMetadata("kotlinPrivateProperty")
public void testKotlinPrivateProperty() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinPrivateProperty/");
}
@TestMetadata("kotlinProperty")
public void testKotlinProperty() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinProperty/");
}
@TestMetadata("kotlinSecondaryConstructor")
public void testKotlinSecondaryConstructor() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinSecondaryConstructor/");
}
@TestMetadata("kotlinSecondaryConstructorImplicitCalls")
public void testKotlinSecondaryConstructorImplicitCalls() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinSecondaryConstructorImplicitCalls/");
}
@TestMetadata("kotlinUnresolvedFunction")
public void testKotlinUnresolvedFunction() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callers/kotlinUnresolvedFunction/");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../idea/tests/testData/hierarchy/calls/callersJava")
public static class CallersJava extends AbstractFirHierarchyTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doCallerJavaHierarchyTest, this, testDataFilePath);
}
@TestMetadata("javaMethod")
public void testJavaMethod() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callersJava/javaMethod/");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../idea/tests/testData/hierarchy/calls/callees")
public static class Callees extends AbstractFirHierarchyTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doCalleeHierarchyTest, this, testDataFilePath);
}
@TestMetadata("kotlinAnonymousObject")
public void testKotlinAnonymousObject() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinAnonymousObject/");
}
@TestMetadata("kotlinClass")
public void testKotlinClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinClass/");
}
@TestMetadata("kotlinClassObject")
public void testKotlinClassObject() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinClassObject/");
}
@TestMetadata("kotlinEnumClass")
public void testKotlinEnumClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinEnumClass/");
}
@TestMetadata("kotlinFunction")
public void testKotlinFunction() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinFunction/");
}
@TestMetadata("kotlinInterface")
public void testKotlinInterface() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinInterface/");
}
@TestMetadata("kotlinLocalClass")
public void testKotlinLocalClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinLocalClass/");
}
@TestMetadata("kotlinLocalFunction")
public void testKotlinLocalFunction() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinLocalFunction/");
}
@TestMetadata("kotlinNestedClass")
public void testKotlinNestedClass() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinNestedClass/");
}
@TestMetadata("kotlinObject")
public void testKotlinObject() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinObject/");
}
@TestMetadata("kotlinPackageFunction")
public void testKotlinPackageFunction() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinPackageFunction/");
}
@TestMetadata("kotlinPackageProperty")
public void testKotlinPackageProperty() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinPackageProperty/");
}
@TestMetadata("kotlinProperty")
public void testKotlinProperty() throws Exception {
runTest("../../idea/tests/testData/hierarchy/calls/callees/kotlinProperty/");
}
}
}

View File

@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.idea.k2.AbstractK2ExpressionTypeTest
import org.jetbrains.kotlin.idea.k2.AbstractKotlinFirBreadcrumbsTest
import org.jetbrains.kotlin.idea.k2.AbstractKotlinFirJoinLinesTest
import org.jetbrains.kotlin.idea.k2.AbstractKotlinFirPairMatcherTest
import org.jetbrains.kotlin.idea.k2.hierarchy.AbstractFirHierarchyTest
import org.jetbrains.kotlin.idea.k2.hints.AbstractKtCallChainHintsProviderTest
import org.jetbrains.kotlin.idea.k2.hints.AbstractKtLambdasHintsProvider
import org.jetbrains.kotlin.idea.k2.hints.AbstractKtParameterHintsProviderTest
@@ -18,6 +19,7 @@ import org.jetbrains.kotlin.idea.k2.surroundWith.AbstractKotlinFirSurroundWithTe
import org.jetbrains.kotlin.idea.k2.unwrap.AbstractKotlinFirUnwrapRemoveTest
import org.jetbrains.kotlin.testGenerator.model.*
import org.jetbrains.kotlin.testGenerator.model.GroupCategory.*
import org.jetbrains.kotlin.testGenerator.model.Patterns.DIRECTORY
import org.jetbrains.kotlin.testGenerator.model.Patterns.KT
import org.jetbrains.kotlin.testGenerator.model.Patterns.KT_OR_KTS
import org.jetbrains.kotlin.testGenerator.model.Patterns.TEST
@@ -114,6 +116,12 @@ internal fun MutableTWorkspace.generateK2CodeInsightTests() {
model("../../../idea/tests/testData/codeInsight/renderingKDoc")
}
testClass<AbstractFirHierarchyTest> {
model("../../../idea/tests/testData/hierarchy/calls/callers", pattern = DIRECTORY, isRecursive = false, testMethodName = "doCallerHierarchyTest")
model("../../../idea/tests/testData/hierarchy/calls/callersJava", pattern = DIRECTORY, isRecursive = false, testMethodName = "doCallerJavaHierarchyTest")
model("../../../idea/tests/testData/hierarchy/calls/callees", pattern = DIRECTORY, isRecursive = false, testMethodName = "doCalleeHierarchyTest")
}
testClass<AbstractKotlinFirJoinLinesTest> {
model("../../../idea/tests/testData/joinLines")
}