FIR IDE: introduce out of block modification tracker tests
This commit is contained in:
+3
-10
@@ -13,7 +13,9 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
|
|||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
|
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider
|
import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.util.findSourceNonLocalFirDeclaration
|
import org.jetbrains.kotlin.idea.fir.low.level.api.util.findSourceNonLocalFirDeclaration
|
||||||
|
import org.jetbrains.kotlin.idea.search.getKotlinFqName
|
||||||
import org.jetbrains.kotlin.idea.util.getElementTextInContext
|
import org.jetbrains.kotlin.idea.util.getElementTextInContext
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||||
@@ -57,7 +59,7 @@ internal class FileStructure(
|
|||||||
ktFile.forEachDescendantOfType<KtDeclaration>(
|
ktFile.forEachDescendantOfType<KtDeclaration>(
|
||||||
canGoInside = { psi -> psi !is KtFunction && psi !is KtValVarKeywordOwner }
|
canGoInside = { psi -> psi !is KtFunction && psi !is KtValVarKeywordOwner }
|
||||||
) { declaration ->
|
) { declaration ->
|
||||||
if (declaration.isStructureElementContainer()) {
|
if (FileStructureUtil.isStructureElementContainer(declaration)) {
|
||||||
add(declaration)
|
add(declaration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,12 +106,3 @@ internal class FileStructure(
|
|||||||
else -> error("Invalid container $container")
|
else -> error("Invalid container $container")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtDeclaration.isStructureElementContainer(): Boolean {
|
|
||||||
if (this !is KtClassOrObject && this !is KtDeclarationWithBody && this !is KtProperty && this !is KtTypeAlias) return false
|
|
||||||
if (this is KtEnumEntry) return false
|
|
||||||
if (containingClassOrObject is KtEnumEntry) return false
|
|
||||||
return !KtPsiUtil.isLocal(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.fir.low.level.api.file.structure
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
|
|
||||||
|
internal object FileStructureUtil {
|
||||||
|
fun isStructureElementContainer(ktDeclaration: KtDeclaration): Boolean = when {
|
||||||
|
ktDeclaration !is KtClassOrObject && ktDeclaration !is KtDeclarationWithBody && ktDeclaration !is KtProperty && ktDeclaration !is KtTypeAlias -> false
|
||||||
|
ktDeclaration is KtEnumEntry -> false
|
||||||
|
ktDeclaration.containingClassOrObject is KtEnumEntry -> false
|
||||||
|
else -> !KtPsiUtil.isLocal(ktDeclaration)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -104,7 +104,7 @@ private class FirSessionWithModificationTracker(
|
|||||||
val firSession: FirIdeSourcesSession,
|
val firSession: FirIdeSourcesSession,
|
||||||
) {
|
) {
|
||||||
private val modificationTracker = firSession.project.service<KotlinFirOutOfBlockModificationTrackerFactory>()
|
private val modificationTracker = firSession.project.service<KotlinFirOutOfBlockModificationTrackerFactory>()
|
||||||
.createModuleOutOfBlockModificationTracker(firSession.moduleInfo.module)
|
.createModuleWithoutDependenciesOutOfBlockModificationTracker(firSession.moduleInfo.module)
|
||||||
|
|
||||||
private val timeStamp = modificationTracker.modificationCount
|
private val timeStamp = modificationTracker.modificationCount
|
||||||
|
|
||||||
|
|||||||
+48
-21
@@ -20,40 +20,42 @@ import com.intellij.pom.tree.TreeAspect
|
|||||||
import com.intellij.pom.tree.events.TreeChangeEvent
|
import com.intellij.pom.tree.events.TreeChangeEvent
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.getNonLocalContainingInBodyDeclarationWith
|
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.getNonLocalContainingInBodyDeclarationWith
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.getNonLocalContainingOrThisDeclaration
|
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileElementFactory
|
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileElementFactory
|
||||||
import org.jetbrains.kotlin.idea.util.module
|
import org.jetbrains.kotlin.idea.util.module
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal class KotlinFirModificationTrackerService(project: Project) : Disposable {
|
internal class KotlinFirModificationTrackerService(project: Project) : Disposable {
|
||||||
init {
|
init {
|
||||||
val model = PomManager.getModel(project)
|
PomManager.getModel(project).addModelListener(Listener())
|
||||||
model.addModelListener(Listener())
|
|
||||||
|
|
||||||
val connection = project.messageBus.connect(this)
|
project.messageBus.connect(this).subscribe(
|
||||||
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
ProjectTopics.PROJECT_ROOTS,
|
||||||
override fun rootsChanged(event: ModuleRootEvent) {
|
object : ModuleRootListener {
|
||||||
projectGlobalOutOfBlockInKotlinFilesModificationCount++
|
override fun rootsChanged(event: ModuleRootEvent) = increaseModificationCountForAllModules()
|
||||||
|
|
||||||
// todo increase modificationCountForModule
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal var projectGlobalOutOfBlockInKotlinFilesModificationCount = 0L
|
var projectGlobalOutOfBlockInKotlinFilesModificationCount = 0L
|
||||||
private set
|
private set
|
||||||
|
|
||||||
internal fun getOutOfBlockModificationCountForModules(module: Module): Long =
|
private val moduleModificationsState = ModuleModificationsState()
|
||||||
modificationCountForModule[module] ?: 0L
|
|
||||||
|
fun getOutOfBlockModificationCountForModules(module: Module): Long =
|
||||||
|
moduleModificationsState.getModificationsCountForModule(module)
|
||||||
|
|
||||||
private val modificationCountForModule = WeakHashMap<Module, Long>()
|
|
||||||
private val treeAspect = TreeAspect.getInstance(project)
|
private val treeAspect = TreeAspect.getInstance(project)
|
||||||
|
|
||||||
override fun dispose() {}
|
override fun dispose() {}
|
||||||
|
|
||||||
|
private fun increaseModificationCountForAllModules() {
|
||||||
|
projectGlobalOutOfBlockInKotlinFilesModificationCount++
|
||||||
|
moduleModificationsState.increaseModificationCountForAllModules()
|
||||||
|
}
|
||||||
|
|
||||||
private inner class Listener : PomModelListener {
|
private inner class Listener : PomModelListener {
|
||||||
|
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean =
|
||||||
|
treeAspect == aspect
|
||||||
|
|
||||||
override fun modelChanged(event: PomModelEvent) {
|
override fun modelChanged(event: PomModelEvent) {
|
||||||
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
||||||
if (changeSet.rootElement.psi.language != KotlinLanguage.INSTANCE) return
|
if (changeSet.rootElement.psi.language != KotlinLanguage.INSTANCE) return
|
||||||
@@ -66,7 +68,7 @@ internal class KotlinFirModificationTrackerService(project: Project) : Disposabl
|
|||||||
isOutOfBlockChangeInAnyModule = isOutOfBlockChangeInAnyModule || isOutOfBlock
|
isOutOfBlockChangeInAnyModule = isOutOfBlockChangeInAnyModule || isOutOfBlock
|
||||||
if (isOutOfBlock) {
|
if (isOutOfBlock) {
|
||||||
element.psi.module?.let { module ->
|
element.psi.module?.let { module ->
|
||||||
modificationCountForModule.compute(module) { _, value -> (value ?: 0) + 1 }
|
moduleModificationsState.increaseModificationCountForModule(module)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,8 +86,33 @@ internal class KotlinFirModificationTrackerService(project: Project) : Disposabl
|
|||||||
!FileElementFactory.isReanalyzableContainer(container)
|
!FileElementFactory.isReanalyzableContainer(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean =
|
|
||||||
treeAspect == aspect
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModuleModificationsState {
|
||||||
|
private val modificationCountForModule = hashMapOf<Module, ModuleModifications>()
|
||||||
|
private var state: Long = 0L
|
||||||
|
|
||||||
|
fun getModificationsCountForModule(module: Module) = modificationCountForModule.compute(module) { _, modifications ->
|
||||||
|
when {
|
||||||
|
modifications == null -> ModuleModifications(0, state)
|
||||||
|
modifications.state == state -> modifications
|
||||||
|
else -> ModuleModifications(modificationsCount = modifications.modificationsCount + 1, state = state)
|
||||||
|
}
|
||||||
|
}!!.modificationsCount
|
||||||
|
|
||||||
|
fun increaseModificationCountForAllModules() {
|
||||||
|
state++
|
||||||
|
}
|
||||||
|
|
||||||
|
fun increaseModificationCountForModule(module: Module) {
|
||||||
|
modificationCountForModule.compute(module) { _, modifications ->
|
||||||
|
when (modifications) {
|
||||||
|
null -> ModuleModifications(0, state)
|
||||||
|
else -> ModuleModifications(ModuleModifications(0, state).modificationsCount + 1, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ModuleModifications(val modificationsCount: Long, val state: Long)
|
||||||
}
|
}
|
||||||
+7
-2
@@ -14,11 +14,16 @@ class KotlinFirOutOfBlockModificationTrackerFactory(private val project: Project
|
|||||||
fun createProjectWideOutOfBlockModificationTracker(): ModificationTracker =
|
fun createProjectWideOutOfBlockModificationTracker(): ModificationTracker =
|
||||||
KotlinFirOutOfBlockModificationTracker(project)
|
KotlinFirOutOfBlockModificationTracker(project)
|
||||||
|
|
||||||
fun createModuleOutOfBlockModificationTracker(module: Module): ModificationTracker =
|
fun createModuleWithoutDependenciesOutOfBlockModificationTracker(module: Module): ModificationTracker =
|
||||||
KotlinFirOutOfBlockModuleModificationTracker(module)
|
KotlinFirOutOfBlockModuleModificationTracker(module)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Project.createProjectWideOutOfBlockModificationTracker() =
|
||||||
|
service<KotlinFirOutOfBlockModificationTrackerFactory>().createProjectWideOutOfBlockModificationTracker()
|
||||||
|
|
||||||
|
fun Module.createModuleWithoutDependenciesOutOfBlockModificationTracker() =
|
||||||
|
project.service<KotlinFirOutOfBlockModificationTrackerFactory>().createModuleWithoutDependenciesOutOfBlockModificationTracker(this)
|
||||||
|
|
||||||
private class KotlinFirOutOfBlockModificationTracker(project: Project) : ModificationTracker {
|
private class KotlinFirOutOfBlockModificationTracker(project: Project) : ModificationTracker {
|
||||||
private val trackerService = project.service<KotlinFirModificationTrackerService>()
|
private val trackerService = project.service<KotlinFirModificationTrackerService>()
|
||||||
|
|
||||||
|
|||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.fir.low.level.api.trackers
|
||||||
|
|
||||||
|
import com.intellij.openapi.command.WriteCommandAction
|
||||||
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.openapi.vfs.LocalFileSystem
|
||||||
|
import com.intellij.openapi.vfs.VirtualFileManager
|
||||||
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
|
import com.intellij.testFramework.PsiTestUtil
|
||||||
|
import junit.framework.Assert
|
||||||
|
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.idea.util.rootManager
|
||||||
|
import org.jetbrains.kotlin.idea.util.sourceRoots
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
|
import java.nio.file.Files
|
||||||
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
|
import kotlin.io.path.writeText
|
||||||
|
|
||||||
|
class KotlinModuleOutOfBlockTrackerTest : AbstractMultiModuleTest() {
|
||||||
|
override fun getTestDataPath(): String = error("Should not be called")
|
||||||
|
|
||||||
|
fun testThatModuleOutOfBlockChangeInfluenceOnlySingleModule() {
|
||||||
|
val moduleA = createModuleWithModificationTracker("a") {
|
||||||
|
listOf(
|
||||||
|
FileWithText("main.kt", "fun main() = 10")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val moduleB = createModuleWithModificationTracker("b")
|
||||||
|
val moduleC = createModuleWithModificationTracker("c")
|
||||||
|
|
||||||
|
|
||||||
|
val moduleAWithTracker = ModuleWithModificationTracker(moduleA)
|
||||||
|
val moduleBWithTracker = ModuleWithModificationTracker(moduleB)
|
||||||
|
val moduleCWithTracker = ModuleWithModificationTracker(moduleC)
|
||||||
|
|
||||||
|
moduleA.typeInFunctionBody("main.kt", textAfterTyping = "fun main() = hello10")
|
||||||
|
|
||||||
|
Assert.assertTrue(
|
||||||
|
"Out of block modification count for module A with out of block should change after typing, modification count is ${moduleAWithTracker.modificationCount}",
|
||||||
|
moduleAWithTracker.changed()
|
||||||
|
)
|
||||||
|
Assert.assertFalse(
|
||||||
|
"Out of block modification count for module B without out of block should not change after typing, modification count is ${moduleBWithTracker.modificationCount}",
|
||||||
|
moduleBWithTracker.changed()
|
||||||
|
)
|
||||||
|
Assert.assertFalse(
|
||||||
|
"Out of block modification count for module C without out of block should not change after typing, modification count is ${moduleCWithTracker.modificationCount}",
|
||||||
|
moduleCWithTracker.changed()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testThatInEveryModuleOutOfBlockWillHappenAfterContentRootChange() {
|
||||||
|
val moduleA = createModuleWithModificationTracker("a")
|
||||||
|
val moduleB = createModuleWithModificationTracker("b")
|
||||||
|
val moduleC = createModuleWithModificationTracker("c")
|
||||||
|
|
||||||
|
val moduleAWithTracker = ModuleWithModificationTracker(moduleA)
|
||||||
|
val moduleBWithTracker = ModuleWithModificationTracker(moduleB)
|
||||||
|
val moduleCWithTracker = ModuleWithModificationTracker(moduleC)
|
||||||
|
|
||||||
|
runWriteAction {
|
||||||
|
moduleA.sourceRoots.first().createChildData(/* requestor = */ null, "file.kt")
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertTrue(
|
||||||
|
"Out of block modification count for module A should change after content root change, modification count is ${moduleAWithTracker.modificationCount}",
|
||||||
|
moduleAWithTracker.changed()
|
||||||
|
)
|
||||||
|
Assert.assertTrue(
|
||||||
|
"Out of block modification count for module B should change after content root change, modification count is ${moduleBWithTracker.modificationCount}",
|
||||||
|
moduleBWithTracker.changed()
|
||||||
|
)
|
||||||
|
Assert.assertTrue(
|
||||||
|
"Out of block modification count for module C should change after content root change modification count is ${moduleCWithTracker.modificationCount}",
|
||||||
|
moduleCWithTracker.changed()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Module.typeInFunctionBody(fileName: String, textAfterTyping: String) {
|
||||||
|
val file = "${sourceRoots.first().url}/$fileName"
|
||||||
|
val virtualFile = VirtualFileManager.getInstance().findFileByUrl(file)!!
|
||||||
|
val ktFile = PsiManager.getInstance(project).findFile(virtualFile) as KtFile
|
||||||
|
configureByExistingFile(virtualFile)
|
||||||
|
|
||||||
|
val singleFunction = ktFile.declarations.single() as KtNamedFunction
|
||||||
|
|
||||||
|
editor.caretModel.moveToOffset(singleFunction.bodyExpression!!.textOffset)
|
||||||
|
type("hello")
|
||||||
|
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||||
|
Assert.assertEquals(textAfterTyping, ktFile.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPathApi::class)
|
||||||
|
private fun createModuleWithModificationTracker(
|
||||||
|
name: String,
|
||||||
|
createFiles: () -> List<FileWithText> = { emptyList() },
|
||||||
|
): Module {
|
||||||
|
val tmpDir = createTempDirectory().toPath()
|
||||||
|
createFiles().forEach { file ->
|
||||||
|
Files.createFile(tmpDir.resolve(file.name)).writeText(file.text)
|
||||||
|
}
|
||||||
|
val module: Module = createModule("$tmpDir/$name", moduleType)
|
||||||
|
val root = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tmpDir.toFile())!!
|
||||||
|
WriteCommandAction.writeCommandAction(module.project).run<RuntimeException> {
|
||||||
|
root.refresh(false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
PsiTestUtil.addSourceContentToRoots(module, root)
|
||||||
|
return module
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class FileWithText(val name: String, val text: String)
|
||||||
|
|
||||||
|
private class ModuleWithModificationTracker(module: Module) {
|
||||||
|
private val modificationTracker = module.createModuleWithoutDependenciesOutOfBlockModificationTracker()
|
||||||
|
private val initialModificationCount = modificationTracker.modificationCount
|
||||||
|
|
||||||
|
val modificationCount: Long
|
||||||
|
get() = modificationTracker.modificationCount
|
||||||
|
|
||||||
|
fun changed(): Boolean =
|
||||||
|
modificationTracker.modificationCount != initialModificationCount
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user