From 3c9dfaea464fb403e149af45e0db180635d97e4d Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 12 Sep 2024 13:10:49 +0200 Subject: [PATCH] [kotlin] Implement 'AddKotlinTestQuickFixProvider' alongside the 'AddKotlinCoroutinesQuickFixProvider' ^KTIJ-23814 Verification Pending ^KTIJ-31125 Verification Pending GitOrigin-RevId: a79570cebf7c77dc5904177c4e034e589ae0a554 --- .../messages/KotlinBundle.properties | 1 + ...kotlin.code-insight.inspections.shared.iml | 1 + ...kotlin.code-insight.inspections.shared.xml | 4 + .../AddKotlinCoroutinesQuickFixProvider.kt | 17 --- .../libraries/AddKotlinLibraryQuickFix.kt | 13 +- .../AddKotlinLibraryQuickFixProvider.kt | 102 +++++++++---- .../SimpleAddKotlinLibraryQuickFixProvider.kt | 23 --- .../defaultLibraryQuickFixProviders.kt | 25 ++++ .../KotlinWithGradleConfigurator.kt | 2 +- .../resources/kotlin.gradle.gradle-java.xml | 4 + ...otlinGradlePluginLibraryVersionProvider.kt | 21 +++ .../KotlinBuildScriptManipulator.kt | 3 +- .../AbstractGradleMultiFileQuickFixTest.kt | 92 ++++++++---- .../codeInsight/gradle/GradleQuickFixTest.kt | 14 ++ .../after/build.gradle.kts | 6 + .../after/src/test/kotlin/MyTest.kt | 9 ++ .../before/build.gradle.kts | 3 + .../before/settings.gradle.kts | 9 ++ .../before/src/test/kotlin/MyTest.kt | 9 ++ .../after/build.gradle.kts | 19 +++ .../after/src/commonTest/kotlin/MyTest.kt | 9 ++ .../before/build.gradle.kts | 12 ++ .../before/settings.gradle.kts | 9 ++ .../before/src/commonTest/kotlin/MyTest.kt | 9 ++ .../ConfigureKotlinInProjectUtils.kt | 10 ++ .../KotlinLibraryVersionProvider.kt | 4 +- .../KotlinProjectConfigurationService.kt | 136 +++++++++++++++--- .../core/kotlin.project-wizard.core.iml | 1 + .../DefaultKotlinLibraryVersionProvider.kt | 12 +- .../tests/kotlin.project-wizard.tests.iml | 1 + ...DefaultKotlinLibraryVersionProviderTest.kt | 37 ++--- 31 files changed, 470 insertions(+), 147 deletions(-) delete mode 100644 plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/AddKotlinCoroutinesQuickFixProvider.kt delete mode 100644 plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/SimpleAddKotlinLibraryQuickFixProvider.kt create mode 100644 plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/defaultLibraryQuickFixProviders.kt create mode 100644 plugins/kotlin/gradle/gradle-java/src/org/jetbrains/kotlin/idea/gradleJava/KotlinGradlePluginLibraryVersionProvider.kt create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/after/build.gradle.kts create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/after/src/test/kotlin/MyTest.kt create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/build.gradle.kts create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/settings.gradle.kts create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/src/test/kotlin/MyTest.kt create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/after/build.gradle.kts create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/after/src/commonTest/kotlin/MyTest.kt create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/build.gradle.kts create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/settings.gradle.kts create mode 100644 plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/src/commonTest/kotlin/MyTest.kt diff --git a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties index 7eb5ff812439..1211f7fda5f6 100644 --- a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties +++ b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties @@ -1750,6 +1750,7 @@ put.expressions.on.separate.lines=Put expressions on separate lines put.calls.on.separate.lines=Put calls on separate lines add.kotlin.coroutines=Add Kotlin coroutines library add.kotlin.coroutines.description=Add Kotlin Coroutines Library +add.0.library=Add ''{0}'' library demorgan.law=DeMorgan Law replace.with.end.of.line.comment=Replace with end of line comment convert.collection.constructor.to.function=Convert Collection constructor to function diff --git a/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml b/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml index 8c11abd19996..da09f1312f75 100644 --- a/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml +++ b/plugins/kotlin/code-insight/inspections-shared/kotlin.code-insight.inspections.shared.iml @@ -53,5 +53,6 @@ + \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-shared/resources/kotlin.code-insight.inspections.shared.xml b/plugins/kotlin/code-insight/inspections-shared/resources/kotlin.code-insight.inspections.shared.xml index 05c5d43a0664..493db847e8b8 100644 --- a/plugins/kotlin/code-insight/inspections-shared/resources/kotlin.code-insight.inspections.shared.xml +++ b/plugins/kotlin/code-insight/inspections-shared/resources/kotlin.code-insight.inspections.shared.xml @@ -334,6 +334,10 @@ + + + () { - /** - * Returns whether the library that will be added by this quick-fix is already present in the module. - */ - protected abstract fun hasLibrary(module: Module): Boolean - /** - * Checks if the reference is a reference to a definition of the library belonging to this quick-fix. - */ - protected abstract fun isLibraryReference(ref: PsiReference): Boolean + fun interface LibraryAvailabilityTester { + /** + * Returns whether the library that will be added by this quick-fix is already present in the module. + */ + fun isAvailable(module: Module): Boolean - private fun PsiReference.getKotlinVersion(): KotlinVersion { - return element.languageVersionSettings.languageVersion.toKotlinVersion() + companion object } - private fun getLibraryDescriptor(kotlinVersion: KotlinVersion): ExternalLibraryDescriptor? { - val versionProvider = KotlinLibraryVersionProvider.EP_NAME.extensionList.firstOrNull() ?: return null - return versionProvider.getVersion(libraryGroupId, libraryArtifactId, kotlinVersion) + fun interface LibraryReferenceTester { + /** + * Checks if the reference is a reference to a definition of the library belonging to this quick-fix. + */ + fun isLibraryReference(ref: PsiReference): Boolean + + companion object + } + + fun interface LibraryDescriptorProvider { + fun getLibraryDescriptor(libraryGroupId: String, libraryArtifactId: String, psiReference: PsiReference): ExternalLibraryDescriptor? + + companion object } override fun registerFixes(ref: PsiReference, registrar: QuickFixActionRegistrar) { - if (!isLibraryReference(ref)) return + if (!libraryReferenceTester.isLibraryReference(ref)) return val module = ref.element.module ?: return - val extensionList = ref.element.project.extensionArea.getExtensionPoint(KotlinBuildSystemDependencyManager.EP_NAME).extensionList val dependencyManager = extensionList.firstOrNull { it.isApplicable(module) } ?: return if (dependencyManager.isProjectSyncPendingOrInProgress()) return - if (hasLibrary(module)) return - val libraryVersionToUse = getLibraryDescriptor(ref.getKotlinVersion()) ?: return + if (libraryAvailabilityTester.isAvailable(module)) return + + val libraryDescriptor = libraryDescriptorProvider.getLibraryDescriptor( + libraryGroupId, libraryArtifactId, ref + ) ?: return val scope = if (ProjectFileIndex.getInstance(module.project).isInTestSourceContent(ref.element.containingFile.virtualFile)) { DependencyScope.TEST @@ -64,11 +83,44 @@ abstract class AddKotlinLibraryQuickFixProvider( registrar.register( AddKotlinLibraryQuickFix( dependencyManager = dependencyManager, - libraryDescriptor = libraryVersionToUse.withScope(scope), - quickFixText = KotlinBundle.message("add.kotlin.coroutines") + libraryDescriptor = libraryDescriptor.withScope(scope), + quickFixText = quickFixText ) ) } override fun getReferenceClass(): Class = PsiReference::class.java +} + +/** + * Will use the [KotlinLibraryVersionProvider] to look up the version + */ +fun LibraryDescriptorProvider.Companion.default() = LibraryDescriptorProvider { libraryGroupId, libraryArtifactId, psiRef -> + val module = psiRef.element.module ?: return@LibraryDescriptorProvider null + val libraryVersion = KotlinLibraryVersionProvider.EP_NAME.extensionList.firstNotNullOfOrNull { provider -> + provider.getVersion(module, libraryGroupId, libraryArtifactId) + } + + ExternalLibraryDescriptor(libraryGroupId, libraryArtifactId, libraryVersion, libraryVersion, libraryVersion) +} + + +/** + * A simple [LibraryReferenceTester] that determines if an unresolved reference belongs to the library if they + * are contained in the [names]. + * The names that are checked are references or function calls that are not part of a dot qualified expression. + */ +fun LibraryReferenceTester.Companion.knownNames(vararg names: String) = LibraryReferenceTester { ref -> + val referenceExpression = ref.element as? KtReferenceExpression ?: return@LibraryReferenceTester false + if (referenceExpression.parent is KtQualifiedExpression) return@LibraryReferenceTester false + referenceExpression.text in names +} + +/** + * Will check if the provided [fqn] is available as class in the given module + */ +fun LibraryAvailabilityTester.Companion.knownClassFqn(fqn: String) = LibraryAvailabilityTester { module -> + val scope = ModulesScope.moduleWithDependenciesAndLibrariesScope(module) + JavaPsiFacade.getInstance(module.project).findClasses(fqn, scope).isNotEmpty() || + KotlinFullClassNameIndex[fqn, module.project, scope].isNotEmpty() } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/SimpleAddKotlinLibraryQuickFixProvider.kt b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/SimpleAddKotlinLibraryQuickFixProvider.kt deleted file mode 100644 index ada57dbc9e55..000000000000 --- a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/SimpleAddKotlinLibraryQuickFixProvider.kt +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package org.jetbrains.kotlin.idea.codeInsight.inspections.shared.libraries - -import com.intellij.psi.PsiReference -import org.jetbrains.kotlin.psi.KtQualifiedExpression -import org.jetbrains.kotlin.psi.KtReferenceExpression - -/** - * A simple [AddKotlinLibraryQuickFixProvider] that determines if an unresolved reference belongs to the library if they - * are contained in the [namesToCheck]. - * The names that are checked are references or function calls that are not part of a dot qualified expression. - */ -abstract class SimpleAddKotlinLibraryQuickFixProvider( - libraryGroupId: String, - libraryArtifactId: String, - private val namesToCheck: Set, -) : AddKotlinLibraryQuickFixProvider(libraryGroupId, libraryArtifactId) { - override fun isLibraryReference(ref: PsiReference): Boolean { - val referenceExpression = ref.element as? KtReferenceExpression ?: return false - if (!namesToCheck.contains(referenceExpression.text)) return false - return referenceExpression.parent !is KtQualifiedExpression - } -} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/defaultLibraryQuickFixProviders.kt b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/defaultLibraryQuickFixProviders.kt new file mode 100644 index 000000000000..7d8e8b48111c --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/libraries/defaultLibraryQuickFixProviders.kt @@ -0,0 +1,25 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.codeInsight.inspections.shared.libraries + +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle + +internal class AddKotlinCoroutinesQuickFixProvider : AddKotlinLibraryQuickFixProvider( + libraryGroupId = "org.jetbrains.kotlinx", + libraryArtifactId = "kotlinx-coroutines-core", + libraryDescriptorProvider = LibraryDescriptorProvider.default(), + libraryAvailabilityTester = LibraryAvailabilityTester.knownClassFqn("kotlinx.coroutines.CoroutineScope"), + libraryReferenceTester = LibraryReferenceTester.knownNames( + "runBlocking", "CoroutineScope", "Dispatchers", "launch", "GlobalScope", + "MainScope", "async", "await", "withContext", "cancel", "isActive", "Job", + ), + quickFixText = KotlinBundle.message("add.kotlin.coroutines") +) + + +internal class AddKotlinTestLibraryQuickFixProvider : AddKotlinLibraryQuickFixProvider( + libraryGroupId = "org.jetbrains.kotlin", + libraryArtifactId = "kotlin-test", + libraryDescriptorProvider = LibraryDescriptorProvider.default(), + libraryAvailabilityTester = LibraryAvailabilityTester.knownClassFqn("kotlin.test.Asserter"), + libraryReferenceTester = LibraryReferenceTester.knownNames("Test", "assertEquals", "assertTrue", "assertFalse", "assertFailsWith"), +) diff --git a/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/KotlinWithGradleConfigurator.kt b/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/KotlinWithGradleConfigurator.kt index ce27d2cc5850..0c10ee828474 100644 --- a/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/KotlinWithGradleConfigurator.kt +++ b/plugins/kotlin/gradle/code-insight-common/src/org/jetbrains/kotlin/idea/gradleCodeInsightCommon/KotlinWithGradleConfigurator.kt @@ -153,7 +153,7 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { } private fun Project.isGradleSyncPending(module: Module): Boolean { - return KotlinProjectConfigurationService.getInstance(this).isSyncPending(module) + return KotlinProjectConfigurationService.getInstance(this).isSyncDesired(module) } private fun Project.isGradleSyncInProgress(): Boolean { diff --git a/plugins/kotlin/gradle/gradle-java/resources/kotlin.gradle.gradle-java.xml b/plugins/kotlin/gradle/gradle-java/resources/kotlin.gradle.gradle-java.xml index b9add8f9eb6c..cdb7a1a980bb 100644 --- a/plugins/kotlin/gradle/gradle-java/resources/kotlin.gradle.gradle-java.xml +++ b/plugins/kotlin/gradle/gradle-java/resources/kotlin.gradle.gradle-java.xml @@ -41,6 +41,10 @@ + + + + diff --git a/plugins/kotlin/gradle/gradle-java/src/org/jetbrains/kotlin/idea/gradleJava/KotlinGradlePluginLibraryVersionProvider.kt b/plugins/kotlin/gradle/gradle-java/src/org/jetbrains/kotlin/idea/gradleJava/KotlinGradlePluginLibraryVersionProvider.kt new file mode 100644 index 000000000000..9e0c88f13d7e --- /dev/null +++ b/plugins/kotlin/gradle/gradle-java/src/org/jetbrains/kotlin/idea/gradleJava/KotlinGradlePluginLibraryVersionProvider.kt @@ -0,0 +1,21 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.gradleJava + +import com.intellij.openapi.module.Module +import org.jetbrains.kotlin.idea.configuration.KotlinLibraryVersionProvider + +/** + * Returns the modules 'Kotlin Gradle Plugin version' as the version for libraries + * which originate from the Kotlin monorepo. Libraries (such as the kotlin stdlib, kotlin-test, ...) are expected + * to have the same version as the Kotlin Gradle Plugin. + */ +class KotlinGradlePluginLibraryVersionProvider : KotlinLibraryVersionProvider { + companion object { + const val STDLIB_GROUP_ID = "org.jetbrains.kotlin" + } + + override fun getVersion(module: Module, groupId: String, artifactId: String): String? { + if (groupId != STDLIB_GROUP_ID) return null + return module.kotlinGradlePluginVersion?.versionString + } +} diff --git a/plugins/kotlin/gradle/gradle-java/src/org/jetbrains/kotlin/idea/gradleJava/configuration/KotlinBuildScriptManipulator.kt b/plugins/kotlin/gradle/gradle-java/src/org/jetbrains/kotlin/idea/gradleJava/configuration/KotlinBuildScriptManipulator.kt index c59496783999..803bf9f8c395 100644 --- a/plugins/kotlin/gradle/gradle-java/src/org/jetbrains/kotlin/idea/gradleJava/configuration/KotlinBuildScriptManipulator.kt +++ b/plugins/kotlin/gradle/gradle-java/src/org/jetbrains/kotlin/idea/gradleJava/configuration/KotlinBuildScriptManipulator.kt @@ -14,6 +14,7 @@ import com.intellij.psi.util.childrenOfType import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.idea.base.codeInsight.CliArgumentStringBuilder.buildArgumentString import org.jetbrains.kotlin.idea.base.codeInsight.CliArgumentStringBuilder.replaceLanguageFeature +import org.jetbrains.kotlin.idea.base.facet.isMultiPlatformModule import org.jetbrains.kotlin.idea.base.util.module import org.jetbrains.kotlin.idea.compiler.configuration.IdeKotlinVersion import org.jetbrains.kotlin.idea.configuration.* @@ -219,7 +220,7 @@ class KotlinBuildScriptManipulator( libraryDescriptor.libraryGroupId, libraryDescriptor.libraryArtifactId, libraryDescriptor.preferredVersion ?: libraryDescriptor.maxVersion ?: libraryDescriptor.minVersion, - scope.toGradleCompileScope(scriptFile.module?.buildSystemType == BuildSystemType.AndroidGradle) + scope.toGradleCompileScope(targetModule) ) if (targetModule != null && usesNewMultiplatform()) { diff --git a/plugins/kotlin/gradle/gradle-java/tests.shared/test/org/jetbrains/kotlin/idea/codeInsight/gradle/AbstractGradleMultiFileQuickFixTest.kt b/plugins/kotlin/gradle/gradle-java/tests.shared/test/org/jetbrains/kotlin/idea/codeInsight/gradle/AbstractGradleMultiFileQuickFixTest.kt index b1c8e8d33492..a52fb9433a80 100644 --- a/plugins/kotlin/gradle/gradle-java/tests.shared/test/org/jetbrains/kotlin/idea/codeInsight/gradle/AbstractGradleMultiFileQuickFixTest.kt +++ b/plugins/kotlin/gradle/gradle-java/tests.shared/test/org/jetbrains/kotlin/idea/codeInsight/gradle/AbstractGradleMultiFileQuickFixTest.kt @@ -2,29 +2,62 @@ package org.jetbrains.kotlin.idea.codeInsight.gradle import com.intellij.codeInsight.daemon.quickFix.ActionHint -import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.* +import com.intellij.openapi.externalSystem.autoimport.AutoImportProjectTracker import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile -import com.intellij.testFramework.IndexingTestUtil -import com.intellij.testFramework.PlatformTestUtil -import com.intellij.testFramework.UsefulTestCase -import com.intellij.testFramework.runInEdtAndWait +import com.intellij.testFramework.* +import com.intellij.testFramework.common.timeoutRunBlocking import com.intellij.testFramework.utils.vfs.refreshAndGetVirtualDirectory +import kotlinx.coroutines.* +import org.jetbrains.kotlin.idea.configuration.KotlinProjectConfigurationService import org.jetbrains.kotlin.idea.core.util.toPsiFile import org.jetbrains.kotlin.idea.test.DirectiveBasedActionUtils import org.jetbrains.kotlin.psi.KtFile import java.io.File import java.nio.file.Files -import kotlin.io.path.isRegularFile -import kotlin.io.path.pathString -import kotlin.io.path.readText +import java.nio.file.Path +import kotlin.io.path.* import kotlin.streams.asSequence +import kotlin.time.Duration.Companion.minutes abstract class AbstractGradleMultiFileQuickFixTest : MultiplePluginVersionGradleImportingCodeInsightTestCase() { override fun testDataDirName() = "fixes" final override fun testDataDirectory(): File = super.testDataDirectory().resolve("before") - open val afterDirectory get() = testDataDirectory().parentFile.resolve("after") + open val afterTestDataDirectory get() = testDataDirectory().parentFile.resolve("after") + + private lateinit var afterDirectory: Path + + @OptIn(ExperimentalPathApi::class) + override fun setUp() { + super.setUp() + + /* Setup 'after' directory: Ensure that we process it similar to the 'before', by also replacing test properties */ + afterDirectory = TemporaryDirectory.generateTemporaryPath("${testDataDirName()}.after") + afterTestDataDirectory.toPath().copyToRecursively(afterDirectory, followLinks = true, copyAction = { source, target -> + if (source.isDirectory()) { + target.createDirectory() + } + + if (source.isRegularFile()) { + target.writeText(configureKotlinVersionAndProperties(source.readText())) + } + + CopyActionResult.CONTINUE + }) + + /* Some quick fix (e.g. AddKotlinLibraryQuickFix) will modify build scripts *and* re-sync the project */ + AutoImportProjectTracker.enableAutoReloadInTests(testRootDisposable) + } + + @OptIn(ExperimentalPathApi::class) + override fun tearDown() { + RunAll.runAll( + { afterDirectory.deleteRecursively() }, + { super.tearDown() }, + ) + } protected fun doMultiFileQuickFixTest( ignoreChangesInBuildScriptFiles: Boolean = true, @@ -49,43 +82,48 @@ abstract class AbstractGradleMultiFileQuickFixTest : MultiplePluginVersionGradle val actionHint = ActionHint.parse(ktFile, mainFileText) codeInsightTestFixture.configureFromExistingVirtualFile(ktFile.virtualFile) - runInEdtAndWait { + timeoutRunBlocking(3.minutes) { val actions = codeInsightTestFixture.availableIntentions val action = actionHint.findAndCheck(actions) { "Test file: ${projectPath.relativize(mainFilePath).pathString}" } if (action != null) { - action.invoke(myProject, null, ktFile) + codeInsightTestFixture.launchAction(action) + KotlinProjectConfigurationService.getInstance(project).awaitSyncFinished() IndexingTestUtil.waitUntilIndexesAreReady(myProject) - val expected = afterDirectory.toPath().refreshAndGetVirtualDirectory() - + val expected = afterDirectory.refreshAndGetVirtualDirectory() val projectVFile = projectPath.refreshAndGetVirtualDirectory() UsefulTestCase.refreshRecursively(expected) UsefulTestCase.refreshRecursively(projectVFile) - PlatformTestUtil.assertDirectoriesEqual( - expected, - projectVFile, - fun(vFile: VirtualFile): Boolean { - if (vFile.parent == projectVFile) { - when (vFile.name) { - ".gradle", "gradle", "build", "gradle.properties", "gradlew", "gradlew.bat", ".kotlin" -> return false + withContext(Dispatchers.EDT) { + PlatformTestUtil.assertDirectoriesEqual( + expected, + projectVFile, + fun(vFile: VirtualFile): Boolean { + if (vFile.parent == projectVFile) { + when (vFile.name) { + ".gradle", "gradle", "build", "gradle.properties", "gradlew", "gradlew.bat", ".kotlin" -> return false + } } - } - if (ignoreChangesInBuildScriptFiles && ".gradle" in vFile.name) return false + if (ignoreChangesInBuildScriptFiles && ".gradle" in vFile.name) return false - return additionalResultFileFilter(vFile) - }, - ) + return additionalResultFileFilter(vFile) + }, + ) + } } IndexingTestUtil.waitUntilIndexesAreReady(myProject) codeInsightTestFixture.doHighlighting() - DirectiveBasedActionUtils.checkAvailableActionsAreExpected(ktFile, action?.let { actions - it } ?: actions) - DirectiveBasedActionUtils.checkForUnexpectedErrors(ktFile) + + readActionBlocking { + DirectiveBasedActionUtils.checkAvailableActionsAreExpected(ktFile, action?.let { actions - it } ?: actions) + DirectiveBasedActionUtils.checkForUnexpectedErrors(ktFile) + } } } } diff --git a/plugins/kotlin/gradle/gradle-java/tests.shared/test/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleQuickFixTest.kt b/plugins/kotlin/gradle/gradle-java/tests.shared/test/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleQuickFixTest.kt index 42f1292cf8c7..ca289e0c605f 100644 --- a/plugins/kotlin/gradle/gradle-java/tests.shared/test/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleQuickFixTest.kt +++ b/plugins/kotlin/gradle/gradle-java/tests.shared/test/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleQuickFixTest.kt @@ -53,4 +53,18 @@ class GradleQuickFixTest : AbstractGradleMultiFileQuickFixTest() { @Test @PluginTargetVersions(pluginVersion = "1.5.31+") fun testCreateActualForGranularSourceSetTarget() = doMultiFileQuickFixTest() + + @Test + @PluginTargetVersions(pluginVersion = "2.0+") + fun testAddKotlinTestLibraryJvm() = doMultiFileQuickFixTest( + ignoreChangesInBuildScriptFiles = false, + additionalResultFileFilter = { file -> file.name != "settings.gradle.kts" } + ) + + @Test + @PluginTargetVersions(pluginVersion = "1.5.31+") + fun testAddKotlinTestLibraryKmp() = doMultiFileQuickFixTest( + ignoreChangesInBuildScriptFiles = false, + additionalResultFileFilter = { file -> file.name != "settings.gradle.kts" } + ) } diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/after/build.gradle.kts b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/after/build.gradle.kts new file mode 100644 index 000000000000..47b875873906 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/after/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { + kotlin("jvm") +} +dependencies { + testImplementation(kotlin("test")) +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/after/src/test/kotlin/MyTest.kt b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/after/src/test/kotlin/MyTest.kt new file mode 100644 index 000000000000..925f8a40ea33 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/after/src/test/kotlin/MyTest.kt @@ -0,0 +1,9 @@ +// "Add 'org.jetbrains.kotlin:kotlin-test' library" "true" +// ERROR: Unresolved reference: Test + +class MyTest { + @Test + fun testFoo() { + + } +} diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/build.gradle.kts b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/build.gradle.kts new file mode 100644 index 000000000000..9bc450610cef --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + kotlin("jvm") +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/settings.gradle.kts b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/settings.gradle.kts new file mode 100644 index 000000000000..bc3dbfcd1d2d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/settings.gradle.kts @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + {{kts_kotlin_plugin_repositories}} + } + + plugins { + kotlin("jvm") version "{{kotlin_plugin_version}}" + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/src/test/kotlin/MyTest.kt b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/src/test/kotlin/MyTest.kt new file mode 100644 index 000000000000..9799272c1bc6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryJvm/before/src/test/kotlin/MyTest.kt @@ -0,0 +1,9 @@ +// "Add 'org.jetbrains.kotlin:kotlin-test' library" "true" +// ERROR: Unresolved reference: Test + +class MyTest { + @Test + fun testFoo() { + + } +} diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/after/build.gradle.kts b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/after/build.gradle.kts new file mode 100644 index 000000000000..588e045f2ba5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/after/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + linuxX64() + sourceSets { + getByName("commonTest") { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +repositories { + {{kts_kotlin_plugin_repositories}} +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/after/src/commonTest/kotlin/MyTest.kt b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/after/src/commonTest/kotlin/MyTest.kt new file mode 100644 index 000000000000..2dd50aca5b9c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/after/src/commonTest/kotlin/MyTest.kt @@ -0,0 +1,9 @@ +// "Add 'org.jetbrains.kotlin:kotlin-test' library" "true" +import kotlin.test.Test + +class MyTest { + @Test + fun testFoo() { + + } +} diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/build.gradle.kts b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/build.gradle.kts new file mode 100644 index 000000000000..89697bf472ac --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + linuxX64() +} + +repositories { + {{kts_kotlin_plugin_repositories}} +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/settings.gradle.kts b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/settings.gradle.kts new file mode 100644 index 000000000000..5baa7cf4f647 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/settings.gradle.kts @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + {{kts_kotlin_plugin_repositories}} + } + + plugins { + kotlin("multiplatform") version "{{kotlin_plugin_version}}" + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/src/commonTest/kotlin/MyTest.kt b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/src/commonTest/kotlin/MyTest.kt new file mode 100644 index 000000000000..fa982ab4e3c1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/gradle/fixes/addKotlinTestLibraryKmp/before/src/commonTest/kotlin/MyTest.kt @@ -0,0 +1,9 @@ +// "Add 'org.jetbrains.kotlin:kotlin-test' library" "true" +import kotlin.test.Test + +class MyTest { + @Test + fun testFoo() { + + } +} diff --git a/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt b/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt index 4436cc736aab..ec424522f066 100644 --- a/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt +++ b/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt @@ -37,6 +37,7 @@ import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.base.facet.isMultiPlatformModule import org.jetbrains.kotlin.idea.base.facet.platform.platform import org.jetbrains.kotlin.idea.base.indices.KotlinPackageIndexUtils import org.jetbrains.kotlin.idea.base.platforms.* @@ -103,6 +104,7 @@ const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin" fun isRepositoryConfigured(repositoriesBlockText: String): Boolean = repositoriesBlockText.contains(MAVEN_CENTRAL) || repositoriesBlockText.contains(JCENTER) +@Deprecated("Use 'toGradleCompileScope(Module) instead") fun DependencyScope.toGradleCompileScope(isAndroidModule: Boolean) = when (this) { DependencyScope.COMPILE -> "implementation" // TODO: We should add testCompile or androidTestCompile @@ -112,6 +114,14 @@ fun DependencyScope.toGradleCompileScope(isAndroidModule: Boolean) = when (this) else -> "implementation" } +fun DependencyScope.toGradleCompileScope(targetModule: Module? = null) = when (this) { + DependencyScope.COMPILE -> "implementation" + DependencyScope.TEST -> if (targetModule?.isMultiPlatformModule == true) "implementation" else "testImplementation" + DependencyScope.RUNTIME -> "runtime" + DependencyScope.PROVIDED -> "implementation" + else -> "implementation" +} + fun RepositoryDescription.toGroovyRepositorySnippet() = "maven { url '$url' }" /** diff --git a/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/KotlinLibraryVersionProvider.kt b/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/KotlinLibraryVersionProvider.kt index b24894ca29a7..08b63cfcd123 100644 --- a/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/KotlinLibraryVersionProvider.kt +++ b/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/KotlinLibraryVersionProvider.kt @@ -2,7 +2,7 @@ package org.jetbrains.kotlin.idea.configuration import com.intellij.openapi.extensions.ExtensionPointName -import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.module.Module import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal @@ -16,5 +16,5 @@ interface KotlinLibraryVersionProvider { * Returns a version of the library identified by the [groupId] and [artifactId] compatible with the [projectKotlinVersion]. * Returns null if the [KotlinLibraryVersionProvider] does not manage the given library or no compatible version could be found. */ - fun getVersion(groupId: String, artifactId: String, projectKotlinVersion: KotlinVersion): ExternalLibraryDescriptor? + fun getVersion(module: Module, groupId: String, artifactId: String): String? } \ No newline at end of file diff --git a/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/KotlinProjectConfigurationService.kt b/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/KotlinProjectConfigurationService.kt index 70d73627f72e..cc10c2444309 100644 --- a/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/KotlinProjectConfigurationService.kt +++ b/plugins/kotlin/project-configuration/src/org/jetbrains/kotlin/idea/configuration/KotlinProjectConfigurationService.kt @@ -5,19 +5,19 @@ import com.intellij.model.SideEffectGuard import com.intellij.openapi.application.readAction import com.intellij.openapi.components.Service import com.intellij.openapi.components.service +import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectId import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectTracker import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.platform.ide.progress.withBackgroundProgress import com.intellij.ui.EditorNotifications -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import org.jetbrains.annotations.ApiStatus import org.jetbrains.kotlin.idea.projectConfiguration.KotlinProjectConfigurationBundle import org.jetbrains.kotlin.idea.statistics.KotlinJ2KOnboardingFUSCollector import org.jetbrains.kotlin.idea.util.isKotlinFileType +import java.util.concurrent.atomic.AtomicReference @Service(Service.Level.PROJECT) class KotlinProjectConfigurationService(private val project: Project, private val coroutineScope: CoroutineScope) { @@ -27,6 +27,35 @@ class KotlinProjectConfigurationService(private val project: Project, private va } } + private data class SyncState( + val level: Level, + + /** + * Jobs waiting for a sync to finish (aka transition back to 'idle' again) + */ + val onFinishedListeners: List + ) { + enum class Level { + /** + * No sync is requested, nor currently in-progress running + */ + Idle, + + /** + * A sync is enqueued but not yet actually running on the external system, or + * a previous sync is already running, but a new one has been requested already + */ + SyncQueued, + + /** + * The sync is currently running by the external system + */ + SyncInProgress, + } + } + + private val syncState = AtomicReference(SyncState(SyncState.Level.Idle, emptyList())) + @Volatile private var checkingAndPerformingAutoConfig: Boolean = false @@ -36,16 +65,13 @@ class KotlinProjectConfigurationService(private val project: Project, private va fun shouldShowNotConfiguredDialog(module: Module): Boolean { if (!findApplicableConfigurator(module).shouldShowNotConfiguredDialog()) return false - if (isSyncPending(module)) return false + if (isSyncDesired(module)) return false if (checkingAndPerformingAutoConfig) return false // If notificationCooldownEnd wasn't set, then the autoconfiguration didn't take place val cooldownEnd = notificationCooldownEnd ?: return true return System.currentTimeMillis() >= cooldownEnd } - fun isSyncPending(module: Module): Boolean { - return KotlinBuildSystemDependencyManager.findApplicableConfigurator(module)?.isProjectSyncPending() == true - } fun refreshEditorNotifications() { // We want to remove the "Kotlin not configured" notification banner as fast as possible @@ -60,14 +86,61 @@ class KotlinProjectConfigurationService(private val project: Project, private va } } - @Volatile - private var syncInProgress: Boolean = false + /** + * @return true if the build configuration is 'dirty' and the user is shown a button to re-sync. + * @see isSyncing + * @see isSyncInProgress + * @see KotlinBuildSystemDependencyManager.isProjectSyncPending + */ + fun isSyncDesired(module: Module): Boolean { + return KotlinBuildSystemDependencyManager.findApplicableConfigurator(module)?.isProjectSyncPending() == true + } - @Volatile - private var syncQueued: Boolean = false + /** + * @return true if the sync is already enqueued or actually in progress by the underlying external system. + */ + fun isSyncing(): Boolean { + return syncState.get().run { + level == SyncState.Level.SyncQueued || level == SyncState.Level.SyncInProgress + } + } + /** + * @return true if the sync is currently in-progress by the underlying external system. + * Also see [isSyncing] for checking if the sync is queued or in progress. + */ fun isSyncInProgress(): Boolean { - return syncInProgress + return syncState.get().level == SyncState.Level.SyncInProgress + } + + /** + * Will suspend until all queued or 'in progress' syncs are completed. + * @return true if the method suspended and had to wait for a sync + * @return false if no sync was queued or in progress and the method was able to return without suspending. + */ + suspend fun awaitSyncFinished(): Boolean { + val job = Job() + + val state = syncState.updateAndGet { state -> + /* Idle: Nothing to do */ + if (state.level == SyncState.Level.Idle) return@updateAndGet state + state.copy(onFinishedListeners = state.onFinishedListeners + job) + } + + if (state.level != SyncState.Level.Idle) { + job.join() + return true + } + + return false + } + + /** + * Triggers a sync ([queueSync]) and will suspend until it completes. + */ + suspend fun sync() { + queueSync() + awaitSyncFinished() } /** @@ -77,24 +150,47 @@ class KotlinProjectConfigurationService(private val project: Project, private va fun queueSync() { // prevents this side effect from being actually run from quickfix previews (e.g. in Fleet) SideEffectGuard.checkSideEffectAllowed(SideEffectGuard.EffectType.PROJECT_MODEL) - - if (syncInProgress) { - syncQueued = true - } else { + val previousState = syncState.getAndUpdate { state -> state.copy(level = SyncState.Level.SyncQueued) } + if (previousState.level < SyncState.Level.SyncInProgress) { ExternalSystemProjectTracker.getInstance(project).scheduleProjectRefresh() } } @ApiStatus.Internal fun onSyncStarted() { - syncInProgress = true + syncState.updateAndGet { state -> + state.copy(level = SyncState.Level.SyncInProgress) + } } @ApiStatus.Internal fun onSyncFinished() { - syncInProgress = false - if (syncQueued) { - syncQueued = false + val previousState = syncState.getAndUpdate { state -> + /* + Only if the previous state was actually 'SyncInProgress', then we can + consider the job to be done and reset the state. If the state is already 'SyncQueued' again, this means + that another queueSync call happened and we should start one more sync. + */ + if (state.level == SyncState.Level.SyncInProgress) { + SyncState(level = SyncState.Level.Idle, emptyList()) + } else { + state + } + } + + /* + We previously were in 'StateInProgress', which means the job now has finished. + Therefore, its time to complete the waiting coroutines. + */ + if (previousState.level == SyncState.Level.SyncInProgress) { + previousState.onFinishedListeners.forEach { job -> job.complete() } + } + + /* + During the current sync, we received another call into 'queueSync', which means + that the currently finished sync is 'dirty' and we should start another one! + */ + if (previousState.level == SyncState.Level.SyncQueued) { ExternalSystemProjectTracker.getInstance(project).scheduleProjectRefresh() } } diff --git a/plugins/kotlin/project-wizard/core/kotlin.project-wizard.core.iml b/plugins/kotlin/project-wizard/core/kotlin.project-wizard.core.iml index 9d37b1d88926..d95a7d70e896 100644 --- a/plugins/kotlin/project-wizard/core/kotlin.project-wizard.core.iml +++ b/plugins/kotlin/project-wizard/core/kotlin.project-wizard.core.iml @@ -33,5 +33,6 @@ + \ No newline at end of file diff --git a/plugins/kotlin/project-wizard/core/src/org/jetbrains/kotlin/tools/projectWizard/compatibility/DefaultKotlinLibraryVersionProvider.kt b/plugins/kotlin/project-wizard/core/src/org/jetbrains/kotlin/tools/projectWizard/compatibility/DefaultKotlinLibraryVersionProvider.kt index 63d8606323ac..f98d36b6f4e2 100644 --- a/plugins/kotlin/project-wizard/core/src/org/jetbrains/kotlin/tools/projectWizard/compatibility/DefaultKotlinLibraryVersionProvider.kt +++ b/plugins/kotlin/project-wizard/core/src/org/jetbrains/kotlin/tools/projectWizard/compatibility/DefaultKotlinLibraryVersionProvider.kt @@ -1,15 +1,17 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.kotlin.tools.projectWizard.compatibility -import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.module.Module +import org.jetbrains.kotlin.config.toKotlinVersion +import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings import org.jetbrains.kotlin.idea.configuration.KotlinLibraryVersionProvider class DefaultKotlinLibraryVersionProvider : KotlinLibraryVersionProvider { - override fun getVersion(groupId: String, artifactId: String, projectKotlinVersion: KotlinVersion): ExternalLibraryDescriptor? { + override fun getVersion(module: Module, groupId: String, artifactId: String): String? { + val projectKotlinVersion = module.languageVersionSettings.languageVersion.toKotlinVersion() + val versions = KotlinLibrariesCompatibilityStore.getInstance().getVersions(groupId, artifactId).orEmpty() val kotlinShortVersion = "${projectKotlinVersion.major}.${projectKotlinVersion.minor}" - val versionToUse = versions[kotlinShortVersion] ?: return null - - return ExternalLibraryDescriptor(groupId, artifactId, versionToUse, versionToUse, versionToUse) + return versions[kotlinShortVersion] } } \ No newline at end of file diff --git a/plugins/kotlin/project-wizard/tests/kotlin.project-wizard.tests.iml b/plugins/kotlin/project-wizard/tests/kotlin.project-wizard.tests.iml index 4126442bb818..717f0d780df1 100644 --- a/plugins/kotlin/project-wizard/tests/kotlin.project-wizard.tests.iml +++ b/plugins/kotlin/project-wizard/tests/kotlin.project-wizard.tests.iml @@ -50,5 +50,6 @@ + \ No newline at end of file diff --git a/plugins/kotlin/project-wizard/tests/test/org/jetbrains/kotlin/tools/projectWizard/compatibility/DefaultKotlinLibraryVersionProviderTest.kt b/plugins/kotlin/project-wizard/tests/test/org/jetbrains/kotlin/tools/projectWizard/compatibility/DefaultKotlinLibraryVersionProviderTest.kt index 8789f36d3893..bd44d271919a 100644 --- a/plugins/kotlin/project-wizard/tests/test/org/jetbrains/kotlin/tools/projectWizard/compatibility/DefaultKotlinLibraryVersionProviderTest.kt +++ b/plugins/kotlin/project-wizard/tests/test/org/jetbrains/kotlin/tools/projectWizard/compatibility/DefaultKotlinLibraryVersionProviderTest.kt @@ -1,9 +1,11 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.kotlin.tools.projectWizard.compatibility -import com.intellij.openapi.roots.ExternalLibraryDescriptor import com.intellij.testFramework.fixtures.BasePlatformTestCase +import org.jetbrains.kotlin.config.LanguageVersion import org.jetbrains.kotlin.idea.configuration.KotlinLibraryVersionProvider +import org.jetbrains.kotlin.idea.facet.setLanguageAndApiVersionInKotlinFacet +import org.jetbrains.kotlin.idea.test.createFacet class DefaultKotlinLibraryVersionProviderTest : BasePlatformTestCase() { private lateinit var provider: DefaultKotlinLibraryVersionProvider @@ -11,46 +13,35 @@ class DefaultKotlinLibraryVersionProviderTest : BasePlatformTestCase() { override fun setUp() { super.setUp() provider = DefaultKotlinLibraryVersionProvider() + module.createFacet(useProjectSettings = false) } - private fun getCoroutinesVersion(kotlinVersion: KotlinVersion): ExternalLibraryDescriptor? { + private fun getCoroutinesVersion(version: LanguageVersion): String? { val groupId = "org.jetbrains.kotlinx" val artifactId = "kotlinx-coroutines-core" - val descriptor = provider.getVersion(groupId, artifactId, kotlinVersion) - if (descriptor != null) { - assertEquals(groupId, descriptor.libraryGroupId) - assertEquals(artifactId, descriptor.libraryArtifactId) - assertNotNull(descriptor.preferredVersion) - assertEquals(descriptor.preferredVersion, descriptor.minVersion) - assertEquals(descriptor.preferredVersion, descriptor.maxVersion) - } - return descriptor + + myFixture.module.setLanguageAndApiVersionInKotlinFacet(version.toString(), version.toString()) + + return provider.getVersion(myFixture.module, groupId, artifactId) } fun testDefaultVersionProviderRegistered() { - assertTrue(KotlinLibraryVersionProvider.Companion.EP_NAME.extensionList.any { it is DefaultKotlinLibraryVersionProvider }) + assertTrue(KotlinLibraryVersionProvider.EP_NAME.extensionList.any { it is DefaultKotlinLibraryVersionProvider }) } fun testKnownVersion() { - val returnedVersion = getCoroutinesVersion(KotlinVersion(1, 9)) + val returnedVersion = getCoroutinesVersion(LanguageVersion.KOTLIN_1_9) assertNotNull(returnedVersion) } - fun testPatchedKotlinVersion() { - val returnedVersion = getCoroutinesVersion(KotlinVersion(1, 9, 23)) - val nonPatchedVersion = getCoroutinesVersion(KotlinVersion(1, 9)) - assertNotNull(returnedVersion?.preferredVersion) - assertEquals(returnedVersion?.preferredVersion, nonPatchedVersion?.preferredVersion) - } - fun testUnknownKotlin() { - assertNull(getCoroutinesVersion(KotlinVersion(0, 3))) + assertNull(getCoroutinesVersion(LanguageVersion.KOTLIN_1_0)) } fun testSpecificCoroutinesVersions() { // Here we test that versions for old Kotlin versions (which will not receive new updates) // return the correct exact values - assertEquals("1.6.4", getCoroutinesVersion(KotlinVersion(1, 6))?.preferredVersion) - assertEquals("1.5.2", getCoroutinesVersion(KotlinVersion(1, 5))?.preferredVersion) + assertEquals("1.6.4", getCoroutinesVersion(LanguageVersion.KOTLIN_1_6)) + assertEquals("1.5.2", getCoroutinesVersion(LanguageVersion.KOTLIN_1_5)) } } \ No newline at end of file