Recreate module descriptors for affected modules only on a change

This commit is contained in:
Dmitry Jemerov
2017-07-27 16:54:52 +02:00
parent 07794c8188
commit bdbe134b60
13 changed files with 262 additions and 40 deletions
@@ -18,7 +18,9 @@ package org.jetbrains.kotlin.analyzer
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.LanguageVersionSettings
@@ -85,9 +87,17 @@ class ResolverForProjectImpl<M : ModuleInfo>(
private val packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty }, private val packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty },
private val firstDependency: M? = null, private val firstDependency: M? = null,
private val modulePlatforms: (M) -> MultiTargetPlatform?, private val modulePlatforms: (M) -> MultiTargetPlatform?,
private val packageOracleFactory: PackageOracleFactory = PackageOracleFactory.OptimisticFactory private val packageOracleFactory: PackageOracleFactory = PackageOracleFactory.OptimisticFactory,
private val invalidateOnOOCB: Boolean = true
) : ResolverForProject<M>() { ) : ResolverForProject<M>() {
private val descriptorByModule = mutableMapOf<M, ModuleDescriptorImpl>()
private class ModuleData(
val moduleDescriptor: ModuleDescriptorImpl,
val modificationTracker: ModificationTracker?,
val modificationCount: Long?
)
private val descriptorByModule = mutableMapOf<M, ModuleData>()
private val moduleInfoByDescriptor = mutableMapOf<ModuleDescriptorImpl, M>() private val moduleInfoByDescriptor = mutableMapOf<ModuleDescriptorImpl, M>()
val modules = modules.toSet() val modules = modules.toSet()
@@ -156,18 +166,42 @@ class ResolverForProjectImpl<M : ModuleInfo>(
private fun doGetDescriptorForModule(module: M): ModuleDescriptorImpl { private fun doGetDescriptorForModule(module: M): ModuleDescriptorImpl {
if (module in modules) { if (module in modules) {
return projectContext.storageManager.compute { return projectContext.storageManager.compute {
descriptorByModule.getOrPut(module) { var moduleData = descriptorByModule.getOrPut(module) {
ModuleDescriptorImpl(module.name, createModuleDescriptor(module)
projectContext.storageManager, builtIns, modulePlatforms(module), module.capabilities).apply {
moduleInfoByDescriptor[this] = module
setupModuleDescriptor(module, this)
}
} }
val currentModCount = moduleData.modificationTracker?.modificationCount
if (currentModCount != null && currentModCount > moduleData.modificationCount!!) {
moduleData = recreateModuleDescriptor(module)
}
moduleData.moduleDescriptor
} }
} }
return delegateResolver.descriptorForModule(module) as ModuleDescriptorImpl return delegateResolver.descriptorForModule(module) as ModuleDescriptorImpl
} }
private fun recreateModuleDescriptor(module: M): ModuleData {
val oldDescriptor = descriptorByModule[module]?.moduleDescriptor
if (oldDescriptor != null) {
oldDescriptor.isValid = false
moduleInfoByDescriptor.remove(oldDescriptor)
resolverByModuleDescriptor.remove(oldDescriptor)
}
val moduleData = createModuleDescriptor(module)
descriptorByModule[module] = moduleData
return moduleData
}
private fun createModuleDescriptor(module: M): ModuleData {
val moduleDescriptor = ModuleDescriptorImpl(module.name,
projectContext.storageManager, builtIns, modulePlatforms(module), module.capabilities)
moduleInfoByDescriptor[moduleDescriptor] = module
setupModuleDescriptor(module, moduleDescriptor)
val modificationTracker = (module as? TrackableModuleInfo)?.createModificationTracker() ?:
(PsiModificationTracker.SERVICE.getInstance(projectContext.project).outOfCodeBlockModificationTracker.takeIf { invalidateOnOOCB })
return ModuleData(moduleDescriptor, modificationTracker, modificationTracker?.modificationCount)
}
} }
data class ModuleContent( data class ModuleContent(
@@ -207,6 +241,10 @@ interface ModuleInfo {
} }
} }
interface TrackableModuleInfo : ModuleInfo {
fun createModificationTracker(): ModificationTracker
}
abstract class AnalyzerFacade { abstract class AnalyzerFacade {
abstract fun <M : ModuleInfo> createResolverForModule( abstract fun <M : ModuleInfo> createResolverForModule(
moduleInfo: M, moduleInfo: M,
@@ -48,4 +48,6 @@ interface ModuleDescriptor : DeclarationDescriptor {
class Capability<T>(val name: String) { class Capability<T>(val name: String) {
override fun toString() = name override fun toString() = name
} }
val isValid: Boolean
} }
@@ -47,6 +47,14 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
private var dependencies: ModuleDependencies? = null private var dependencies: ModuleDependencies? = null
private var packageFragmentProviderForModuleContent: PackageFragmentProvider? = null private var packageFragmentProviderForModuleContent: PackageFragmentProvider? = null
override var isValid: Boolean = true
private fun assertValid() {
if (!isValid) {
throw IllegalStateException("Accessing invalid module descriptor $this")
}
}
private val packages = storageManager.createMemoizedFunction<FqName, PackageViewDescriptor> { private val packages = storageManager.createMemoizedFunction<FqName, PackageViewDescriptor> {
fqName: FqName -> LazyPackageViewDescriptorImpl(this, fqName, storageManager) fqName: FqName -> LazyPackageViewDescriptorImpl(this, fqName, storageManager)
} }
@@ -60,9 +68,13 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
override val allImplementingModules: Set<ModuleDescriptor> override val allImplementingModules: Set<ModuleDescriptor>
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allImplementingModules get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allImplementingModules
override fun getPackage(fqName: FqName): PackageViewDescriptor = packages(fqName) override fun getPackage(fqName: FqName): PackageViewDescriptor {
assertValid()
return packages(fqName)
}
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> { override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> {
assertValid()
return packageFragmentProvider.getSubPackagesOf(fqName, nameFilter) return packageFragmentProvider.getSubPackagesOf(fqName, nameFilter)
} }
@@ -117,7 +129,10 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
} }
val packageFragmentProvider: PackageFragmentProvider val packageFragmentProvider: PackageFragmentProvider
get() = packageFragmentProviderForWholeModuleWithDependencies get() {
assertValid()
return packageFragmentProviderForWholeModuleWithDependencies
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T> getCapability(capability: ModuleDescriptor.Capability<T>) = capabilities[capability] as? T override fun <T> getCapability(capability: ModuleDescriptor.Capability<T>) = capabilities[capability] as? T
@@ -122,6 +122,11 @@ public class ErrorUtils {
public KotlinBuiltIns getBuiltIns() { public KotlinBuiltIns getBuiltIns() {
return DefaultBuiltIns.getInstance(); return DefaultBuiltIns.getInstance();
} }
@Override
public boolean isValid() {
return false;
}
}; };
} }
@@ -23,6 +23,7 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.* import com.intellij.openapi.roots.*
import com.intellij.openapi.roots.impl.libraries.LibraryEx import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.util.ModificationTracker
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValueProvider
@@ -30,9 +31,11 @@ import com.intellij.psi.util.CachedValuesManager
import com.intellij.util.PathUtil import com.intellij.util.PathUtil
import com.intellij.util.SmartList import com.intellij.util.SmartList
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.TrackableModuleInfo
import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.framework.getLibraryPlatform import org.jetbrains.kotlin.idea.framework.getLibraryPlatform
import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected
@@ -97,7 +100,7 @@ private fun ideaModelDependencies(module: Module, productionOnly: Boolean): List
return result.toList() return result.toList()
} }
interface ModuleSourceInfo : IdeaModuleInfo { interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo {
val module: Module val module: Module
override val displayedName get() = module.name override val displayedName get() = module.name
@@ -107,6 +110,9 @@ interface ModuleSourceInfo : IdeaModuleInfo {
override val platform: TargetPlatform override val platform: TargetPlatform
get() = TargetPlatformDetector.getPlatform(module) get() = TargetPlatformDetector.getPlatform(module)
override fun createModificationTracker(): ModificationTracker =
KotlinModuleModificationTracker(module)
} }
data class ModuleProductionSourceInfo(override val module: Module) : ModuleSourceInfo { data class ModuleProductionSourceInfo(override val module: Module) : ModuleSourceInfo {
@@ -125,6 +125,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
LibraryModificationTracker.getInstance(project), LibraryModificationTracker.getInstance(project),
ProjectRootModificationTracker.getInstance(project) ProjectRootModificationTracker.getInstance(project)
), ),
invalidateOnOOCB = false,
reuseDataFrom = null reuseDataFrom = null
) )
@@ -134,6 +135,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
project, librariesContext, settings, project, librariesContext, settings,
reuseDataFrom = facadeForSdk, reuseDataFrom = facadeForSdk,
moduleFilter = { it is LibraryInfo }, moduleFilter = { it is LibraryInfo },
invalidateOnOOCB = false,
dependencies = listOf( dependencies = listOf(
LibraryModificationTracker.getInstance(project), LibraryModificationTracker.getInstance(project),
ProjectRootModificationTracker.getInstance(project) ProjectRootModificationTracker.getInstance(project)
@@ -146,7 +148,10 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
project, modulesContext, settings, project, modulesContext, settings,
reuseDataFrom = facadeForLibraries, reuseDataFrom = facadeForLibraries,
moduleFilter = { !it.isLibraryClasses() }, moduleFilter = { !it.isLibraryClasses() },
dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) dependencies = listOf(
LibraryModificationTracker.getInstance(project),
ProjectRootModificationTracker.getInstance(project)
)
) )
} }
@@ -52,7 +52,8 @@ fun createModuleResolverProvider(
moduleFilter: (IdeaModuleInfo) -> Boolean, moduleFilter: (IdeaModuleInfo) -> Boolean,
allModules: Collection<IdeaModuleInfo>?, allModules: Collection<IdeaModuleInfo>?,
providedBuiltIns: KotlinBuiltIns?, // null means create new builtins based on SDK providedBuiltIns: KotlinBuiltIns?, // null means create new builtins based on SDK
dependencies: Collection<Any> dependencies: Collection<Any>,
invalidateOnOOCB: Boolean = true
): ModuleResolverProvider { ): ModuleResolverProvider {
val builtIns = providedBuiltIns ?: createBuiltIns(analysisSettings, globalContext) val builtIns = providedBuiltIns ?: createBuiltIns(analysisSettings, globalContext)
@@ -82,7 +83,8 @@ fun createModuleResolverProvider(
delegateResolver, { _, c -> IDEPackagePartProvider(c.moduleContentScope) }, delegateResolver, { _, c -> IDEPackagePartProvider(c.moduleContentScope) },
analysisSettings.sdk?.let { SdkInfo(project, it) }, analysisSettings.sdk?.let { SdkInfo(project, it) },
modulePlatforms = { module -> module.platform?.multiTargetPlatform }, modulePlatforms = { module -> module.platform?.multiTargetPlatform },
packageOracleFactory = project.service<IdePackageOracleFactory>() packageOracleFactory = project.service<IdePackageOracleFactory>(),
invalidateOnOOCB = invalidateOnOOCB
) )
if (providedBuiltIns == null && builtIns is JvmBuiltIns) { if (providedBuiltIns == null && builtIns is JvmBuiltIns) {
@@ -30,14 +30,15 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.CompositeBindingContext import org.jetbrains.kotlin.resolve.CompositeBindingContext
internal class ProjectResolutionFacade( internal class ProjectResolutionFacade(
val debugString: String, private val debugString: String,
val resolverDebugName: String, private val resolverDebugName: String,
val project: Project, val project: Project,
val globalContext: GlobalContextImpl, val globalContext: GlobalContextImpl,
val settings: PlatformAnalysisSettings, val settings: PlatformAnalysisSettings,
val reuseDataFrom: ProjectResolutionFacade?, val reuseDataFrom: ProjectResolutionFacade?,
val moduleFilter: (IdeaModuleInfo) -> Boolean, val moduleFilter: (IdeaModuleInfo) -> Boolean,
val dependencies: List<Any>, val dependencies: List<Any>,
private val invalidateOnOOCB: Boolean = true,
val syntheticFiles: Collection<KtFile> = listOf(), val syntheticFiles: Collection<KtFile> = listOf(),
val allModules: Collection<IdeaModuleInfo>? = null // null means create resolvers for modules from idea model val allModules: Collection<IdeaModuleInfo>? = null // null means create resolvers for modules from idea model
) { ) {
@@ -61,11 +62,12 @@ internal class ProjectResolutionFacade(
delegateResolver = delegateResolverForProject, moduleFilter = moduleFilter, delegateResolver = delegateResolverForProject, moduleFilter = moduleFilter,
allModules = allModules, allModules = allModules,
providedBuiltIns = delegateResolverProvider?.builtIns, providedBuiltIns = delegateResolverProvider?.builtIns,
dependencies = dependencies dependencies = dependencies,
invalidateOnOOCB = invalidateOnOOCB
) )
} }
val moduleResolverProvider: ModuleResolverProvider private val moduleResolverProvider: ModuleResolverProvider
get() = globalContext.storageManager.compute { cachedValue.value } get() = globalContext.storageManager.compute { cachedValue.value }
fun resolverForModuleInfo(moduleInfo: IdeaModuleInfo) = moduleResolverProvider.resolverForProject.resolverForModule(moduleInfo) fun resolverForModuleInfo(moduleInfo: IdeaModuleInfo) = moduleResolverProvider.resolverForProject.resolverForModule(moduleInfo)
@@ -16,8 +16,12 @@
package org.jetbrains.kotlin.idea.project package org.jetbrains.kotlin.idea.project
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.ModificationTracker
import com.intellij.pom.PomManager import com.intellij.pom.PomManager
import com.intellij.pom.PomModelAspect import com.intellij.pom.PomModelAspect
import com.intellij.pom.event.PomModelEvent import com.intellij.pom.event.PomModelEvent
@@ -28,6 +32,7 @@ import com.intellij.psi.PsiCodeFragment
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.impl.PsiModificationTrackerImpl import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.CommonProcessors
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.isAncestor
@@ -41,10 +46,27 @@ class KotlinCodeBlockModificationListener(
project: Project, project: Project,
private val treeAspect: TreeAspect private val treeAspect: TreeAspect
) { ) {
private val perModuleModCount = mutableMapOf<Module, Long>()
private val modificationTrackerImpl = modificationTracker as PsiModificationTrackerImpl
private var lastAffectedModule: Module? = null
private var lastAffectedModuleModCount = -1L
// All modifications since that count are known to be single-module modifications reflected in
// perModuleModCount map
private var perModuleChangesHighwatermark: Long? = null
fun getModificationCount(module: Module): Long {
return perModuleModCount[module] ?:
perModuleChangesHighwatermark ?:
modificationTrackerImpl.outOfCodeBlockModificationCount
}
fun hasPerModuleModificationCounts() = perModuleChangesHighwatermark != null
init { init {
val model = PomManager.getModel(project) val model = PomManager.getModel(project)
@Suppress("NAME_SHADOWING") val messageBusConnection = project.messageBus.connect()
val modificationTracker = modificationTracker as PsiModificationTrackerImpl
model.addModelListener(object: PomModelListener { model.addModelListener(object: PomModelListener {
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean { override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
return aspect == treeAspect return aspect == treeAspect
@@ -58,13 +80,31 @@ class KotlinCodeBlockModificationListener(
// contents to be replaced, which is represented in a POM event as an empty list of changed elements // contents to be replaced, which is represented in a POM event as an empty list of changed elements
if (changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null } || if (changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null } ||
(file is PsiCodeFragment && changedElements.isEmpty())) { (file is PsiCodeFragment && changedElements.isEmpty())) {
messageBusConnection.deliverImmediately()
if (file.isPhysical) { if (file.isPhysical) {
modificationTracker.incCounter() lastAffectedModule = ModuleUtil.findModuleForPsiElement(file)
lastAffectedModuleModCount = modificationTrackerImpl.outOfCodeBlockModificationCount
modificationTrackerImpl.incCounter()
} }
incOutOfBlockModificationCount(file) incOutOfBlockModificationCount(file)
} }
} }
}) })
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
val newModCount = modificationTrackerImpl.outOfCodeBlockModificationCount
val affectedModule = lastAffectedModule
if (affectedModule != null && newModCount == lastAffectedModuleModCount + 1) {
if (perModuleChangesHighwatermark== null) {
perModuleChangesHighwatermark = lastAffectedModuleModCount
}
perModuleModCount[affectedModule] = newModCount
}
else {
perModuleChangesHighwatermark = null
perModuleModCount.clear()
}
})
} }
companion object { companion object {
@@ -117,6 +157,8 @@ class KotlinCodeBlockModificationListener(
KtProperty::class.java, KtProperty::class.java,
KtNamedFunction::class.java KtNamedFunction::class.java
) )
fun getInstance(project: Project) = project.getComponent(KotlinCodeBlockModificationListener::class.java)
} }
} }
@@ -124,3 +166,31 @@ private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_
val KtFile.outOfBlockModificationCount: Long val KtFile.outOfBlockModificationCount: Long
get() = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0 get() = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
class KotlinModuleModificationTracker(val module: Module): ModificationTracker {
private val kotlinModCountListener = KotlinCodeBlockModificationListener.getInstance(module.project)
private val psiModificationTracker = PsiModificationTracker.SERVICE.getInstance(module.project)
private val dependencies by lazy {
HashSet<Module>().apply {
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule(
CommonProcessors.CollectProcessor(this))
}
}
override fun getModificationCount(): Long {
val currentGlobalCount = psiModificationTracker.outOfCodeBlockModificationCount
if (kotlinModCountListener.hasPerModuleModificationCounts()) {
val selfCount = kotlinModCountListener.getModificationCount(module)
if (selfCount == currentGlobalCount) return selfCount
var maxCount = selfCount
for (dependency in dependencies) {
val depCount = kotlinModCountListener.getModificationCount(dependency)
if (depCount == currentGlobalCount) return currentGlobalCount
if (depCount > maxCount) maxCount = depCount
}
return maxCount
}
return currentGlobalCount
}
}
@@ -0,0 +1,4 @@
package foo.bar.used
public fun m1() {
}
@@ -0,0 +1,5 @@
import foo.bar.used.*
public fun m2() {
m1()
}
@@ -17,8 +17,16 @@
package org.jetbrains.kotlin.idea.caches.resolve package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.facet.FacetManager import com.intellij.facet.FacetManager
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.module.Module import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.DependencyScope import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.ResolverForModuleComputationTracker import org.jetbrains.kotlin.analyzer.ResolverForModuleComputationTracker
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
@@ -30,8 +38,12 @@ import org.jetbrains.kotlin.idea.completion.test.withServiceRegistered
import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration
import org.jetbrains.kotlin.idea.facet.KotlinFacetType import org.jetbrains.kotlin.idea.facet.KotlinFacetType
import org.jetbrains.kotlin.idea.framework.JSLibraryKind import org.jetbrains.kotlin.idea.framework.JSLibraryKind
import org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.projectStructure.sdk
import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK
open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
@@ -66,20 +78,9 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
} }
fun testLazyResolvers() { fun testLazyResolvers() {
val resolversComputed = mutableSetOf<Module>() val tracker = ResolverTracker()
val resolversTracker = object : ResolverForModuleComputationTracker { project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(tracker) {
override fun onResolverComputed(moduleInfo: ModuleInfo) {
(moduleInfo as IdeaModuleInfo).let {
if (it is ModuleSourceInfo) {
val module = it.module
resolversComputed.add(module)
}
}
}
}
project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(resolversTracker) {
val module1 = module("m1") val module1 = module("m1")
val module2 = module("m2") val module2 = module("m2")
val module3 = module("m3") val module3 = module("m3")
@@ -87,15 +88,82 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
module3.addDependency(module2) module3.addDependency(module2)
module3.addDependency(module1) module3.addDependency(module1)
assertTrue(module1 !in resolversComputed) assertTrue(module1 !in tracker.moduleResolversComputed)
assertTrue(module2 !in resolversComputed) assertTrue(module2 !in tracker.moduleResolversComputed)
assertTrue(module3 !in resolversComputed) assertTrue(module3 !in tracker.moduleResolversComputed)
checkHighlightingInAllFiles { "m3" in file.name } checkHighlightingInAllFiles { "m3" in file.name }
assertTrue(module1 in resolversComputed) assertTrue(module1 in tracker.moduleResolversComputed)
assertTrue(module2 !in resolversComputed) assertTrue(module2 !in tracker.moduleResolversComputed)
assertTrue(module3 in resolversComputed) assertTrue(module3 in tracker.moduleResolversComputed)
}
}
class ResolverTracker : ResolverForModuleComputationTracker {
val moduleResolversComputed = mutableListOf<Module>()
val sdkResolversComputed = mutableListOf<Sdk>()
override fun onResolverComputed(moduleInfo: ModuleInfo) {
when (moduleInfo) {
is ModuleSourceInfo -> moduleResolversComputed.add(moduleInfo.module)
is SdkInfo -> sdkResolversComputed.add(moduleInfo.sdk)
}
}
}
fun testRecomputeResolversOnChange() {
val tracker = ResolverTracker()
project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(tracker) {
val module1 = module("m1")
val module2 = module("m2")
val module3 = module("m3")
module2.addDependency(module1)
module3.addDependency(module2)
// Ensure modules have the same SDK instance, and not two distinct SDKs with the same path
ModuleRootModificationUtil.setModuleSdk(module2, module1.sdk)
assertEquals(0, tracker.sdkResolversComputed.size)
checkHighlightingInAllFiles { "m2" in file.name }
assertEquals(2, tracker.moduleResolversComputed.size)
tracker.sdkResolversComputed.clear()
tracker.moduleResolversComputed.clear()
val module1ModCount = KotlinCodeBlockModificationListener.getInstance(myProject).getModificationCount(module1)
val module1ModTracker = KotlinModuleModificationTracker(module1)
val module2ModTracker = KotlinModuleModificationTracker(module2)
val module3ModTracker = KotlinModuleModificationTracker(module3)
val contentRoot = ModuleRootManager.getInstance(module2).contentRoots.single()
val m2 = contentRoot.findChild("m2.kt")!!
val m2doc = FileDocumentManager.getInstance().getDocument(m2)!!
project.executeWriteCommand("a") {
m2doc.insertString(m2doc.textLength , "fun foo() = 1")
PsiDocumentManager.getInstance(myProject).commitAllDocuments()
}
val currentModCount = PsiManager.getInstance(project).modificationTracker.outOfCodeBlockModificationCount
assertEquals(module1ModCount, KotlinCodeBlockModificationListener.getInstance(myProject).getModificationCount(module1))
assertEquals(module1ModCount, module1ModTracker.modificationCount)
assertEquals(currentModCount, module2ModTracker.modificationCount)
assertEquals(currentModCount, module3ModTracker.modificationCount)
checkHighlightingInAllFiles { "m2" in file.name }
assertEquals(0, tracker.sdkResolversComputed.size)
assertEquals(1, tracker.moduleResolversComputed.size)
tracker.moduleResolversComputed.clear()
(PsiModificationTracker.SERVICE.getInstance(myProject) as PsiModificationTrackerImpl).incOutOfCodeBlockModificationCounter()
checkHighlightingInAllFiles { "m2" in file.name }
assertEquals(0, tracker.sdkResolversComputed.size)
assertEquals(2, tracker.moduleResolversComputed.size)
} }
} }