Completion for fqNames in KDoc links
Tests, also proved that KT-14432 are fixed Added completion and tests for extension functions.
This commit is contained in:
@@ -53,11 +53,16 @@ fun resolveKDocLink(context: BindingContext,
|
|||||||
val descriptorsByName = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == shortName })
|
val descriptorsByName = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == shortName })
|
||||||
// Try to find a matching local descriptor (parameter or type parameter) first.
|
// Try to find a matching local descriptor (parameter or type parameter) first.
|
||||||
val localDescriptors = descriptorsByName.filter { it.containingDeclaration == fromDescriptor }
|
val localDescriptors = descriptorsByName.filter { it.containingDeclaration == fromDescriptor }
|
||||||
|
|
||||||
if (localDescriptors.isNotEmpty()) return localDescriptors
|
if (localDescriptors.isNotEmpty()) return localDescriptors
|
||||||
if (descriptorsByName.isNotEmpty()) return descriptorsByName
|
|
||||||
|
|
||||||
val moduleDescriptor = fromDescriptor.module
|
val moduleDescriptor = fromDescriptor.module
|
||||||
return moduleDescriptor.getSubPackagesOf(FqName.ROOT, { it.asString() == shortName }).map { moduleDescriptor.getPackage(it) }
|
|
||||||
|
val packagesByName = moduleDescriptor.getSubPackagesOf(FqName.ROOT, { true })
|
||||||
|
.filter { it.asString() == shortName }
|
||||||
|
.map { moduleDescriptor.getPackage(it) }
|
||||||
|
|
||||||
|
return descriptorsByName + packagesByName
|
||||||
}
|
}
|
||||||
|
|
||||||
val moduleDescriptor = fromDescriptor.module
|
val moduleDescriptor = fromDescriptor.module
|
||||||
|
|||||||
+54
-6
@@ -25,9 +25,11 @@ import com.intellij.patterns.StandardPatterns
|
|||||||
import com.intellij.util.ProcessingContext
|
import com.intellij.util.ProcessingContext
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
|
||||||
import org.jetbrains.kotlin.idea.core.ExpectedInfo
|
import org.jetbrains.kotlin.idea.core.ExpectedInfo
|
||||||
import org.jetbrains.kotlin.idea.kdoc.getKDocLinkResolutionScope
|
import org.jetbrains.kotlin.idea.kdoc.getKDocLinkResolutionScope
|
||||||
import org.jetbrains.kotlin.idea.kdoc.getParamDescriptors
|
import org.jetbrains.kotlin.idea.kdoc.getParamDescriptors
|
||||||
|
import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
|
||||||
import org.jetbrains.kotlin.idea.util.CallType
|
import org.jetbrains.kotlin.idea.util.CallType
|
||||||
import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable
|
import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable
|
||||||
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
|
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
|
||||||
@@ -35,6 +37,8 @@ import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
|
|||||||
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
||||||
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
|
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
|
||||||
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
|
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
@@ -43,7 +47,10 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
|||||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
|
||||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
|
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
|
||||||
|
|
||||||
@@ -83,11 +90,13 @@ class KDocNameCompletionSession(
|
|||||||
val declarationDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return
|
val declarationDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return
|
||||||
if (kdocLink.getTagIfSubject()?.knownTag == KDocKnownTag.PARAM) {
|
if (kdocLink.getTagIfSubject()?.knownTag == KDocKnownTag.PARAM) {
|
||||||
addParamCompletions(position, declarationDescriptor)
|
addParamCompletions(position, declarationDescriptor)
|
||||||
} else {
|
}
|
||||||
addLinkCompletions(declarationDescriptor)
|
else {
|
||||||
|
addLinkCompletions(declarationDescriptor, kdocLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun addParamCompletions(position: KDocName,
|
private fun addParamCompletions(position: KDocName,
|
||||||
declarationDescriptor: DeclarationDescriptor) {
|
declarationDescriptor: DeclarationDescriptor) {
|
||||||
val section = position.getContainingSection()
|
val section = position.getContainingSection()
|
||||||
@@ -100,8 +109,13 @@ class KDocNameCompletionSession(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addLinkCompletions(declarationDescriptor: DeclarationDescriptor) {
|
fun collectPackageViewDescriptors(qualifiedLink: List<String>, nameFilter: (Name) -> Boolean): Sequence<PackageViewDescriptor> {
|
||||||
val scope = getKDocLinkResolutionScope(resolutionFacade, declarationDescriptor)
|
val fqName = if (qualifiedLink.isEmpty()) FqName.ROOT else FqName.fromSegments(qualifiedLink)
|
||||||
|
return moduleDescriptor.getSubPackagesOf(fqName, nameFilter).asSequence()
|
||||||
|
.map { moduleDescriptor.getPackage(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun collectDescriptorsFromScope(scope: LexicalScope, nameFilter: (Name) -> Boolean, collectFormParentScopes: Boolean): Sequence<DeclarationDescriptor> {
|
||||||
val implicitReceivers = scope.getImplicitReceiversHierarchy().map { it.value }
|
val implicitReceivers = scope.getImplicitReceiversHierarchy().map { it.value }
|
||||||
|
|
||||||
fun isApplicable(descriptor: DeclarationDescriptor): Boolean {
|
fun isApplicable(descriptor: DeclarationDescriptor): Boolean {
|
||||||
@@ -110,13 +124,47 @@ class KDocNameCompletionSession(
|
|||||||
if (extensionReceiver != null) {
|
if (extensionReceiver != null) {
|
||||||
val substituted = descriptor.substituteExtensionIfCallable(implicitReceivers, bindingContext, DataFlowInfo.EMPTY,
|
val substituted = descriptor.substituteExtensionIfCallable(implicitReceivers, bindingContext, DataFlowInfo.EMPTY,
|
||||||
CallType.DEFAULT, moduleDescriptor)
|
CallType.DEFAULT, moduleDescriptor)
|
||||||
return !substituted.isEmpty()
|
return substituted.isNotEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.collectDescriptorsFiltered(nameFilter = descriptorNameFilter.toNameFilter()).filter(::isApplicable).forEach {
|
@Suppress("IfThenToElvis")
|
||||||
|
return (
|
||||||
|
if (collectFormParentScopes)
|
||||||
|
scope.collectDescriptorsFiltered(nameFilter = nameFilter).asSequence()
|
||||||
|
else if (scope is LexicalScope.Empty)
|
||||||
|
scope.parent.getContributedDescriptors(nameFilter = nameFilter).asSequence()
|
||||||
|
else
|
||||||
|
(scope.getContributedDescriptors(nameFilter = nameFilter).asSequence()
|
||||||
|
+ scope.parent.collectDescriptorsFiltered(nameFilter = nameFilter).asSequence()
|
||||||
|
.filter { it.isExtension || it.isExtensionProperty })
|
||||||
|
).filter(::isApplicable)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun collectDescriptorsForLinkCompletion(declarationDescriptor: DeclarationDescriptor, kDocLink: KDocLink): Sequence<DeclarationDescriptor> {
|
||||||
|
val qualifiedLink = kDocLink.getLinkText().split('.').dropLast(1)
|
||||||
|
val nameFilter = descriptorNameFilter.toNameFilter()
|
||||||
|
if (qualifiedLink.isNotEmpty()) {
|
||||||
|
val parentDescriptors = resolveKDocLink(bindingContext, resolutionFacade, declarationDescriptor, null, qualifiedLink)
|
||||||
|
val childDescriptorsOfPartialLink = parentDescriptors.asSequence().flatMap {
|
||||||
|
val scope = getKDocLinkResolutionScope(resolutionFacade, it)
|
||||||
|
collectDescriptorsFromScope(scope, nameFilter, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (collectPackageViewDescriptors(qualifiedLink, nameFilter) + childDescriptorsOfPartialLink)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val scope = getKDocLinkResolutionScope(resolutionFacade, declarationDescriptor)
|
||||||
|
return (collectDescriptorsFromScope(scope, nameFilter, true)
|
||||||
|
+ collectPackageViewDescriptors(qualifiedLink, nameFilter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addLinkCompletions(declarationDescriptor: DeclarationDescriptor, kDocLink: KDocLink) {
|
||||||
|
collectDescriptorsForLinkCompletion(declarationDescriptor, kDocLink).forEach {
|
||||||
val element = basicLookupElementFactory.createLookupElement(it, parametersAndTypeGrayed = true)
|
val element = basicLookupElementFactory.createLookupElement(it, parametersAndTypeGrayed = true)
|
||||||
collector.addElement(object : LookupElementDecorator<LookupElement>(element) {
|
collector.addElement(object : LookupElementDecorator<LookupElement>(element) {
|
||||||
override fun handleInsert(context: InsertionContext?) {
|
override fun handleInsert(context: InsertionContext?) {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
class B {
|
||||||
|
/**
|
||||||
|
* [a.B.<caret>]
|
||||||
|
*/
|
||||||
|
fun member() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun B.ext() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val B.extVal: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
// EXIST: ext
|
||||||
|
// EXIST: extVal
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
class C {
|
||||||
|
|
||||||
|
class B {
|
||||||
|
/**
|
||||||
|
* [a.C.B.<caret>]
|
||||||
|
*/
|
||||||
|
fun member() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun B.ext() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val B.extVal: String
|
||||||
|
get() = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun C.B.topExt() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val C.B.topExtVal: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
|
||||||
|
// EXIST: ext
|
||||||
|
// EXIST: extVal
|
||||||
|
// EXIST: topExt
|
||||||
|
// EXIST: topExtVal
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package z
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [z.<caret>]
|
||||||
|
*/
|
||||||
|
fun f(xyzzy: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Z
|
||||||
|
|
||||||
|
// EXIST: Z
|
||||||
|
// EXIST: f
|
||||||
|
// EXIST: bar
|
||||||
|
// ABSENT: z
|
||||||
+6
@@ -1,3 +1,5 @@
|
|||||||
|
package z
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [<caret>]
|
* [<caret>]
|
||||||
*/
|
*/
|
||||||
@@ -9,5 +11,9 @@ fun bar() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Z
|
||||||
|
|
||||||
|
// EXIST: z
|
||||||
|
// EXIST: Z
|
||||||
// EXIST: xyzzy
|
// EXIST: xyzzy
|
||||||
// EXIST: bar
|
// EXIST: bar
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package z
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [z.X.Y.<caret>]
|
||||||
|
*/
|
||||||
|
class X {
|
||||||
|
enum class Y { First, Second }
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXIST: First
|
||||||
|
// EXIST: Second
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* [Y.<caret>]
|
||||||
|
*/
|
||||||
|
class X {
|
||||||
|
enum class Y { First, Second }
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXIST: First
|
||||||
|
// EXIST: Second
|
||||||
+30
@@ -36,12 +36,42 @@ public class KDocCompletionTestGenerated extends AbstractJvmBasicCompletionTest
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/kdoc"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/kdoc"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ExtensionsFQLink.kt")
|
||||||
|
public void testExtensionsFQLink() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/ExtensionsFQLink.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ExtensionsForSubClassFQLink.kt")
|
||||||
|
public void testExtensionsForSubClassFQLink() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/ExtensionsForSubClassFQLink.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("FQLink.kt")
|
||||||
|
public void testFQLink() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/FQLink.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("Link.kt")
|
@TestMetadata("Link.kt")
|
||||||
public void testLink() throws Exception {
|
public void testLink() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/Link.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/Link.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("MemberEnumEntryFQLink.kt")
|
||||||
|
public void testMemberEnumEntryFQLink() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/MemberEnumEntryFQLink.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("MemberEnumEntryLink.kt")
|
||||||
|
public void testMemberEnumEntryLink() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/MemberEnumEntryLink.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("MemberLink.kt")
|
@TestMetadata("MemberLink.kt")
|
||||||
public void testMemberLink() throws Exception {
|
public void testMemberLink() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/MemberLink.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/MemberLink.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user