diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 1b339c0f600..ea49712ab5a 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -86,6 +86,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleLazyRes import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureTest import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest +import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.AbstractProjectWideOutOfBlockKotlinModificationTrackerTest import org.jetbrains.kotlin.idea.frontend.api.scopes.AbstractMemberScopeByFqNameTest import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolFromLibraryPointerRestoreTest import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolsByFqNameBuildingTest @@ -1037,6 +1038,9 @@ fun main(args: Array) { testClass { model("lazyResolve") } + testClass { + model("outOfBlockProjectWide") + } testClass { model("fileStructure") } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeResolveStateService.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeResolveStateService.kt index 66d3303041d..111ed2d5f0b 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeResolveStateService.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeResolveStateService.kt @@ -7,21 +7,13 @@ package org.jetbrains.kotlin.idea.fir.low.level.api import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import com.intellij.openapi.util.ModificationTracker -import com.intellij.psi.util.CachedValue -import com.intellij.psi.util.CachedValueProvider -import com.intellij.psi.util.CachedValuesManager import org.jetbrains.annotations.TestOnly -import org.jetbrains.kotlin.fir.dependenciesWithoutSelf import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo -import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver -import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSessionFactory -import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSessionProvider import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSessionProviderStorage -import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSourcesSession +import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.KotlinFirOutOfBlockModificationTrackerFactory import org.jetbrains.kotlin.idea.util.cachedValue import org.jetbrains.kotlin.idea.util.getValue import java.util.concurrent.ConcurrentHashMap @@ -29,7 +21,10 @@ import java.util.concurrent.ConcurrentHashMap internal class FirIdeResolveStateService(project: Project) { private val sessionProviderStorage = FirIdeSessionProviderStorage(project) - private val stateCache by cachedValue(project, KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker) { + private val stateCache by cachedValue( + project, + project.service().createProjectWideOutOfBlockModificationTracker(), + ) { ConcurrentHashMap() } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/element/builder/FirElementBuilder.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/element/builder/FirElementBuilder.kt index e61bc0475ab..7821d2cbe36 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/element/builder/FirElementBuilder.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/element/builder/FirElementBuilder.kt @@ -68,7 +68,8 @@ private fun KtElement.getFirOfClosestParent(cache: Map): return null } -fun KtElement.getNonLocalContainingOrThisDeclaration(): KtNamedDeclaration? { + +internal inline fun PsiElement.getNonLocalContainingOrThisDeclaration(predicate: (KtDeclaration) -> Boolean = { true }): KtNamedDeclaration? { var container: PsiElement? = this while (container != null && container !is KtFile) { if (container is KtNamedDeclaration @@ -77,10 +78,19 @@ fun KtElement.getNonLocalContainingOrThisDeclaration(): KtNamedDeclaration? { && !KtPsiUtil.isLocal(container) && container !is KtEnumEntry && container.containingClassOrObject !is KtEnumEntry + && predicate(container) ) { return container } container = container.parent } return null -} \ No newline at end of file +} + +internal fun PsiElement.getNonLocalContainingInBodyDeclarationWith(): KtNamedDeclaration? = + getNonLocalContainingOrThisDeclaration { declaration -> + when (declaration) { + is KtNamedFunction -> declaration.bodyExpression?.isAncestor(this) == true + else -> false + } + } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionProviderStorage.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionProviderStorage.kt index 0a676323168..6c84e5ceb3d 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionProviderStorage.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/sessions/FirIdeSessionProviderStorage.kt @@ -5,12 +5,13 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.sessions +import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import org.jetbrains.kotlin.fir.BuiltinTypes import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo -import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleOutOfCodeBlockModificationTracker import org.jetbrains.kotlin.idea.fir.low.level.api.FirPhaseRunner import org.jetbrains.kotlin.idea.fir.low.level.api.FirTransformerProvider +import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.KotlinFirOutOfBlockModificationTrackerFactory import org.jetbrains.kotlin.idea.fir.low.level.api.util.executeWithoutPCE import java.util.concurrent.ConcurrentHashMap @@ -102,7 +103,9 @@ private class FromModuleViewSessionCache( private class FirSessionWithModificationTracker( val firSession: FirIdeSourcesSession, ) { - private val modificationTracker = KotlinModuleOutOfCodeBlockModificationTracker(firSession.moduleInfo.module) + private val modificationTracker = firSession.project.service() + .createModuleOutOfBlockModificationTracker(firSession.moduleInfo.module) + private val timeStamp = modificationTracker.modificationCount @Volatile 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 new file mode 100644 index 00000000000..696fdfe14c4 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinFirOutOfBlockModificationTracker.kt @@ -0,0 +1,91 @@ +/* + * 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.ProjectTopics +import com.intellij.lang.ASTNode +import com.intellij.openapi.Disposable +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModuleRootEvent +import com.intellij.openapi.roots.ModuleRootListener +import com.intellij.pom.PomManager +import com.intellij.pom.PomModelAspect +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 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.getNonLocalContainingOrThisDeclaration +import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileElementFactory +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 { + init { + val model = PomManager.getModel(project) + model.addModelListener(Listener()) + + val connection = project.messageBus.connect(this) + connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener { + override fun rootsChanged(event: ModuleRootEvent) { + projectGlobalOutOfBlockInKotlinFilesModificationCount++ + + // todo increase modificationCountForModule + } + }) + } + + internal var projectGlobalOutOfBlockInKotlinFilesModificationCount = 0L + private set + + internal fun getOutOfBlockModificationCountForModules(module: Module): Long = + modificationCountForModule[module] ?: 0L + + private val modificationCountForModule = WeakHashMap() + private val treeAspect = TreeAspect.getInstance(project) + + override fun dispose() {} + + private inner class Listener : PomModelListener { + override fun modelChanged(event: PomModelEvent) { + val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return + if (changeSet.rootElement.psi.language != KotlinLanguage.INSTANCE) return + val changedElements = changeSet.changedElements + + var isOutOfBlockChangeInAnyModule = false + + changedElements.forEach { element -> + val isOutOfBlock = element.isOutOfBlockChange(changeSet) + isOutOfBlockChangeInAnyModule = isOutOfBlockChangeInAnyModule || isOutOfBlock + if (isOutOfBlock) { + element.psi.module?.let { module -> + modificationCountForModule.compute(module) { _, value -> (value ?: 0) + 1 } + } + } + } + + if (isOutOfBlockChangeInAnyModule) { + projectGlobalOutOfBlockInKotlinFilesModificationCount++ + } + } + + private fun ASTNode.isOutOfBlockChange(changeSet: TreeChangeEvent): Boolean { + val nodes = changeSet.getChangesByElement(this).affectedChildren + return nodes.any { node -> + val psi = node.psi ?: return@any true + val container = psi.getNonLocalContainingInBodyDeclarationWith() ?: return@any true + !FileElementFactory.isReanalyzableContainer(container) + } + } + + override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean = + treeAspect == aspect + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinFirOutOfBlockModificationTrackerFactory.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinFirOutOfBlockModificationTrackerFactory.kt new file mode 100644 index 00000000000..c3e676801d8 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/KotlinFirOutOfBlockModificationTrackerFactory.kt @@ -0,0 +1,34 @@ +/* + * 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.components.service +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.ModificationTracker + +class KotlinFirOutOfBlockModificationTrackerFactory(private val project: Project) { + fun createProjectWideOutOfBlockModificationTracker(): ModificationTracker = + KotlinFirOutOfBlockModificationTracker(project) + + fun createModuleOutOfBlockModificationTracker(module: Module): ModificationTracker = + KotlinFirOutOfBlockModuleModificationTracker(module) + +} + +private class KotlinFirOutOfBlockModificationTracker(project: Project) : ModificationTracker { + private val trackerService = project.service() + + override fun getModificationCount(): Long = + trackerService.projectGlobalOutOfBlockInKotlinFilesModificationCount +} + +private class KotlinFirOutOfBlockModuleModificationTracker(private val module: Module) : ModificationTracker { + private val trackerService = module.project.service() + + override fun getModificationCount(): Long = + trackerService.getOutOfBlockModificationCountForModules(module) +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/localFun.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/localFun.kt new file mode 100644 index 00000000000..af38fb8e029 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/localFun.kt @@ -0,0 +1,5 @@ +fun x() { + fun a() = +} + +// OUT_OF_BLOCK: false diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelExpressionBodyFunWithType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelExpressionBodyFunWithType.kt new file mode 100644 index 00000000000..b62e4fefa36 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelExpressionBodyFunWithType.kt @@ -0,0 +1,3 @@ +fun foo(): Int = 42 + +// OUT_OF_BLOCK: false diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelExpressionBodyFunWithoutType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelExpressionBodyFunWithoutType.kt new file mode 100644 index 00000000000..00bc44b5979 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelExpressionBodyFunWithoutType.kt @@ -0,0 +1,3 @@ +fun foo() = 42 + +// OUT_OF_BLOCK: true \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelFunWithType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelFunWithType.kt new file mode 100644 index 00000000000..93458daa087 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelFunWithType.kt @@ -0,0 +1,6 @@ +fun foo(): Int { + println("") + return 10 +} + +// OUT_OF_BLOCK: false \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelUnitFun.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelUnitFun.kt new file mode 100644 index 00000000000..2e345179be7 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelUnitFun.kt @@ -0,0 +1,5 @@ +fun foo() { + println("") +} + +// OUT_OF_BLOCK: false diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotation.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotation.kt new file mode 100644 index 00000000000..c139bb121a2 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotation.kt @@ -0,0 +1,6 @@ +@Ann +fun foo() { + println("") +} + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotationParameter.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotationParameter.kt new file mode 100644 index 00000000000..b207535eb5f --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotationParameter.kt @@ -0,0 +1,6 @@ +@Ann() +fun foo() { + println("") +} + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionModifiers.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionModifiers.kt new file mode 100644 index 00000000000..91a064a5b98 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionModifiers.kt @@ -0,0 +1,5 @@ + fun foo() { + println("") +} + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionName.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionName.kt new file mode 100644 index 00000000000..b1176d98c1c --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionName.kt @@ -0,0 +1,5 @@ +fun foo() { + println("") +} + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionParams.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionParams.kt new file mode 100644 index 00000000000..1498070df38 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionParams.kt @@ -0,0 +1,5 @@ +fun foo() { + println("") +} + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionParamsType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionParamsType.kt new file mode 100644 index 00000000000..bf7f33f6530 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionParamsType.kt @@ -0,0 +1,5 @@ +fun foo(x: In) { + println("") +} + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionReturnType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionReturnType.kt new file mode 100644 index 00000000000..68bf647b51d --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionReturnType.kt @@ -0,0 +1,5 @@ +fun foo(): In { + println("") +} + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionTypeParams.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionTypeParams.kt new file mode 100644 index 00000000000..7ea9b3f3cd9 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionTypeParams.kt @@ -0,0 +1,5 @@ +fun >foo() { + println("") +} + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractReanalyzableFileStructureElementCreationTest.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractReanalyzableFileStructureElementCreationTest.kt new file mode 100644 index 00000000000..69d35919185 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractReanalyzableFileStructureElementCreationTest.kt @@ -0,0 +1,85 @@ +/* + * 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 com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.util.parentOfType +import junit.framework.Assert +import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveStateImpl +import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState +import org.jetbrains.kotlin.idea.fir.low.level.api.api.LowLevelFirApiFacade +import org.jetbrains.kotlin.idea.search.getKotlinFqName +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.util.getElementTextInContext +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import java.io.File + +abstract class AbstractReanalyzableFileStructureElementCreationTest : KotlinLightCodeInsightFixtureTestCase() { + override fun isFirPlugin(): Boolean = true + + fun doTest(path: String) { + val testDataFile = File(path) + val initialFileText = FileUtil.loadFile(testDataFile) + val ktFile = myFixture.configureByText(testDataFile.name, initialFileText) as KtFile + val expectedFqName = + InTextDirectivesUtils.findStringWithPrefixes(initialFileText, STRUCTURE_ELEMENT_FQ_NAME_DIRECTIVE) + ?: error("Please specify // STRUCTURE_ELEMENT directive") + val shouldStructureElementBeRecreated = + InTextDirectivesUtils.getPrefixedBoolean(initialFileText, SHOULD_ELEMENT_BE_RECREATED) + ?: error("Please specify // SHOULD_ELEMENT_BE_RECREATED directive") + + val elementAtCaret = ktFile.findElementAtCaret() + val (initialStructureElement, initialFileStructure, initialModuleResolveState) = getStructureElementForKtElement(elementAtCaret) + Assert.assertEquals(expectedFqName, initialStructureElement.psi.getKotlinFqName()?.asString()) + + myFixture.type("hello") + PsiDocumentManager.getInstance(project).commitAllDocuments() + + val newElementAtCaret = ktFile.findElementAtCaret() + val (newStructureElement, newFileStructure, newModuleResolveState) = getStructureElementForKtElement(newElementAtCaret) + Assert.assertEquals( + "Structure elements should be build by the same KtDeclaration's", + expectedFqName, + newStructureElement.psi.getKotlinFqName()?.asString() + ) + Assert.assertTrue("Structure elements should be different after typing", newStructureElement !== initialStructureElement) + Assert.assertEquals( + "FirModuleResolveState should change only of out of block modification", + shouldStructureElementBeRecreated, + newModuleResolveState !== initialModuleResolveState + ) + Assert.assertEquals( + "FileStructure state should change only of out of block modification", + shouldStructureElementBeRecreated, + initialFileStructure !== newFileStructure + ) + } + + private fun KtFile.findElementAtCaret(): KtElement { + val elementAtCaret = findElementAt(myFixture.caretOffset)!!.parentOfType()!! + if (elementAtCaret is KtDeclaration) { + error("Expected element inside declaration but was\n${elementAtCaret.getElementTextInContext()}") + } + return elementAtCaret + } + + private fun getStructureElementForKtElement(element: KtElement): Triple { + val moduleResolveState = LowLevelFirApiFacade.getResolveStateFor(element) as FirModuleResolveStateImpl + val fileStructure = + moduleResolveState.fileStructureCache.getFileStructure(element.containingKtFile, moduleResolveState.rootModuleSession.cache) + val fileStructureElement = fileStructure.getStructureElementFor(element) + return Triple(fileStructureElement, fileStructure, moduleResolveState) + } + + companion object { + private const val STRUCTURE_ELEMENT_FQ_NAME_DIRECTIVE = "// STRUCTURE_ELEMENT:" + private const val SHOULD_ELEMENT_BE_RECREATED = "// SHOULD_ELEMENT_BE_RECREATED:" + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/ReanalyzableFileStructureElementCreationTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/ReanalyzableFileStructureElementCreationTestGenerated.java new file mode 100644 index 00000000000..9c0d25e4ebe --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/ReanalyzableFileStructureElementCreationTestGenerated.java @@ -0,0 +1,50 @@ +/* + * 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 com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/elementBuilder") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ReanalyzableFileStructureElementCreationTestGenerated extends AbstractReanalyzableFileStructureElementCreationTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInElementBuilder() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/elementBuilder"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("topLevelExpressionBodyFunWithType.kt") + public void testTopLevelExpressionBodyFunWithType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/elementBuilder/topLevelExpressionBodyFunWithType.kt"); + } + + @TestMetadata("topLevelExpressionBodyFunWithoutType.kt") + public void testTopLevelExpressionBodyFunWithoutType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/elementBuilder/topLevelExpressionBodyFunWithoutType.kt"); + } + + @TestMetadata("topLevelFunWithType.kt") + public void testTopLevelFunWithType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/elementBuilder/topLevelFunWithType.kt"); + } + + @TestMetadata("topLevelUnitFun.kt") + public void testTopLevelUnitFun() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/elementBuilder/topLevelUnitFun.kt"); + } +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/AbstractProjectWideOutOfBlockKotlinModificationTrackerTest.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/AbstractProjectWideOutOfBlockKotlinModificationTrackerTest.kt new file mode 100644 index 00000000000..8c35c487bdf --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/AbstractProjectWideOutOfBlockKotlinModificationTrackerTest.kt @@ -0,0 +1,37 @@ +/* + * 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.components.service +import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiDocumentManager +import junit.framework.Assert +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import java.io.File + +abstract class AbstractProjectWideOutOfBlockKotlinModificationTrackerTest : KotlinLightCodeInsightFixtureTestCase() { + override fun isFirPlugin(): Boolean = true + + fun doTest(path: String) { + val testDataFile = File(path) + val fileText = FileUtil.loadFile(testDataFile) + myFixture.configureByText(testDataFile.name, fileText) + val textToType = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// TYPE:") ?: DEFAULT_TEXT_TO_TYPE + val outOfBlock = InTextDirectivesUtils.getPrefixedBoolean(fileText, "// OUT_OF_BLOCK:") + ?: error("Please, specify should out of block change happen or not by `// OUT_OF_BLOCK:` directive") + val tracker = project.service().createProjectWideOutOfBlockModificationTracker() + val initialModificationCount = tracker.modificationCount + myFixture.type(textToType) + PsiDocumentManager.getInstance(project).commitAllDocuments() + val afterTypingModificationCount = tracker.modificationCount + Assert.assertEquals(outOfBlock, initialModificationCount != afterTypingModificationCount) + } + + companion object { + private const val DEFAULT_TEXT_TO_TYPE = "hello" + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/ProjectWideOutOfBlockKotlinModificationTrackerTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/ProjectWideOutOfBlockKotlinModificationTrackerTestGenerated.java new file mode 100644 index 00000000000..ae77d2477b6 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/trackers/ProjectWideOutOfBlockKotlinModificationTrackerTestGenerated.java @@ -0,0 +1,95 @@ +/* + * 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.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ProjectWideOutOfBlockKotlinModificationTrackerTestGenerated extends AbstractProjectWideOutOfBlockKotlinModificationTrackerTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInOutOfBlockProjectWide() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("localFun.kt") + public void testLocalFun() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/localFun.kt"); + } + + @TestMetadata("topLevelExpressionBodyFunWithType.kt") + public void testTopLevelExpressionBodyFunWithType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelExpressionBodyFunWithType.kt"); + } + + @TestMetadata("topLevelExpressionBodyFunWithoutType.kt") + public void testTopLevelExpressionBodyFunWithoutType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelExpressionBodyFunWithoutType.kt"); + } + + @TestMetadata("topLevelFunWithType.kt") + public void testTopLevelFunWithType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelFunWithType.kt"); + } + + @TestMetadata("topLevelUnitFun.kt") + public void testTopLevelUnitFun() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelUnitFun.kt"); + } + + @TestMetadata("typeInFunctionAnnotation.kt") + public void testTypeInFunctionAnnotation() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotation.kt"); + } + + @TestMetadata("typeInFunctionAnnotationParameter.kt") + public void testTypeInFunctionAnnotationParameter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotationParameter.kt"); + } + + @TestMetadata("typeInFunctionModifiers.kt") + public void testTypeInFunctionModifiers() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionModifiers.kt"); + } + + @TestMetadata("typeInFunctionName.kt") + public void testTypeInFunctionName() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionName.kt"); + } + + @TestMetadata("typeInFunctionParams.kt") + public void testTypeInFunctionParams() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionParams.kt"); + } + + @TestMetadata("typeInFunctionParamsType.kt") + public void testTypeInFunctionParamsType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionParamsType.kt"); + } + + @TestMetadata("typeInFunctionReturnType.kt") + public void testTypeInFunctionReturnType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionReturnType.kt"); + } + + @TestMetadata("typeInFunctionTypeParams.kt") + public void testTypeInFunctionTypeParams() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionTypeParams.kt"); + } +} diff --git a/idea/resources-fir/META-INF/plugin.xml b/idea/resources-fir/META-INF/plugin.xml index fefad5ae0a1..418756c6f64 100644 --- a/idea/resources-fir/META-INF/plugin.xml +++ b/idea/resources-fir/META-INF/plugin.xml @@ -111,7 +111,8 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu implementationClass="org.jetbrains.kotlin.idea.codeInsight.KotlinHighLevelExpressionTypeProvider"/> - + +