diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt index 2418183bd32..30e50fa7c1f 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt @@ -1,33 +1,26 @@ /* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2000-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.caches +import com.intellij.ProjectTopics import com.intellij.openapi.Disposable import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.project.rootManager +import com.intellij.openapi.roots.ModuleRootEvent +import com.intellij.openapi.roots.ModuleRootListener import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.newvfs.BulkFileListener import com.intellij.openapi.vfs.newvfs.events.* +import com.intellij.psi.PsiManager import com.intellij.psi.impl.PsiTreeChangeEventImpl import com.intellij.psi.impl.PsiTreeChangePreprocessor import com.intellij.psi.search.GlobalSearchScope @@ -38,6 +31,8 @@ import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.project.getModuleInfoByVirtualFile import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil +import org.jetbrains.kotlin.idea.util.getSourceRoot +import org.jetbrains.kotlin.idea.util.sourceRoot import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtPackageDirective @@ -58,8 +53,7 @@ class KotlinPackageContentModificationListener(private val project: Project) { it is VFileMoveEvent || it is VFileCreateEvent || it is VFileCopyEvent || it is VFileDeleteEvent fun onEvents(events: List) { - - val service = ServiceManager.getService(project, PerModulePackageCacheService::class.java) + val service = PerModulePackageCacheService.getInstance(project) if (events.size >= FULL_DROP_THRESHOLD) { service.onTooComplexChange() } else { @@ -67,12 +61,20 @@ class KotlinPackageContentModificationListener(private val project: Project) { .asSequence() .filter { it.file != null } .filter(::isRelevant) - .mapNotNull { it.file } - .filter { it.isDirectory || FileTypeRegistry.getInstance().getFileTypeByFileName(it.name) == KotlinFileType.INSTANCE } - .forEach { file -> service.notifyPackageChange(file) } + .filter { + val vFile = it.file!! + vFile.isDirectory || FileTypeRegistry.getInstance().getFileTypeByFileName(vFile.name) == KotlinFileType.INSTANCE + } + .forEach { event -> service.notifyPackageChange(event) } } } }) + + connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener { + override fun rootsChanged(event: ModuleRootEvent?) { + PerModulePackageCacheService.getInstance(project).onTooComplexChange() + } + }) } } @@ -100,6 +102,96 @@ class KotlinPackageStatementPsiTreeChangePreprocessor(private val project: Proje } } +private typealias ImplicitPackageData = MutableMap> + +class ImplicitPackagePrefixCache(private val project: Project) { + private val implicitPackageCache = ConcurrentHashMap() + + fun getPrefix(sourceRoot: VirtualFile): FqName { + val implicitPackageMap = implicitPackageCache.getOrPut(sourceRoot) { analyzeImplicitPackagePrefixes(sourceRoot) } + return implicitPackageMap.keys.singleOrNull() ?: FqName.ROOT + } + + internal fun clear() { + implicitPackageCache.clear() + } + + private fun analyzeImplicitPackagePrefixes(sourceRoot: VirtualFile): MutableMap> { + val result = mutableMapOf>() + val ktFiles = sourceRoot.children.filter { it.fileType == KotlinFileType.INSTANCE } + for (ktFile in ktFiles) { + result.addFile(ktFile) + } + return result + } + + private fun ImplicitPackageData.addFile(ktFile: VirtualFile) { + synchronized(this) { + val psiFile = PsiManager.getInstance(project).findFile(ktFile) as? KtFile ?: return + addPsiFile(psiFile, ktFile) + } + } + + private fun ImplicitPackageData.addPsiFile( + psiFile: KtFile, + ktFile: VirtualFile + ) = getOrPut(psiFile.packageFqName) { mutableListOf() }.add(ktFile) + + private fun ImplicitPackageData.removeFile(file: VirtualFile) { + synchronized(this) { + for ((key, value) in this) { + if (value.remove(file)) { + if (value.isEmpty()) remove(key) + break + } + } + } + } + + private fun ImplicitPackageData.updateFile(file: KtFile) { + synchronized(this) { + removeFile(file.virtualFile) + addPsiFile(file, file.virtualFile) + } + } + + internal fun update(event: VFileEvent) { + when (event) { + is VFileCreateEvent -> checkNewFileInSourceRoot(event.file) + is VFileDeleteEvent -> checkDeletedFileInSourceRoot(event.file) + is VFileCopyEvent -> checkNewFileInSourceRoot(event.newParent.findChild(event.newChildName)) + is VFileMoveEvent -> { + checkNewFileInSourceRoot(event.file) + if (event.oldParent.getSourceRoot(project) == event.oldParent) { + implicitPackageCache[event.oldParent]?.removeFile(event.file) + } + } + } + } + + private fun checkNewFileInSourceRoot(file: VirtualFile?) { + if (file == null) return + if (file.getSourceRoot(project) == file.parent) { + implicitPackageCache[file.parent]?.addFile(file) + } + } + + private fun checkDeletedFileInSourceRoot(file: VirtualFile?) { + val directory = file?.parent + if (directory == null || !directory.isValid) return + if (directory.getSourceRoot(project) == directory) { + implicitPackageCache[directory]?.removeFile(file) + } + } + + internal fun update(ktFile: KtFile) { + val parent = ktFile.virtualFile.parent + if (ktFile.sourceRoot == parent) { + implicitPackageCache[parent]?.updateFile(ktFile) + } + } +} + class PerModulePackageCacheService(private val project: Project) { /* @@ -107,8 +199,9 @@ class PerModulePackageCacheService(private val project: Project) { * Actually an StrongMap>> */ private val cache = ConcurrentHashMap>>() + private val implicitPackagePrefixCache = ImplicitPackagePrefixCache(project) - private val pendingVFileChanges: MutableSet = mutableSetOf() + private val pendingVFileChanges: MutableSet = mutableSetOf() private val pendingKtFileChanges: MutableSet = mutableSetOf() private val projectScope = GlobalSearchScope.projectScope(project) @@ -117,9 +210,10 @@ class PerModulePackageCacheService(private val project: Project) { pendingVFileChanges.clear() pendingKtFileChanges.clear() cache.clear() + implicitPackagePrefixCache.clear() } - internal fun notifyPackageChange(file: VirtualFile): Unit = synchronized(this) { + internal fun notifyPackageChange(file: VFileEvent): Unit = synchronized(this) { pendingVFileChanges += file } @@ -138,7 +232,8 @@ class PerModulePackageCacheService(private val project: Project) { onTooComplexChange() } else { - pendingVFileChanges.forEach { vfile -> + pendingVFileChanges.forEach { event -> + val vfile = event.file ?: return@forEach // When VirtualFile !isValid (deleted for example), it impossible to use getModuleInfoByVirtualFile // For directory we must check both is it in some sourceRoot, and is it contains some sourceRoot if (vfile.isDirectory || !vfile.isValid) { @@ -155,6 +250,7 @@ class PerModulePackageCacheService(private val project: Project) { invalidateCacheForModuleSourceInfo(it) } } + implicitPackagePrefixCache.update(event) } pendingVFileChanges.clear() @@ -163,6 +259,7 @@ class PerModulePackageCacheService(private val project: Project) { return@forEach } (file.getNullableModuleInfo() as? ModuleSourceInfo)?.let { invalidateCacheForModuleSourceInfo(it) } + implicitPackagePrefixCache.update(file) } pendingKtFileChanges.clear() } @@ -189,7 +286,15 @@ class PerModulePackageCacheService(private val project: Project) { } } + fun getImplicitPackagePrefix(sourceRoot: VirtualFile): FqName { + checkPendingChanges() + return implicitPackagePrefixCache.getPrefix(sourceRoot) + } + companion object { - val FULL_DROP_THRESHOLD = 1000 + const val FULL_DROP_THRESHOLD = 1000 + + fun getInstance(project: Project): PerModulePackageCacheService = + ServiceManager.getService(project, PerModulePackageCacheService::class.java) } } \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactory.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactory.kt index 1b84105e42b..0a574517824 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactory.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/resolve/PluginDeclarationProviderFactory.kt @@ -63,7 +63,7 @@ class PluginDeclarationProviderFactory( private fun stubBasedPackageExists(name: FqName): Boolean { // We're only looking for source-based declarations val moduleSourceInfo = moduleInfo as? ModuleSourceInfo ?: return false - return ServiceManager.getService(project, PerModulePackageCacheService::class.java).packageExists(name, moduleInfo) + return PerModulePackageCacheService.getInstance(project).packageExists(name, moduleInfo) } private fun getStubBasedPackageMemberDeclarationProvider(name: FqName): PackageMemberDeclarationProvider? { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt index c54aa55ffdd..394609465d7 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt @@ -1,17 +1,6 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2000-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.util @@ -148,5 +137,10 @@ object ProjectRootsUtil { val Module.rootManager: ModuleRootManager get() = ModuleRootManager.getInstance(this) +val Module.sourceRoots: Array + get() = rootManager.sourceRoots + val PsiElement.module: Module? get() = ModuleUtilCore.findModuleForPsiElement(this) + +fun VirtualFile.findModule(project: Project) = ModuleUtilCore.findModuleForFile(this, project) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/packageUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/packageUtils.kt index c6647454c8d..cd730283087 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/packageUtils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/packageUtils.kt @@ -1,17 +1,6 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2000-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.core @@ -20,6 +9,8 @@ import com.intellij.psi.JavaDirectoryService import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiFile import com.intellij.psi.PsiPackage +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 @@ -30,4 +21,16 @@ fun PsiFile.getFqNameByDirectory(): FqName { return qualifiedNameByDirectory?.let(::FqName) ?: FqName.ROOT } -fun KtFile.packageMatchesDirectory(): Boolean = packageFqName == getFqNameByDirectory() \ No newline at end of file +fun PsiDirectory.getFqNameWithImplicitPrefix(): FqName { + val packageFqName = getPackage()?.qualifiedName?.let(::FqName) ?: FqName.ROOT + sourceRoot?.let { + val implicitPrefix = PerModulePackageCacheService.getInstance(project).getImplicitPackagePrefix(it) + return FqName.fromSegments((implicitPrefix.pathSegments() + packageFqName.pathSegments()).map { it.asString() }) + } + return packageFqName +} + +fun KtFile.packageMatchesDirectory(): Boolean = packageFqName == getFqNameByDirectory() + +fun KtFile.packageMatchesDirectoryOrImplicit() = + packageFqName == getFqNameByDirectory() || packageFqName == parent?.getFqNameWithImplicitPrefix() diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index b832b6b043e..c629cdd6d6c 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -777,6 +777,7 @@ + diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/KotlinDefaultTemplatePropertiesProvider.kt b/idea/src/org/jetbrains/kotlin/idea/actions/KotlinDefaultTemplatePropertiesProvider.kt new file mode 100644 index 00000000000..aeabc4342bb --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/KotlinDefaultTemplatePropertiesProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2000-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.actions + +import com.intellij.ide.fileTemplates.DefaultTemplatePropertiesProvider +import com.intellij.ide.fileTemplates.FileTemplate +import com.intellij.psi.PsiDirectory +import org.jetbrains.kotlin.idea.core.getFqNameWithImplicitPrefix +import java.util.* + +class KotlinDefaultTemplatePropertiesProvider : DefaultTemplatePropertiesProvider { + override fun fillProperties(directory: PsiDirectory?, props: Properties?) { + if (directory != null && props != null) { + props.setProperty( + FileTemplate.ATTRIBUTE_PACKAGE_NAME, + directory.getFqNameWithImplicitPrefix().asString() + ) + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/PackageDirectoryMismatchInspection.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/PackageDirectoryMismatchInspection.kt index 9768453d193..33b6b4c39ff 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/PackageDirectoryMismatchInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/PackageDirectoryMismatchInspection.kt @@ -1,17 +1,6 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2000-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.refactoring.move.changePackage @@ -28,7 +17,8 @@ import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackages import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil import com.intellij.refactoring.util.RefactoringMessageUtil import org.jetbrains.kotlin.idea.core.getFqNameByDirectory -import org.jetbrains.kotlin.idea.core.packageMatchesDirectory +import org.jetbrains.kotlin.idea.core.getFqNameWithImplicitPrefix +import org.jetbrains.kotlin.idea.core.packageMatchesDirectoryOrImplicit import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly import org.jetbrains.kotlin.idea.refactoring.isInjectedFragment @@ -40,34 +30,38 @@ import org.jetbrains.kotlin.psi.KtVisitorVoid class PackageDirectoryMismatchInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) = - object : KtVisitorVoid() { - override fun visitPackageDirective(directive: KtPackageDirective) { - super.visitPackageDirective(directive) - if (directive.text.isEmpty()) return + object : KtVisitorVoid() { + override fun visitPackageDirective(directive: KtPackageDirective) { + super.visitPackageDirective(directive) + if (directive.text.isEmpty()) return - val file = directive.containingKtFile - if (file.isInjectedFragment || file.packageMatchesDirectory()) return + val file = directive.containingKtFile + if (file.isInjectedFragment || file.packageMatchesDirectoryOrImplicit()) return - val fixes = mutableListOf() - val qualifiedName = directive.qualifiedName - val dirName = if (qualifiedName.isEmpty()) "source root" else "'${qualifiedName.replace('.', '/')}'" - fixes += MoveFileToPackageFix(dirName) - val fqNameByDirectory = file.getFqNameByDirectory() - when { - fqNameByDirectory.isRoot -> - fixes += ChangePackageFix("source root") - fqNameByDirectory.hasIdentifiersOnly() -> - fixes += ChangePackageFix("'${fqNameByDirectory.asString()}'") - } - - holder.registerProblem( - directive, - "Package directive doesn't match file location", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - *fixes.toTypedArray() - ) + val fixes = mutableListOf() + val qualifiedName = directive.qualifiedName + val dirName = if (qualifiedName.isEmpty()) "source root" else "'${qualifiedName.replace('.', '/')}'" + fixes += MoveFileToPackageFix(dirName) + val fqNameByDirectory = file.getFqNameByDirectory() + when { + fqNameByDirectory.isRoot -> + fixes += ChangePackageFix("source root", fqNameByDirectory) + fqNameByDirectory.hasIdentifiersOnly() -> + fixes += ChangePackageFix("'${fqNameByDirectory.asString()}'", fqNameByDirectory) } + val fqNameWithImplicitPrefix = file.parent?.getFqNameWithImplicitPrefix() + if (fqNameWithImplicitPrefix != null && fqNameWithImplicitPrefix != fqNameByDirectory) { + fixes += ChangePackageFix("'${fqNameWithImplicitPrefix.asString()}'", fqNameWithImplicitPrefix) + } + + holder.registerProblem( + directive, + "Package directive doesn't match file location", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + *fixes.toTypedArray() + ) } + } private class MoveFileToPackageFix(val dirName: String) : LocalQuickFix { override fun getFamilyName() = "Move file to package-matching directory" @@ -84,10 +78,9 @@ class PackageDirectoryMismatchInspection : AbstractKotlinInspection() { val packageWrapper = PackageWrapper(PsiManager.getInstance(project), directive.qualifiedName) val fileToMove = directive.containingFile val chosenRoot = - sourceRoots.singleOrNull() - ?: MoveClassesOrPackagesUtil.chooseSourceRoot(packageWrapper, sourceRoots, - fileToMove.containingDirectory) - ?: return + sourceRoots.singleOrNull() + ?: MoveClassesOrPackagesUtil.chooseSourceRoot(packageWrapper, sourceRoots, fileToMove.containingDirectory) + ?: return val targetDirFactory = AutocreatingSingleSourceRootMoveDestination(packageWrapper, chosenRoot) targetDirFactory.verify(fileToMove)?.let { Messages.showMessageDialog(project, it, CommonBundle.getErrorTitle(), Messages.getErrorIcon()) @@ -108,7 +101,7 @@ class PackageDirectoryMismatchInspection : AbstractKotlinInspection() { } } - private class ChangePackageFix(val packageName: String) : LocalQuickFix { + private class ChangePackageFix(val packageName: String, val packageFqName: FqName) : LocalQuickFix { override fun getFamilyName() = "Change file's package to match directory" override fun getName() = "Change file's package to $packageName" @@ -116,10 +109,7 @@ class PackageDirectoryMismatchInspection : AbstractKotlinInspection() { override fun applyFix(project: Project, descriptor: ProblemDescriptor) { val directive = descriptor.psiElement as? KtPackageDirective ?: return val file = directive.containingKtFile - val newFqName = file.getFqNameByDirectory() - if (!newFqName.hasIdentifiersOnly()) return - KotlinChangePackageRefactoring(file).run(newFqName) - + KotlinChangePackageRefactoring(file).run(packageFqName) } } } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/ImplicitPackagePrefixTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/ImplicitPackagePrefixTest.kt new file mode 100644 index 00000000000..a9c18c15e76 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/ImplicitPackagePrefixTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2000-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.caches + +import com.intellij.psi.PsiDocumentManager +import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.util.sourceRoots + +class ImplicitPackagePrefixTest : KotlinLightCodeInsightFixtureTestCase() { + private lateinit var cacheService: PerModulePackageCacheService + + override fun getProjectDescriptor(): LightProjectDescriptor = KotlinLightProjectDescriptor.INSTANCE + + override fun setUp() { + super.setUp() + cacheService = PerModulePackageCacheService.getInstance(myFixture.project) + } + + private fun prefix(): String { + return cacheService.getImplicitPackagePrefix(myFixture.module.sourceRoots[0]).asString() + } + + fun testSimple() { + myFixture.configureByText("foo.kt", "package com.example.foo") + assertEquals("com.example.foo", prefix()) + } + + fun testAmbiguous() { + myFixture.configureByText("foo.kt", "package com.example.foo") + myFixture.configureByText("bar.kt", "package com.example.bar") + assertEquals("", prefix()) + } + + fun testUpdateOnCreate() { + myFixture.configureByText("foo.kt", "package com.example.foo") + assertEquals("com.example.foo", prefix()) + myFixture.configureByText("bar.kt", "package com.example.bar") + assertEquals("", prefix()) + } + + fun testUpdateOnDelete() { + myFixture.configureByText("foo.kt", "package com.example.foo") + val bar = myFixture.configureByText("bar.kt", "package com.example.bar") + assertEquals("", prefix()) + myFixture.project.executeWriteCommand("") { + bar.delete() + } + assertEquals("com.example.foo", prefix()) + } + + fun testUpdateOnChangeContent() { + val foo = myFixture.configureByText("foo.kt", "package com.example.foo") + assertEquals("com.example.foo", prefix()) + myFixture.project.executeWriteCommand("") { + foo.viewProvider.document!!.let { + it.replaceString(0, it.textLength, "package com.example.bar") + PsiDocumentManager.getInstance(project).commitDocument(it) + } + } + assertEquals("com.example.bar", prefix()) + } +}