diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 6b04d526dc7..1b339c0f600 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -83,6 +83,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolve import org.jetbrains.kotlin.idea.fir.AbstractKtDeclarationAndFirDeclarationEqualityChecker import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyDeclarationResolveTest import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleLazyResolveTest +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.frontend.api.scopes.AbstractMemberScopeByFqNameTest @@ -1036,6 +1037,9 @@ fun main(args: Array) { testClass { model("lazyResolve") } + testClass { + model("fileStructure") + } } testGroup("idea/idea-fir/tests", "idea") { diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/class.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/class.kt new file mode 100644 index 00000000000..ffa1a2204b4 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/class.kt @@ -0,0 +1,6 @@ +class A {/* NonLocalDeclarationFileStructureElement */ + fun x() {/* IncrementallyReanalyzableFunction */ + + } + fun y(): Int = 10/* IncrementallyReanalyzableFunction */ +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localClass.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localClass.kt new file mode 100644 index 00000000000..2a943ad2047 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localClass.kt @@ -0,0 +1,9 @@ +fun a() {/* IncrementallyReanalyzableFunction */ + class X +} + +class Y {/* NonLocalDeclarationFileStructureElement */ + fun b() {/* IncrementallyReanalyzableFunction */ + class Z + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localFun.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localFun.kt new file mode 100644 index 00000000000..4331ea276c4 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localFun.kt @@ -0,0 +1,13 @@ +fun x() {/* IncrementallyReanalyzableFunction */ + fun y() { + + } +} + +class A {/* NonLocalDeclarationFileStructureElement */ + fun z() {/* IncrementallyReanalyzableFunction */ + fun q() { + + } + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/nestedClasses.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/nestedClasses.kt new file mode 100644 index 00000000000..f4d1fc3f430 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/nestedClasses.kt @@ -0,0 +1,16 @@ +class A {/* NonLocalDeclarationFileStructureElement */ + class B {/* NonLocalDeclarationFileStructureElement */ + fun x() {/* IncrementallyReanalyzableFunction */ + } + + class C {/* NonLocalDeclarationFileStructureElement */ + + } + } + + class E {/* NonLocalDeclarationFileStructureElement */ + + } + + fun y(): Int = 10/* IncrementallyReanalyzableFunction */ +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithType.kt new file mode 100644 index 00000000000..672f5070290 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithType.kt @@ -0,0 +1 @@ +fun foo(): Int = 42/* IncrementallyReanalyzableFunction */ \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithoutType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithoutType.kt new file mode 100644 index 00000000000..f9f7b502c6b --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithoutType.kt @@ -0,0 +1 @@ +fun foo() = 42/* NonLocalDeclarationFileStructureElement */ \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelFunWithType.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelFunWithType.kt new file mode 100644 index 00000000000..d1eab252b95 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelFunWithType.kt @@ -0,0 +1,4 @@ +fun foo(): Int {/* IncrementallyReanalyzableFunction */ + println("") + return 10 +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelUnitFun.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelUnitFun.kt new file mode 100644 index 00000000000..774c4cd6abd --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelUnitFun.kt @@ -0,0 +1,4 @@ +fun foo() {/* IncrementallyReanalyzableFunction */ + println("") +} + 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 new file mode 100644 index 00000000000..254ab3ad4af --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/AbstractFileStructureTest.kt @@ -0,0 +1,87 @@ +/* + * 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.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.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.* +import org.jetbrains.kotlin.resolve.calls.callUtil.isFakeElement +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File + +abstract class AbstractFileStructureTest : 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 fileStructure = ktFile.getFileStructure() + val allStructureElements = fileStructure.getAllStructureElements(ktFile) + val declarationToStructureElement = allStructureElements.associateBy { it.psi } + runUndoTransparentWriteAction { + ktFile.collectDescendantsOfType().forEach { it.delete() } + 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) + } + } + else -> error("Unsupported declaration $ktDeclaration") + } + } + } + KotlinTestUtils.assertEqualsToFile(testDataFile, ktFile.text) + } + + private fun FileStructureElement.createComment(): PsiComment { + val text = """/* ${this::class.simpleName!!} */""" + return KtPsiFactory(psi.project).createComment(text) + } + + private fun KtFile.getFileStructure(): FileStructure { + val moduleResolveState = LowLevelFirApiFacade.getResolveStateFor(this) as FirModuleResolveStateImpl + return moduleResolveState.fileStructureCache.getFileStructure( + ktFile = this, + moduleFileCache = moduleResolveState.rootModuleSession.cache + ) + } + + @OptIn(ExperimentalStdlibApi::class) + private fun FileStructure.getAllStructureElements(ktFile: KtFile): Collection = buildSet { + ktFile.forEachDescendantOfType { ktElement -> + add(getStructureElementFor(ktElement)) + } + } + +} \ 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/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 new file mode 100644 index 00000000000..597f3bde44c --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureTestGenerated.java @@ -0,0 +1,70 @@ +/* + * 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/fileStructure") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class FileStructureTestGenerated extends AbstractFileStructureTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInFileStructure() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("class.kt") + public void testClass() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/class.kt"); + } + + @TestMetadata("localClass.kt") + public void testLocalClass() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localClass.kt"); + } + + @TestMetadata("localFun.kt") + public void testLocalFun() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/localFun.kt"); + } + + @TestMetadata("nestedClasses.kt") + public void testNestedClasses() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/nestedClasses.kt"); + } + + @TestMetadata("topLevelExpressionBodyFunWithType.kt") + public void testTopLevelExpressionBodyFunWithType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithType.kt"); + } + + @TestMetadata("topLevelExpressionBodyFunWithoutType.kt") + public void testTopLevelExpressionBodyFunWithoutType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelExpressionBodyFunWithoutType.kt"); + } + + @TestMetadata("topLevelFunWithType.kt") + public void testTopLevelFunWithType() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelFunWithType.kt"); + } + + @TestMetadata("topLevelUnitFun.kt") + public void testTopLevelUnitFun() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/fileStructure/topLevelUnitFun.kt"); + } +}