use QualifiedNameResolver for resolving links in KDoc
#KT-10398 Fixed
This commit is contained in:
@@ -463,13 +463,44 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable<Classifi
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class QualifiedExpressionResolveResult(val classOrPackage: DeclarationDescriptor?,
|
||||||
|
val memberName: Name?) {
|
||||||
|
companion object {
|
||||||
|
val UNRESOLVED = QualifiedExpressionResolveResult(null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveClassOrPackageInQualifiedExpression(expression: KtQualifiedExpression,
|
||||||
|
scope: LexicalScope,
|
||||||
|
context: BindingContext): QualifiedExpressionResolveResult {
|
||||||
|
val qualifiedExpressions = unrollToLeftMostQualifiedExpression(expression)
|
||||||
|
val path = mapToQualifierParts(qualifiedExpressions, 0)
|
||||||
|
val trace = DelegatingBindingTrace(context, "Temp trace for resolving qualified expression")
|
||||||
|
|
||||||
|
val (result, index) = resolveToPackageOrClassPrefix(
|
||||||
|
path = path,
|
||||||
|
moduleDescriptor = scope.ownerDescriptor.module,
|
||||||
|
trace = trace,
|
||||||
|
shouldBeVisibleFrom = scope.ownerDescriptor,
|
||||||
|
scopeForFirstPart = scope,
|
||||||
|
position = QualifierPosition.EXPRESSION
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result == null) return QualifiedExpressionResolveResult.UNRESOLVED
|
||||||
|
return when(index) {
|
||||||
|
path.size -> QualifiedExpressionResolveResult(result, null)
|
||||||
|
path.size - 1 -> QualifiedExpressionResolveResult(result, path[index].name)
|
||||||
|
else -> QualifiedExpressionResolveResult.UNRESOLVED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun resolveQualifierInExpressionAndUnroll(
|
fun resolveQualifierInExpressionAndUnroll(
|
||||||
expression: KtQualifiedExpression,
|
expression: KtQualifiedExpression,
|
||||||
context: ExpressionTypingContext,
|
context: ExpressionTypingContext,
|
||||||
isValue: (KtSimpleNameExpression) -> Boolean
|
isValue: (KtSimpleNameExpression) -> Boolean
|
||||||
): List<CallExpressionElement> {
|
): List<CallExpressionElement> {
|
||||||
val qualifiedExpressions = unrollToLeftMostQualifiedExpression(expression)
|
val qualifiedExpressions = unrollToLeftMostQualifiedExpression(expression)
|
||||||
val maxPossibleQualifierPrefix = getMaxPossibleQualifierPrefix(qualifiedExpressions)
|
val maxPossibleQualifierPrefix = mapToQualifierParts(qualifiedExpressions, 1)
|
||||||
|
|
||||||
val nextIndexAfterPrefix = resolveToPackageOrClassPrefix(
|
val nextIndexAfterPrefix = resolveToPackageOrClassPrefix(
|
||||||
path = maxPossibleQualifierPrefix,
|
path = maxPossibleQualifierPrefix,
|
||||||
@@ -489,7 +520,8 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable<Classifi
|
|||||||
.map { CallExpressionElement(it) }
|
.map { CallExpressionElement(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMaxPossibleQualifierPrefix(qualifiedExpressions: List<KtQualifiedExpression>) : List<QualifierPart> {
|
private fun mapToQualifierParts(qualifiedExpressions: List<KtQualifiedExpression>,
|
||||||
|
skipLast: Int): List<QualifierPart> {
|
||||||
if (qualifiedExpressions.isEmpty()) return emptyList()
|
if (qualifiedExpressions.isEmpty()) return emptyList()
|
||||||
|
|
||||||
val first = qualifiedExpressions.first()
|
val first = qualifiedExpressions.first()
|
||||||
@@ -508,7 +540,7 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable<Classifi
|
|||||||
val qualifierParts = arrayListOf<QualifierPart>()
|
val qualifierParts = arrayListOf<QualifierPart>()
|
||||||
qualifierParts.add(QualifierPart(firstReceiver))
|
qualifierParts.add(QualifierPart(firstReceiver))
|
||||||
|
|
||||||
for (qualifiedExpression in qualifiedExpressions.dropLast(1)) {
|
for (qualifiedExpression in qualifiedExpressions.dropLast(skipLast)) {
|
||||||
if (qualifiedExpression !is KtDotQualifiedExpression) break
|
if (qualifiedExpression !is KtDotQualifiedExpression) break
|
||||||
val selector = qualifiedExpression.selectorExpression
|
val selector = qualifiedExpression.selectorExpression
|
||||||
if (selector !is KtSimpleNameExpression) break
|
if (selector !is KtSimpleNameExpression) break
|
||||||
|
|||||||
@@ -22,44 +22,54 @@ import org.jetbrains.kotlin.idea.util.getFileResolutionScope
|
|||||||
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
|
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
|
||||||
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
|
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||||
|
import org.jetbrains.kotlin.psi.KtQualifiedExpression
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||||
import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil
|
import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil
|
||||||
|
import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
import org.jetbrains.kotlin.resolve.scopes.*
|
import org.jetbrains.kotlin.resolve.scopes.*
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope
|
import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope
|
||||||
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
|
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
|
||||||
|
|
||||||
fun resolveKDocLink(resolutionFacade: ResolutionFacade,
|
fun resolveKDocLink(context: BindingContext,
|
||||||
|
resolutionFacade: ResolutionFacade,
|
||||||
fromDescriptor: DeclarationDescriptor,
|
fromDescriptor: DeclarationDescriptor,
|
||||||
fromSubjectOfTag: KDocTag?,
|
fromSubjectOfTag: KDocTag?,
|
||||||
qualifiedName: List<String>): Collection<DeclarationDescriptor> {
|
qualifiedName: List<String>): Collection<DeclarationDescriptor> {
|
||||||
|
|
||||||
if (fromSubjectOfTag?.knownTag == KDocKnownTag.PARAM) {
|
if (fromSubjectOfTag?.knownTag == KDocKnownTag.PARAM) {
|
||||||
return resolveParamLink(fromDescriptor, qualifiedName)
|
return resolveParamLink(fromDescriptor, qualifiedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find a matching local descriptor (parameter or type parameter) first.
|
|
||||||
if (qualifiedName.size == 1) {
|
|
||||||
val localResult = resolveInLocalScope(fromDescriptor, qualifiedName.single(), resolutionFacade)
|
|
||||||
if (!localResult.isEmpty()) {
|
|
||||||
return localResult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result: Collection<DeclarationDescriptor> = listOf(fromDescriptor)
|
|
||||||
qualifiedName.forEach { nameComponent ->
|
|
||||||
val scope = getKDocLinkResolutionScope(resolutionFacade, result.singleOrNull() ?: return emptyList())
|
|
||||||
result = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == nameComponent})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resolveInLocalScope(fromDescriptor: DeclarationDescriptor,
|
|
||||||
name: String,
|
|
||||||
resolutionFacade: ResolutionFacade): List<DeclarationDescriptor> {
|
|
||||||
val scope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor)
|
val scope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor)
|
||||||
return scope.collectDescriptorsFiltered(nameFilter = { it.asString() == name }).filter {
|
|
||||||
it.containingDeclaration == fromDescriptor
|
if (qualifiedName.size == 1) {
|
||||||
|
val descriptorsByName = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == qualifiedName.single() })
|
||||||
|
// Try to find a matching local descriptor (parameter or type parameter) first.
|
||||||
|
val localDescriptors = descriptorsByName.filter { it.containingDeclaration == fromDescriptor }
|
||||||
|
if (localDescriptors.isNotEmpty()) return localDescriptors
|
||||||
|
return descriptorsByName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val moduleDescriptor = fromDescriptor.module
|
||||||
|
val qualifiedExpressionResolver = resolutionFacade.getFrontendService(moduleDescriptor, QualifiedExpressionResolver::class.java)
|
||||||
|
|
||||||
|
val contextElement = DescriptorToSourceUtils.descriptorToDeclaration(fromDescriptor)
|
||||||
|
val factory = KtPsiFactory(resolutionFacade.project)
|
||||||
|
// TODO escape identifiers
|
||||||
|
val codeFragment = factory.createExpressionCodeFragment(qualifiedName.joinToString("."), contextElement)
|
||||||
|
val qualifiedExpression = codeFragment.findElementAt(codeFragment.textLength - 1)?.getStrictParentOfType<KtQualifiedExpression>() ?: return emptyList()
|
||||||
|
val (descriptor, memberName) = qualifiedExpressionResolver.resolveClassOrPackageInQualifiedExpression(qualifiedExpression, scope, context)
|
||||||
|
if (descriptor == null) return emptyList()
|
||||||
|
if (memberName != null) {
|
||||||
|
val memberScope = getKDocLinkResolutionScope(resolutionFacade, descriptor)
|
||||||
|
return memberScope.collectDescriptorsFiltered(nameFilter = { it == memberName })
|
||||||
|
}
|
||||||
|
return listOf(descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getParamDescriptors(fromDescriptor: DeclarationDescriptor): List<DeclarationDescriptor> {
|
fun getParamDescriptors(fromDescriptor: DeclarationDescriptor): List<DeclarationDescriptor> {
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ class KDocReference(element: KDocName): KtMultiReference<KDocName>(element) {
|
|||||||
val declarationDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return arrayListOf()
|
val declarationDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return arrayListOf()
|
||||||
|
|
||||||
val kdocLink = element.getStrictParentOfType<KDocLink>()!!
|
val kdocLink = element.getStrictParentOfType<KDocLink>()!!
|
||||||
return resolveKDocLink(element.getResolutionFacade(),
|
return resolveKDocLink(context,
|
||||||
|
element.getResolutionFacade(),
|
||||||
declarationDescriptor,
|
declarationDescriptor,
|
||||||
kdocLink.getTagIfSubject(),
|
kdocLink.getTagIfSubject(),
|
||||||
element.getQualifiedName())
|
element.getQualifiedName())
|
||||||
|
|||||||
@@ -59,11 +59,9 @@ class KotlinQuickDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val bindingContext = context.analyze(BodyResolveMode.PARTIAL)
|
val bindingContext = context.analyze(BodyResolveMode.PARTIAL)
|
||||||
val contextDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, context]
|
val contextDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, context] ?: return null
|
||||||
if (contextDescriptor == null) {
|
val descriptors = resolveKDocLink(bindingContext, context.getResolutionFacade(),
|
||||||
return null
|
contextDescriptor, null, StringUtil.split(link, ","))
|
||||||
}
|
|
||||||
val descriptors = resolveKDocLink(context.getResolutionFacade(), contextDescriptor, null, StringUtil.split(link, ","))
|
|
||||||
val target = descriptors.firstOrNull()
|
val target = descriptors.firstOrNull()
|
||||||
if (target is DeclarationDescriptorWithSource) {
|
if (target is DeclarationDescriptorWithSource) {
|
||||||
val source = target.source
|
val source = target.source
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains [kotlin.requi<caret>re]
|
||||||
|
*/
|
||||||
|
|
||||||
|
class C {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// REF: (kotlin).require(kotlin.Boolean)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains [kotlin.collections.Collecti<caret>on]
|
||||||
|
*/
|
||||||
|
|
||||||
|
class C {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// REF: (kotlin.collections).Collection
|
||||||
@@ -102,6 +102,18 @@ public class KdocResolveTestGenerated extends AbstractReferenceResolveTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("QualifiedNameFunctionReference.kt")
|
||||||
|
public void testQualifiedNameFunctionReference() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/QualifiedNameFunctionReference.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("QualifiedNameReference.kt")
|
||||||
|
public void testQualifiedNameReference() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/QualifiedNameReference.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("SeeReference.kt")
|
@TestMetadata("SeeReference.kt")
|
||||||
public void testSeeReference() throws Exception {
|
public void testSeeReference() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/SeeReference.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/SeeReference.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user