AA: migrate PSI declaration provider (from IJ)

This commit is contained in:
Jinseong Jeon
2022-05-05 01:33:45 -07:00
committed by Ilya Kirillov
parent 3f6b386681
commit 55e24451a4
4 changed files with 235 additions and 0 deletions
@@ -5,6 +5,9 @@ plugins {
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())
@@ -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)
}
}
@@ -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)
@@ -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)
}
}