diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt index 89ae63f00c6..059efa8bce8 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.caches.resolve import com.intellij.openapi.module.Module import com.intellij.openapi.module.impl.scopes.LibraryScopeBase import com.intellij.openapi.project.Project +import com.intellij.openapi.project.rootManager import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.* import com.intellij.openapi.roots.impl.libraries.LibraryEx @@ -30,6 +31,7 @@ import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.util.PathUtil import com.intellij.util.SmartList +import org.jetbrains.jps.model.java.JavaSourceRootType import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.TrackableModuleInfo import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo @@ -42,6 +44,7 @@ import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.TargetPlatform import org.jetbrains.kotlin.resolve.jvm.GlobalSearchScopeWithModuleSources +import org.jetbrains.kotlin.utils.addIfNotNull import java.util.* interface IdeaModuleInfo : ModuleInfo { @@ -56,7 +59,7 @@ interface IdeaModuleInfo : ModuleInfo { } private fun orderEntryToModuleInfo(project: Project, orderEntry: OrderEntry, productionOnly: Boolean): List { - fun Module.toInfos() = if (productionOnly) listOf(productionSourceInfo()) else listOf(testSourceInfo(), productionSourceInfo()) + fun Module.toInfos() = correspondingModuleInfos().filter { !productionOnly || it is ModuleProductionSourceInfo } if (!orderEntry.isValid) return emptyList() @@ -115,7 +118,7 @@ interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo { KotlinModuleModificationTracker(module) } -data class ModuleProductionSourceInfo(override val module: Module) : ModuleSourceInfo { +data class ModuleProductionSourceInfo internal constructor(override val module: Module) : ModuleSourceInfo { override val name = Name.special("") override fun contentScope(): GlobalSearchScope = ModuleProductionSourceScope(module) @@ -128,7 +131,7 @@ data class ModuleProductionSourceInfo(override val module: Module) : ModuleSourc } //TODO: (module refactoring) do not create ModuleTestSourceInfo when there are no test roots for module -data class ModuleTestSourceInfo(override val module: Module) : ModuleSourceInfo { +data class ModuleTestSourceInfo internal constructor(override val module: Module) : ModuleSourceInfo { override val name = Name.special("") override val displayedName get() = module.name + " (test)" @@ -142,7 +145,9 @@ data class ModuleTestSourceInfo(override val module: Module) : ModuleSourceInfo }) override fun modulesWhoseInternalsAreVisible() = module.cached(CachedValueProvider { - val list = SmartList(module.productionSourceInfo()) + val list = SmartList() + + list.addIfNotNull(module.productionSourceInfo()) TestModuleProperties.getInstance(module).productionModule?.let { list.add(it.productionSourceInfo()) @@ -154,8 +159,16 @@ data class ModuleTestSourceInfo(override val module: Module) : ModuleSourceInfo internal fun ModuleSourceInfo.isTests() = this is ModuleTestSourceInfo -fun Module.productionSourceInfo(): ModuleProductionSourceInfo = ModuleProductionSourceInfo(this) -fun Module.testSourceInfo(): ModuleTestSourceInfo = ModuleTestSourceInfo(this) +fun Module.productionSourceInfo(): ModuleProductionSourceInfo? = if (hasProductionRoots()) ModuleProductionSourceInfo(this) else null +fun Module.testSourceInfo(): ModuleTestSourceInfo? = if (hasTestRoots()) ModuleTestSourceInfo(this) else null + +internal fun Module.correspondingModuleInfos(): List = listOf(testSourceInfo(), productionSourceInfo()).filterNotNull() + +private fun Module.hasProductionRoots() = hasRootsOfType(JavaSourceRootType.SOURCE) +private fun Module.hasTestRoots() = hasRootsOfType(JavaSourceRootType.TEST_SOURCE) + +private fun Module.hasRootsOfType(sourceRootType: JavaSourceRootType): Boolean = + rootManager.contentEntries.any { it.getSourceFolders(sourceRootType).isNotEmpty() } private abstract class ModuleSourceScope(val module: Module) : GlobalSearchScope(module.project), GlobalSearchScopeWithModuleSources { override fun compare(file1: VirtualFile, file2: VirtualFile) = 0 @@ -266,7 +279,7 @@ data class SdkInfo(val project: Project, val sdk: Sdk) : IdeaModuleInfo { override fun dependencies(): List = listOf(this) } -internal object NotUnderContentRootModuleInfo : IdeaModuleInfo { +object NotUnderContentRootModuleInfo : IdeaModuleInfo { override val moduleOrigin: ModuleOrigin get() = ModuleOrigin.OTHER diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ModuleDependencyMapper.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ModuleDependencyMapper.kt index a0211cd194f..01511ccdd8a 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ModuleDependencyMapper.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ModuleDependencyMapper.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.caches.resolve import com.intellij.openapi.components.service +import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.ProjectJdkTable @@ -102,7 +103,7 @@ fun createModuleResolverProvider( fun collectAllModuleInfosFromIdeaModel(project: Project): List { val ideaModules = ModuleManager.getInstance(project).modules.toList() - val modulesSourcesInfos = ideaModules.flatMap { listOf(it.productionSourceInfo(), it.testSourceInfo()) } + val modulesSourcesInfos = ideaModules.flatMap(Module::correspondingModuleInfos) //TODO: (module refactoring) include libraries that are not among dependencies of any module val ideaLibraries = ideaModules.flatMap { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/moduleDependents.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/moduleDependents.kt index 765db6451d9..8ccff5bec88 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/moduleDependents.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/moduleDependents.kt @@ -33,10 +33,10 @@ import java.util.HashSet fun ModuleSourceInfo.getDependentModules(): Set { val dependents = getDependents(module) return if (isTests()) { - dependents.mapTo(HashSet(), Module::testSourceInfo) + dependents.mapNotNullTo(HashSet(), Module::testSourceInfo) } else { - dependents.flatMapTo(HashSet()) { listOf(it.productionSourceInfo(), it.testSourceInfo()) } + dependents.flatMapTo(HashSet()) { it.correspondingModuleInfos() } } } diff --git a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt index 28b36a3e099..9a582d5c824 100644 --- a/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt +++ b/idea/idea-repl/src/org/jetbrains/kotlin/console/KotlinConsoleRunner.kt @@ -54,8 +54,7 @@ import org.jetbrains.kotlin.console.gutter.ConsoleIndicatorRenderer import org.jetbrains.kotlin.console.gutter.IconWithTooltip import org.jetbrains.kotlin.console.gutter.ReplIcons import org.jetbrains.kotlin.idea.KotlinLanguage -import org.jetbrains.kotlin.idea.caches.resolve.ModuleTestSourceInfo -import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.* import org.jetbrains.kotlin.idea.project.KOTLIN_CONSOLE_KEY import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.name.Name @@ -160,7 +159,7 @@ class KotlinConsoleRunner( private fun enableCompletion(consoleView: LanguageConsoleView) { val consoleKtFile = PsiManager.getInstance(project).findFile(consoleView.virtualFile) as? KtFile ?: return - consoleKtFile.moduleInfo = ModuleTestSourceInfo(module) + configureFileDependencies(consoleKtFile) } override fun createProcessHandler(process: Process): OSProcessHandler { @@ -268,7 +267,7 @@ class KotlinConsoleRunner( ?: error("Failed to setup PSI for file:\n$text") replState.submitLine(psiFile) - psiFile.moduleInfo = ModuleTestSourceInfo(module) + configureFileDependencies(psiFile) val scriptDescriptor = psiFile.script!!.resolveToDescriptor() as? LazyScriptDescriptor ?: error("Failed to analyze line:\n$text") ForceResolveUtil.forceResolveAllContents(scriptDescriptor) replState.lineSuccess(psiFile, scriptDescriptor) @@ -282,4 +281,8 @@ class KotlinConsoleRunner( val consoleFile = consoleView.virtualFile return PsiManager.getInstance(project).findFile(consoleFile) as KtFile } + + private fun configureFileDependencies(psiFile: KtFile) { + psiFile.moduleInfo = module.testSourceInfo() ?: module.productionSourceInfo() ?: NotUnderContentRootModuleInfo + } } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfoTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfoTest.kt index 983a0f854ed..44cfabfd840 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfoTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfoTest.kt @@ -21,7 +21,9 @@ import com.intellij.openapi.module.StdModuleTypes import com.intellij.openapi.roots.DependencyScope import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.testFramework.ModuleTestCase +import com.intellij.testFramework.PsiTestUtil import com.intellij.testFramework.UsefulTestCase import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime import org.jetbrains.kotlin.idea.framework.CommonLibraryKind @@ -271,6 +273,33 @@ class IdeaModuleInfoTest : ModuleTestCase() { lib3.classes.assertAdditionalLibraryDependencies(lib1.classes, lib2.classes) } + fun testRoots() { + val a = module("a", hasProductionRoot = true, hasTestRoot = false) + + val empty = module("empty", hasProductionRoot = false, hasTestRoot = false) + a.addDependency(empty) + + val b = module("b", hasProductionRoot = false, hasTestRoot = true) + b.addDependency(a) + + val c = module("c") + c.addDependency(b) + c.addDependency(a) + + assertNotNull(a.productionSourceInfo()) + assertNull(a.testSourceInfo()) + + assertNull(empty.productionSourceInfo()) + assertNull(empty.testSourceInfo()) + + assertNull(b.productionSourceInfo()) + assertNotNull(b.testSourceInfo()) + + b.test.assertDependenciesEqual(b.test, a.production) + c.test.assertDependenciesEqual(c.test, c.production, b.test, a.production) + c.production.assertDependenciesEqual(c.production, a.production) + } + fun testCommonLibraryDoesNotDependOnPlatform() { val stdlibCommon = stdlibCommon() val stdlibJvm = stdlibJvm() @@ -296,18 +325,25 @@ class IdeaModuleInfoTest : ModuleTestCase() { ) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported) private val Module.production: ModuleProductionSourceInfo - get() = productionSourceInfo() + get() = productionSourceInfo()!! private val Module.test: ModuleTestSourceInfo - get() = testSourceInfo() + get() = testSourceInfo()!! private val Library.classes: LibraryInfo get() = LibraryInfo(project!!, this) - private fun module(name: String): Module { - return createModuleFromTestData(createTempDirectory()!!.absolutePath, name, StdModuleTypes.JAVA, false)!! + private fun module(name: String, hasProductionRoot: Boolean = true, hasTestRoot: Boolean = true): Module { + return createModuleFromTestData(createTempDirectory()!!.absolutePath, name, StdModuleTypes.JAVA, false)!!.apply { + if (hasProductionRoot) + PsiTestUtil.addSourceContentToRoots(this, dir(), false) + if (hasTestRoot) + PsiTestUtil.addSourceContentToRoots(this, dir(), true) + } } + private fun dir() = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(createTempDirectory())!! + private fun modules(name1: String = "a", name2: String = "b", name3: String = "c") = Triple(module(name1), module(name2), module(name3)) private fun IdeaModuleInfo.assertDependenciesEqual(vararg expected: IdeaModuleInfo) { diff --git a/idea/tests/org/jetbrains/kotlin/idea/repl/AbstractIdeReplCompletionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/repl/AbstractIdeReplCompletionTest.kt index b732554569b..647a6098017 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/repl/AbstractIdeReplCompletionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/repl/AbstractIdeReplCompletionTest.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.repl import com.intellij.codeInsight.completion.CompletionType import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess import com.intellij.psi.PsiDocumentManager import com.intellij.testFramework.LightProjectDescriptor import org.jetbrains.kotlin.console.KotlinConsoleKeeper @@ -28,6 +29,7 @@ import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescrip import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform +import org.jetbrains.kotlin.test.KotlinTestUtils import java.io.File abstract class AbstractIdeReplCompletionTest : KotlinFixtureCompletionBaseTestCase() { @@ -36,9 +38,11 @@ abstract class AbstractIdeReplCompletionTest : KotlinFixtureCompletionBaseTestCa override fun setUp() { super.setUp() consoleRunner = KotlinConsoleKeeper.getInstance(project).run(myModule)!! + VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory()) } override fun tearDown() { + VfsRootAccess.disallowRootAccess(KotlinTestUtils.getHomeDirectory()) consoleRunner?.dispose() consoleRunner = null super.tearDown()