From 4f042cbea5da61879fdbe3dca7ea8d10448efec5 Mon Sep 17 00:00:00 2001 From: Zalim Bashorov Date: Tue, 17 Mar 2020 02:55:29 +0300 Subject: [PATCH] [IDE KJS] Recognize and load correctly Kotlin/JS klibs in IDE #KT-36276 Fixed --- .../CompositeResolverForModuleFactory.kt | 16 +---- .../caches/resolve/JsKlibLibraryInfo.kt | 30 +++++++++ .../resolve/JsPlatformKindResolution.kt | 55 ++++++++++++++- .../resolve/JsResolverForModuleFactory.kt | 67 ++++++++++++++++--- 4 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsKlibLibraryInfo.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/CompositeResolverForModuleFactory.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/CompositeResolverForModuleFactory.kt index 3ec732980d0..5ef801a8614 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/CompositeResolverForModuleFactory.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/CompositeResolverForModuleFactory.kt @@ -1,6 +1,6 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the license/LICENSE.txt file. + * Copyright 2010-2020 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. */ @file:Suppress("Duplicates") @@ -186,17 +186,7 @@ class CompositeResolverForModuleFactory( ): List { if (moduleInfo !is LibraryModuleInfo || !moduleInfo.platform.isJs()) return emptyList() - return moduleInfo.getLibraryRoots() - .flatMap { KotlinJavascriptMetadataUtils.loadMetadata(it) } - .filter { it.version.isCompatible() } - .map { metadata -> - val (header, packageFragmentProtos) = - KotlinJavascriptSerializationUtil.readModuleAsProto(metadata.body, metadata.version) - createKotlinJavascriptPackageFragmentProvider( - moduleContext.storageManager, moduleDescriptor, header, packageFragmentProtos, metadata.version, - container.get(), LookupTracker.DO_NOTHING - ) - } + return createPackageFragmentProvider(moduleInfo, container, moduleContext, moduleDescriptor) } fun createContainerForCompositePlatform( diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsKlibLibraryInfo.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsKlibLibraryInfo.kt new file mode 100644 index 00000000000..f69ef46cd05 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsKlibLibraryInfo.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2020 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.caches.resolve + +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.libraries.Library +import org.jetbrains.kotlin.idea.caches.project.LibraryInfo +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.library.resolveSingleFileKlib +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.platform.js.JsPlatforms + +internal class JsKlibLibraryInfo( + project: Project, + library: Library, + private val libraryRoot: String +) : LibraryInfo(project, library) { + + val kotlinLibrary = resolveSingleFileKlib(File(libraryRoot)) + + override fun getLibraryRoots() = listOf(libraryRoot) + + override val platform: TargetPlatform + get() = JsPlatforms.defaultJsPlatform + + override fun toString() = "JsKlib" + super.toString() +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsPlatformKindResolution.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsPlatformKindResolution.kt index a2511a79b87..a65479e4660 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsPlatformKindResolution.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsPlatformKindResolution.kt @@ -5,24 +5,35 @@ package org.jetbrains.kotlin.caches.resolve +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.libraries.Library import com.intellij.openapi.roots.libraries.PersistentLibraryKind import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.PathUtil import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.PlatformAnalysisParameters import org.jetbrains.kotlin.analyzer.ResolverForModuleFactory import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.context.ProjectContext +import org.jetbrains.kotlin.idea.caches.project.LibraryInfo import org.jetbrains.kotlin.idea.caches.project.SdkInfo import org.jetbrains.kotlin.idea.caches.resolve.BuiltInsCacheKey import org.jetbrains.kotlin.idea.framework.JSLibraryKind +import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION +import org.jetbrains.kotlin.library.KLIB_MANIFEST_FILE_NAME +import org.jetbrains.kotlin.library.KLIB_PROPERTY_BUILTINS_PLATFORM +import org.jetbrains.kotlin.library.impl.BuiltInsPlatform +import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.impl.JsIdePlatformKind import org.jetbrains.kotlin.resolve.TargetEnvironment -import org.jetbrains.kotlin.platform.TargetPlatform +import java.io.IOException +import java.util.* class JsPlatformKindResolution : IdePlatformKindResolution { override fun isLibraryFileForPlatform(virtualFile: VirtualFile): Boolean { - return virtualFile.extension == "js" || virtualFile.extension == "kjsm" + return virtualFile.extension == "js" || virtualFile.extension == "kjsm" || virtualFile.isJsKlibRoot } override val libraryKind: PersistentLibraryKind<*>? @@ -43,4 +54,44 @@ class JsPlatformKindResolution : IdePlatformKindResolution { environment: TargetEnvironment, platform: TargetPlatform ): ResolverForModuleFactory = JsResolverForModuleFactory(environment) + + override fun createLibraryInfo(project: Project, library: Library): List { + val klibFiles = library.getFiles(OrderRootType.CLASSES).filter { it.isJsKlibRoot } + + return if (klibFiles.isNotEmpty()) { + klibFiles.mapNotNull { + // TODO report error? + val path = PathUtil.getLocalPath(it) ?: return@mapNotNull null + JsKlibLibraryInfo(project, library, path) + } + } else { + super.createLibraryInfo(project, library) + } + } } + +// TODO unify the solution between Native and Common +private val VirtualFile.isJsKlibRoot: Boolean + get() { + // The virtual file for a library packed in a ZIP file will have path like "/some/path/to/the/file.klib!/", + // and therefore will be recognized by VFS as a directory (isDirectory == true). + // So, first, let's check the extension. + val extension = extension + if (!extension.isNullOrEmpty() && extension != KLIB_FILE_EXTENSION) return false + + fun checkComponent(componentFile: VirtualFile): Boolean { + val manifestFile = componentFile.findChild(KLIB_MANIFEST_FILE_NAME)?.takeIf { !it.isDirectory } ?: return false + + val manifestProperties = try { + manifestFile.inputStream.use { Properties().apply { load(it) } } + } catch (_: IOException) { + return false + } + + return manifestProperties.getProperty(KLIB_PROPERTY_BUILTINS_PLATFORM) == BuiltInsPlatform.JS.name + } + + // run check for library root too + // this is necessary to recognize old style KLIBs that do not have components, and report tem to user appropriately + return checkComponent(this) || children?.any(::checkComponent) == true + } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsResolverForModuleFactory.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsResolverForModuleFactory.kt index ca977dde207..5a65a7cbb1b 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsResolverForModuleFactory.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JsResolverForModuleFactory.kt @@ -6,20 +6,29 @@ package org.jetbrains.kotlin.caches.resolve import org.jetbrains.kotlin.analyzer.* +import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.get import org.jetbrains.kotlin.context.ModuleContext +import org.jetbrains.kotlin.descriptors.PackageFragmentProvider import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl import org.jetbrains.kotlin.frontend.di.createContainerForLazyResolve +import org.jetbrains.kotlin.idea.klib.getCompatibilityInfo import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices +import org.jetbrains.kotlin.konan.util.KlibMetadataFactories +import org.jetbrains.kotlin.library.metadata.parseModuleHeader import org.jetbrains.kotlin.resolve.BindingTraceContext +import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration import org.jetbrains.kotlin.resolve.TargetEnvironment import org.jetbrains.kotlin.resolve.lazy.ResolveSession import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService +import org.jetbrains.kotlin.serialization.js.DynamicTypeDeserializer import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil import org.jetbrains.kotlin.serialization.js.createKotlinJavascriptPackageFragmentProvider +import org.jetbrains.kotlin.serialization.konan.impl.KlibMetadataModuleDescriptorFactoryImpl import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils class JsResolverForModuleFactory( @@ -53,7 +62,54 @@ class JsResolverForModuleFactory( ) var packageFragmentProvider = container.get().packageFragmentProvider - val libraryProviders = (moduleInfo as? LibraryModuleInfo)?.getLibraryRoots().orEmpty() + val libraryProviders = createPackageFragmentProvider(moduleInfo, container, moduleContext, moduleDescriptor) + + if (libraryProviders.isNotEmpty()) { + packageFragmentProvider = CompositePackageFragmentProvider(listOf(packageFragmentProvider) + libraryProviders) + } + + return ResolverForModule(packageFragmentProvider, container) + } +} + +internal fun createPackageFragmentProvider( + moduleInfo: M, + container: StorageComponentContainer, + moduleContext: ModuleContext, + moduleDescriptor: ModuleDescriptorImpl +): List = when (moduleInfo) { + is JsKlibLibraryInfo -> { + val library = moduleInfo.kotlinLibrary + + if (library.getCompatibilityInfo().isCompatible) { + val metadataFactories = KlibMetadataFactories({ DefaultBuiltIns.Instance }, DynamicTypeDeserializer) + + val klibMetadataModuleDescriptorFactory = KlibMetadataModuleDescriptorFactoryImpl( + metadataFactories.DefaultDescriptorFactory, + metadataFactories.DefaultPackageFragmentsFactory, + metadataFactories.flexibleTypeDeserializer, + metadataFactories.platformDependentTypeTransformer + ) + + val packageFragmentNames = parseModuleHeader(library.moduleHeaderData).packageFragmentNameList + + val klibBasedPackageFragmentProvider = klibMetadataModuleDescriptorFactory.createPackageFragmentProvider( + library, + packageAccessHandler = null, + packageFragmentNames = packageFragmentNames, + storageManager = moduleContext.storageManager, + moduleDescriptor = moduleDescriptor, + configuration = CompilerDeserializationConfiguration(container.get()), + compositePackageFragmentAddend = null + ) + + listOf(klibBasedPackageFragmentProvider) + } else { + emptyList() + } + } + is LibraryModuleInfo -> { + moduleInfo.getLibraryRoots() .flatMap { KotlinJavascriptMetadataUtils.loadMetadata(it) } .filter { it.version.isCompatible() } .map { metadata -> @@ -64,11 +120,6 @@ class JsResolverForModuleFactory( container.get(), LookupTracker.DO_NOTHING ) } - - if (libraryProviders.isNotEmpty()) { - packageFragmentProvider = CompositePackageFragmentProvider(listOf(packageFragmentProvider) + libraryProviders) - } - - return ResolverForModule(packageFragmentProvider, container) } -} + else -> emptyList() +} \ No newline at end of file