Skip to content

Commit

Permalink
KTIJ-18482 resolve links in KDoc despite current resolve scope
Browse files Browse the repository at this point in the history
GitOrigin-RevId: eb3b90ca05b78c06569005c6082245922a4fabcd
  • Loading branch information
dymova authored and intellij-monorepo-bot committed Sep 9, 2021
1 parent 6763aaa commit e4af6f9
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface KDocLinkResolutionService {
companion object {

/**
* It's internal implementation, please use [resolveKDocSampleLink], or [resolveKDocLink]
* It's internal implementation, please use [resolveKDocLink]
*/
internal fun resolveKDocLinkGlobal(
context: BindingContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,28 @@ fun resolveKDocLink(
fromDescriptor: DeclarationDescriptor,
fromSubjectOfTag: KDocTag?,
qualifiedName: List<String>
): Collection<DeclarationDescriptor> = when (fromSubjectOfTag?.knownTag) {
KDocKnownTag.PARAM -> resolveParamLink(fromDescriptor, qualifiedName)
KDocKnownTag.SAMPLE -> resolveKDocSampleLink(context, resolutionFacade, fromDescriptor, qualifiedName)
else -> resolveDefaultKDocLink(context, resolutionFacade, fromDescriptor, qualifiedName)
): Collection<DeclarationDescriptor> {
val tag = fromSubjectOfTag?.knownTag
if (tag == KDocKnownTag.PARAM) {
return resolveParamLink(fromDescriptor, qualifiedName)
}
val contextScope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor)
if (qualifiedName.size == 1) {
val localDescriptors = resolveLocal(qualifiedName.single(), contextScope, fromDescriptor)
if (localDescriptors.isNotEmpty()) {
return localDescriptors
}
}
val isMarkdownLink = tag == null
if (tag == KDocKnownTag.SAMPLE || tag == KDocKnownTag.SEE || isMarkdownLink) {
val declarationDescriptors = KDocLinkResolutionService.resolveKDocLinkGlobal(context, fromDescriptor, resolutionFacade, qualifiedName)
if (declarationDescriptors.isNotEmpty()) {
return declarationDescriptors
}
}
return resolveDefaultKDocLink(context, resolutionFacade, fromDescriptor, qualifiedName, contextScope)
}


fun getParamDescriptors(fromDescriptor: DeclarationDescriptor): List<DeclarationDescriptor> {
// TODO resolve parameters of functions passed as parameters
when (fromDescriptor) {
Expand Down Expand Up @@ -74,48 +89,37 @@ private fun resolveParamLink(fromDescriptor: DeclarationDescriptor, qualifiedNam
return getParamDescriptors(fromDescriptor).filter { it.name.asString() == name }
}

fun resolveKDocSampleLink(
context: BindingContext,
resolutionFacade: ResolutionFacade,
fromDescriptor: DeclarationDescriptor,
qualifiedName: List<String>
): Collection<DeclarationDescriptor> {
private fun resolveLocal(
nameToResolve: String,
contextScope: LexicalScope,
fromDescriptor: DeclarationDescriptor
): List<DeclarationDescriptor> {
val shortName = Name.identifier(nameToResolve)

val descriptorsByName = SmartList<DeclarationDescriptor>()
descriptorsByName.addIfNotNull(contextScope.findClassifier(shortName, NoLookupLocation.FROM_IDE))
descriptorsByName.addIfNotNull(contextScope.findPackage(shortName))
descriptorsByName.addAll(contextScope.collectFunctions(shortName, NoLookupLocation.FROM_IDE))
descriptorsByName.addAll(contextScope.collectVariables(shortName, NoLookupLocation.FROM_IDE))

if (fromDescriptor is FunctionDescriptor && fromDescriptor.isExtension && shortName.asString() == "this") {
return listOfNotNull(fromDescriptor.extensionReceiverParameter)
}

val resolvedViaService = KDocLinkResolutionService.resolveKDocLinkGlobal(context, fromDescriptor, resolutionFacade, qualifiedName)
if (resolvedViaService.isNotEmpty()) return resolvedViaService
// 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 resolveDefaultKDocLink(context, resolutionFacade, fromDescriptor, qualifiedName)
return descriptorsByName
}

private fun resolveDefaultKDocLink(
context: BindingContext,
resolutionFacade: ResolutionFacade,
fromDescriptor: DeclarationDescriptor,
qualifiedName: List<String>
qualifiedName: List<String>,
contextScope: LexicalScope
): Collection<DeclarationDescriptor> {

val contextScope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor)

if (qualifiedName.size == 1) {
val shortName = Name.identifier(qualifiedName.single())

val descriptorsByName = SmartList<DeclarationDescriptor>()
descriptorsByName.addIfNotNull(contextScope.findClassifier(shortName, NoLookupLocation.FROM_IDE))
descriptorsByName.addIfNotNull(contextScope.findPackage(shortName))
descriptorsByName.addAll(contextScope.collectFunctions(shortName, NoLookupLocation.FROM_IDE))
descriptorsByName.addAll(contextScope.collectVariables(shortName, NoLookupLocation.FROM_IDE))

if (fromDescriptor is FunctionDescriptor && fromDescriptor.isExtension && shortName.asString() == "this") {
return listOfNotNull(fromDescriptor.extensionReceiverParameter)
}

// 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

@OptIn(FrontendInternals::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import org.jetbrains.kotlin.idea.editor.quickDoc.AbstractQuickDocProviderTest.wr
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
import org.jetbrains.kotlin.idea.test.IDEA_TEST_DATA_DIR
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.junit.internal.runners.JUnit38ClassRunner
Expand All @@ -29,7 +31,7 @@ class KDocLinkMultiModuleResolveTest : AbstractMultiModuleTest() {

samples.addDependency(code)

doInfoTest("simple/code/usage.kt")
doInfoTest("code/usage.kt")
}

fun testFqName() {
Expand All @@ -38,18 +40,18 @@ class KDocLinkMultiModuleResolveTest : AbstractMultiModuleTest() {

samples.addDependency(code)

doInfoTest("fqName/code/usage.kt")
doResolveTest("fqName/code/usage.kt", "samples")
doResolveTest("fqName/code/usage.kt", "samples.SampleGroup")
doResolveTest("fqName/code/usage.kt", "samples.megasamples")
doResolveTest("fqName/code/usage.kt", "samples.megasamples.MegaSamplesGroup")
doResolveTest("fqName/code/usage.kt", "samples.notindir")
doResolveTest("fqName/code/usage.kt", "samples.notindir.NotInDirSamples")
doResolveTest("fqName/code/usage.kt", "samplez")
doResolveTest("fqName/code/usage.kt", "samplez.a")
doResolveTest("fqName/code/usage.kt", "samplez.a.b")
doResolveTest("fqName/code/usage.kt", "samplez.a.b.c")
doResolveTest("fqName/code/usage.kt", "samplez.a.b.c.Samplez")
doInfoTest("code/usage.kt")
doResolveSampleTest("samples")
doResolveSampleTest("samples.SampleGroup")
doResolveSampleTest("samples.megasamples")
doResolveSampleTest("samples.megasamples.MegaSamplesGroup")
doResolveSampleTest("samples.notindir")
doResolveSampleTest("samples.notindir.NotInDirSamples")
doResolveSampleTest("samplez")
doResolveSampleTest("samplez.a")
doResolveSampleTest("samplez.a.b")
doResolveSampleTest("samplez.a.b.c")
doResolveSampleTest("samplez.a.b.c.Samplez")
}

fun testTypeParameters() {
Expand All @@ -58,12 +60,12 @@ class KDocLinkMultiModuleResolveTest : AbstractMultiModuleTest() {

samples.addDependency(code)

doInfoTest("typeParameters/code/usageSingleTypeParameter.kt")
doInfoTest("typeParameters/code/usageNestedTypeParameters.kt")
doInfoTest("code/usageSingleTypeParameter.kt")
doInfoTest("code/usageNestedTypeParameters.kt")
}

fun doResolveTest(path: String, link: String) {
configureByFile(path)
private fun doResolveSampleTest(link: String) {
configureByFile("${getTestName(true)}/code/usage.kt")
val documentationManager = DocumentationManager.getInstance(myProject)
val targetElement = documentationManager.findTargetElement(myEditor, file)

Expand All @@ -74,12 +76,12 @@ class KDocLinkMultiModuleResolveTest : AbstractMultiModuleTest() {
val kdoc = descriptor.findKDoc()!! as KDocSection
val resolutionFacade = targetElement.getResolutionFacade()
assertNotEmpty(resolveKDocLink(bindingContext, resolutionFacade, descriptor, kdoc.findTagByName("sample")!!, link.split(".")))

}

fun doInfoTest(path: String) {
val testDataFile = File(testDataPath, path)
configureByFile(path)
private fun doInfoTest(path: String) {
val fullPath = "${getTestName(true)}/$path"
val testDataFile = File(testDataPath, fullPath)
configureByFile(fullPath)
val documentationManager = DocumentationManager.getInstance(myProject)
val targetElement = documentationManager.findTargetElement(myEditor, file)
val originalElement = DocumentationManager.getOriginalElement(targetElement)
Expand Down Expand Up @@ -114,4 +116,34 @@ class KDocLinkMultiModuleResolveTest : AbstractMultiModuleTest() {
}
}
}

fun testSeeTagFqName() {
module("usage")
module("code")

doResolveTest("usage/usage.kt", KtClass::class.java)
}

fun testMarkdownLinkFqName() {
module("usage")
module("code")

doResolveTest("usage/usage.kt", KtNamedFunction::class.java)
}

fun testSamePackages() {
module("usage")
module("code")

doResolveTest("usage/foo/bar/usage.kt", KtClass::class.java)
}

private fun doResolveTest(path: String, clazz: Class<*>) {
configureByFile("${getTestName(true)}/$path")
val element = file.findReferenceAt(editor.caretModel.offset)
val resolvedElement = element?.resolve()
assertNotNull(resolvedElement)
assertInstanceOf(resolvedElement, clazz)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package code

class Bar {
fun bar() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* [code.Bar.b<caret>ar]
*
*/
fun some() {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package foo

class Bar {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package foo.bar
/**
* @see foo.B<caret>ar
*/
fun some() {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package code

class Bar {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @see code.B<caret>ar
*/
fun some() {

}

0 comments on commit e4af6f9

Please sign in to comment.