From 325ad56dcdc26223c128cfd1bc2c4a91423bf649 Mon Sep 17 00:00:00 2001 From: Yaroslav Chernyshev Date: Thu, 23 Jul 2020 19:42:48 +0300 Subject: [PATCH] Populate Kotlin source set roots with KotlinSourceRootTypes By default Kotlin source root is populated with JavaSourceRootType and changed during import only if real source folder exists. Now Kotlin source roots has valid source type right after creation #KT-37106 Fixed --- .../KotlinSourceSetDataService.kt | 2 + .../kotlin/idea/roots/projectRootUtils.kt | 33 +++++ .../kotlin/idea/roots/projectRootUtils.kt.192 | 121 ++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt.192 diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt index a0d5119756d..ac5fa767a3a 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.inspections.gradle.findAll import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion import org.jetbrains.kotlin.idea.platform.IdePlatformKindTooling import org.jetbrains.kotlin.idea.roots.migrateNonJvmSourceFolders +import org.jetbrains.kotlin.idea.roots.populateNonJvmSourceRootTypes import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.impl.JvmIdePlatformKind import org.jetbrains.kotlin.platform.impl.NativeIdePlatformKind @@ -61,6 +62,7 @@ class KotlinSourceSetDataService : AbstractProjectDataService diff --git a/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt b/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt index 8ac32cd8b7a..221d492e0bd 100644 --- a/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt @@ -5,6 +5,12 @@ package org.jetbrains.kotlin.idea.roots +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.ContentRootData +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType +import com.intellij.openapi.externalSystem.service.project.manage.SourceFolderManager +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project @@ -13,11 +19,14 @@ import com.intellij.openapi.roots.ModifiableRootModel import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.roots.SourceFolder +import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.NonPhysicalFileSystem import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiCodeFragment import com.intellij.psi.PsiFile import com.intellij.util.containers.ContainerUtil +import com.intellij.util.io.URLUtil import org.jetbrains.jps.model.JpsElement import org.jetbrains.jps.model.ex.JpsElementBase import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes @@ -28,6 +37,7 @@ import org.jetbrains.jps.model.module.JpsModuleSourceRoot import org.jetbrains.jps.model.module.JpsModuleSourceRootType import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.idea.framework.KotlinSdkType +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData import java.util.* private fun JpsModuleSourceRoot.getOrCreateProperties() = @@ -59,6 +69,29 @@ fun migrateNonJvmSourceFolders(modifiableRootModel: ModifiableRootModel) { KotlinSdkType.setUpIfNeeded() } +private val ContentRootData.SourceRoot.pathAsUrl + get() = VirtualFileManager.constructUrl(URLUtil.FILE_PROTOCOL, FileUtil.toSystemIndependentName(path)) + +fun populateNonJvmSourceRootTypes(sourceSetNode: DataNode, module: Module) { + val sourceFolderManager = SourceFolderManager.getInstance(module.project) + val contentRootDataNodes = ExternalSystemApiUtil.findAll(sourceSetNode, ProjectKeys.CONTENT_ROOT) + val contentRootDataList = contentRootDataNodes.mapNotNull { it.data } + if (contentRootDataList.isEmpty()) return + + val externalToKotlinSourceTypes = mapOf( + ExternalSystemSourceType.SOURCE to SourceKotlinRootType, + ExternalSystemSourceType.TEST to TestSourceKotlinRootType + ) + externalToKotlinSourceTypes.forEach { (externalType, kotlinType) -> + val sourcesRoots = contentRootDataList.flatMap { it.getPaths(externalType) } + sourcesRoots.forEach { + if (!FileUtil.exists(it.path)) { + sourceFolderManager.addSourceFolder(module, it.pathAsUrl, kotlinType) + } + } + } +} + fun getKotlinAwareDestinationSourceRoots(project: Project): List { return ModuleManager.getInstance(project).modules.flatMap { it.collectKotlinAwareDestinationSourceRoots() } } diff --git a/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt.192 b/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt.192 new file mode 100644 index 00000000000..00e481bf956 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt.192 @@ -0,0 +1,121 @@ +/* + * Copyright 2010-2019 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.idea.roots + +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.rootManager +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.roots.SourceFolder +import com.intellij.openapi.vfs.NonPhysicalFileSystem +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiCodeFragment +import com.intellij.psi.PsiFile +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.jps.model.JpsElement +import org.jetbrains.jps.model.ex.JpsElementBase +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JavaSourceRootProperties +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.* +import org.jetbrains.kotlin.idea.framework.KotlinSdkType +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData +import java.util.* + +private fun JpsModuleSourceRoot.getOrCreateProperties() = + getProperties(rootType)?.also { (it as? JpsElementBase<*>)?.setParent(null) } ?: rootType.createDefaultProperties() + +fun JpsModuleSourceRoot.getMigratedSourceRootTypeWithProperties(): Pair, JpsElement>? { + val currentRootType = rootType + + @Suppress("UNCHECKED_CAST") + val newSourceRootType: JpsModuleSourceRootType = when (currentRootType) { + JavaSourceRootType.SOURCE -> SourceKotlinRootType as JpsModuleSourceRootType + JavaSourceRootType.TEST_SOURCE -> TestSourceKotlinRootType + JavaResourceRootType.RESOURCE -> ResourceKotlinRootType + JavaResourceRootType.TEST_RESOURCE -> TestResourceKotlinRootType + else -> return null + } as JpsModuleSourceRootType + return newSourceRootType to getOrCreateProperties() +} + +fun migrateNonJvmSourceFolders(modifiableRootModel: ModifiableRootModel) { + for (contentEntry in modifiableRootModel.contentEntries) { + for (sourceFolder in contentEntry.sourceFolders) { + val (newSourceRootType, properties) = sourceFolder.jpsElement.getMigratedSourceRootTypeWithProperties() ?: continue + val url = sourceFolder.url + contentEntry.removeSourceFolder(sourceFolder) + contentEntry.addSourceFolder(url, newSourceRootType, properties) + } + } + KotlinSdkType.setUpIfNeeded() +} + +fun populateNonJvmSourceRootTypes(sourceSetNode: DataNode, module: Module) = Unit + +fun getKotlinAwareDestinationSourceRoots(project: Project): List { + return ModuleManager.getInstance(project).modules.flatMap { it.collectKotlinAwareDestinationSourceRoots() } +} + +private val KOTLIN_AWARE_SOURCE_ROOT_TYPES: Set> = + JavaModuleSourceRootTypes.SOURCES + ALL_KOTLIN_SOURCE_ROOT_TYPES + +private fun Module.collectKotlinAwareDestinationSourceRoots(): List { + return rootManager + .contentEntries + .asSequence() + .flatMap { it.getSourceFolders(KOTLIN_AWARE_SOURCE_ROOT_TYPES).asSequence() } + .filterNot { isForGeneratedSources(it) } + .mapNotNull { it.file } + .toList() +} + +fun isOutsideSourceRootSet(psiFile: PsiFile?, sourceRootTypes: Set>): Boolean { + if (psiFile == null || psiFile is PsiCodeFragment) return false + val file = psiFile.virtualFile ?: return false + if (file.fileSystem is NonPhysicalFileSystem) return false + val projectFileIndex = ProjectRootManager.getInstance(psiFile.project).fileIndex + return !projectFileIndex.isUnderSourceRootOfType(file, sourceRootTypes) && !projectFileIndex.isInLibrary(file) +} + +fun isOutsideKotlinAwareSourceRoot(psiFile: PsiFile?) = isOutsideSourceRootSet(psiFile, KOTLIN_AWARE_SOURCE_ROOT_TYPES) + +/** + * @return list of all java source roots in the project which can be suggested as a target directory for a class created by user + */ +fun getSuitableDestinationSourceRoots(project: Project): List { + val roots = ArrayList() + for (module in ModuleManager.getInstance(project).modules) { + collectSuitableDestinationSourceRoots(module, roots) + } + return roots +} + +fun collectSuitableDestinationSourceRoots(module: Module, result: MutableList) { + for (entry in ModuleRootManager.getInstance(module).contentEntries) { + for (sourceFolder in entry.getSourceFolders(KOTLIN_AWARE_SOURCE_ROOT_TYPES)) { + if (!isForGeneratedSources(sourceFolder)) { + ContainerUtil.addIfNotNull(result, sourceFolder.file) + } + } + } +} + +fun isForGeneratedSources(sourceFolder: SourceFolder): Boolean { + val properties = sourceFolder.jpsElement.getProperties(KOTLIN_AWARE_SOURCE_ROOT_TYPES) + val javaResourceProperties = sourceFolder.jpsElement.getProperties(JavaModuleSourceRootTypes.RESOURCES) + val kotlinResourceProperties = sourceFolder.jpsElement.getProperties(ALL_KOTLIN_RESOURCE_ROOT_TYPES) + return properties != null && properties.isForGeneratedSources + || (javaResourceProperties != null && javaResourceProperties.isForGeneratedSources) + || (kotlinResourceProperties != null && kotlinResourceProperties.isForGeneratedSources) +}