From 724386a5f188e2f5747632f89495399ded84e7d4 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Fri, 9 Jun 2023 18:06:41 +0200 Subject: [PATCH] [Analysis API] rework the resolve of the nested name references To properly resolve qualifier parts in the middle, we need to resolve the whole qualifier to understand which parts of the qualifier are package or class qualifiers. And then we will be able to resolve the qualifier selected by the user to the proper class, package or callable. ^KT-59189 --- ...ceModuleReferenceResolveTestGenerated.java | 32 +++++++++++ .../fir/references/KDocReferenceResolver.kt | 55 ++++++++++++++++++- .../api/fir/references/KtFirKDocReference.kt | 4 +- ...ceModuleReferenceResolveTestGenerated.java | 32 +++++++++++ ...ceModuleReferenceResolveTestGenerated.java | 16 ++++++ ...ceModuleReferenceResolveTestGenerated.java | 32 +++++++++++ ...ceModuleReferenceResolveTestGenerated.java | 32 +++++++++++ .../conflictResolution/resolveToPackage.kt | 12 ++++ .../conflictResolution/resolveToPackage.txt | 2 + .../kDoc/withErrors/errorInLatestQualifer.kt | 9 +++ .../kDoc/withErrors/errorInLatestQualifer.txt | 2 + 11 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt create mode 100644 analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.txt create mode 100644 analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.kt create mode 100644 analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.txt diff --git a/analysis/analysis-api-fe10/tests-gen/org/jetbrains/kotlin/analysis/api/fe10/test/cases/generated/cases/references/Fe10IdeNormalAnalysisSourceModuleReferenceResolveTestGenerated.java b/analysis/analysis-api-fe10/tests-gen/org/jetbrains/kotlin/analysis/api/fe10/test/cases/generated/cases/references/Fe10IdeNormalAnalysisSourceModuleReferenceResolveTestGenerated.java index 75608c08659..2db6cd3a878 100644 --- a/analysis/analysis-api-fe10/tests-gen/org/jetbrains/kotlin/analysis/api/fe10/test/cases/generated/cases/references/Fe10IdeNormalAnalysisSourceModuleReferenceResolveTestGenerated.java +++ b/analysis/analysis-api-fe10/tests-gen/org/jetbrains/kotlin/analysis/api/fe10/test/cases/generated/cases/references/Fe10IdeNormalAnalysisSourceModuleReferenceResolveTestGenerated.java @@ -1408,6 +1408,22 @@ public class Fe10IdeNormalAnalysisSourceModuleReferenceResolveTestGenerated exte KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified"), Pattern.compile("^([^.]+)\\.kt$"), null, true); } + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution") + @TestDataPath("$PROJECT_ROOT") + public class ConflictResolution { + @Test + public void testAllFilesPresentInConflictResolution() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("resolveToPackage.kt") + public void testResolveToPackage() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt"); + } + } + @Nested @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/fromOtherFile") @TestDataPath("$PROJECT_ROOT") @@ -1512,6 +1528,22 @@ public class Fe10IdeNormalAnalysisSourceModuleReferenceResolveTestGenerated exte } } } + + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors") + @TestDataPath("$PROJECT_ROOT") + public class WithErrors { + @Test + public void testAllFilesPresentInWithErrors() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("errorInLatestQualifer.kt") + public void testErrorInLatestQualifer() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.kt"); + } + } } @Nested diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/KDocReferenceResolver.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/KDocReferenceResolver.kt index 9cb2cfd9194..0a506ff667e 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/KDocReferenceResolver.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/KDocReferenceResolver.kt @@ -22,8 +22,61 @@ import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol internal object KDocReferenceResolver { + /** + * Resolves the [selectedFqName] of KDoc + * + * To properly resolve qualifier parts in the middle, + * we need to resolve the whole qualifier to understand which parts of the qualifier are package or class qualifiers. + * And then we will be able to resolve the qualifier selected by the user to the proper class, package or callable. + * + * It's possible that the whole qualifier is invalid, in this case we still want to resolve our [selectedFqName]. + * To do this, we are trying to resolve the whole qualifier until we succeed. + * + * @param selectedFqName the selected fully qualified name of the KDoc + * @param fullFqName the whole fully qualified name of the KDoc + * @param contextElement the context element in which the KDoc is defined + * + * @return the collection of KtSymbol(s) resolved from the fully qualified name + * based on the selected FqName and context element + */ context(KtAnalysisSession) - internal fun resolveKdocFqName(fqName: FqName, contextElement: KtElement): Collection { + internal fun resolveKdocFqName(selectedFqName: FqName, fullFqName: FqName, contextElement: KtElement): Collection { + val fullSymbolsResolved = resolveKdocFqName(fullFqName, contextElement) + if (selectedFqName == fullFqName) return fullSymbolsResolved + if (fullSymbolsResolved.isEmpty()) { + val parent = fullFqName.parent() + return resolveKdocFqName(selectedFqName = selectedFqName, fullFqName = parent, contextElement = contextElement) + } + val goBackSteps = fullFqName.pathSegments().size - selectedFqName.pathSegments().size + check(goBackSteps > 0) { + "Selected FqName ($selectedFqName) should be smaller than the whole FqName ($fullFqName)" + } + return fullSymbolsResolved.mapNotNullTo(mutableSetOf()) { findParentSymbol(it, goBackSteps, selectedFqName) } + } + + + /** + * Finds the parent symbol of the given KtSymbol by traversing back up the symbol hierarchy a certain number of steps, + * or until the containing class or object symbol is found. + * + * @param symbol The KtSymbol whose parent symbol needs to be found. + * @param goBackSteps The number of steps to go back up the symbol hierarchy. + * @param selectedFqName The fully qualified name of the selected package. + * @return The [goBackSteps]-th parent [KtSymbol] + */ + context(KtAnalysisSession) + private fun findParentSymbol(symbol: KtSymbol, goBackSteps: Int, selectedFqName: FqName): KtSymbol? { + if (symbol !is KtDeclarationSymbol) return null + var currentSymbol: KtDeclarationSymbol? = symbol + repeat(goBackSteps) { + currentSymbol = currentSymbol?.getContainingSymbol() as? KtClassOrObjectSymbol + } + currentSymbol?.let { return it } + return getPackageSymbolIfPackageExists(selectedFqName) + } + + context(KtAnalysisSession) + private fun resolveKdocFqName(fqName: FqName, contextElement: KtElement): Collection { getSymbolsFromParentMemberScopes(fqName, contextElement).ifNotEmpty { return this } val importScopeContext = contextElement.containingKtFile.getImportingScopeContext() getSymbolsFromImportingScope(importScopeContext, fqName, KtScopeKind.ExplicitSimpleImportingScope::class).ifNotEmpty { return this } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/KtFirKDocReference.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/KtFirKDocReference.kt index 9f30746118a..74a5bdb5911 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/KtFirKDocReference.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/KtFirKDocReference.kt @@ -14,6 +14,8 @@ import org.jetbrains.kotlin.kdoc.psi.impl.KDocName internal class KtFirKDocReference(element: KDocName) : KDocReference(element), KtFirReference { override fun KtAnalysisSession.resolveToSymbols(): Collection { - return KDocReferenceResolver.resolveKdocFqName(element.getQualifiedNameAsFqName(), element) + val fullFqName = generateSequence(element) { it.parent as? KDocName }.last().getQualifiedNameAsFqName() + val selectedFqName = element.getQualifiedNameAsFqName() + return KDocReferenceResolver.resolveKdocFqName(selectedFqName, fullFqName, element) } } \ No newline at end of file diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeDependentAnalysisSourceModuleReferenceResolveTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeDependentAnalysisSourceModuleReferenceResolveTestGenerated.java index 685f0f68674..8c310c0a70b 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeDependentAnalysisSourceModuleReferenceResolveTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeDependentAnalysisSourceModuleReferenceResolveTestGenerated.java @@ -1408,6 +1408,22 @@ public class FirIdeDependentAnalysisSourceModuleReferenceResolveTestGenerated ex KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified"), Pattern.compile("^([^.]+)\\.kt$"), null, true); } + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution") + @TestDataPath("$PROJECT_ROOT") + public class ConflictResolution { + @Test + public void testAllFilesPresentInConflictResolution() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("resolveToPackage.kt") + public void testResolveToPackage() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt"); + } + } + @Nested @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/fromOtherFile") @TestDataPath("$PROJECT_ROOT") @@ -1512,6 +1528,22 @@ public class FirIdeDependentAnalysisSourceModuleReferenceResolveTestGenerated ex } } } + + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors") + @TestDataPath("$PROJECT_ROOT") + public class WithErrors { + @Test + public void testAllFilesPresentInWithErrors() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("errorInLatestQualifer.kt") + public void testErrorInLatestQualifer() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.kt"); + } + } } @Nested diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisLibrarySourceModuleReferenceResolveTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisLibrarySourceModuleReferenceResolveTestGenerated.java index 4c644745fd5..ff717fd9c1c 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisLibrarySourceModuleReferenceResolveTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisLibrarySourceModuleReferenceResolveTestGenerated.java @@ -1292,6 +1292,22 @@ public class FirIdeNormalAnalysisLibrarySourceModuleReferenceResolveTestGenerate KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified"), Pattern.compile("^([^.]+)\\.kt$"), null, true, "withErrors"); } + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution") + @TestDataPath("$PROJECT_ROOT") + public class ConflictResolution { + @Test + public void testAllFilesPresentInConflictResolution() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution"), Pattern.compile("^([^.]+)\\.kt$"), null, true, "withErrors"); + } + + @Test + @TestMetadata("resolveToPackage.kt") + public void testResolveToPackage() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt"); + } + } + @Nested @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/fromOtherFile") @TestDataPath("$PROJECT_ROOT") diff --git a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisSourceModuleReferenceResolveTestGenerated.java b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisSourceModuleReferenceResolveTestGenerated.java index 4957e50e5d0..105c9fe3dea 100644 --- a/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisSourceModuleReferenceResolveTestGenerated.java +++ b/analysis/analysis-api-fir/tests-gen/org/jetbrains/kotlin/analysis/api/fir/test/cases/generated/cases/references/FirIdeNormalAnalysisSourceModuleReferenceResolveTestGenerated.java @@ -1408,6 +1408,22 @@ public class FirIdeNormalAnalysisSourceModuleReferenceResolveTestGenerated exten KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified"), Pattern.compile("^([^.]+)\\.kt$"), null, true); } + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution") + @TestDataPath("$PROJECT_ROOT") + public class ConflictResolution { + @Test + public void testAllFilesPresentInConflictResolution() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("resolveToPackage.kt") + public void testResolveToPackage() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt"); + } + } + @Nested @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/fromOtherFile") @TestDataPath("$PROJECT_ROOT") @@ -1512,6 +1528,22 @@ public class FirIdeNormalAnalysisSourceModuleReferenceResolveTestGenerated exten } } } + + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors") + @TestDataPath("$PROJECT_ROOT") + public class WithErrors { + @Test + public void testAllFilesPresentInWithErrors() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("errorInLatestQualifer.kt") + public void testErrorInLatestQualifer() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.kt"); + } + } } @Nested diff --git a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/references/FirStandaloneNormalAnalysisSourceModuleReferenceResolveTestGenerated.java b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/references/FirStandaloneNormalAnalysisSourceModuleReferenceResolveTestGenerated.java index 842aa14b3a1..03d9f490470 100644 --- a/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/references/FirStandaloneNormalAnalysisSourceModuleReferenceResolveTestGenerated.java +++ b/analysis/analysis-api-standalone/tests-gen/org/jetbrains/kotlin/analysis/api/standalone/fir/test/cases/generated/cases/references/FirStandaloneNormalAnalysisSourceModuleReferenceResolveTestGenerated.java @@ -1408,6 +1408,22 @@ public class FirStandaloneNormalAnalysisSourceModuleReferenceResolveTestGenerate KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified"), Pattern.compile("^([^.]+)\\.kt$"), null, true); } + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution") + @TestDataPath("$PROJECT_ROOT") + public class ConflictResolution { + @Test + public void testAllFilesPresentInConflictResolution() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("resolveToPackage.kt") + public void testResolveToPackage() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt"); + } + } + @Nested @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/qualified/fromOtherFile") @TestDataPath("$PROJECT_ROOT") @@ -1512,6 +1528,22 @@ public class FirStandaloneNormalAnalysisSourceModuleReferenceResolveTestGenerate } } } + + @Nested + @TestMetadata("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors") + @TestDataPath("$PROJECT_ROOT") + public class WithErrors { + @Test + public void testAllFilesPresentInWithErrors() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("errorInLatestQualifer.kt") + public void testErrorInLatestQualifer() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.kt"); + } + } } @Nested diff --git a/analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt b/analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt new file mode 100644 index 00000000000..d02af2d7f6b --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.kt @@ -0,0 +1,12 @@ +// FILE: main.kt +val p = 10 + +/** + * [pp].bar] + */ +fun foo() {} + +// FILE: pp/dependent.kt +package pp + +fun bar(){} \ No newline at end of file diff --git a/analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.txt b/analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.txt new file mode 100644 index 00000000000..b98bafe6050 --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/kDoc/qualified/conflictResolution/resolveToPackage.txt @@ -0,0 +1,2 @@ +Resolved to: +0: package pp diff --git a/analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.kt b/analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.kt new file mode 100644 index 00000000000..7a941b078d9 --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.kt @@ -0,0 +1,9 @@ +/** + * [A.BB.C] + */ +fun foo() {} + + +class A { + class BB +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.txt b/analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.txt new file mode 100644 index 00000000000..9cd17bef227 --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/kDoc/withErrors/errorInLatestQualifer.txt @@ -0,0 +1,2 @@ +Resolved to: +0: (in A) class BB \ No newline at end of file