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
This commit is contained in:
Yaroslav Chernyshev
2020-07-23 19:42:48 +03:00
parent fec845365a
commit 325ad56dcd
3 changed files with 156 additions and 0 deletions
@@ -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<GradleSourceSetDat
if (platform.platforms.any { it != KotlinPlatform.JVM && it != KotlinPlatform.ANDROID }) {
migrateNonJvmSourceFolders(rootModel)
populateNonJvmSourceRootTypes(nodeToImport, ideModule)
}
configureFacet(sourceSetData, kotlinSourceSet, mainModuleData, ideModule, modelsProvider)?.let { facet ->
@@ -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<GradleSourceSetData>, 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<VirtualFile> {
return ModuleManager.getInstance(project).modules.flatMap { it.collectKotlinAwareDestinationSourceRoots() }
}
@@ -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<JpsModuleSourceRootType<JpsElement>, JpsElement>? {
val currentRootType = rootType
@Suppress("UNCHECKED_CAST")
val newSourceRootType: JpsModuleSourceRootType<JpsElement> = when (currentRootType) {
JavaSourceRootType.SOURCE -> SourceKotlinRootType as JpsModuleSourceRootType<JpsElement>
JavaSourceRootType.TEST_SOURCE -> TestSourceKotlinRootType
JavaResourceRootType.RESOURCE -> ResourceKotlinRootType
JavaResourceRootType.TEST_RESOURCE -> TestResourceKotlinRootType
else -> return null
} as JpsModuleSourceRootType<JpsElement>
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<GradleSourceSetData>, module: Module) = Unit
fun getKotlinAwareDestinationSourceRoots(project: Project): List<VirtualFile> {
return ModuleManager.getInstance(project).modules.flatMap { it.collectKotlinAwareDestinationSourceRoots() }
}
private val KOTLIN_AWARE_SOURCE_ROOT_TYPES: Set<JpsModuleSourceRootType<JavaSourceRootProperties>> =
JavaModuleSourceRootTypes.SOURCES + ALL_KOTLIN_SOURCE_ROOT_TYPES
private fun Module.collectKotlinAwareDestinationSourceRoots(): List<VirtualFile> {
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<JpsModuleSourceRootType<*>>): 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<VirtualFile> {
val roots = ArrayList<VirtualFile>()
for (module in ModuleManager.getInstance(project).modules) {
collectSuitableDestinationSourceRoots(module, roots)
}
return roots
}
fun collectSuitableDestinationSourceRoots(module: Module, result: MutableList<VirtualFile>) {
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)
}