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