Kotlinize findOrCreateDirectoryForPackage in create actual fix

Before this commit, without existing directory
we tried to create it only inside Java source roots,
which is incorrect for JS and other non-JVM modules.
Source roots themselves still aren't created if they don't exist.

This fixes KT-19586 partially
This commit is contained in:
Mikhail Glukhikh
2018-08-31 18:18:48 +03:00
parent 3276e348cc
commit caee77cdae
2 changed files with 112 additions and 7 deletions
@@ -5,14 +5,25 @@
package org.jetbrains.kotlin.idea.core
import com.intellij.psi.JavaDirectoryService
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiPackage
import com.intellij.ide.util.DirectoryChooserUtil
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.roots.ModulePackageIndex
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.Query
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes
import org.jetbrains.jps.model.java.JavaSourceRootProperties
import org.jetbrains.jps.model.module.JpsModuleSourceRootType
import org.jetbrains.kotlin.config.KotlinSourceRootType
import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService
import org.jetbrains.kotlin.idea.util.sourceRoot
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
fun PsiDirectory.getPackage(): PsiPackage? = JavaDirectoryService.getInstance()!!.getPackage(this)
@@ -34,3 +45,98 @@ fun KtFile.packageMatchesDirectory(): Boolean = packageFqName == getFqNameByDire
fun KtFile.packageMatchesDirectoryOrImplicit() =
packageFqName == getFqNameByDirectory() || packageFqName == parent?.getFqNameWithImplicitPrefix()
private fun getWritableModuleDirectory(vFiles: Query<VirtualFile>, module: Module, manager: PsiManager): PsiDirectory? {
for (vFile in vFiles) {
if (ModuleUtil.findModuleForFile(vFile, module.project) !== module) continue
val directory = manager.findDirectory(vFile)
if (directory != null && directory.isValid && directory.isWritable) {
return directory
}
}
return null
}
private fun findLongestExistingPackage(module: Module, packageName: String): PsiPackage? {
val manager = PsiManager.getInstance(module.project)
var nameToMatch = packageName
while (true) {
val vFiles = ModulePackageIndex.getInstance(module).getDirsByPackageName(nameToMatch, false)
val directory = getWritableModuleDirectory(vFiles, module, manager)
if (directory != null) return directory.getPackage()
val lastDotIndex = nameToMatch.lastIndexOf('.')
if (lastDotIndex < 0) {
return null
}
nameToMatch = nameToMatch.substring(0, lastDotIndex)
}
}
private val kotlinSourceRootTypes: Set<JpsModuleSourceRootType<JavaSourceRootProperties>> =
setOf(KotlinSourceRootType.Source, KotlinSourceRootType.TestSource) + JavaModuleSourceRootTypes.SOURCES
private fun Module.getSourceRoots(): List<VirtualFile> =
ModuleRootManager.getInstance(this).getSourceRoots(kotlinSourceRootTypes)
private fun getPackageDirectoriesInModule(rootPackage: PsiPackage, module: Module): Array<PsiDirectory> =
rootPackage.getDirectories(GlobalSearchScope.moduleScope(module))
// This is Kotlin version of PackageUtil.findOrCreateDirectoryForPackage
fun findOrCreateDirectoryForPackage(module: Module, packageName: String): PsiDirectory? {
val project = module.project
var existingDirectoryByPackage: PsiDirectory? = null
var restOfName = packageName
if (!packageName.isEmpty()) {
val rootPackage = findLongestExistingPackage(module, packageName)
if (rootPackage != null) {
val beginIndex = rootPackage.qualifiedName.length + 1
val subPackageName = if (beginIndex < packageName.length) packageName.substring(beginIndex) else ""
var postfixToShow = subPackageName.replace('.', File.separatorChar)
if (subPackageName.isNotEmpty()) {
postfixToShow = File.separatorChar + postfixToShow
}
val moduleDirectories = getPackageDirectoriesInModule(rootPackage, module)
existingDirectoryByPackage =
DirectoryChooserUtil.selectDirectory(project, moduleDirectories, null, postfixToShow) ?: return null
restOfName = subPackageName
}
}
val existingDirectory = existingDirectoryByPackage ?: run {
val sourceRoots = module.getSourceRoots()
if (sourceRoots.isEmpty()) {
return null
}
val directoryList = mutableListOf<PsiDirectory>()
for (sourceRoot in sourceRoots) {
val directory = PsiManager.getInstance(project).findDirectory(sourceRoot) ?: continue
directoryList += directory
}
val sourceDirectories = directoryList.toTypedArray()
DirectoryChooserUtil.selectDirectory(
project, sourceDirectories, null,
File.separatorChar + packageName.replace('.', File.separatorChar)
) ?: return null
}
fun getLeftPart(packageName: String): String {
val index = packageName.indexOf('.')
return if (index > -1) packageName.substring(0, index) else packageName
}
fun cutLeftPart(packageName: String): String {
val index = packageName.indexOf('.')
return if (index > -1) packageName.substring(index + 1) else ""
}
var psiDirectory = existingDirectory
while (restOfName.isNotEmpty()) {
val name = getLeftPart(restOfName)
val foundExistingDirectory = psiDirectory.findSubdirectory(name)
psiDirectory = foundExistingDirectory ?: WriteAction.compute<PsiDirectory, Exception> { psiDirectory.createSubdirectory(name) }
restOfName = cutLeftPart(restOfName)
}
return psiDirectory
}
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.quickfix.expectactual
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.ide.util.EditorHelper
import com.intellij.ide.util.PackageUtil
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
@@ -112,8 +111,8 @@ sealed class CreateActualFix<out D : KtNamedDeclaration>(
val expectedDir = declaration.containingFile.containingDirectory
val expectedPackage = JavaDirectoryService.getInstance().getPackage(expectedDir)
val actualDirectory = PackageUtil.findOrCreateDirectoryForPackage(
actualModule, expectedPackage?.qualifiedName ?: "", null, false
val actualDirectory = findOrCreateDirectoryForPackage(
actualModule, expectedPackage?.qualifiedName ?: ""
) ?: return null
return runWriteAction {
val fileName = "$name.kt"