[analysis api, test] rework test infrastructure, add multimodule tests
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(intellijCore())
|
||||
implementation(kotlinStdlib())
|
||||
implementation(project(":compiler:psi"))
|
||||
implementation(project(":compiler:cli"))
|
||||
api(project(":analysis:analysis-api"))
|
||||
api(project(":analysis:analysis-api-providers"))
|
||||
api(project(":analysis:project-structure"))
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { none() }
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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.api.standalone.base.project.structure
|
||||
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
|
||||
data class KtModuleWithFiles(
|
||||
val ktModule: KtModule,
|
||||
val files: List<PsiFile>
|
||||
)
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.api.standalone.base.project.structure
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
|
||||
import org.jetbrains.kotlin.psi.psiUtil.contains
|
||||
|
||||
class KtStaticModuleProvider(private val modules: List<KtModule>) : ProjectStructureProvider() {
|
||||
override fun getKtModuleForKtElement(element: PsiElement): KtModule {
|
||||
return modules.first { module ->
|
||||
module.contentScope.contains(element)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getKtLibraryModules(): Collection<KtLibraryModule> {
|
||||
return modules.filterIsInstance<KtLibraryModule>()
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.api.standalone.base.project.structure
|
||||
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager
|
||||
import com.intellij.codeInsight.InferredAnnotationsManager
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiJavaFile
|
||||
import com.intellij.psi.impl.file.impl.JavaFileManager
|
||||
import com.intellij.psi.search.ProjectScope
|
||||
import org.jetbrains.kotlin.analysis.project.structure.*
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.*
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JavaRoot
|
||||
import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndexImpl
|
||||
import org.jetbrains.kotlin.cli.jvm.index.SingleJavaFileRootsIndex
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver
|
||||
import org.jetbrains.kotlin.cli.jvm.modules.JavaModuleGraph
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.load.kotlin.MetadataFinderFactory
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
|
||||
import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver
|
||||
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.popLast
|
||||
import java.nio.file.Path
|
||||
|
||||
object StandaloneProjectFactory {
|
||||
fun createProjectEnvironment(
|
||||
projectDisposable: Disposable,
|
||||
applicationDisposable: Disposable
|
||||
): KotlinCoreProjectEnvironment {
|
||||
val applicationEnvironment =
|
||||
KotlinCoreEnvironment.getOrCreateApplicationEnvironmentForTests(applicationDisposable, CompilerConfiguration())
|
||||
|
||||
return KotlinCoreProjectEnvironment(projectDisposable, applicationEnvironment)
|
||||
}
|
||||
|
||||
|
||||
fun registerServicesForProjectEnvironment(
|
||||
environment: KotlinCoreProjectEnvironment,
|
||||
modules: List<KtModuleWithFiles>,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
jdkHome: Path?,
|
||||
) {
|
||||
val project = environment.project
|
||||
|
||||
KotlinCoreEnvironment.registerProjectExtensionPoints(project.extensionArea)
|
||||
KotlinCoreEnvironment.registerProjectServices(project)
|
||||
|
||||
project.registerService(ProjectStructureProvider::class.java, KtStaticModuleProvider(modules.map { it.ktModule }))
|
||||
initialiseVirtualFinderFinderServices(modules, environment, jdkHome, languageVersionSettings)
|
||||
initialiseAnnotationServices(project)
|
||||
|
||||
project.setupHighestLanguageLevel()
|
||||
}
|
||||
|
||||
private fun initialiseAnnotationServices(project: MockProject) {
|
||||
project.registerService(ExternalAnnotationsManager::class.java, MockExternalAnnotationsManager())
|
||||
project.registerService(InferredAnnotationsManager::class.java, MockInferredAnnotationsManager())
|
||||
}
|
||||
|
||||
private fun initialiseVirtualFinderFinderServices(
|
||||
modules: List<KtModuleWithFiles>,
|
||||
environment: KotlinCoreProjectEnvironment,
|
||||
jdkHome: Path?,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
) {
|
||||
val project = environment.project
|
||||
|
||||
val allSourceFiles = buildList {
|
||||
val files = modules.flatMap { it.files }
|
||||
addAll(files)
|
||||
addAll(findJvmRootsForJavaFiles(files.filterIsInstance<PsiJavaFile>()))
|
||||
}
|
||||
val allSourceFileRoots = allSourceFiles.map { JavaRoot(it.virtualFile, JavaRoot.RootType.SOURCE) }
|
||||
val libraryRoots = getAllBinaryRoots(modules.map { it.ktModule }, environment)
|
||||
libraryRoots.forEach { environment.addSourcesToClasspath(it.file) }
|
||||
|
||||
val sourceAndLibraryRoots = buildList {
|
||||
addAll(libraryRoots)
|
||||
addAll(allSourceFileRoots)
|
||||
}
|
||||
|
||||
val javaFileManager = project.getService(JavaFileManager::class.java) as KotlinCliJavaFileManagerImpl
|
||||
val javaModuleFinder = CliJavaModuleFinder(jdkHome?.toFile(), null, javaFileManager, project, null)
|
||||
|
||||
javaFileManager.initialize(
|
||||
JvmDependenciesIndexImpl(sourceAndLibraryRoots),
|
||||
listOf(createPackagePartsProvider(languageVersionSettings, project, libraryRoots)),
|
||||
SingleJavaFileRootsIndex(emptyList()),
|
||||
true
|
||||
)
|
||||
|
||||
project.registerService(
|
||||
JavaModuleResolver::class.java,
|
||||
CliJavaModuleResolver(JavaModuleGraph(javaModuleFinder), emptyList(), javaModuleFinder.systemModules.toList(), project)
|
||||
)
|
||||
|
||||
val finderFactory = CliVirtualFileFinderFactory(JvmDependenciesIndexImpl(sourceAndLibraryRoots), false)
|
||||
|
||||
project.registerService(MetadataFinderFactory::class.java, finderFactory)
|
||||
project.registerService(VirtualFileFinderFactory::class.java, finderFactory)
|
||||
}
|
||||
|
||||
private fun findJvmRootsForJavaFiles(files: List<PsiJavaFile>): List<PsiDirectory> {
|
||||
if (files.isEmpty()) return emptyList()
|
||||
val result = mutableSetOf<PsiDirectory>()
|
||||
for (file in files) {
|
||||
val packageParts = file.packageName.takeIf { it.isNotEmpty() }?.split('.') ?: emptyList()
|
||||
val javaDir = packageParts
|
||||
.reversed()
|
||||
.fold(file.parent) { dir, part ->
|
||||
if (dir?.name == part) {
|
||||
dir.parent
|
||||
} else {
|
||||
error("File package ${file.packageName} does not match file path ${file.virtualFile.path}")
|
||||
}
|
||||
}
|
||||
result += javaDir as PsiDirectory
|
||||
}
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
fun getAllBinaryRoots(
|
||||
modules: List<KtModule>,
|
||||
environment: KotlinCoreProjectEnvironment
|
||||
): List<JavaRoot> = withAllTransitiveDependencies(modules)
|
||||
.filterIsInstance<KtLibraryModule>()
|
||||
.flatMap { it.getBinaryRoots() }
|
||||
.map {
|
||||
val jar = environment.environment.jarFileSystem.findFileByPath(it.toAbsolutePath().toString() + "!/")!!
|
||||
JavaRoot(jar, JavaRoot.RootType.BINARY)
|
||||
}
|
||||
|
||||
private fun withAllTransitiveDependencies(ktModules: List<KtModule>): List<KtModule> {
|
||||
val visited = hashSetOf<KtModule>()
|
||||
val stack = ktModules.toMutableList()
|
||||
while (stack.isNotEmpty()) {
|
||||
val module = stack.popLast()
|
||||
if (module in visited) continue
|
||||
visited += module
|
||||
for (dependency in module.allDependencies()) {
|
||||
if (dependency !in visited) {
|
||||
stack += dependency
|
||||
}
|
||||
}
|
||||
}
|
||||
return visited.toList()
|
||||
}
|
||||
|
||||
private fun KtModule.allDependencies(): List<KtModule> = buildList {
|
||||
addAll(allDirectDependencies())
|
||||
when (this) {
|
||||
is KtLibrarySourceModule -> {
|
||||
add(binaryLibrary)
|
||||
}
|
||||
is KtLibraryModule -> {
|
||||
addIfNotNull(librarySources)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun createPackagePartsProvider(
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
project: MockProject,
|
||||
libraryRoots: List<JavaRoot>
|
||||
): JvmPackagePartProvider {
|
||||
return JvmPackagePartProvider(languageVersionSettings, ProjectScope.getLibrariesScope(project)).apply {
|
||||
addRoots(libraryRoots, MessageCollector.NONE)
|
||||
(ModuleAnnotationsResolver
|
||||
.getInstance(project) as CliModuleAnnotationsResolver)
|
||||
.addPackagePartProvider(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.PackagePartP
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModuleScopeProvider
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModuleScopeProviderImpl
|
||||
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
|
||||
import org.jetbrains.kotlin.analysis.project.structure.impl.ProjectStructureProviderByCompilerConfiguration
|
||||
import org.jetbrains.kotlin.analysis.project.structure.impl.KtModuleProviderByCompilerConfiguration
|
||||
import org.jetbrains.kotlin.analysis.providers.*
|
||||
import org.jetbrains.kotlin.analysis.providers.impl.*
|
||||
import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
|
||||
@@ -177,7 +177,7 @@ internal fun configureProjectEnvironment(
|
||||
|
||||
project.picoContainer.registerComponentInstance(
|
||||
ProjectStructureProvider::class.qualifiedName,
|
||||
ProjectStructureProviderByCompilerConfiguration(
|
||||
KtModuleProviderByCompilerConfiguration(
|
||||
compilerConfig,
|
||||
project,
|
||||
ktFiles,
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
internal class ProjectStructureProviderByCompilerConfiguration(
|
||||
internal class KtModuleProviderByCompilerConfiguration(
|
||||
compilerConfig: CompilerConfiguration,
|
||||
project: Project,
|
||||
ktFiles: List<KtFile>,
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.analysis.api.standalone
|
||||
|
||||
import org.jetbrains.kotlin.analysis.test.framework.*
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.*
|
||||
|
||||
object AnalysisApiFirStandaloneModeTestConfiguratorFactory : AnalysisApiTestConfiguratorFactory() {
|
||||
override fun createConfigurator(data: AnalysisApiTestConfiguratorFactoryData): AnalysisApiTestConfigurator {
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.api.standalone
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.analysis.api.fir.test.configurators.source.AnalysisApiFirSourceTestConfigurator
|
||||
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator
|
||||
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestServiceRegistrar
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
||||
import org.jetbrains.kotlin.test.services.TestModuleStructure
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
|
||||
object StandaloneModeConfigurator : AnalysisApiTestConfigurator() {
|
||||
override val analyseInDependentSession: Boolean get() = false
|
||||
|
||||
override fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable) {
|
||||
AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false).configureTest(builder, disposable)
|
||||
}
|
||||
|
||||
override val serviceRegistrars: List<AnalysisApiTestServiceRegistrar>
|
||||
get() = AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false).serviceRegistrars
|
||||
|
||||
override fun createModules(
|
||||
moduleStructure: TestModuleStructure,
|
||||
testServices: TestServices,
|
||||
project: Project
|
||||
): List<KtModuleWithFiles> {
|
||||
return AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false).createModules(moduleStructure, testServices, project)
|
||||
}
|
||||
|
||||
override fun doOutOfBlockModification(file: KtFile) {
|
||||
AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false).doOutOfBlockModification(file)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user