diff --git a/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/defaultPackage.kt b/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/defaultPackage.kt new file mode 100644 index 00000000000..882b73705c3 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/defaultPackage.kt @@ -0,0 +1,9 @@ +// CLASS: Foo +// CLASS: Foo.Bar +// FUNCTION: /#foo + +class Foo { + object Bar +} + +fun foo() {} \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/local.kt b/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/local.kt new file mode 100644 index 00000000000..178dedd2504 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/local.kt @@ -0,0 +1,12 @@ +// FUNCTION: test/#foo + +package test + +fun foo() { + fun bar() { + fun baz() { + class Foo + object Bar + } + } +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/sameNames.kt b/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/sameNames.kt new file mode 100644 index 00000000000..18833d687e2 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/sameNames.kt @@ -0,0 +1,29 @@ +// CLASS: test/Foo +// CLASS: test/Bar +// CLASS: test/Bar.Baz +// CLASS: test/Bar.Boo +// CLASS: test/Bar.Bok +// FUNCTION: test/#foo +// PROPERTY: test/#foo + +package test + +class Foo +interface Foo +typealias Foo = Foo +enum class Foo + +class Bar { + class Baz + object Boo +} + +interface Bar { + object Baz + interface Bok +} + +fun foo() {} +fun foo(a: Int) {} + +val foo: String = "foo" \ No newline at end of file diff --git a/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/simple.kt b/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/simple.kt new file mode 100644 index 00000000000..2d02eeb7c65 --- /dev/null +++ b/analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/simple.kt @@ -0,0 +1,22 @@ +// CLASS: test/app/Foo +// CLASS: test/app/Foo.Bar +// CLASS: test/app/Foo.Bar.Baz +// TYPE_ALIAS: test/app/MyString +// FUNCTION: test/app/#foo +// PROPERTY: test/app/#bar + +package test.app + +interface Foo { + class Bar { + object Baz + } + + fun nested() {} +} + +typealias MyString = String + +fun foo() {} + +val bar: String = "bar" \ No newline at end of file diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractFileBasedKotlinDeclarationProviderTest.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractFileBasedKotlinDeclarationProviderTest.kt new file mode 100644 index 00000000000..03d405cd701 --- /dev/null +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/AbstractFileBasedKotlinDeclarationProviderTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2010-2023 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.analysis.low.level.api.fir + +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.FileBasedKotlinDeclarationProvider +import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AbstractLowLevelApiSingleFileTest +import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirSourceTestConfigurator +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer +import org.jetbrains.kotlin.test.services.TestModuleStructure +import org.jetbrains.kotlin.test.services.TestServices +import kotlin.test.assertContains +import kotlin.test.assertNotNull + +abstract class AbstractFileBasedKotlinDeclarationProviderTest : AbstractLowLevelApiSingleFileTest() { + override val configurator = AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false) + + override fun configureTest(builder: TestConfigurationBuilder) { + super.configureTest(builder) + with(builder) { + useDirectives(Directives) + } + } + + override fun doTestByFileStructure(ktFile: KtFile, moduleStructure: TestModuleStructure, testServices: TestServices) { + val provider = FileBasedKotlinDeclarationProvider(ktFile) + assertContains(provider.findFilesForFacadeByPackage(ktFile.packageFqName), ktFile) + + checkByDirectives(moduleStructure, provider) + checkByVisitor(ktFile, provider) + } + + private fun checkByDirectives(moduleStructure: TestModuleStructure, provider: FileBasedKotlinDeclarationProvider) { + for (directive in moduleStructure.allDirectives[Directives.CLASS]) { + val classId = ClassId.fromString(directive) + assert(provider.getAllClassesByClassId(classId).isNotEmpty()) { "Class $classId not found" } + assertNotNull(provider.getClassLikeDeclarationByClassId(classId)) { "Class-like declaration $classId not found" } + } + + for (directive in moduleStructure.allDirectives[Directives.TYPE_ALIAS]) { + val classId = ClassId.fromString(directive) + assert(provider.getAllTypeAliasesByClassId(classId).isNotEmpty()) { "Type alias $classId not found" } + assertNotNull(provider.getClassLikeDeclarationByClassId(classId)) { "Class-like declaration $classId not found" } + } + + for (directive in moduleStructure.allDirectives[Directives.FUNCTION]) { + val callableId = parseCallableId(directive) + assert(provider.getTopLevelFunctions(callableId).isNotEmpty()) { "Function $callableId not found" } + } + + for (directive in moduleStructure.allDirectives[Directives.PROPERTY]) { + val callableId = parseCallableId(directive) + assert(provider.getTopLevelProperties(callableId).isNotEmpty()) { "Property $callableId not found" } + } + } + + private fun checkByVisitor(ktFile: KtFile, provider: FileBasedKotlinDeclarationProvider) { + ktFile.accept(object : KtTreeVisitorVoid() { + override fun visitClass(klass: KtClass) { + super.visitClass(klass) + processClassLikeDeclaration(klass) + } + + override fun visitTypeAlias(typeAlias: KtTypeAlias) { + super.visitTypeAlias(typeAlias) + processClassLikeDeclaration(typeAlias) + } + + private fun processClassLikeDeclaration(declaration: KtClassLikeDeclaration) { + val classId = declaration.getClassId() ?: return + val shortName = classId.shortClassName + + if (!classId.isNestedClass) { + assertContains(provider.getTopLevelKotlinClassLikeDeclarationNamesInPackage(classId.packageFqName), shortName) + } + + when (declaration) { + is KtClassOrObject -> assertContains(provider.getAllClassesByClassId(classId), declaration) + is KtTypeAlias -> assertContains(provider.getAllTypeAliasesByClassId(classId), declaration) + } + } + + override fun visitNamedFunction(function: KtNamedFunction) { + super.visitNamedFunction(function) + processCallableDeclaration(function) + } + + override fun visitProperty(property: KtProperty) { + super.visitProperty(property) + processCallableDeclaration(property) + } + + private fun processCallableDeclaration(declaration: KtCallableDeclaration) { + val callableId = declaration.callableId ?: return + + if (callableId.classId == null) { + assertContains(provider.getTopLevelCallableFiles(callableId), ktFile) + assertContains(provider.getTopLevelCallableNamesInPackage(callableId.packageName), callableId.callableName) + + when (declaration) { + is KtFunction -> assertContains(provider.getTopLevelFunctions(callableId), declaration) + is KtProperty -> assertContains(provider.getTopLevelProperties(callableId), declaration) + } + } + } + }) + } + + object Directives : SimpleDirectivesContainer() { + val CLASS by stringDirective("ClassId of a class or object to be checked for presence") + val TYPE_ALIAS by stringDirective("ClassId of a type alias to be checked for presence") + val FUNCTION by stringDirective("CallableId of a function to be checked for presence") + val PROPERTY by stringDirective("CallableId of a property to be checked for presence") + } +} + +private val KtCallableDeclaration.callableId: CallableId? + get() { + val callableName = this.nameAsName ?: return null + when (val owner = PsiTreeUtil.getParentOfType(this, KtDeclaration::class.java, KtFile::class.java)) { + is KtClassOrObject -> { + val classId = owner.getClassId() ?: return null + return CallableId(classId, callableName) + } + is KtFile -> { + return CallableId(owner.packageFqName, callableName) + } + else -> return null + } + } + +private fun parseCallableId(rawString: String): CallableId { + val chunks = rawString.split('#') + assert(chunks.size == 2) { "Invalid CallableId string format: $rawString" } + + val rawQualifier = chunks[0] + val rawCallableName = chunks[1] + + val callableName = Name.identifier(rawCallableName) + + return when { + rawQualifier.endsWith('/') -> CallableId(FqName(rawQualifier.dropLast(1).replace('/', '.')), callableName) + else -> CallableId(ClassId.fromString(rawQualifier, false), callableName) + } +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/FileBasedKotlinDeclarationProviderTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/FileBasedKotlinDeclarationProviderTestGenerated.java new file mode 100644 index 00000000000..7f7e44998d1 --- /dev/null +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/FileBasedKotlinDeclarationProviderTestGenerated.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2023 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.analysis.low.level.api.fir; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.analysis.api.GenerateAnalysisApiTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider") +@TestDataPath("$PROJECT_ROOT") +public class FileBasedKotlinDeclarationProviderTestGenerated extends AbstractFileBasedKotlinDeclarationProviderTest { + @Test + public void testAllFilesPresentInFileBasedDeclarationProvider() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("defaultPackage.kt") + public void testDefaultPackage() throws Exception { + runTest("analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/defaultPackage.kt"); + } + + @Test + @TestMetadata("local.kt") + public void testLocal() throws Exception { + runTest("analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/local.kt"); + } + + @Test + @TestMetadata("sameNames.kt") + public void testSameNames() throws Exception { + runTest("analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/sameNames.kt"); + } + + @Test + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("analysis/low-level-api-fir/testdata/fileBasedDeclarationProvider/simple.kt"); + } +} diff --git a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt index 45a31354596..cae235c6e2f 100644 --- a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt +++ b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt @@ -86,6 +86,10 @@ internal fun TestGroupSuite.generateFirLowLevelApiTests() { testClass { model("getOrBuildFir") } + + testClass { + model("fileBasedDeclarationProvider") + } } testGroup(