diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinFirOutOfBlockModificationTracker.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinFirOutOfBlockModificationTracker.kt index 33e1797ce82..41f4b5e058b 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinFirOutOfBlockModificationTracker.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinFirOutOfBlockModificationTracker.kt @@ -18,6 +18,7 @@ import com.intellij.pom.event.PomModelEvent import com.intellij.pom.event.PomModelListener import com.intellij.pom.tree.TreeAspect import com.intellij.pom.tree.events.TreeChangeEvent +import com.intellij.psi.PsiElement import com.intellij.psi.impl.source.tree.FileElement import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.KotlinLanguage @@ -68,16 +69,24 @@ internal class KotlinFirModificationTrackerService(project: Project) : Disposabl override fun modelChanged(event: PomModelEvent) { val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return - if (changeSet.rootElement.psi.language != KotlinLanguage.INSTANCE) return + val psi = changeSet.rootElement.psi + if (psi.language != KotlinLanguage.INSTANCE) return val changedElements = changeSet.changedElements - handleChangedElementsInAllModules(changedElements, changeSet) + handleChangedElementsInAllModules(changedElements, changeSet, psi) } private fun handleChangedElementsInAllModules( changedElements: Array, - changeSet: TreeChangeEvent + changeSet: TreeChangeEvent, + changeSetRootElementPsi: PsiElement ) { + if (!changeSetRootElementPsi.isPhysical) { + /** + * Element which do not belong to a project should not cause OOBM + */ + return + } if (changedElements.isEmpty()) { incrementModificationCountForFileChange(changeSet) } else { diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinModuleOutOfBlockTrackerTest.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinModuleOutOfBlockTrackerTest.kt index d34b3d764f8..8e14d7ec2a4 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinModuleOutOfBlockTrackerTest.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinModuleOutOfBlockTrackerTest.kt @@ -7,6 +7,8 @@ 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.project.Project +import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiDocumentManager @@ -15,11 +17,14 @@ 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 org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.trackers.KotlinOutOfBlockModificationTrackerFactory import org.jetbrains.kotlin.trackers.createModuleWithoutDependenciesOutOfBlockModificationTracker +import org.jetbrains.kotlin.trackers.createProjectWideOutOfBlockModificationTracker import java.nio.file.Files import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.writeText @@ -84,6 +89,40 @@ class KotlinModuleOutOfBlockTrackerTest : AbstractMultiModuleTest() { ) } + fun testThatNonPhysicalFileChangeNotCausingBOOM() { + val moduleA = createModuleWithModificationTracker("a") { + listOf( + FileWithText("main.kt", "fun main() {}") + ) + } + val moduleB = createModuleWithModificationTracker("b") + + val moduleAWithTracker = ModuleWithModificationTracker(moduleA) + val moduleBWithTracker = ModuleWithModificationTracker(moduleB) + + + val projectWithModificationTracker = ProjectWithModificationTracker(project) + + runWriteAction { + val nonPhysicalPsi = KtPsiFactory(moduleA.project).createFile("nonPhysical", "val a = c") + nonPhysicalPsi.add(KtPsiFactory(moduleA.project).createFunction("fun x(){}")) + } + + Assert.assertFalse( + "Out of block modification count for module A should not change after non physical file change, modification count is ${moduleAWithTracker.modificationCount}", + moduleAWithTracker.changed() + ) + Assert.assertFalse( + "Out of block modification count for module B should not change after non physical file change, modification count is ${moduleBWithTracker.modificationCount}", + moduleBWithTracker.changed() + ) + + Assert.assertFalse( + "Out of block modification count for project should not change after non physical file change, modification count is ${projectWithModificationTracker.modificationCount}", + projectWithModificationTracker.changed() + ) + } + private fun Module.typeInFunctionBody(fileName: String, textAfterTyping: String) { val file = "${sourceRoots.first().url}/$fileName" val virtualFile = VirtualFileManager.getInstance().findFileByUrl(file)!! @@ -119,14 +158,19 @@ class KotlinModuleOutOfBlockTrackerTest : AbstractMultiModuleTest() { private data class FileWithText(val name: String, val text: String) - private class ModuleWithModificationTracker(module: Module) { - private val modificationTracker = module.createModuleWithoutDependenciesOutOfBlockModificationTracker() + abstract class WithModificationTracker(protected val modificationTracker: ModificationTracker) { private val initialModificationCount = modificationTracker.modificationCount - - val modificationCount: Long - get() = modificationTracker.modificationCount + val modificationCount: Long get() = modificationTracker.modificationCount fun changed(): Boolean = modificationTracker.modificationCount != initialModificationCount } + + private class ModuleWithModificationTracker(module: Module) : WithModificationTracker( + module.createModuleWithoutDependenciesOutOfBlockModificationTracker() + ) + + private class ProjectWithModificationTracker(project: Project) : WithModificationTracker( + project.createProjectWideOutOfBlockModificationTracker() + ) } \ No newline at end of file