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 })
|
||||
// Try to find a matching local descriptor (parameter or type parameter) first.
|
||||
val localDescriptors = descriptorsByName.filter { it.containingDeclaration == fromDescriptor }
|
||||
|
||||
if (localDescriptors.isNotEmpty()) return localDescriptors
|
||||
if (descriptorsByName.isNotEmpty()) return descriptorsByName
|
||||
|
||||
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
|
||||
|
||||
+61
-13
@@ -25,9 +25,11 @@ import com.intellij.patterns.StandardPatterns
|
||||
import com.intellij.util.ProcessingContext
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.ExpectedInfo
|
||||
import org.jetbrains.kotlin.idea.kdoc.getKDocLinkResolutionScope
|
||||
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.substituteExtensionIfCallable
|
||||
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.impl.KDocLink
|
||||
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.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
@@ -43,18 +47,21 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
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.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
|
||||
|
||||
class KDocCompletionContributor(): CompletionContributor() {
|
||||
class KDocCompletionContributor() : CompletionContributor() {
|
||||
init {
|
||||
extend(CompletionType.BASIC, psiElement().inside(KDocName::class.java),
|
||||
KDocNameCompletionProvider)
|
||||
|
||||
extend(CompletionType.BASIC,
|
||||
psiElement().afterLeaf(
|
||||
StandardPatterns.or(psiElement(KDocTokens.LEADING_ASTERISK), psiElement(KDocTokens.START))),
|
||||
StandardPatterns.or(psiElement(KDocTokens.LEADING_ASTERISK), psiElement(KDocTokens.START))),
|
||||
KDocTagCompletionProvider)
|
||||
|
||||
extend(CompletionType.BASIC,
|
||||
@@ -62,7 +69,7 @@ class KDocCompletionContributor(): CompletionContributor() {
|
||||
}
|
||||
}
|
||||
|
||||
object KDocNameCompletionProvider: CompletionProvider<CompletionParameters>() {
|
||||
object KDocNameCompletionProvider : CompletionProvider<CompletionParameters>() {
|
||||
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
|
||||
KDocNameCompletionSession(parameters, ToFromOriginalFileMapper.create(parameters), result).complete()
|
||||
}
|
||||
@@ -72,7 +79,7 @@ class KDocNameCompletionSession(
|
||||
parameters: CompletionParameters,
|
||||
toFromOriginalFileMapper: ToFromOriginalFileMapper,
|
||||
resultSet: CompletionResultSet
|
||||
): CompletionSession(CompletionSessionConfiguration(parameters), parameters, toFromOriginalFileMapper, resultSet) {
|
||||
) : CompletionSession(CompletionSessionConfiguration(parameters), parameters, toFromOriginalFileMapper, resultSet) {
|
||||
override val descriptorKindFilter: DescriptorKindFilter? get() = null
|
||||
override val expectedInfos: Collection<ExpectedInfo> get() = emptyList()
|
||||
|
||||
@@ -83,11 +90,13 @@ class KDocNameCompletionSession(
|
||||
val declarationDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return
|
||||
if (kdocLink.getTagIfSubject()?.knownTag == KDocKnownTag.PARAM) {
|
||||
addParamCompletions(position, declarationDescriptor)
|
||||
} else {
|
||||
addLinkCompletions(declarationDescriptor)
|
||||
}
|
||||
else {
|
||||
addLinkCompletions(declarationDescriptor, kdocLink)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun addParamCompletions(position: KDocName,
|
||||
declarationDescriptor: DeclarationDescriptor) {
|
||||
val section = position.getContainingSection()
|
||||
@@ -100,8 +109,13 @@ class KDocNameCompletionSession(
|
||||
}
|
||||
}
|
||||
|
||||
private fun addLinkCompletions(declarationDescriptor: DeclarationDescriptor) {
|
||||
val scope = getKDocLinkResolutionScope(resolutionFacade, declarationDescriptor)
|
||||
fun collectPackageViewDescriptors(qualifiedLink: List<String>, nameFilter: (Name) -> Boolean): Sequence<PackageViewDescriptor> {
|
||||
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 }
|
||||
|
||||
fun isApplicable(descriptor: DeclarationDescriptor): Boolean {
|
||||
@@ -110,15 +124,49 @@ class KDocNameCompletionSession(
|
||||
if (extensionReceiver != null) {
|
||||
val substituted = descriptor.substituteExtensionIfCallable(implicitReceivers, bindingContext, DataFlowInfo.EMPTY,
|
||||
CallType.DEFAULT, moduleDescriptor)
|
||||
return !substituted.isEmpty()
|
||||
return substituted.isNotEmpty()
|
||||
}
|
||||
}
|
||||
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)
|
||||
collector.addElement(object: LookupElementDecorator<LookupElement>(element) {
|
||||
collector.addElement(object : LookupElementDecorator<LookupElement>(element) {
|
||||
override fun handleInsert(context: InsertionContext?) {
|
||||
// insert only plain name here, no qualifier/parentheses/etc.
|
||||
}
|
||||
@@ -127,7 +175,7 @@ class KDocNameCompletionSession(
|
||||
}
|
||||
}
|
||||
|
||||
object KDocTagCompletionProvider: CompletionProvider<CompletionParameters>() {
|
||||
object KDocTagCompletionProvider : CompletionProvider<CompletionParameters>() {
|
||||
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
|
||||
// findIdentifierPrefix() requires identifier part characters to be a superset of identifier start characters
|
||||
val prefix = CompletionUtil.findIdentifierPrefix(
|
||||
@@ -148,7 +196,7 @@ object KDocTagCompletionProvider: CompletionProvider<CompletionParameters>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun KDocKnownTag.isApplicable(declaration: KtDeclaration) = when(this) {
|
||||
private fun KDocKnownTag.isApplicable(declaration: KtDeclaration) = when (this) {
|
||||
KDocKnownTag.CONSTRUCTOR, KDocKnownTag.PROPERTY -> declaration is KtClassOrObject
|
||||
KDocKnownTag.RETURN -> declaration is KtNamedFunction
|
||||
KDocKnownTag.RECEIVER -> declaration is KtNamedFunction && declaration.receiverTypeReference != null
|
||||
|
||||
@@ -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>]
|
||||
*/
|
||||
@@ -9,5 +11,9 @@ fun bar() {
|
||||
|
||||
}
|
||||
|
||||
class Z
|
||||
|
||||
// EXIST: z
|
||||
// EXIST: Z
|
||||
// EXIST: xyzzy
|
||||
// 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);
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testLink() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/Link.kt");
|
||||
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")
|
||||
public void testMemberLink() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/MemberLink.kt");
|
||||
|
||||
Reference in New Issue
Block a user