diff --git a/java/testFramework/src/com/intellij/testFramework/fixtures/MavenDependencyUtil.java b/java/testFramework/src/com/intellij/testFramework/fixtures/MavenDependencyUtil.java index c12a19ed47ef..44bae091ec9c 100644 --- a/java/testFramework/src/com/intellij/testFramework/fixtures/MavenDependencyUtil.java +++ b/java/testFramework/src/com/intellij/testFramework/fixtures/MavenDependencyUtil.java @@ -14,6 +14,7 @@ import com.intellij.openapi.roots.libraries.ui.OrderRoot; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties; +import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor; import java.util.Collection; import java.util.Collections; @@ -57,7 +58,7 @@ public final class MavenDependencyUtil { } /** - * Adds a Maven library to {@code model} + * Adds a Maven library with the default packaging (JAR) to {@code model} * @param additionalRepositories additional Maven repositories where the artifacts should be searched (in addition to repositories * configured in intellij project) */ @@ -66,9 +67,24 @@ public final class MavenDependencyUtil { boolean includeTransitiveDependencies, DependencyScope dependencyScope, List additionalRepositories) { + addFromMaven(model, mavenCoordinates, includeTransitiveDependencies, dependencyScope, additionalRepositories, + JpsMavenRepositoryLibraryDescriptor.DEFAULT_PACKAGING); + } + + /** + * Adds a Maven library to {@code model} + * @param packaging artifact packaging matching {@link org.jetbrains.idea.maven.aether.ArtifactKind} of the requested library + */ + public static void addFromMaven(@NotNull ModifiableRootModel model, + String mavenCoordinates, + boolean includeTransitiveDependencies, + DependencyScope dependencyScope, + List additionalRepositories, + @NotNull String packaging) { List remoteRepositoryDescriptions = ContainerUtil.concat(getRemoteRepositoryDescriptions(), additionalRepositories); - RepositoryLibraryProperties libraryProperties = new RepositoryLibraryProperties(mavenCoordinates, includeTransitiveDependencies); + RepositoryLibraryProperties libraryProperties = + new RepositoryLibraryProperties(mavenCoordinates, packaging, includeTransitiveDependencies); Collection roots = JarRepositoryManager.loadDependenciesModal(model.getProject(), libraryProperties, false, false, null, remoteRepositoryDescriptions); LibraryTable.ModifiableModel tableModel = model.getModuleLibraryTable().getModifiableModel(); diff --git a/plugins/kotlin/base/platforms/src/org/jetbrains/kotlin/idea/base/platforms/KotlinJavaScriptStdlibDetectorFacility.kt b/plugins/kotlin/base/platforms/src/org/jetbrains/kotlin/idea/base/platforms/KotlinJavaScriptStdlibDetectorFacility.kt index 6eea2d0db29e..9c7aced08bcf 100644 --- a/plugins/kotlin/base/platforms/src/org/jetbrains/kotlin/idea/base/platforms/KotlinJavaScriptStdlibDetectorFacility.kt +++ b/plugins/kotlin/base/platforms/src/org/jetbrains/kotlin/idea/base/platforms/KotlinJavaScriptStdlibDetectorFacility.kt @@ -2,20 +2,16 @@ package org.jetbrains.kotlin.idea.base.platforms -import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.annotations.ApiStatus import org.jetbrains.kotlin.idea.base.plugin.artifacts.KotlinArtifactNames -import org.jetbrains.kotlin.utils.LibraryUtils import org.jetbrains.kotlin.utils.PathUtil -import java.io.File import java.util.regex.Pattern @ApiStatus.Internal object KotlinJavaScriptStdlibDetectorFacility : StdlibDetectorFacility() { - private val IS_JS_LIBRARY_STD_LIB = Key.create("IS_JS_LIBRARY_STD_LIB") private val KOTLIN_JS_LIBRARY_KLIB_PATTERN = Pattern.compile("kotlin-stdlib-js.*\\.klib") override val supportedLibraryKind: KotlinLibraryKind @@ -23,6 +19,7 @@ object KotlinJavaScriptStdlibDetectorFacility : StdlibDetectorFacility() { override fun getStdlibJar(roots: List): VirtualFile? { for (root in roots) { + // KLIBs fall under the JAR file system as well if (root.fileSystem.protocol !== StandardFileSystems.JAR_PROTOCOL) continue val name = root.url.substringBefore("!/").substringAfterLast('/') @@ -33,16 +30,7 @@ object KotlinJavaScriptStdlibDetectorFacility : StdlibDetectorFacility() { || KOTLIN_JS_LIBRARY_KLIB_PATTERN.matcher(name).matches() ) { val jar = VfsUtilCore.getVirtualFileForJar(root) ?: continue - var isJSStdLib = jar.getUserData(IS_JS_LIBRARY_STD_LIB) - if (isJSStdLib == null) { - isJSStdLib = KOTLIN_JS_LIBRARY_KLIB_PATTERN.matcher(name).matches() - || LibraryUtils.isKotlinJavascriptStdLibrary(File(jar.path)) - jar.putUserData(IS_JS_LIBRARY_STD_LIB, isJSStdLib) - } - - if (isJSStdLib) { - return jar - } + return jar } } diff --git a/plugins/kotlin/base/platforms/src/org/jetbrains/kotlin/idea/base/platforms/LibraryEffectiveKindProvider.kt b/plugins/kotlin/base/platforms/src/org/jetbrains/kotlin/idea/base/platforms/LibraryEffectiveKindProvider.kt index 2244749c3920..494898e469c1 100644 --- a/plugins/kotlin/base/platforms/src/org/jetbrains/kotlin/idea/base/platforms/LibraryEffectiveKindProvider.kt +++ b/plugins/kotlin/base/platforms/src/org/jetbrains/kotlin/idea/base/platforms/LibraryEffectiveKindProvider.kt @@ -25,6 +25,7 @@ import com.intellij.util.indexing.roots.kind.LibraryOrigin import com.intellij.util.io.* import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinBuiltInFileType import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.base.platforms.LibraryEffectiveKindProvider.LibraryKindScanner import org.jetbrains.kotlin.platform.idePlatformKind import org.jetbrains.kotlin.platform.jvm.JvmPlatforms import org.jetbrains.kotlin.serialization.deserialization.DOT_METADATA_FILE_EXTENSION @@ -33,42 +34,6 @@ import java.io.DataOutput @Service(Service.Level.PROJECT) class LibraryEffectiveKindProvider(private val project: Project) { - private companion object { - val LIBRARY_KIND_KEY: Key> = Key.create("LibraryEffectiveKind") - val CLASS_ROOTS_KEY: Key> = Key.create("LibraryClassRoots") - val NEEDS_TO_BE_CLARIFIED_KIND: UnknownLibraryKind = UnknownLibraryKind.getOrCreate("Needs to be clarified") - - @JvmStatic - private val KOTLIN_LIBRARY_KIND_GIST: VirtualFileGist> = GistManager.getInstance().newVirtualFileGist( - "kotlin-library-kind", - 1, - object : DataExternalizer> { - override fun save(out: DataOutput, value: PersistentLibraryKind<*>) { - val kindId = value.kindId - IOUtil.writeString(kindId, out) - } - - override fun read(`in`: DataInput): PersistentLibraryKind<*>? = - when (val kindId = IOUtil.readString(`in`)) { - // as KotlinJvmEffectiveLibraryKind is a fake library kind - KotlinJvmEffectiveLibraryKind.kindId -> KotlinJvmEffectiveLibraryKind - else -> LibraryKindRegistry.getInstance().findKindById(kindId) as? PersistentLibraryKind<*> - } - } - ) { _, file -> - val classRoots = file.getUserData(CLASS_ROOTS_KEY) ?: arrayOf(file) - LibraryKindScanner.runScannerOutsideScanningSession(classRoots) - var platformKind: PersistentLibraryKind<*>? = file.getUserData(LIBRARY_KIND_KEY) - if (platformKind == NEEDS_TO_BE_CLARIFIED_KIND) { - val matchingPlatformKind = IdePlatformKindProjectStructure.getLibraryPlatformKind(file) - ?: JvmPlatforms.defaultJvmPlatform.idePlatformKind - platformKind = IdePlatformKindProjectStructure.getLibraryKind(matchingPlatformKind) - } - platformKind - } - - } - private enum class KnownLibraryKindForIndex { COMMON, JS, UNKNOWN } @@ -97,8 +62,7 @@ class LibraryEffectiveKindProvider(private val project: Project) { val classRoot = classRoots.firstOrNull() ?: return null val platformKind: PersistentLibraryKind<*>? = - classRoot.getUserData(LIBRARY_KIND_KEY)?.takeIf { it != NEEDS_TO_BE_CLARIFIED_KIND } ?: - findKind(classRoots)?.let { + classRoot.getUserData(LIBRARY_KIND_KEY)?.takeIf { it != NEEDS_TO_BE_CLARIFIED_KIND } ?: findKind(classRoots)?.let { classRoot.putUserData(LIBRARY_KIND_KEY, it) it } @@ -122,7 +86,7 @@ class LibraryEffectiveKindProvider(private val project: Project) { scannerVisitor.result = null VfsUtil.visitChildrenRecursively(classRoot, object : VirtualFileVisitor() { override fun visitFileEx(file: VirtualFile): Result = - if (visitFile(file)) CONTINUE else skipTo(classRoot) + if (visitFile(file)) CONTINUE else skipTo(classRoot) override fun visitFile(file: VirtualFile): Boolean { ProgressManager.checkCanceled() @@ -210,3 +174,37 @@ class LibraryEffectiveKindProvider(private val project: Project) { } } + +private val LIBRARY_KIND_KEY: Key> = Key.create("LibraryEffectiveKind") +private val CLASS_ROOTS_KEY: Key> = Key.create("LibraryClassRoots") +private val NEEDS_TO_BE_CLARIFIED_KIND: UnknownLibraryKind = UnknownLibraryKind.getOrCreate("Needs to be clarified") + +private val KOTLIN_LIBRARY_KIND_GIST: VirtualFileGist> by lazy { + GistManager.getInstance().newVirtualFileGist( + "kotlin-library-kind", + 1, + object : DataExternalizer> { + override fun save(out: DataOutput, value: PersistentLibraryKind<*>) { + val kindId = value.kindId + IOUtil.writeString(kindId, out) + } + + override fun read(`in`: DataInput): PersistentLibraryKind<*>? = + when (val kindId = IOUtil.readString(`in`)) { + // as KotlinJvmEffectiveLibraryKind is a fake library kind + KotlinJvmEffectiveLibraryKind.kindId -> KotlinJvmEffectiveLibraryKind + else -> LibraryKindRegistry.getInstance().findKindById(kindId) as? PersistentLibraryKind<*> + } + } + ) { _, file -> + val classRoots = file.getUserData(CLASS_ROOTS_KEY) ?: arrayOf(file) + LibraryKindScanner.runScannerOutsideScanningSession(classRoots) + var platformKind: PersistentLibraryKind<*>? = file.getUserData(LIBRARY_KIND_KEY) + if (platformKind == NEEDS_TO_BE_CLARIFIED_KIND) { + val matchingPlatformKind = IdePlatformKindProjectStructure.getLibraryPlatformKind(file) + ?: JvmPlatforms.defaultJvmPlatform.idePlatformKind + platformKind = IdePlatformKindProjectStructure.getLibraryKind(matchingPlatformKind) + } + platformKind + } +} diff --git a/plugins/kotlin/idea/tests/kotlin.idea.tests.iml b/plugins/kotlin/idea/tests/kotlin.idea.tests.iml index c1decb02b0eb..8504ae731804 100644 --- a/plugins/kotlin/idea/tests/kotlin.idea.tests.iml +++ b/plugins/kotlin/idea/tests/kotlin.idea.tests.iml @@ -140,5 +140,10 @@ + + + + + \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/configuration/StdlibJsDetectorFacilityTest.kt b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/configuration/StdlibJsDetectorFacilityTest.kt new file mode 100644 index 000000000000..552b30fcaf61 --- /dev/null +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/configuration/StdlibJsDetectorFacilityTest.kt @@ -0,0 +1,95 @@ +// 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.configuration + +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.roots.OrderEnumerator +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.libraries.Library +import com.intellij.testFramework.fixtures.MavenDependencyUtil +import com.intellij.testFramework.junit5.TestApplication +import com.intellij.testFramework.junit5.fixture.TestFixture +import com.intellij.testFramework.junit5.fixture.moduleFixture +import com.intellij.testFramework.junit5.fixture.projectFixture +import org.jetbrains.kotlin.idea.base.platforms.KotlinJavaScriptStdlibDetectorFacility +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +@TestApplication +class StdlibJsDetectorFacilityTest { + companion object { + // the project is reused + val project: TestFixture = projectFixture() + } + + // the module is recreated after each test to clean up module libraries + val module: TestFixture = project.moduleFixture() + + @Test + fun testJsStdlib_1_0_7() { + doTest("org.jetbrains.kotlin:kotlin-js-library:1.0.7", isJsStdlib = true) + } + + @Test + fun testJsStdlib_1_1_0() { + doTest("org.jetbrains.kotlin:kotlin-stdlib-js:1.1.0", isJsStdlib = true) + } + + @Test + fun testJsStdlib_1_3_0() { + doTest("org.jetbrains.kotlin:kotlin-stdlib-js:1.3.0", isJsStdlib = true) + } + + @Test + fun testJsStdlib_1_6_20() { + doTest("org.jetbrains.kotlin:kotlin-stdlib-js:1.6.20", isJsStdlib = true) + } + + @Test + fun testJsStdlib_1_9_24() { + doTest("org.jetbrains.kotlin:kotlin-stdlib-js:1.9.24", isJsStdlib = true) + } + + @Test + fun testJsStdlib_2_0_0() { + doTest("org.jetbrains.kotlin:kotlin-stdlib-js:2.0.0", isJsStdlib = true, packaging = "klib") + } + + @Test + fun testNonStdlib() { + doTest("org.jetbrains:annotations:24.0.1", isJsStdlib = false) + } + + @Test + fun testNonJsStdlib() { + doTest("org.jetbrains.kotlin:kotlin-stdlib:1.9.24", isJsStdlib = false) + } + + private fun doTest(coordinates: String, isJsStdlib: Boolean, packaging: String = "jar") { + ModuleRootModificationUtil.updateModel(module.get()) { modifiableModel -> + MavenDependencyUtil.addFromMaven( + modifiableModel, coordinates, /* includeTransitiveDependencies = */ false, + DependencyScope.COMPILE, /* additionalRepositories = */ emptyList(), packaging, + ) + } + val libraries = mutableListOf() + OrderEnumerator.orderEntries(module.get()).forEachLibrary { library -> + libraries.add(library) + true + } + + val theLibrary = libraries.singleOrNull() + Assertions.assertNotNull(theLibrary, "Expected a single library, got: $libraries") + KotlinJavaScriptStdlibDetectorFacility.isStdlib(module.get().project, library = theLibrary!!) + Assertions.assertEquals( + isJsStdlib, KotlinJavaScriptStdlibDetectorFacility.isStdlib(module.get().project, library = theLibrary), + "Expected $theLibrary to be${" not".takeIf { !isJsStdlib }.orEmpty()} detected as stdlib", + ) + + val stdlibJar = KotlinJavaScriptStdlibDetectorFacility.getStdlibJar(theLibrary.getFiles(OrderRootType.CLASSES).toList()) + if (isJsStdlib) Assertions.assertNotNull(stdlibJar) else Assertions.assertNull(stdlibJar) + } +}