diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/JsLibraryStdDetectionUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/JsLibraryStdDetectionUtil.kt index 9136071f442..625364846e4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/JsLibraryStdDetectionUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/JsLibraryStdDetectionUtil.kt @@ -49,7 +49,7 @@ object JsLibraryStdDetectionUtil { return JarUtil.getJarAttribute(VfsUtilCore.virtualToIoFile(jar), Attributes.Name.IMPLEMENTATION_VERSION) } - private fun getJsStdLibJar(classesRoots: List): VirtualFile? { + fun getJsStdLibJar(classesRoots: List): VirtualFile? { for (root in classesRoots) { if (root.fileSystem.protocol !== StandardFileSystems.JAR_PROTOCOL) continue diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/LibraryKinds.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/LibraryKinds.kt index 7c15d8cbde6..be4c3f432d1 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/LibraryKinds.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/LibraryKinds.kt @@ -42,15 +42,18 @@ object CommonLibraryKind : PersistentLibraryKind("kotlin override fun createDefaultProperties() = DummyLibraryProperties.INSTANCE!! } -fun getLibraryPlatform(project: Project, library: Library): TargetPlatform { - library as? LibraryEx ?: return JvmPlatform - if (library.isDisposed) return JvmPlatform - - return when (library.effectiveKind(project)) { +val PersistentLibraryKind<*>?.platform: TargetPlatform + get() = when (this) { JSLibraryKind -> JsPlatform CommonLibraryKind -> TargetPlatform.Common else -> JvmPlatform } + +fun getLibraryPlatform(project: Project, library: Library): TargetPlatform { + library as? LibraryEx ?: return JvmPlatform + if (library.isDisposed) return JvmPlatform + + return library.effectiveKind(project).platform } fun detectLibraryKind(roots: Array): PersistentLibraryKind<*>? { @@ -89,14 +92,13 @@ private fun detectLibraryKindFromJarContents(jarRoot: VirtualFile): PersistentLi return result } -fun getLibraryJarVersion(library: Library, jarPattern: Pattern): String? { - for (file in library.getFiles(OrderRootType.CLASSES)) { - if (jarPattern.matcher(file.name).matches()) { - return JarUtil.getJarAttribute(VfsUtilCore.virtualToIoFile(file), Attributes.Name.IMPLEMENTATION_VERSION) - } - } - return null +fun getLibraryJar(roots: Array, jarPattern: Pattern): VirtualFile? { + return roots.firstOrNull { jarPattern.matcher(it.name).matches() } } -fun getCommonRuntimeLibraryVersion(library: Library) = - getLibraryJarVersion(library, PathUtil.KOTLIN_STDLIB_COMMON_JAR_PATTERN) +fun getLibraryJarVersion(library: Library, jarPattern: Pattern): String? { + val libraryJar = getLibraryJar(library.getFiles(OrderRootType.CLASSES), jarPattern) ?: return null + return JarUtil.getJarAttribute(VfsUtilCore.virtualToIoFile(libraryJar), Attributes.Name.IMPLEMENTATION_VERSION) +} + +fun getCommonRuntimeLibraryVersion(library: Library) = getLibraryJarVersion(library, PathUtil.KOTLIN_STDLIB_COMMON_JAR_PATTERN) diff --git a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt index 82374598345..21ecb8fd1c3 100644 --- a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt +++ b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt @@ -25,6 +25,7 @@ import org.jdom.Element import org.jdom.Text import org.jetbrains.kotlin.cli.common.arguments.* import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import java.lang.reflect.Modifier import kotlin.reflect.KClass import kotlin.reflect.full.superclasses @@ -93,11 +94,15 @@ private fun readV1Config(element: Element): KotlinFacetSettings { } } +fun Element.getFacetPlatformByConfigurationElement(): TargetPlatformKind<*> { + val platformName = getAttributeValue("platform") + return TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM +} + private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings { return KotlinFacetSettings().apply { element.getAttributeValue("useProjectSettings")?.let { useProjectSettings = it.toBoolean() } - val platformName = element.getAttributeValue("platform") - val platformKind = TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM + val platformKind = element.getFacetPlatformByConfigurationElement() element.getChild("implements")?.let { val items = it.getChildren("implement") implementedModuleNames = if (items.isNotEmpty()) { diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index a609be49e03..136c9eefcd5 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -824,6 +824,8 @@ + + org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention Kotlin diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index b080fc933e1..9f05da7b396 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -824,6 +824,8 @@ + + org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention Kotlin diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index 5347e06f10e..1f4a6ee522e 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -824,6 +824,8 @@ + + org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention Kotlin diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index 0b4a36feea1..d6451cde4ec 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -825,6 +825,8 @@ + + org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention Kotlin diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 990c654f37f..112fcb64691 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -824,6 +824,8 @@ + + org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention Kotlin diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index fa030a8cf8f..a0af09b96e5 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -824,6 +824,8 @@ + + org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/roots/KotlinNonJvmSourceRootConverterProvider.kt b/idea/src/org/jetbrains/kotlin/idea/roots/KotlinNonJvmSourceRootConverterProvider.kt new file mode 100644 index 00000000000..846be3039d5 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/roots/KotlinNonJvmSourceRootConverterProvider.kt @@ -0,0 +1,220 @@ +/* + * Copyright 2010-2018 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. + */ + +package org.jetbrains.kotlin.idea.roots + +import com.intellij.conversion.* +import com.intellij.conversion.impl.ConversionContextImpl +import com.intellij.conversion.impl.ModuleSettingsImpl +import com.intellij.openapi.roots.ExternalProjectSystemRegistry +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.impl.ContentEntryImpl +import com.intellij.openapi.roots.impl.OrderEntryFactory.ORDER_ENTRY_TYPE_ATTR +import com.intellij.openapi.roots.impl.SourceFolderImpl +import com.intellij.openapi.roots.impl.libraries.ApplicationLibraryTable +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import com.intellij.openapi.roots.impl.libraries.LibraryImpl +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.roots.libraries.LibraryKind +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar +import com.intellij.openapi.roots.libraries.PersistentLibraryKind +import com.intellij.openapi.vfs.JarFileSystem +import com.intellij.openapi.vfs.VirtualFile +import org.jdom.Element +import org.jetbrains.jps.model.JpsElement +import org.jetbrains.jps.model.JpsElementFactory +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.jps.model.module.JpsTypedModuleSourceRoot +import org.jetbrains.jps.model.serialization.facet.JpsFacetSerializer +import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer +import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer.* +import org.jetbrains.kotlin.config.getFacetPlatformByConfigurationElement +import org.jetbrains.kotlin.idea.facet.KotlinFacetType +import org.jetbrains.kotlin.idea.framework.* +import org.jetbrains.kotlin.idea.refactoring.toVirtualFile +import org.jetbrains.kotlin.js.resolve.JsPlatform +import org.jetbrains.kotlin.resolve.TargetPlatform +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform +import org.jetbrains.kotlin.utils.PathUtil + +class KotlinNonJvmSourceRootConverterProvider : ConverterProvider("kotlin-non-jvm-source-roots") { + companion object { + private val rootTypesToMigrate: List> = listOf( + JavaSourceRootType.SOURCE, + JavaSourceRootType.TEST_SOURCE, + JavaResourceRootType.RESOURCE, + JavaResourceRootType.TEST_RESOURCE + ) + + private val TargetPlatform.stdlibDetector: ((Array) -> Boolean)? + get() = when (this) { + is JvmPlatform -> { roots -> JavaRuntimeDetectionUtil.getRuntimeJar(roots.toList()) != null } + is JsPlatform -> { roots -> JsLibraryStdDetectionUtil.getJsStdLibJar(roots.toList()) != null } + is TargetPlatform.Common -> { roots -> getLibraryJar(roots, PathUtil.KOTLIN_STDLIB_COMMON_JAR_PATTERN) != null } + else -> null + } + } + + sealed class LibInfo { + class ByXml( + private val element: Element, + private val conversionContext: ConversionContext, + private val moduleSettings: ModuleSettings + ) : LibInfo() { + override val explicitKind: PersistentLibraryKind<*>? + get() = LibraryKind.findById(element.getAttributeValue("type")) as? PersistentLibraryKind<*> + + override fun getRoots(): Array { + val contextImpl = conversionContext as? ConversionContextImpl ?: return VirtualFile.EMPTY_ARRAY + val moduleSettingsImpl = moduleSettings as? ModuleSettingsImpl ?: return VirtualFile.EMPTY_ARRAY + return contextImpl + .getClassRoots(element, moduleSettingsImpl) + .mapNotNull { it.toVirtualFile()?.let { JarFileSystem.getInstance().getJarRootForLocalFile(it) } } + .toTypedArray() + } + } + + class ByLibrary(private val library: Library) : LibInfo() { + override val explicitKind: PersistentLibraryKind<*>? + get() = (library as? LibraryEx)?.kind + + override fun getRoots() = library.getFiles(OrderRootType.CLASSES) + } + + abstract val explicitKind: PersistentLibraryKind<*>? + abstract fun getRoots(): Array + + val platform by lazy { + val explicitKind = explicitKind + val kind = if (explicitKind is KotlinLibraryKind) explicitKind else detectLibraryKind(getRoots()) + kind?.platform ?: JvmPlatform + } + + val isStdlib: Boolean + get() = platform.stdlibDetector?.invoke(getRoots()) ?: false + } + + class ConverterImpl(private val context: ConversionContext) : ProjectConverter() { + private val projectLibrariesByName by lazy { + context.projectLibrariesSettings.projectLibraries.groupBy { it.getAttributeValue(LibraryImpl.LIBRARY_NAME_ATTR) } + } + + private fun findGlobalLibrary(name: String) = ApplicationLibraryTable.getApplicationTable().getLibraryByName(name) + + private fun findProjectLibrary(name: String) = projectLibrariesByName[name]?.firstOrNull() + + private fun createLibInfo(orderEntryElement: Element, moduleSettings: ModuleSettings): LibInfo? { + val entryType = orderEntryElement.getAttributeValue(ORDER_ENTRY_TYPE_ATTR) + return when (entryType) { + JpsModuleRootModelSerializer.MODULE_LIBRARY_TYPE -> { + orderEntryElement.getChild(LIBRARY_TAG)?.let { LibInfo.ByXml(it, context, moduleSettings) } + } + + JpsModuleRootModelSerializer.LIBRARY_TYPE -> { + val libraryName = orderEntryElement.getAttributeValue(NAME_ATTRIBUTE) ?: return null + val level = orderEntryElement.getAttributeValue(LEVEL_ATTRIBUTE) + when (level) { + LibraryTablesRegistrar.PROJECT_LEVEL -> + findProjectLibrary(libraryName)?.let { LibInfo.ByXml(it, context, moduleSettings) } + LibraryTablesRegistrar.APPLICATION_LEVEL -> + findGlobalLibrary(libraryName)?.let { LibInfo.ByLibrary(it) } + else -> + null + } + } + + else -> null + } + } + + override fun createModuleFileConverter(): ConversionProcessor { + return object : ConversionProcessor() { + private fun ModuleSettings.detectPlatformByFacet() = + getFacetElement(KotlinFacetType.ID) + ?.getChild(JpsFacetSerializer.CONFIGURATION_TAG) + ?.getFacetPlatformByConfigurationElement() + ?.asTargetPlatform() + + private fun ModuleSettings.detectPlatformByDependencies(): TargetPlatform? { + var hasCommonStdlib = false + + orderEntries + .asSequence() + .mapNotNull { createLibInfo(it, this) } + .forEach { + val platform = it.platform + when (platform) { + is TargetPlatform.Common -> { + if (!hasCommonStdlib && it.isStdlib) { + hasCommonStdlib = true + } + } + + else -> { + if (it.isStdlib) return platform + } + } + } + + return if (hasCommonStdlib) TargetPlatform.Common else null + } + + private fun ModuleSettings.detectPlatform(): TargetPlatform { + return detectPlatformByFacet() + ?: detectPlatformByDependencies() + ?: JvmPlatform + } + + private fun ModuleSettings.getSourceFolderElements(): List { + val rootManagerElement = getComponentElement(ModuleSettings.MODULE_ROOT_MANAGER_COMPONENT) ?: return emptyList() + return rootManagerElement + .getChildren(ContentEntryImpl.ELEMENT_NAME) + .flatMap { it.getChildren(SourceFolderImpl.ELEMENT_NAME) } + } + + private fun ModuleSettings.isExternalModule(): Boolean { + return when { + rootElement.getAttributeValue(ExternalProjectSystemRegistry.EXTERNAL_SYSTEM_ID_KEY) != null -> true + rootElement.getAttributeValue(ExternalProjectSystemRegistry.IS_MAVEN_MODULE_KEY)?.toBoolean() ?: false -> true + else -> false + } + } + + override fun isConversionNeeded(settings: ModuleSettings): Boolean { + if (settings.isExternalModule()) return false + + val targetPlatform = settings.detectPlatform() + if (targetPlatform == JvmPlatform) return false + + return settings.getSourceFolderElements().any { + JpsModuleRootModelSerializer.loadSourceRoot(it).rootType in rootTypesToMigrate + } + } + + override fun process(settings: ModuleSettings) { + for (sourceFolder in settings.getSourceFolderElements()) { + val contentRoot = sourceFolder.parent as? Element ?: continue + val oldSourceRoot = JpsModuleRootModelSerializer.loadSourceRoot(sourceFolder) + val url = sourceFolder.getAttributeValue(JpsModuleRootModelSerializer.URL_ATTRIBUTE) + + val (newRootType, data) = oldSourceRoot.getMigratedSourceRootTypeWithProperties() ?: continue + @Suppress("UNCHECKED_CAST") + val newSourceRoot = JpsElementFactory.getInstance().createModuleSourceRoot(url, newRootType, data) + as? JpsTypedModuleSourceRoot ?: continue + + contentRoot.removeContent(sourceFolder) + JpsModuleRootModelSerializer.saveSourceRoot(contentRoot, url, newSourceRoot) + } + } + } + } + } + + override fun getConversionDescription() = "Update source roots for non-JVM modules in Kotlin project" + + override fun createConverter(context: ConversionContext) = ConverterImpl(context) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt b/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt index 06f884bf9b9..23833ea999e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt @@ -6,19 +6,23 @@ package org.jetbrains.kotlin.idea.roots import com.intellij.openapi.roots.ModifiableRootModel -import com.intellij.openapi.roots.SourceFolder import org.jetbrains.jps.model.JpsElement import org.jetbrains.jps.model.ex.JpsElementBase import org.jetbrains.jps.model.java.JavaResourceRootType import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.module.JpsModuleSourceRoot import org.jetbrains.jps.model.module.JpsModuleSourceRootType import org.jetbrains.kotlin.config.KotlinResourceRootType import org.jetbrains.kotlin.config.KotlinSourceRootType +import org.jetbrains.kotlin.config.TargetPlatformKind +import org.jetbrains.kotlin.js.resolve.JsPlatform +import org.jetbrains.kotlin.resolve.TargetPlatform +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform -private fun SourceFolder.getOrCreateProperties() = - jpsElement.getProperties(rootType)?.also { (it as? JpsElementBase<*>)?.setParent(null) } ?: rootType.createDefaultProperties() +private fun JpsModuleSourceRoot.getOrCreateProperties() = + getProperties(rootType)?.also { (it as? JpsElementBase<*>)?.setParent(null) } ?: rootType.createDefaultProperties() -private fun SourceFolder.getNewSourceRootTypeWithProperties(): Pair, JpsElement>? { +fun JpsModuleSourceRoot.getMigratedSourceRootTypeWithProperties(): Pair, JpsElement>? { val currentRootType = rootType @Suppress("UNCHECKED_CAST") val newSourceRootType: JpsModuleSourceRootType = when (currentRootType) { @@ -34,10 +38,16 @@ private fun SourceFolder.getNewSourceRootTypeWithProperties(): Pair.asTargetPlatform() = when (this) { + is TargetPlatformKind.Jvm -> JvmPlatform + is TargetPlatformKind.JavaScript -> JsPlatform + is TargetPlatformKind.Common -> TargetPlatform.Common } \ No newline at end of file