[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
This commit is contained in:
Ilya Kirillov
2023-06-09 18:06:41 +02:00
committed by Space Team
parent 785778511b
commit 724386a5f1
11 changed files with 226 additions and 2 deletions
@@ -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
@@ -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<KtSymbol> {
internal fun resolveKdocFqName(selectedFqName: FqName, fullFqName: FqName, contextElement: KtElement): Collection<KtSymbol> {
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<KtSymbol> {
getSymbolsFromParentMemberScopes(fqName, contextElement).ifNotEmpty { return this }
val importScopeContext = contextElement.containingKtFile.getImportingScopeContext()
getSymbolsFromImportingScope(importScopeContext, fqName, KtScopeKind.ExplicitSimpleImportingScope::class).ifNotEmpty { return this }
@@ -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<KtSymbol> {
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)
}
}
@@ -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
@@ -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")
@@ -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
@@ -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
@@ -0,0 +1,12 @@
// FILE: main.kt
val p = 10
/**
* [p<caret>p].bar]
*/
fun foo() {}
// FILE: pp/dependent.kt
package pp
fun bar(){}
@@ -0,0 +1,2 @@
Resolved to:
0: package pp
@@ -0,0 +1,9 @@
/**
* [A.<caret>BB.C]
*/
fun foo() {}
class A {
class BB
}
@@ -0,0 +1,2 @@
Resolved to:
0: (in A) class BB