AA: migrate PSI declaration provider (from IJ)
diff --git a/analysis/decompiled/light-classes-for-decompiled/build.gradle.kts b/analysis/decompiled/light-classes-for-decompiled/build.gradle.kts
index 6a376e3..6be903c 100644
--- a/analysis/decompiled/light-classes-for-decompiled/build.gradle.kts
+++ b/analysis/decompiled/light-classes-for-decompiled/build.gradle.kts
@@ -5,6 +5,9 @@
dependencies {
api(project(":compiler:psi"))
+ api(project(":analysis:analysis-api"))
+ api(project(":analysis:analysis-api-providers"))
+ api(project(":analysis:project-structure"))
api(project(":analysis:decompiled:decompiler-to-psi"))
api(project(":compiler:light-classes"))
implementation(intellijCore())
diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt
new file mode 100644
index 0000000..315c853
--- /dev/null
+++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.analysis.providers
+
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElement
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.kotlin.analysis.api.symbols.*
+
+public object DecompiledPsiDeclarationProvider {
+ public fun findPsi(ktSymbol: KtSymbol, project: Project): PsiElement? {
+ return when (ktSymbol) {
+ is KtConstructorSymbol -> providePsiForConstructor(ktSymbol, project)
+ is KtFunctionLikeSymbol -> providePsiForFunction(ktSymbol, project)
+ is KtEnumEntrySymbol -> providePsiForEnumEntry(ktSymbol, project)
+ is KtVariableLikeSymbol -> providePsiForProperty(ktSymbol, project)
+ is KtClassLikeSymbol -> providePsiForClass(ktSymbol, project)
+ else -> null
+ }
+ }
+
+ private fun providePsiForConstructor(
+ constructorSymbol: KtConstructorSymbol,
+ project: Project
+ ): PsiElement? {
+ val classId = constructorSymbol.containingClassIdIfNonLocal ?: return null
+ val psiClass = project.createPsiDeclarationProvider(constructorSymbol.scope(project))
+ ?.getClassesByClassId(classId)
+ ?.firstOrNull() ?: return null
+ return psiClass.constructors.firstOrNull()
+ }
+
+ private fun providePsiForFunction(
+ functionLikeSymbol: KtFunctionLikeSymbol,
+ project: Project
+ ): PsiElement? {
+ return functionLikeSymbol.callableIdIfNonLocal?.let {
+ project.createPsiDeclarationProvider(functionLikeSymbol.scope(project))
+ ?.getFunctions(it)
+ ?.firstOrNull()
+ }
+ }
+
+ private fun providePsiForProperty(
+ variableLikeSymbol: KtVariableLikeSymbol,
+ project: Project
+ ): PsiElement? {
+ return variableLikeSymbol.callableIdIfNonLocal?.let {
+ project.createPsiDeclarationProvider(variableLikeSymbol.scope(project))
+ ?.getProperties(it)
+ // TODO: needs to pick field/getter/setter accordingly?
+ ?.firstOrNull()
+ }
+ }
+
+ private fun providePsiForClass(
+ classLikeSymbol: KtClassLikeSymbol,
+ project: Project
+ ): PsiElement? {
+ return classLikeSymbol.classIdIfNonLocal?.let {
+ project.createPsiDeclarationProvider(classLikeSymbol.scope(project))
+ ?.getClassesByClassId(it)
+ ?.firstOrNull()
+ }
+ }
+
+ private fun providePsiForEnumEntry(
+ enumEntrySymbol: KtEnumEntrySymbol,
+ project: Project
+ ): PsiElement? {
+ val classId = enumEntrySymbol.containingEnumClassIdIfNonLocal ?: return null
+ val psiClass = project.createPsiDeclarationProvider(enumEntrySymbol.scope(project))
+ ?.getClassesByClassId(classId)
+ ?.firstOrNull() ?: return null
+ return psiClass.fields.find {
+ it.name == enumEntrySymbol.name.asString()
+ }
+ }
+
+ private fun KtSymbol.scope(project: Project): GlobalSearchScope {
+ // TODO: finding containing module and use a narrower scope?
+ return GlobalSearchScope.allScope(project)
+ }
+}
\ No newline at end of file
diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/KotlinPsiDeclarationProvider.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/KotlinPsiDeclarationProvider.kt
new file mode 100644
index 0000000..75966c8
--- /dev/null
+++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/KotlinPsiDeclarationProvider.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.analysis.providers
+
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiMember
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.kotlin.name.CallableId
+import org.jetbrains.kotlin.name.ClassId
+
+/**
+ * A [PsiMember] declaration provider for a given scope. Can be created via [KotlinPsiDeclarationProviderFactory].
+ */
+public abstract class KotlinPsiDeclarationProvider {
+ /**
+ * Gets a collection of [PsiClass] by [ClassId]
+ *
+ * In standalone mode, this is simply [PsiClassStub]-based [PsiClass]
+ */
+ public abstract fun getClassesByClassId(classId: ClassId): Collection<PsiClass>
+
+ public abstract fun getProperties(callableId: CallableId): Collection<PsiMember>
+ public abstract fun getFunctions(callableId: CallableId): Collection<PsiMethod>
+}
+
+public abstract class KotlinPsiDeclarationProviderFactory {
+ public abstract fun createPsiDeclarationProvider(searchScope: GlobalSearchScope): KotlinPsiDeclarationProvider
+}
+
+public fun Project.createPsiDeclarationProvider(searchScope: GlobalSearchScope): KotlinPsiDeclarationProvider? =
+ // TODO: avoid using fail-safe service loading once the factory has an easy-to-register ctor.
+ getServiceIfCreated(KotlinPsiDeclarationProviderFactory::class.java)
+ ?.createPsiDeclarationProvider(searchScope)
diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticPsiDeclarationFromBinaryModuleProvider.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticPsiDeclarationFromBinaryModuleProvider.kt
new file mode 100644
index 0000000..39a8a91
--- /dev/null
+++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticPsiDeclarationFromBinaryModuleProvider.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.analysis.providers.impl
+
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
+import com.intellij.psi.*
+import com.intellij.psi.impl.compiled.ClsClassImpl
+import com.intellij.psi.impl.compiled.ClsFileImpl
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.kotlin.analysis.decompiled.light.classes.ClsJavaStubByVirtualFileCache
+import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule
+import org.jetbrains.kotlin.analysis.providers.KotlinPsiDeclarationProvider
+import org.jetbrains.kotlin.analysis.providers.KotlinPsiDeclarationProviderFactory
+import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory
+import org.jetbrains.kotlin.name.CallableId
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart
+
+private class KotlinStaticPsiDeclarationFromBinaryModuleProvider(
+ private val project: Project,
+ override val scope: GlobalSearchScope,
+ private val binaryModules: Collection<KtBinaryModule>,
+ override val jarFileSystem: CoreJarFileSystem,
+) : KotlinPsiDeclarationProvider(), AbstractDeclarationFromBinaryModuleProvider {
+ private val psiManager by lazy { PsiManager.getInstance(project) }
+
+ private fun clsClassImplsByFqName(
+ fqName: FqName,
+ isPackageName: Boolean = true,
+ ): Collection<ClsClassImpl> {
+ return binaryModules
+ .flatMap {
+ virtualFilesFromModule(it, fqName, isPackageName)
+ }
+ .mapNotNull {
+ createClsJavaClassFromVirtualFile(it)
+ }
+ }
+
+ private fun createClsJavaClassFromVirtualFile(
+ classFile: VirtualFile,
+ ): ClsClassImpl? {
+ val javaFileStub = ClsJavaStubByVirtualFileCache.getInstance(project).get(classFile) ?: return null
+ javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE
+ val fakeFile = object : ClsFileImpl(ClassFileViewProvider(psiManager, classFile)) {
+ override fun getStub() = javaFileStub
+
+ override fun isPhysical() = false
+ }
+ javaFileStub.psi = fakeFile
+ return fakeFile.classes.single() as ClsClassImpl
+ }
+
+ override fun getClassesByClassId(classId: ClassId): Collection<ClsClassImpl> {
+ return clsClassImplsByFqName(classId.asSingleFqName(), isPackageName = false)
+ }
+
+ override fun getProperties(callableId: CallableId): Collection<PsiMember> {
+ val classes = callableId.classId?.let { classId ->
+ getClassesByClassId(classId)
+ } ?: clsClassImplsByFqName(callableId.packageName)
+ return classes.flatMap { psiClass ->
+ psiClass.children
+ .filterIsInstance<PsiMember>()
+ .filter { psiMember ->
+ if (psiMember !is PsiMethod && psiMember !is PsiField) return@filter false
+ val name = psiMember.name ?: return@filter false
+ // PsiField a.k.a. backing field
+ name == callableId.callableName.identifier ||
+ // PsiMethod, i.e., accessors
+ (name.startsWith("get") || name.startsWith("set")) &&
+ // E.g., getFooBar -> FooBar -> fooBar
+ (name.substring(3).decapitalizeSmart().endsWith(callableId.callableName.identifier))
+
+ }
+ }.toList()
+ }
+
+ override fun getFunctions(callableId: CallableId): Collection<PsiMethod> {
+ val classes = callableId.classId?.let { classId ->
+ getClassesByClassId(classId)
+ } ?: clsClassImplsByFqName(callableId.packageName)
+ return classes.flatMap { psiClass ->
+ psiClass.methods.filter { psiMethod ->
+ psiMethod.name == callableId.callableName.identifier
+ }
+ }.toList()
+ }
+}
+
+// TODO: we can't register this in IDE yet due to non-trivial parameters: lib modules and jar file system.
+// We need a session or facade that maintains such information
+public class KotlinStaticPsiDeclarationProviderFactory(
+ private val project: Project,
+ private val binaryModules: Collection<KtBinaryModule>,
+ private val jarFileSystem: CoreJarFileSystem,
+) : KotlinPsiDeclarationProviderFactory() {
+ override fun createPsiDeclarationProvider(searchScope: GlobalSearchScope): KotlinPsiDeclarationProvider {
+ return KotlinStaticPsiDeclarationFromBinaryModuleProvider(project, searchScope, binaryModules, jarFileSystem)
+ }
+}