AA: introduce static project structure provider by compiler configuration

Also add standalone mode utils to configure Application/Project environments
This commit is contained in:
Jinseong Jeon
2022-02-02 00:02:36 -08:00
committed by Ilya Kirillov
parent d219de0f88
commit 26e923e3ae
30 changed files with 713 additions and 88 deletions
@@ -0,0 +1,45 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
kotlin {
explicitApiWarning()
}
dependencies {
implementation(intellijCore())
implementation(project(":compiler:psi"))
implementation(project(":compiler:cli"))
api(project(":analysis:analysis-api"))
api(project(":analysis:analysis-api-providers"))
api(project(":analysis:project-structure"))
api(project(":analysis:analysis-api-fir"))
api(project(":analysis:low-level-api-fir"))
api(project(":analysis:symbol-light-classes"))
testApi(projectTests(":analysis:analysis-api-impl-base"))
testApi(projectTests(":analysis:analysis-api-fir"))
testApi(project(":kotlin-test:kotlin-test-junit"))
testApi(toolsJar())
testApiJUnit5()
}
kotlin {
explicitApi()
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
projectTest(jUnitMode = JUnitMode.JUnit5) {
dependsOn(":dist")
workingDir = rootDir
useJUnitPlatform()
}
testsJar()
@@ -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;
import com.intellij.mock.MockProject;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirResolveStateService;
class RegisterComponentService {
static void registerLLFirResolveStateService(MockProject project) {
project.registerService(
LLFirResolveStateService.class,
new LLFirResolveStateService(project)
);
}
@TestOnly
static void unregisterLLFirResolveStateService(MockProject project) {
project.getPicoContainer().unregisterComponent(LLFirResolveStateService.class.getName());
}
}
@@ -0,0 +1,154 @@
/*
* 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.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.InvalidWayOfUsingAnalysisSession
import org.jetbrains.kotlin.analysis.api.KtAnalysisSessionProvider
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSessionProvider
import org.jetbrains.kotlin.analysis.api.impl.base.references.HLApiReferenceProviderService
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.FirSealedClassInheritorsProcessorFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.PackagePartProviderFactory
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.providers.KotlinDeclarationProviderFactory
import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory
import org.jetbrains.kotlin.analysis.providers.KotlinPackageProviderFactory
import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticDeclarationProviderFactory
import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticModificationTrackerFactory
import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticPackageProviderFactory
import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProviderImpl
import org.jetbrains.kotlin.idea.references.KotlinFirReferenceContributor
import org.jetbrains.kotlin.idea.references.KotlinReferenceProviderContributor
import org.jetbrains.kotlin.light.classes.symbol.IDEKotlinAsJavaFirSupport
import org.jetbrains.kotlin.light.classes.symbol.caches.SymbolLightClassFacadeCache
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
import org.jetbrains.kotlin.psi.KotlinReferenceProvidersService
import org.jetbrains.kotlin.psi.KtFile
/**
* Configure Application environment for Analysis API standalone mode.
*
* In particular, this will register:
* * [KotlinReferenceProvidersService]
* * [KotlinReferenceProviderContributor]
*/
public fun configureApplicationEnvironment(app: MockApplication) {
if (app.getServiceIfCreated(KotlinReferenceProvidersService::class.java) == null) {
app.registerService(
KotlinReferenceProvidersService::class.java,
HLApiReferenceProviderService::class.java
)
}
if (app.getServiceIfCreated(KotlinReferenceProviderContributor::class.java) == null) {
app.registerService(
KotlinReferenceProviderContributor::class.java,
KotlinFirReferenceContributor::class.java
)
}
}
/**
* Configure Project environment for Analysis API standalone mode.
*
* In particular, this will register:
* * [KtAnalysisSessionProvider]
* * [KotlinAsJavaSupport] (a FIR version)
* * [SymbolLightClassFacadeCache] for FIR light class support
* * [KotlinModificationTrackerFactory]
* * [LLFirResolveStateService]
* * [FirSealedClassInheritorsProcessorFactory]
* * [KtModuleScopeProvider]
* * [ProjectStructureProvider]
* * [KotlinDeclarationProviderFactory]
* * [KotlinPackageProviderFactory]
* * [PackagePartProviderFactory]
*
* Note that [ProjectStructureProvider] is built by using
* * given [ktFiles] as Kotlin sources
* * other Java sources in [compilerConfig] (set via [addJavaSourceRoots])
* * JVM class paths in [compilerConfig] (set via [addJvmClasspathRoots]) as library.
*/
public fun configureProjectEnvironment(
project: MockProject,
compilerConfig: CompilerConfiguration,
ktFiles: List<KtFile>,
packagePartProvider: (GlobalSearchScope) -> PackagePartProvider,
) {
reRegisterJavaElementFinder(project)
// FIR LC
project.registerService(
SymbolLightClassFacadeCache::class.java
)
project.picoContainer.registerComponentInstance(
KotlinModificationTrackerFactory::class.qualifiedName,
KotlinStaticModificationTrackerFactory()
)
RegisterComponentService.registerLLFirResolveStateService(project)
project.picoContainer.registerComponentInstance(
FirSealedClassInheritorsProcessorFactory::class.qualifiedName,
object : FirSealedClassInheritorsProcessorFactory() {
override fun createSealedClassInheritorsProvider(): SealedClassInheritorsProvider {
return SealedClassInheritorsProviderImpl
}
}
)
project.picoContainer.registerComponentInstance(
KtModuleScopeProvider::class.qualifiedName,
KtModuleScopeProviderImpl()
)
project.picoContainer.registerComponentInstance(
ProjectStructureProvider::class.qualifiedName,
ProjectStructureProviderByCompilerConfiguration(compilerConfig, project, ktFiles)
)
project.picoContainer.registerComponentInstance(
KotlinDeclarationProviderFactory::class.qualifiedName,
KotlinStaticDeclarationProviderFactory(ktFiles)
)
project.picoContainer.registerComponentInstance(
KotlinPackageProviderFactory::class.qualifiedName,
KotlinStaticPackageProviderFactory(ktFiles)
)
project.picoContainer.registerComponentInstance(
PackagePartProviderFactory::class.qualifiedName,
object : PackagePartProviderFactory() {
override fun createPackagePartProviderForLibrary(scope: GlobalSearchScope): PackagePartProvider {
return packagePartProvider(scope)
}
}
)
}
@OptIn(InvalidWayOfUsingAnalysisSession::class)
private fun reRegisterJavaElementFinder(project: Project) {
PsiElementFinder.EP.getPoint(project).unregisterExtension(JavaElementFinder::class.java)
with(project as MockProject) {
picoContainer.registerComponentInstance(
KtAnalysisSessionProvider::class.qualifiedName,
KtFirAnalysisSessionProvider(this)
)
picoContainer.unregisterComponent(KotlinAsJavaSupport::class.qualifiedName)
picoContainer.registerComponentInstance(
KotlinAsJavaSupport::class.qualifiedName,
IDEKotlinAsJavaFirSupport(project)
)
}
@Suppress("DEPRECATION")
PsiElementFinder.EP.getPoint(project).registerExtension(JavaElementFinder(project))
}
@@ -0,0 +1,90 @@
/*
* 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.project.structure.impl
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.impl.base.util.LibraryUtils
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.KtLibrarySourceModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.config.jvmModularRoots
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.jvm.JdkPlatform
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
import java.nio.file.Path
internal abstract class BaseKtModuleByCompilerConfiguration(
private val compilerConfig: CompilerConfiguration,
val project: Project,
) {
val analyzerServices: PlatformDependentAnalyzerServices
get() = JvmPlatformAnalyzerServices
val directFriendDependencies: List<KtModule>
get() = emptyList()
val directRefinementDependencies: List<KtModule>
get() = emptyList()
val languageVersionSettings: LanguageVersionSettings
get() = LanguageVersionSettingsImpl(LanguageVersion.LATEST_STABLE, ApiVersion.LATEST)
val moduleName: String
get() = compilerConfig.get(CommonConfigurationKeys.MODULE_NAME) ?: "<no module name provided>"
val platform: TargetPlatform
get() = TargetPlatform(setOf(JdkPlatform(JvmTarget.DEFAULT)))
}
internal class KtSourceModuleByCompilerConfiguration(
compilerConfig: CompilerConfiguration,
project: Project,
ktFiles: List<KtFile>,
) : BaseKtModuleByCompilerConfiguration(compilerConfig, project), KtSourceModule {
override val directRegularDependencies: List<KtModule> by lazy {
(compilerConfig.jvmModularRoots + compilerConfig.jvmClasspathRoots).map {
KtLibraryModuleByCompilerConfiguration(compilerConfig, project, it.toPath())
}
}
override val contentScope: GlobalSearchScope =
TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, ktFiles)
}
internal class KtLibraryModuleByCompilerConfiguration(
compilerConfig: CompilerConfiguration,
project: Project,
private val jar: Path,
) : BaseKtModuleByCompilerConfiguration(compilerConfig, project), KtLibraryModule {
override val directRegularDependencies: List<KtModule> get() = emptyList()
internal val virtualFiles: Collection<VirtualFile> by lazy {
LibraryUtils.getAllVirtualFilesFromJar(jar)
}
override val libraryName: String
get() = moduleName
override val librarySources: KtLibrarySourceModule?
get() = null
override val contentScope: GlobalSearchScope =
GlobalSearchScope.filesScope(project, virtualFiles)
private val binaryRoots by lazy {
listOf(jar)
}
override fun getBinaryRoots(): Collection<Path> = binaryRoots
}
@@ -0,0 +1,52 @@
/*
* 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.project.structure.impl
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.cli.jvm.config.javaSourceRoots
import org.jetbrains.kotlin.psi.KtFile
import java.nio.file.Files
import java.nio.file.Paths
internal class ProjectStructureProviderByCompilerConfiguration(
private val compilerConfig: CompilerConfiguration,
project: Project,
ktFiles: List<KtFile>,
) : ProjectStructureProvider() {
private val sourceFiles: Set<String> by lazy {
buildSet {
compilerConfig.javaSourceRoots.forEach { srcRoot ->
val path = Paths.get(srcRoot)
if (Files.isDirectory(path)) {
// E.g., project/app/src
Files.walk(Paths.get(srcRoot))
.filter(Files::isRegularFile)
.forEach { add(it.toString()) }
} else {
// E.g., project/app/src/some/pkg/main.kt
add(srcRoot)
}
}
}
}
private val sourceModule = KtSourceModuleByCompilerConfiguration(compilerConfig, project, ktFiles)
override fun getKtModuleForKtElement(element: PsiElement): KtModule {
val containingFilePath = element.containingFile.virtualFile.path
return if (containingFilePath in sourceFiles) {
sourceModule
} else {
sourceModule.directRegularDependencies.find { libModule ->
(libModule as KtLibraryModuleByCompilerConfiguration).virtualFiles.any { it.path == containingFilePath }
} ?: error("Can't find module for $containingFilePath")
}
}
}
@@ -0,0 +1,86 @@
/*
* 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.mock.MockProject
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.analysis.api.InvalidWayOfUsingAnalysisSession
import org.jetbrains.kotlin.analysis.api.KtAnalysisSessionProvider
import org.jetbrains.kotlin.analysis.api.components.KtDeclarationRendererOptions
import org.jetbrains.kotlin.analysis.api.components.RendererModifier
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.TestReferenceResolveResultRenderer.renderResolvedTo
import org.jetbrains.kotlin.analysis.api.impl.base.test.findReferencesAtCaret
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleModuleTest
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.FirSealedClassInheritorsProcessorFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.PackagePartProviderFactory
import org.jetbrains.kotlin.analysis.project.structure.KtModuleScopeProvider
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProviderFactory
import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory
import org.jetbrains.kotlin.analysis.providers.KotlinPackageProviderFactory
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoots
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
import java.io.File
abstract class AbstractStandaloneModeSingleModuleTest : AbstractHLApiSingleModuleTest() {
override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
val project = testServices.compilerConfigurationProvider.getProject(module) as MockProject
// (project.picoContainer as DefaultPicoContainer).release() // is way too strong :( even PsiManager is gone
unregisterServices(project)
// TODO: better to be part of `StandaloneModeUtilsConfiguratorService`
val compilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module)
compilerConfiguration.addJavaSourceRoots(ktFiles.map { File(it.virtualFilePath) })
configureProjectEnvironment(
project,
compilerConfiguration,
ktFiles,
testServices.compilerConfigurationProvider.getPackagePartProviderFactory(module)
)
val mainKtFile = ktFiles.singleOrNull() ?: ktFiles.first { it.name == "main.kt" }
val caretPosition = testServices.expressionMarkerProvider.getCaretPosition(mainKtFile)
val ktReferences = findReferencesAtCaret(mainKtFile, caretPosition)
if (ktReferences.isEmpty()) {
testServices.assertions.fail { "No references at caret found" }
}
val resolvedTo =
analyseForTest(
PsiTreeUtil.findElementOfClassAtOffset(mainKtFile, caretPosition, KtDeclaration::class.java, false) ?: mainKtFile
) {
val symbols = ktReferences.flatMap { it.resolveToSymbols() }
renderResolvedTo(symbols, renderingOptions)
}
val actual = "Resolved to:\n$resolvedTo"
testServices.assertions.assertEqualsToTestDataFileSibling(actual)
}
private val renderingOptions = KtDeclarationRendererOptions.DEFAULT.copy(
modifiers = RendererModifier.DEFAULT - RendererModifier.ANNOTATIONS,
sortNestedDeclarations = true
)
@OptIn(InvalidWayOfUsingAnalysisSession::class)
private fun unregisterServices(project: MockProject) {
project.picoContainer.unregisterComponent(KtAnalysisSessionProvider::class.qualifiedName)
project.picoContainer.unregisterComponent(KotlinModificationTrackerFactory::class.qualifiedName)
RegisterComponentService.unregisterLLFirResolveStateService(project)
project.picoContainer.unregisterComponent(FirSealedClassInheritorsProcessorFactory::class.qualifiedName)
project.picoContainer.unregisterComponent(KtModuleScopeProvider::class.qualifiedName)
project.picoContainer.unregisterComponent(ProjectStructureProvider::class.qualifiedName)
project.picoContainer.unregisterComponent(KotlinDeclarationProviderFactory::class.qualifiedName)
project.picoContainer.unregisterComponent(KotlinPackageProviderFactory::class.qualifiedName)
project.picoContainer.unregisterComponent(PackagePartProviderFactory::class.qualifiedName)
}
}
@@ -0,0 +1,36 @@
/*
* 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.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.analysis.api.fir.FirFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
object StandaloneModeConfiguratorService : FrontendApiTestConfiguratorService {
override val allowDependedAnalysisSession: Boolean
get() = false
override fun TestConfigurationBuilder.configureTest(disposable: Disposable) {
with(FirFrontendApiTestConfiguratorService) { configureTest(disposable) }
}
// TODO: other variant to register project services by compiler configuration?
override fun registerProjectServices(project: MockProject) {
// Will be configured at the test
}
override fun registerApplicationServices(application: MockApplication) {
configureApplicationEnvironment(application)
}
override fun doOutOfBlockModification(file: KtFile) {
FirFrontendApiTestConfiguratorService.doOutOfBlockModification(file)
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2021 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.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService;
import org.jetbrains.kotlin.analysis.api.standalone.StandaloneModeConfiguratorService;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("analysis/analysis-api/testData/standalone/singleModule")
@TestDataPath("$PROJECT_ROOT")
public class StandaloneModeSingleModuleTestGenerated extends AbstractStandaloneModeSingleModuleTest {
@NotNull
@Override
public FrontendApiTestConfiguratorService getConfigurator() {
return StandaloneModeConfiguratorService.INSTANCE;
}
@Test
public void testAllFilesPresentInSingleModule() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/standalone/singleModule"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("multipleFiles.kt")
public void testMultipleFiles() throws Exception {
runTest("analysis/analysis-api/testData/standalone/singleModule/multipleFiles.kt");
}
@Test
@TestMetadata("singleFile.kt")
public void testSingleFile() throws Exception {
runTest("analysis/analysis-api/testData/standalone/singleModule/singleFile.kt");
}
}