[Analysis API] Extract dangling file module computation

Use the same code for creating dangling file modules inside tests,
the stand-alone API, and in the IDE.
This commit is contained in:
Yan Zhulanow
2023-11-13 21:19:24 +09:00
committed by Space Team
parent 441f3e0209
commit 2899822102
4 changed files with 90 additions and 45 deletions
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.LLFirBu
import org.jetbrains.kotlin.analysis.project.structure.*
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.analysisContext
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
@@ -40,38 +39,39 @@ internal class KtStandaloneProjectStructureProvider(
LLFirBuiltinsSessionFactory.getInstance(project).getBuiltinsSession(platform).ktModule as KtBuiltinsModule
}
override fun getNotUnderContentRootModule(project: Project, file: PsiFile?): KtNotUnderContentRootModule {
if (file == null) {
return ktNotUnderContentRootModuleWithoutPsiFile
}
return notUnderContentRootModuleCache.getOrPut(file) {
KtNotUnderContentRootModuleImpl(
name = file.name,
moduleDescription = "Standalone-not-under-content-root-module-for-$file",
file = file,
project = project,
)
}
}
@OptIn(KtModuleStructureInternals::class)
override fun getModule(element: PsiElement, contextualModule: KtModule?): KtModule {
val containingFileAsPsiFile = element.containingFile
val containingFile = element.containingFile
?: return ktNotUnderContentRootModuleWithoutPsiFile
// If an [element] is created on the fly, e.g., via [KtPsiFactory],
// its containing [PsiFile] may not have [VirtualFile].
// We can attempt to use an associated [analysisContext] (from [KtPsiFactory]) if any.
// If both fail, the [element] is not bound to any [KtModule], and we bail out early
// by returning a [KtNotUnderContentRootModule] (for that specific unbound file).
val containingFileAsVirtualFile = containingFileAsPsiFile.virtualFile
?: (containingFileAsPsiFile as? KtFile)?.analysisContext?.containingFile?.virtualFile
?: return notUnderContentRootModuleCache.getOrPut(containingFileAsPsiFile) {
KtNotUnderContentRootModuleImpl(
name = containingFileAsPsiFile.name,
moduleDescription = "Standalone-not-under-content-root-module-for-$containingFileAsPsiFile",
file = containingFileAsPsiFile,
project = project,
)
}
if (containingFileAsVirtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) {
val virtualFile = containingFile.virtualFile
?: error("${containingFile.name} is not a physical file")
if (virtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) {
return builtinsModule
}
containingFileAsVirtualFile.analysisExtensionFileContextModule?.let { return it }
computeSpecialModule(containingFile)?.let { return it }
return allKtModules.firstOrNull { module ->
containingFileAsVirtualFile in module.contentScope
}
return allKtModules.firstOrNull { module -> virtualFile in module.contentScope }
?: throw KotlinExceptionWithAttachments("Cannot find KtModule; see the attachment for more details.")
.withAttachment(
containingFileAsVirtualFile.path,
virtualFile.path,
allKtModules.joinToString(separator = System.lineSeparator()) { it.asDebugString() }
)
}
@@ -5,14 +5,15 @@
package org.jetbrains.kotlin.analysis.test.framework.services
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileSystemItem
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtStaticProjectStructureProvider
import org.jetbrains.kotlin.analysis.project.structure.*
import org.jetbrains.kotlin.analysis.project.structure.impl.KtDanglingFileModuleImpl
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.contains
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
@@ -22,37 +23,35 @@ class KtTestProjectStructureProvider(
private val builtinsModule: KtBuiltinsModule,
private val projectStructure: KtModuleProjectStructure,
) : KtStaticProjectStructureProvider() {
override fun getNotUnderContentRootModule(project: Project, file: PsiFile?): KtNotUnderContentRootModule {
error("Not-under content root modules most be initialized explicitly in tests")
}
@OptIn(KtModuleStructureInternals::class)
override fun getModule(element: PsiElement, contextualModule: KtModule?): KtModule {
// Unwrap context-dependent file copies coming from dependent sessions
val containingPsiFile = element.containingFile.originalFile
val containingFile = element.containingFile.originalFile
if (containingPsiFile is KtCodeFragment) {
val contextElement = containingPsiFile.context
?: throw KotlinExceptionWithAttachments("Context not found for a code fragment")
.withAttachment("codeFragment.kt", containingPsiFile.text)
val virtualFile = containingFile.virtualFile
val contextModule = getModule(contextElement, contextualModule)
return KtDanglingFileModuleImpl(containingPsiFile, contextModule)
if (virtualFile != null) {
if (virtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) {
return builtinsModule
}
projectStructure.binaryModules
.firstOrNull { binaryModule -> virtualFile in binaryModule.contentScope }
?.let { return it }
}
val containingVirtualFile = containingPsiFile.virtualFile
if (containingVirtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) {
return builtinsModule
}
containingVirtualFile.analysisExtensionFileContextModule?.let { return it }
projectStructure.binaryModules.firstOrNull { binaryModule ->
containingVirtualFile in binaryModule.contentScope
}?.let { return it }
computeSpecialModule(containingFile)?.let { return it }
return projectStructure.mainModules.firstOrNull { module ->
element in module.ktModule.contentScope
}?.ktModule
?: throw KotlinExceptionWithAttachments("Cannot find KtModule; see the attachment for more details.")
.withAttachment(
containingVirtualFile.path,
virtualFile?.path ?: containingFile.name,
allKtModules.joinToString(separator = System.lineSeparator()) { it.asDebugString() }
)
}
@@ -65,7 +65,7 @@ private object KtOutOfContentRootModuleFactory : KtModuleFactory {
}
}
private class KtNotUnderContentRootModuleForTest(
internal class KtNotUnderContentRootModuleForTest(
override val name: String,
override val file: PsiFile,
override val platform: TargetPlatform
@@ -7,8 +7,14 @@ package org.jetbrains.kotlin.analysis.project.structure
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.analysis.project.structure.impl.KtDanglingFileModuleImpl
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.analysisContext
import org.jetbrains.kotlin.psi.doNotAnalyze
public abstract class ProjectStructureProvider {
/**
@@ -34,6 +40,39 @@ public abstract class ProjectStructureProvider {
*/
public abstract fun getModule(element: PsiElement, contextualModule: KtModule?): KtModule
protected abstract fun getNotUnderContentRootModule(project: Project, file: PsiFile?): KtNotUnderContentRootModule
@OptIn(KtModuleStructureInternals::class)
protected fun computeSpecialModule(file: PsiFile): KtModule? {
val virtualFile = file.virtualFile
if (virtualFile != null) {
val contextModule = virtualFile.analysisExtensionFileContextModule
if (contextModule != null) {
return contextModule
}
}
if (file is KtFile && file.isDangling) {
val contextModule = computeContextModule(file)
return KtDanglingFileModuleImpl(file, contextModule)
}
return null
}
@OptIn(KtModuleStructureInternals::class)
private fun computeContextModule(file: KtFile): KtModule {
val contextElement = file.context
?: file.analysisContext
?: file.originalFile.takeIf { it !== file }
if (contextElement != null) {
return getModule(contextElement, contextualModule = null)
}
return getNotUnderContentRootModule(file.project, file = null)
}
/**
* Project-global [LanguageVersionSettings] for source modules lacking explicit settings (such as [KtNotUnderContentRootModule]).
*/
@@ -55,4 +94,11 @@ public abstract class ProjectStructureProvider {
return getInstance(project).getModule(element, contextualModule)
}
}
}
}
@OptIn(KtModuleStructureInternals::class)
private val KtFile.isDangling: Boolean
get() = this is KtCodeFragment
|| !isPhysical
|| analysisContext != null
|| doNotAnalyze != null