diff --git a/analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/project/structure/StandaloneProjectFactory.kt b/analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/project/structure/StandaloneProjectFactory.kt index f5f7e3e23de..96542a37b4b 100644 --- a/analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/project/structure/StandaloneProjectFactory.kt +++ b/analysis/analysis-api-standalone/analysis-api-standalone-base/src/org/jetbrains/kotlin/analysis/api/standalone/base/project/structure/StandaloneProjectFactory.kt @@ -69,14 +69,15 @@ object StandaloneProjectFactory { fun createProjectEnvironment( projectDisposable: Disposable, applicationDisposable: Disposable, - unitTestMode: Boolean = false, + applicationEnvironmentMode: KotlinCoreApplicationEnvironmentMode, compilerConfiguration: CompilerConfiguration = CompilerConfiguration(), classLoader: ClassLoader = MockProject::class.java.classLoader, ): KotlinCoreProjectEnvironment { - val applicationEnvironment = if (unitTestMode) - KotlinCoreEnvironment.getOrCreateApplicationEnvironmentForTests(applicationDisposable, compilerConfiguration) - else - KotlinCoreEnvironment.getOrCreateApplicationEnvironmentForProduction(applicationDisposable, compilerConfiguration) + val applicationEnvironment = KotlinCoreEnvironment.getOrCreateApplicationEnvironment( + applicationDisposable, + compilerConfiguration, + applicationEnvironmentMode, + ) registerApplicationExtensionPoints(applicationEnvironment, applicationDisposable) diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneAnalysisAPISessionBuilder.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneAnalysisAPISessionBuilder.kt index 5d55307f05c..770a1029c6c 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneAnalysisAPISessionBuilder.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneAnalysisAPISessionBuilder.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.analysis.project.structure.impl.getPsiFilesFromPaths import org.jetbrains.kotlin.analysis.project.structure.impl.getSourceFilePaths import org.jetbrains.kotlin.analysis.providers.* import org.jetbrains.kotlin.analysis.providers.impl.* +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironmentMode import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreProjectEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.setupIdeaStandaloneExecution import org.jetbrains.kotlin.config.CompilerConfiguration @@ -61,7 +62,7 @@ public class StandaloneAnalysisAPISessionBuilder( StandaloneProjectFactory.createProjectEnvironment( projectDisposable, applicationDisposable, - unitTestMode, + KotlinCoreApplicationEnvironmentMode.fromUnitTestModeFlag(unitTestMode), classLoader = classLoader ) diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/base/registerBaseTestServices.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/base/registerBaseTestServices.kt index 396617a8692..6b6ce295b7a 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/base/registerBaseTestServices.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/base/registerBaseTestServices.kt @@ -26,7 +26,7 @@ fun TestConfigurationBuilder.registerAnalysisApiBaseTestServices( ) { useAdditionalService(::TestDisposableProviderImpl) useAdditionalService(::AnalysisApiKtModuleProviderImpl) - useAdditionalService(::AnalysisApiEnvironmentManagerImpl.bind(testDisposable)) + useAdditionalService(::AnalysisApiEnvironmentManagerImpl.bind(testDisposable, configurator)) useAdditionalService { ExecutionListenerBasedDisposableProvider() } useAdditionalService { StandardLibrariesPathProviderForKotlinProject } diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/AnalysisApiEnvironmentManager.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/AnalysisApiEnvironmentManager.kt index 1790116f73a..7de356619b9 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/AnalysisApiEnvironmentManager.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/AnalysisApiEnvironmentManager.kt @@ -12,7 +12,9 @@ import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtSta import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory import org.jetbrains.kotlin.analysis.project.structure.KtBuiltinsModule import org.jetbrains.kotlin.analysis.test.framework.project.structure.ktModuleProvider +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironment +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironmentMode import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreProjectEnvironment import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.languageVersionSettings @@ -38,12 +40,13 @@ abstract class AnalysisApiEnvironmentManager : TestService { class AnalysisApiEnvironmentManagerImpl( override val testServices: TestServices, override val testRootDisposable: Disposable, + private val configurator: AnalysisApiTestConfigurator, ) : AnalysisApiEnvironmentManager() { private val _projectEnvironment: KotlinCoreProjectEnvironment by lazy { StandaloneProjectFactory.createProjectEnvironment( testRootDisposable, testServices.applicationDisposableProvider.getApplicationRootDisposable(), - unitTestMode = true + KotlinCoreApplicationEnvironmentMode.UnitTest(configurator.isWriteAccessAllowed), ) } diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/test/configurators/AnalysisApiTestConfigurator.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/test/configurators/AnalysisApiTestConfigurator.kt index 95a8960610a..dc426ab25cd 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/test/configurators/AnalysisApiTestConfigurator.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/test/configurators/AnalysisApiTestConfigurator.kt @@ -36,6 +36,15 @@ abstract class AnalysisApiTestConfigurator { abstract val serviceRegistrars: List + /** + * Whether the associated [core application environment][org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironment] should + * permit write access. + * + * Write access is disabled by default because the Analysis API should not be used during write actions. Nevertheless, some tests may + * need write access, for example to modify PSI. + */ + open val isWriteAccessAllowed get() = false + open fun prepareFilesInModule(files: List, module: TestModule, testServices: TestServices) {} open fun doGlobalModuleStateModification(project: Project) { diff --git a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt index 8bcb3c1fceb..ce2f5da4690 100644 --- a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt +++ b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt @@ -24,13 +24,31 @@ import org.jetbrains.kotlin.cli.jvm.compiler.IdeaExtensionPoints.registerVersion import org.jetbrains.kotlin.cli.jvm.compiler.jarfs.FastJarFileSystem import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem +sealed interface KotlinCoreApplicationEnvironmentMode { + val isWriteAccessAllowed: Boolean + + object Production : KotlinCoreApplicationEnvironmentMode { + override val isWriteAccessAllowed: Boolean get() = true + } + + class UnitTest(override val isWriteAccessAllowed: Boolean) : KotlinCoreApplicationEnvironmentMode + + companion object { + fun fromUnitTestModeFlag(isUnitTestMode: Boolean): KotlinCoreApplicationEnvironmentMode = + if (isUnitTestMode) UnitTest(isWriteAccessAllowed = false) else Production + } +} + class KotlinCoreApplicationEnvironment private constructor( - parentDisposable: Disposable, unitTestMode: Boolean -) : JavaCoreApplicationEnvironment(parentDisposable, unitTestMode) { + parentDisposable: Disposable, + private val environmentMode: KotlinCoreApplicationEnvironmentMode, +) : JavaCoreApplicationEnvironment(parentDisposable, environmentMode is KotlinCoreApplicationEnvironmentMode.UnitTest) { init { + application.initializeEnvironmentMode(environmentMode) + registerApplicationService(JavaFileCodeStyleFacadeFactory::class.java, DummyJavaFileCodeStyleFacadeFactory()) - registerFileType(JavaClassFileType.INSTANCE, "sig"); + registerFileType(JavaClassFileType.INSTANCE, "sig") } override fun createJrtFileSystem(): VirtualFileSystem { @@ -41,15 +59,11 @@ class KotlinCoreApplicationEnvironment private constructor( val mock = super.createApplication(parentDisposable) /** - * We can't use just [unitTestMode] from constructor, because at this moment - * the corresponding property is not yet initialized, so [unitTestMode] is effectively always false, - * because this function called from super class constructor + * We can't use [environmentMode] from the constructor to decide whether we're in unit test mode, because the corresponding property + * is not yet initialized when this function is called from the superclass constructor. */ return if (mock.isUnitTestMode) { - object : MockApplication(parentDisposable) { - override fun isUnitTestMode(): Boolean = true - override fun isWriteAccessAllowed(): Boolean = false - } + MockUnitTestApplication(parentDisposable) } else { mock } @@ -80,10 +94,22 @@ class KotlinCoreApplicationEnvironment private constructor( } companion object { + @Deprecated( + message = "The `unitTestMode` flag is deprecated in favor of `KotlinCoreApplicationEnvironmentMode` configuration.", + replaceWith = ReplaceWith("create(parentDisposable, KotlinCoreApplicationEnvironmentMode.fromUnitTestModeFlag(unitTestMode))"), + ) fun create( - parentDisposable: Disposable, unitTestMode: Boolean + parentDisposable: Disposable, + unitTestMode: Boolean, ): KotlinCoreApplicationEnvironment { - val environment = KotlinCoreApplicationEnvironment(parentDisposable, unitTestMode) + return create(parentDisposable, KotlinCoreApplicationEnvironmentMode.fromUnitTestModeFlag(unitTestMode)) + } + + fun create( + parentDisposable: Disposable, + environmentMode: KotlinCoreApplicationEnvironmentMode, + ): KotlinCoreApplicationEnvironment { + val environment = KotlinCoreApplicationEnvironment(parentDisposable, environmentMode) registerExtensionPoints() return environment } @@ -102,3 +128,26 @@ class KotlinCoreApplicationEnvironment private constructor( } } } + +private class MockUnitTestApplication(parentDisposable: Disposable) : MockApplication(parentDisposable) { + /** + * We can't use [KotlinCoreApplicationEnvironment.environmentMode] in [KotlinCoreApplicationEnvironment.createApplication], because the + * corresponding property is not yet initialized when `createApplication` is called from the superclass constructor. So we have to + * initialize it later. + */ + lateinit var environmentMode: KotlinCoreApplicationEnvironmentMode + + override fun isUnitTestMode(): Boolean = true + + override fun isWriteAccessAllowed(): Boolean = environmentMode.isWriteAccessAllowed +} + +private fun MockApplication.initializeEnvironmentMode(environmentMode: KotlinCoreApplicationEnvironmentMode) { + if (this !is MockUnitTestApplication) return + + this.environmentMode = environmentMode + + require(isWriteAccessAllowed == environmentMode.isWriteAccessAllowed) { + "The mock application's `isWriteAccessAllowed` should correspond to the environment mode configuration." + } +} diff --git a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index b145729f4ce..a420afcb5dd 100644 --- a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -469,7 +469,9 @@ class KotlinCoreEnvironment private constructor( // Tests are supposed to create a single project and dispose it right after use val appEnv = createApplicationEnvironment( - parentDisposable, configuration, unitTestMode = true + parentDisposable, + configuration, + KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false), ) val projectEnv = ProjectEnvironment(parentDisposable, appEnv, configuration) return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs) @@ -499,7 +501,7 @@ class KotlinCoreEnvironment private constructor( val appEnv = createApplicationEnvironment( parentDisposable, configuration, - unitTestMode = true + KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false), ) return ProjectEnvironment(parentDisposable, appEnv, configuration) } @@ -509,14 +511,25 @@ class KotlinCoreEnvironment private constructor( fun getOrCreateApplicationEnvironmentForProduction( parentDisposable: Disposable, configuration: CompilerConfiguration - ): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(parentDisposable, configuration, unitTestMode = false) + ): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment( + parentDisposable, + configuration, + KotlinCoreApplicationEnvironmentMode.Production, + ) fun getOrCreateApplicationEnvironmentForTests( - parentDisposable: Disposable, configuration: CompilerConfiguration - ): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(parentDisposable, configuration, unitTestMode = true) + parentDisposable: Disposable, + configuration: CompilerConfiguration, + ): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment( + parentDisposable, + configuration, + KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false), + ) - private fun getOrCreateApplicationEnvironment( - parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean + fun getOrCreateApplicationEnvironment( + parentDisposable: Disposable, + configuration: CompilerConfiguration, + environmentMode: KotlinCoreApplicationEnvironmentMode, ): KotlinCoreApplicationEnvironment { synchronized(APPLICATION_LOCK) { if (ourApplicationEnvironment == null) { @@ -525,7 +538,7 @@ class KotlinCoreEnvironment private constructor( createApplicationEnvironment( disposable, configuration, - unitTestMode + environmentMode, ) ourProjectCount = 0 Disposer.register(disposable, Disposable { @@ -606,11 +619,11 @@ class KotlinCoreEnvironment private constructor( } private fun createApplicationEnvironment( - parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean + parentDisposable: Disposable, + configuration: CompilerConfiguration, + environmentMode: KotlinCoreApplicationEnvironmentMode, ): KotlinCoreApplicationEnvironment { - val applicationEnvironment = KotlinCoreApplicationEnvironment.create( - parentDisposable, unitTestMode - ) + val applicationEnvironment = KotlinCoreApplicationEnvironment.create(parentDisposable, environmentMode) registerApplicationExtensionPointsAndExtensionsFrom(configuration, "extensions/compiler.xml")