From 76c0dc7dba68e5c787f21a253b7d87dc17a6e0c1 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Thu, 26 Nov 2020 13:46:58 +0100 Subject: [PATCH] FIR IDE: add property support for incremental analysis --- .../level/api/api/FirModuleResolveState.kt | 1 - .../api/element/builder/FirElementBuilder.kt | 6 +- .../api/file/structure/FileElementFactory.kt | 33 ++++++--- .../level/api/file/structure/FileStructure.kt | 5 +- .../file/structure/FileStructureElement.kt | 70 ++++++++++++------- .../api/file/structure/FileStructureUtil.kt | 36 ++++++++++ .../fileStructure/classMemberProperty.kt | 11 +++ .../testdata/fileStructure/localProperty.kt | 15 ++++ .../fileStructure/topLevelProperty.kt | 9 +++ .../topPropertyWithTypeInGetter.kt | 3 + .../topPropertyWithTypeInGetterOnNextLine.kt | 4 ++ .../topPropertyWithTypeInInititalzer.kt | 3 + .../topPropertyWithTypeInSetter.kt | 4 ++ .../topPropertyWithoutTypeInGetter.kt | 3 + .../topPropertyWithoutTypeInInititalzer.kt | 3 + .../topPropertyWithoutTypeInSetter.kt | 8 +++ ...BlockModificationTrackerConsistencyTest.kt | 10 ++- .../structure/AbstractFileStructureTest.kt | 50 ++++++------- ...cationTrackerConsistencyTestGenerated.java | 35 ++++++++++ .../structure/FileStructureTestGenerated.java | 15 ++++ ...otlinModificationTrackerTestGenerated.java | 35 ++++++++++ 21 files changed, 290 insertions(+), 69 deletions(-) create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/classMemberProperty.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localProperty.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelProperty.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetter.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetterOnNextLine.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInInititalzer.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInSetter.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInGetter.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInInititalzer.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInSetter.kt diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/api/FirModuleResolveState.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/api/FirModuleResolveState.kt index 2399012538a..7b2d1585c34 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/api/FirModuleResolveState.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/api/FirModuleResolveState.kt @@ -23,7 +23,6 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataC import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeSourcesSession -import org.jetbrains.kotlin.idea.fir.low.level.api.util.ktDeclaration import org.jetbrains.kotlin.idea.util.getElementTextInContext import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtElement 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 9e0d633e5b7..fdbc9b84ee3 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 @@ -8,10 +8,7 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.element.builder import com.intellij.psi.PsiElement import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.fir.FirElement -import org.jetbrains.kotlin.fir.declarations.FirResolvePhase -import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver import org.jetbrains.kotlin.idea.fir.low.level.api.annotations.ThreadSafe -import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileStructureCache import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileStructureElement @@ -104,6 +101,9 @@ internal fun PsiElement.getNonLocalContainingInBodyDeclarationWith(): KtNamedDec getNonLocalContainingOrThisDeclaration { declaration -> when (declaration) { is KtNamedFunction -> declaration.bodyExpression?.isAncestor(this) == true + is KtProperty -> declaration.initializer?.isAncestor(this) == true || + declaration.getter?.isAncestor(this) == true || + declaration.setter?.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/file/structure/FileElementFactory.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileElementFactory.kt index d06478a7833..c47f74f7572 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileElementFactory.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileElementFactory.kt @@ -7,9 +7,11 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.file.structure import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtProperty internal object FileElementFactory { /** @@ -20,13 +22,19 @@ internal object FileElementFactory { ktDeclaration: KtDeclaration, firFile: FirFile, ): FileStructureElement = when { - ktDeclaration is KtNamedFunction && ktDeclaration.name != null && ktDeclaration.hasExplicitTypeOrUnit -> - ReanalyzableFunctionStructureElement( - firFile, - ktDeclaration, - (firDeclaration as FirSimpleFunction).symbol, - ktDeclaration.modificationStamp - ) + ktDeclaration is KtNamedFunction && ktDeclaration.isReanalyzableContainer() -> ReanalyzableFunctionStructureElement( + firFile, + ktDeclaration, + (firDeclaration as FirSimpleFunction).symbol, + ktDeclaration.modificationStamp + ) + + ktDeclaration is KtProperty && ktDeclaration.isReanalyzableContainer() -> ReanalyzablePropertyStructureElement( + firFile, + ktDeclaration, + (firDeclaration as FirProperty).symbol, + ktDeclaration.modificationStamp + ) else -> NonReanalyzableDeclarationStructureElement( firFile, @@ -40,11 +48,18 @@ internal object FileElementFactory { */ fun isReanalyzableContainer( ktDeclaration: KtDeclaration, - ): Boolean = when { - ktDeclaration is KtNamedFunction && ktDeclaration.name != null && ktDeclaration.hasExplicitTypeOrUnit -> true + ): Boolean = when (ktDeclaration) { + is KtNamedFunction -> ktDeclaration.isReanalyzableContainer() + is KtProperty -> ktDeclaration.isReanalyzableContainer() else -> false } + private fun KtNamedFunction.isReanalyzableContainer() = + name != null && hasExplicitTypeOrUnit + + private fun KtProperty.isReanalyzableContainer() = + name != null && typeReference != null + private val KtNamedFunction.hasExplicitTypeOrUnit get() = hasBlockBody() || typeReference != null } \ 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/file/structure/FileStructure.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt index 586b1e8fe9e..078179f7d62 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt @@ -36,11 +36,12 @@ internal class FileStructure( } private fun getStructureElementForDeclaration(declaration: KtAnnotated): FileStructureElement { + @Suppress("CANNOT_CHECK_FOR_ERASED") val structureElement = structureElements.compute(declaration) { _, structureElement -> when { structureElement == null -> createStructureElement(declaration) - structureElement is ReanalyzableStructureElement<*> && !structureElement.isUpToDate() -> { - structureElement.reanalyze(declaration as KtNamedFunction, moduleFileCache, firLazyDeclarationResolver, firIdeProvider) + structureElement is ReanalyzableStructureElement && !structureElement.isUpToDate() -> { + structureElement.reanalyze(declaration as KtDeclaration, moduleFileCache, firLazyDeclarationResolver, firIdeProvider) } else -> structureElement } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt index be61b9deecd..dbe74c5d806 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt @@ -11,10 +11,10 @@ import org.jetbrains.kotlin.fir.analysis.collectors.DiagnosticCollectorDeclarati import org.jetbrains.kotlin.fir.containingClass import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.psi -import org.jetbrains.kotlin.fir.realPsi import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.FirIdeStructureElementDiagnosticsCollector import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver @@ -40,16 +40,16 @@ internal sealed class FileStructureElement { } internal sealed class ReanalyzableStructureElement : FileStructureElement() { - abstract override val psi: KT + abstract override val psi: KtDeclaration abstract val firSymbol: AbstractFirBasedSymbol<*> abstract val timestamp: Long /** * Creates new declaration by [newKtDeclaration] which will serve as replacement of [firSymbol] - * Also, modify [firFile] & replace old version of declaration to a new one + * Also, modify [firFile] & replace old version of declaration with a new one */ abstract fun reanalyze( - newKtDeclaration: KtNamedFunction, + newKtDeclaration: KT, cache: ModuleFileCache, firLazyDeclarationResolver: FirLazyDeclarationResolver, firIdeProvider: FirIdeProvider, @@ -75,18 +75,6 @@ internal class ReanalyzableFunctionStructureElement( override val mappings: Map = FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder) - private fun replaceFunction(from: FirSimpleFunction, to: FirSimpleFunction) { - val declarations = if (from.symbol.callableId.className == null) { - firFile.declarations as MutableList - } else { - val classLikeLookupTag = from.containingClass() - ?: error("Class name should not be null for non-top-level & non-local declarations") - val containingClass = classLikeLookupTag.toSymbol(firFile.session)?.fir as FirRegularClass - containingClass.declarations as MutableList - } - declarations.replaceFirst(from, to) - } - override fun reanalyze( newKtDeclaration: KtNamedFunction, cache: ModuleFileCache, @@ -96,12 +84,7 @@ internal class ReanalyzableFunctionStructureElement( val newFunction = firIdeProvider.buildFunctionWithBody(newKtDeclaration) as FirSimpleFunction val originalFunction = firSymbol.fir as FirSimpleFunction - cache.firFileLockProvider.withWriteLock(firFile) { - replaceFunction(originalFunction, newFunction) - } - - //todo remap symbol under firFile write lock - try { + return FileStructureUtil.withDeclarationReplaced(firFile, cache, originalFunction, newFunction) { firLazyDeclarationResolver.lazyResolveDeclaration( newFunction, cache, @@ -109,7 +92,7 @@ internal class ReanalyzableFunctionStructureElement( checkPCE = true, reresolveFile = true, ) - return cache.firFileLockProvider.withReadLock(firFile) { + cache.firFileLockProvider.withReadLock(firFile) { ReanalyzableFunctionStructureElement( firFile, newKtDeclaration, @@ -117,11 +100,44 @@ internal class ReanalyzableFunctionStructureElement( newKtDeclaration.modificationStamp, ) } - } catch (e: Throwable) { - cache.firFileLockProvider.withWriteLock(firFile) { - replaceFunction(newFunction, originalFunction) + } + } +} + +internal class ReanalyzablePropertyStructureElement( + override val firFile: FirFile, + override val psi: KtProperty, + override val firSymbol: FirPropertySymbol, + override val timestamp: Long +) : ReanalyzableStructureElement() { + override val mappings: Map = + FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder) + + override fun reanalyze( + newKtDeclaration: KtProperty, + cache: ModuleFileCache, + firLazyDeclarationResolver: FirLazyDeclarationResolver, + firIdeProvider: FirIdeProvider, + ): ReanalyzablePropertyStructureElement { + val newProperty = firIdeProvider.buildPropertyWithBody(newKtDeclaration) + val originalProperty = firSymbol.fir + + return FileStructureUtil.withDeclarationReplaced(firFile, cache, originalProperty, newProperty) { + firLazyDeclarationResolver.lazyResolveDeclaration( + newProperty, + cache, + FirResolvePhase.BODY_RESOLVE, + checkPCE = true, + reresolveFile = true, + ) + cache.firFileLockProvider.withReadLock(firFile) { + ReanalyzablePropertyStructureElement( + firFile, + newKtDeclaration, + newProperty.symbol, + newKtDeclaration.modificationStamp, + ) } - throw e } } } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureUtil.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureUtil.kt index d3f7b1bd765..b3ff3c5aadb 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureUtil.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureUtil.kt @@ -5,6 +5,14 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.file.structure +import org.jetbrains.kotlin.fir.containingClass +import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration +import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache +import org.jetbrains.kotlin.idea.fir.low.level.api.util.replaceFirst import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject @@ -15,4 +23,32 @@ internal object FileStructureUtil { ktDeclaration.containingClassOrObject is KtEnumEntry -> false else -> !KtPsiUtil.isLocal(ktDeclaration) } + + fun replaceDeclaration(firFile: FirFile, from: FirCallableDeclaration<*>, to: FirCallableDeclaration<*>) { + val declarations = if (from.symbol.callableId.className == null) { + firFile.declarations as MutableList + } else { + val classLikeLookupTag = from.containingClass() + ?: error("Class name should not be null for non-top-level & non-local declarations") + val containingClass = classLikeLookupTag.toSymbol(firFile.session)?.fir as FirRegularClass + containingClass.declarations as MutableList + } + declarations.replaceFirst(from, to) + } + + inline fun withDeclarationReplaced( + firFile: FirFile, + cache: ModuleFileCache, + from: FirCallableDeclaration<*>, + to: FirCallableDeclaration<*>, + action: () -> R, + ): R { + cache.firFileLockProvider.withWriteLock(firFile) { replaceDeclaration(firFile, from, to) } + return try { + action() + } catch (e: Throwable) { + cache.firFileLockProvider.withWriteLock(firFile) { replaceDeclaration(firFile, to, from) } + throw e + } + } } \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/classMemberProperty.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/classMemberProperty.kt new file mode 100644 index 00000000000..81221eecbae --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/classMemberProperty.kt @@ -0,0 +1,11 @@ +class X {/* NonReanalyzableDeclarationStructureElement */ + var x: Int/* ReanalyzablePropertyStructureElement */ + get() = field + set(value) { + field = value + } + + val y = 42/* NonReanalyzableDeclarationStructureElement */ + + var z: Int = 15/* ReanalyzablePropertyStructureElement */ +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localProperty.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localProperty.kt new file mode 100644 index 00000000000..654d56f3ac9 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localProperty.kt @@ -0,0 +1,15 @@ +fun foo() {/* ReanalyzableFunctionStructureElement */ + var x: Int +} +class A {/* NonReanalyzableDeclarationStructureElement */ + fun q() {/* ReanalyzableFunctionStructureElement */ + val y = 42 + } +} +class B {/* NonReanalyzableDeclarationStructureElement */ + class C {/* NonReanalyzableDeclarationStructureElement */ + fun u() {/* ReanalyzableFunctionStructureElement */ + var z: Int = 15 + } + } +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelProperty.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelProperty.kt new file mode 100644 index 00000000000..120de847eef --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelProperty.kt @@ -0,0 +1,9 @@ +var x: Int/* ReanalyzablePropertyStructureElement */ + get() = field + set(value) { + field = value + } + +val y = 42/* NonReanalyzableDeclarationStructureElement */ + +var z: Int = 15/* ReanalyzablePropertyStructureElement */ \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetter.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetter.kt new file mode 100644 index 00000000000..5a5dc8777a1 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetter.kt @@ -0,0 +1,3 @@ +val x: Int get() = y + +// OUT_OF_BLOCK: false diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetterOnNextLine.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetterOnNextLine.kt new file mode 100644 index 00000000000..5db82bd97b7 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetterOnNextLine.kt @@ -0,0 +1,4 @@ +val x: Int + get() = y + +// OUT_OF_BLOCK: false diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInInititalzer.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInInititalzer.kt new file mode 100644 index 00000000000..f5d54824464 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInInititalzer.kt @@ -0,0 +1,3 @@ +val x: Int = y + +// OUT_OF_BLOCK: false diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInSetter.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInSetter.kt new file mode 100644 index 00000000000..4c8e7b42452 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInSetter.kt @@ -0,0 +1,4 @@ +val x: Int + set(value) = y + +// OUT_OF_BLOCK: false diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInGetter.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInGetter.kt new file mode 100644 index 00000000000..e4d46a5e98c --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInGetter.kt @@ -0,0 +1,3 @@ +val x get() = y + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInInititalzer.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInInititalzer.kt new file mode 100644 index 00000000000..5b5cce7d867 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInInititalzer.kt @@ -0,0 +1,3 @@ +val x = y + +// OUT_OF_BLOCK: true diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInSetter.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInSetter.kt new file mode 100644 index 00000000000..2723c109c1c --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInSetter.kt @@ -0,0 +1,8 @@ +val x + get() = 1 + set(value) { + + } + +// OUT_OF_BLOCK: true +// TODO should not be out of block diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest.kt index e0c900d83f4..4b06426a9f1 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest.kt @@ -7,6 +7,7 @@ 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.PsiWhiteSpace import com.intellij.psi.util.parentOfType import junit.framework.Assert import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveStateImpl @@ -53,8 +54,13 @@ abstract class AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyT ) } - private fun KtFile.findElementAtCaret(): KtElement = - findElementAt(myFixture.caretOffset)!!.parentOfType()!! + private fun KtFile.findElementAtCaret(): KtElement { + val element = when (val elementAtOffset = findElementAt(myFixture.caretOffset)) { + is PsiWhiteSpace -> findElementAt((myFixture.caretOffset - 1).coerceAtLeast(0)) + else -> elementAtOffset + } + return element!!.parentOfType()!! + } private fun getStructureElementForKtElement(element: KtElement): Triple { val moduleResolveState = element.getResolveState() as FirModuleResolveStateImpl diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureTest.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureTest.kt index 5c2d246e575..c130c2980fd 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureTest.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureTest.kt @@ -8,20 +8,12 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.file.structure import com.intellij.openapi.application.runUndoTransparentWriteAction import com.intellij.openapi.util.io.FileUtil import com.intellij.psi.PsiComment -import com.intellij.psi.PsiDocumentManager import com.intellij.psi.util.collectDescendantsOfType import com.intellij.psi.util.forEachDescendantOfType -import com.intellij.psi.util.parentOfType -import junit.framework.Assert -import org.jetbrains.kotlin.fir.render import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveStateImpl import org.jetbrains.kotlin.idea.fir.low.level.api.api.getResolveState -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.* -import org.jetbrains.kotlin.resolve.calls.callUtil.isFakeElement -import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.KotlinTestUtils import java.io.File @@ -40,23 +32,31 @@ abstract class AbstractFileStructureTest : KotlinLightCodeInsightFixtureTestCase ktFile.forEachDescendantOfType { ktDeclaration -> val structureElement = declarationToStructureElement[ktDeclaration] ?: return@forEachDescendantOfType val comment = structureElement.createComment() - when (ktDeclaration) { - is KtClassOrObject -> { - val lBrace = ktDeclaration.body?.lBrace - if (lBrace != null) { - ktDeclaration.body!!.addAfter(comment, lBrace) - } else { - ktDeclaration.parent.addAfter(comment, ktDeclaration) - } - } - is KtFunction -> { - val lBrace = ktDeclaration.bodyBlockExpression?.lBrace - if (lBrace != null) { - ktDeclaration.bodyBlockExpression!!.addAfter(comment, lBrace) - } else { - ktDeclaration.parent.addAfter(comment, ktDeclaration) - } - } + when (ktDeclaration) { + is KtClassOrObject -> { + val lBrace = ktDeclaration.body?.lBrace + if (lBrace != null) { + ktDeclaration.body!!.addAfter(comment, lBrace) + } else { + ktDeclaration.parent.addAfter(comment, ktDeclaration) + } + } + is KtFunction -> { + val lBrace = ktDeclaration.bodyBlockExpression?.lBrace + if (lBrace != null) { + ktDeclaration.bodyBlockExpression!!.addAfter(comment, lBrace) + } else { + ktDeclaration.parent.addAfter(comment, ktDeclaration) + } + } + is KtProperty -> { + val initializerOrTypeReference = ktDeclaration.initializer ?: ktDeclaration.typeReference + if (initializerOrTypeReference != null) { + ktDeclaration.addAfter(comment, initializerOrTypeReference) + } else { + ktDeclaration.parent.addAfter(comment, ktDeclaration) + } + } else -> error("Unsupported declaration $ktDeclaration") } } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureAndOutOfBlockModificationTrackerConsistencyTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureAndOutOfBlockModificationTrackerConsistencyTestGenerated.java index 82efa737118..5f2d976ca41 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureAndOutOfBlockModificationTrackerConsistencyTestGenerated.java +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureAndOutOfBlockModificationTrackerConsistencyTestGenerated.java @@ -53,6 +53,41 @@ public class FileStructureAndOutOfBlockModificationTrackerConsistencyTestGenerat runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelUnitFun.kt"); } + @TestMetadata("topPropertyWithTypeInGetter.kt") + public void testTopPropertyWithTypeInGetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetter.kt"); + } + + @TestMetadata("topPropertyWithTypeInGetterOnNextLine.kt") + public void testTopPropertyWithTypeInGetterOnNextLine() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetterOnNextLine.kt"); + } + + @TestMetadata("topPropertyWithTypeInInititalzer.kt") + public void testTopPropertyWithTypeInInititalzer() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInInititalzer.kt"); + } + + @TestMetadata("topPropertyWithTypeInSetter.kt") + public void testTopPropertyWithTypeInSetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInSetter.kt"); + } + + @TestMetadata("topPropertyWithoutTypeInGetter.kt") + public void testTopPropertyWithoutTypeInGetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInGetter.kt"); + } + + @TestMetadata("topPropertyWithoutTypeInInititalzer.kt") + public void testTopPropertyWithoutTypeInInititalzer() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInInititalzer.kt"); + } + + @TestMetadata("topPropertyWithoutTypeInSetter.kt") + public void testTopPropertyWithoutTypeInSetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInSetter.kt"); + } + @TestMetadata("typeInFunctionAnnotation.kt") public void testTypeInFunctionAnnotation() throws Exception { runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotation.kt"); diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureTestGenerated.java index 597f3bde44c..5bf5f17f1ed 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureTestGenerated.java +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureTestGenerated.java @@ -33,6 +33,11 @@ public class FileStructureTestGenerated extends AbstractFileStructureTest { runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/class.kt"); } + @TestMetadata("classMemberProperty.kt") + public void testClassMemberProperty() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/classMemberProperty.kt"); + } + @TestMetadata("localClass.kt") public void testLocalClass() throws Exception { runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localClass.kt"); @@ -43,6 +48,11 @@ public class FileStructureTestGenerated extends AbstractFileStructureTest { runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localFun.kt"); } + @TestMetadata("localProperty.kt") + public void testLocalProperty() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localProperty.kt"); + } + @TestMetadata("nestedClasses.kt") public void testNestedClasses() throws Exception { runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/nestedClasses.kt"); @@ -63,6 +73,11 @@ public class FileStructureTestGenerated extends AbstractFileStructureTest { runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelFunWithType.kt"); } + @TestMetadata("topLevelProperty.kt") + public void testTopLevelProperty() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelProperty.kt"); + } + @TestMetadata("topLevelUnitFun.kt") public void testTopLevelUnitFun() throws Exception { runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelUnitFun.kt"); 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 index ae77d2477b6..fb8abeebde0 100644 --- 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 @@ -53,6 +53,41 @@ public class ProjectWideOutOfBlockKotlinModificationTrackerTestGenerated extends runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topLevelUnitFun.kt"); } + @TestMetadata("topPropertyWithTypeInGetter.kt") + public void testTopPropertyWithTypeInGetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetter.kt"); + } + + @TestMetadata("topPropertyWithTypeInGetterOnNextLine.kt") + public void testTopPropertyWithTypeInGetterOnNextLine() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInGetterOnNextLine.kt"); + } + + @TestMetadata("topPropertyWithTypeInInititalzer.kt") + public void testTopPropertyWithTypeInInititalzer() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInInititalzer.kt"); + } + + @TestMetadata("topPropertyWithTypeInSetter.kt") + public void testTopPropertyWithTypeInSetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithTypeInSetter.kt"); + } + + @TestMetadata("topPropertyWithoutTypeInGetter.kt") + public void testTopPropertyWithoutTypeInGetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInGetter.kt"); + } + + @TestMetadata("topPropertyWithoutTypeInInititalzer.kt") + public void testTopPropertyWithoutTypeInInititalzer() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInInititalzer.kt"); + } + + @TestMetadata("topPropertyWithoutTypeInSetter.kt") + public void testTopPropertyWithoutTypeInSetter() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/topPropertyWithoutTypeInSetter.kt"); + } + @TestMetadata("typeInFunctionAnnotation.kt") public void testTypeInFunctionAnnotation() throws Exception { runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/outOfBlockProjectWide/typeInFunctionAnnotation.kt");