Skip to content

Commit 1a211ee

Browse files
committed
Completion for fqNames in KDoc links
Tests, also proved that KT-14432 are fixed Added completion and tests for extension functions.
1 parent 62077da commit 1a211ee

File tree

9 files changed

+196
-15
lines changed

9 files changed

+196
-15
lines changed

idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,16 @@ fun resolveKDocLink(context: BindingContext,
5353
val descriptorsByName = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == shortName })
5454
// Try to find a matching local descriptor (parameter or type parameter) first.
5555
val localDescriptors = descriptorsByName.filter { it.containingDeclaration == fromDescriptor }
56+
5657
if (localDescriptors.isNotEmpty()) return localDescriptors
57-
if (descriptorsByName.isNotEmpty()) return descriptorsByName
5858

5959
val moduleDescriptor = fromDescriptor.module
60-
return moduleDescriptor.getSubPackagesOf(FqName.ROOT, { it.asString() == shortName }).map { moduleDescriptor.getPackage(it) }
60+
61+
val packagesByName = moduleDescriptor.getSubPackagesOf(FqName.ROOT, { true })
62+
.filter { it.asString() == shortName }
63+
.map { moduleDescriptor.getPackage(it) }
64+
65+
return descriptorsByName + packagesByName
6166
}
6267

6368
val moduleDescriptor = fromDescriptor.module

idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KDocCompletionContributor.kt

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,20 @@ import com.intellij.patterns.StandardPatterns
2525
import com.intellij.util.ProcessingContext
2626
import org.jetbrains.kotlin.descriptors.CallableDescriptor
2727
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
28+
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
2829
import org.jetbrains.kotlin.idea.core.ExpectedInfo
2930
import org.jetbrains.kotlin.idea.kdoc.getKDocLinkResolutionScope
3031
import org.jetbrains.kotlin.idea.kdoc.getParamDescriptors
32+
import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
3133
import org.jetbrains.kotlin.idea.util.CallType
3234
import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable
3335
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
3436
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
3537
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
3638
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
3739
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
40+
import org.jetbrains.kotlin.name.FqName
41+
import org.jetbrains.kotlin.name.Name
3842
import org.jetbrains.kotlin.psi.KtClassOrObject
3943
import org.jetbrains.kotlin.psi.KtDeclaration
4044
import org.jetbrains.kotlin.psi.KtNamedFunction
@@ -43,26 +47,29 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
4347
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
4448
import org.jetbrains.kotlin.resolve.BindingContext
4549
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
50+
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
51+
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
4652
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
53+
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
4754
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
4855
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
4956

50-
class KDocCompletionContributor(): CompletionContributor() {
57+
class KDocCompletionContributor() : CompletionContributor() {
5158
init {
5259
extend(CompletionType.BASIC, psiElement().inside(KDocName::class.java),
5360
KDocNameCompletionProvider)
5461

5562
extend(CompletionType.BASIC,
5663
psiElement().afterLeaf(
57-
StandardPatterns.or(psiElement(KDocTokens.LEADING_ASTERISK), psiElement(KDocTokens.START))),
64+
StandardPatterns.or(psiElement(KDocTokens.LEADING_ASTERISK), psiElement(KDocTokens.START))),
5865
KDocTagCompletionProvider)
5966

6067
extend(CompletionType.BASIC,
6168
psiElement(KDocTokens.TAG_NAME), KDocTagCompletionProvider)
6269
}
6370
}
6471

65-
object KDocNameCompletionProvider: CompletionProvider<CompletionParameters>() {
72+
object KDocNameCompletionProvider : CompletionProvider<CompletionParameters>() {
6673
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
6774
KDocNameCompletionSession(parameters, ToFromOriginalFileMapper.create(parameters), result).complete()
6875
}
@@ -72,7 +79,7 @@ class KDocNameCompletionSession(
7279
parameters: CompletionParameters,
7380
toFromOriginalFileMapper: ToFromOriginalFileMapper,
7481
resultSet: CompletionResultSet
75-
): CompletionSession(CompletionSessionConfiguration(parameters), parameters, toFromOriginalFileMapper, resultSet) {
82+
) : CompletionSession(CompletionSessionConfiguration(parameters), parameters, toFromOriginalFileMapper, resultSet) {
7683
override val descriptorKindFilter: DescriptorKindFilter? get() = null
7784
override val expectedInfos: Collection<ExpectedInfo> get() = emptyList()
7885

@@ -83,11 +90,13 @@ class KDocNameCompletionSession(
8390
val declarationDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return
8491
if (kdocLink.getTagIfSubject()?.knownTag == KDocKnownTag.PARAM) {
8592
addParamCompletions(position, declarationDescriptor)
86-
} else {
87-
addLinkCompletions(declarationDescriptor)
93+
}
94+
else {
95+
addLinkCompletions(declarationDescriptor, kdocLink)
8896
}
8997
}
9098

99+
91100
private fun addParamCompletions(position: KDocName,
92101
declarationDescriptor: DeclarationDescriptor) {
93102
val section = position.getContainingSection()
@@ -100,8 +109,13 @@ class KDocNameCompletionSession(
100109
}
101110
}
102111

103-
private fun addLinkCompletions(declarationDescriptor: DeclarationDescriptor) {
104-
val scope = getKDocLinkResolutionScope(resolutionFacade, declarationDescriptor)
112+
fun collectPackageViewDescriptors(qualifiedLink: List<String>, nameFilter: (Name) -> Boolean): Sequence<PackageViewDescriptor> {
113+
val fqName = if (qualifiedLink.isEmpty()) FqName.ROOT else FqName.fromSegments(qualifiedLink)
114+
return moduleDescriptor.getSubPackagesOf(fqName, nameFilter).asSequence()
115+
.map { moduleDescriptor.getPackage(it) }
116+
}
117+
118+
fun collectDescriptorsFromScope(scope: LexicalScope, nameFilter: (Name) -> Boolean, collectFormParentScopes: Boolean): Sequence<DeclarationDescriptor> {
105119
val implicitReceivers = scope.getImplicitReceiversHierarchy().map { it.value }
106120

107121
fun isApplicable(descriptor: DeclarationDescriptor): Boolean {
@@ -110,15 +124,49 @@ class KDocNameCompletionSession(
110124
if (extensionReceiver != null) {
111125
val substituted = descriptor.substituteExtensionIfCallable(implicitReceivers, bindingContext, DataFlowInfo.EMPTY,
112126
CallType.DEFAULT, moduleDescriptor)
113-
return !substituted.isEmpty()
127+
return substituted.isNotEmpty()
114128
}
115129
}
116130
return true
117131
}
118132

119-
scope.collectDescriptorsFiltered(nameFilter = descriptorNameFilter.toNameFilter()).filter(::isApplicable).forEach {
133+
@Suppress("IfThenToElvis")
134+
return (
135+
if (collectFormParentScopes)
136+
scope.collectDescriptorsFiltered(nameFilter = nameFilter).asSequence()
137+
else if (scope is LexicalScope.Empty)
138+
scope.parent.getContributedDescriptors(nameFilter = nameFilter).asSequence()
139+
else
140+
(scope.getContributedDescriptors(nameFilter = nameFilter).asSequence()
141+
+ scope.parent.collectDescriptorsFiltered(nameFilter = nameFilter).asSequence()
142+
.filter { it.isExtension || it.isExtensionProperty })
143+
).filter(::isApplicable)
144+
}
145+
146+
147+
fun collectDescriptorsForLinkCompletion(declarationDescriptor: DeclarationDescriptor, kDocLink: KDocLink): Sequence<DeclarationDescriptor> {
148+
val qualifiedLink = kDocLink.getLinkText().split('.').dropLast(1)
149+
val nameFilter = descriptorNameFilter.toNameFilter()
150+
if (qualifiedLink.isNotEmpty()) {
151+
val parentDescriptors = resolveKDocLink(bindingContext, resolutionFacade, declarationDescriptor, null, qualifiedLink)
152+
val childDescriptorsOfPartialLink = parentDescriptors.asSequence().flatMap {
153+
val scope = getKDocLinkResolutionScope(resolutionFacade, it)
154+
collectDescriptorsFromScope(scope, nameFilter, false)
155+
}
156+
157+
return (collectPackageViewDescriptors(qualifiedLink, nameFilter) + childDescriptorsOfPartialLink)
158+
}
159+
else {
160+
val scope = getKDocLinkResolutionScope(resolutionFacade, declarationDescriptor)
161+
return (collectDescriptorsFromScope(scope, nameFilter, true)
162+
+ collectPackageViewDescriptors(qualifiedLink, nameFilter))
163+
}
164+
}
165+
166+
private fun addLinkCompletions(declarationDescriptor: DeclarationDescriptor, kDocLink: KDocLink) {
167+
collectDescriptorsForLinkCompletion(declarationDescriptor, kDocLink).forEach {
120168
val element = basicLookupElementFactory.createLookupElement(it, parametersAndTypeGrayed = true)
121-
collector.addElement(object: LookupElementDecorator<LookupElement>(element) {
169+
collector.addElement(object : LookupElementDecorator<LookupElement>(element) {
122170
override fun handleInsert(context: InsertionContext?) {
123171
// insert only plain name here, no qualifier/parentheses/etc.
124172
}
@@ -127,7 +175,7 @@ class KDocNameCompletionSession(
127175
}
128176
}
129177

130-
object KDocTagCompletionProvider: CompletionProvider<CompletionParameters>() {
178+
object KDocTagCompletionProvider : CompletionProvider<CompletionParameters>() {
131179
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
132180
// findIdentifierPrefix() requires identifier part characters to be a superset of identifier start characters
133181
val prefix = CompletionUtil.findIdentifierPrefix(
@@ -148,7 +196,7 @@ object KDocTagCompletionProvider: CompletionProvider<CompletionParameters>() {
148196
}
149197
}
150198

151-
private fun KDocKnownTag.isApplicable(declaration: KtDeclaration) = when(this) {
199+
private fun KDocKnownTag.isApplicable(declaration: KtDeclaration) = when (this) {
152200
KDocKnownTag.CONSTRUCTOR, KDocKnownTag.PROPERTY -> declaration is KtClassOrObject
153201
KDocKnownTag.RETURN -> declaration is KtNamedFunction
154202
KDocKnownTag.RECEIVER -> declaration is KtNamedFunction && declaration.receiverTypeReference != null
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package a
2+
3+
class B {
4+
/**
5+
* [a.B.<caret>]
6+
*/
7+
fun member() {
8+
9+
}
10+
}
11+
12+
fun B.ext() {
13+
14+
}
15+
16+
val B.extVal: String
17+
get() = ""
18+
19+
// EXIST: ext
20+
// EXIST: extVal
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package a
2+
3+
class C {
4+
5+
class B {
6+
/**
7+
* [a.C.B.<caret>]
8+
*/
9+
fun member() {
10+
11+
}
12+
}
13+
14+
fun B.ext() {
15+
16+
}
17+
18+
val B.extVal: String
19+
get() = ""
20+
}
21+
22+
fun C.B.topExt() {
23+
24+
}
25+
26+
val C.B.topExtVal: String
27+
get() = ""
28+
29+
30+
// EXIST: ext
31+
// EXIST: extVal
32+
// EXIST: topExt
33+
// EXIST: topExtVal
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package z
2+
3+
/**
4+
* [z.<caret>]
5+
*/
6+
fun f(xyzzy: String) {
7+
8+
}
9+
10+
fun bar() {
11+
12+
}
13+
14+
class Z
15+
16+
// EXIST: Z
17+
// EXIST: f
18+
// EXIST: bar
19+
// ABSENT: z

idea/idea-completion/testData/kdoc/Link.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
package z
2+
13
/**
24
* [<caret>]
35
*/
@@ -9,5 +11,9 @@ fun bar() {
911

1012
}
1113

14+
class Z
15+
16+
// EXIST: z
17+
// EXIST: Z
1218
// EXIST: xyzzy
1319
// EXIST: bar
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package z
2+
3+
/**
4+
* [z.X.Y.<caret>]
5+
*/
6+
class X {
7+
enum class Y { First, Second }
8+
}
9+
10+
// EXIST: First
11+
// EXIST: Second
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* [Y.<caret>]
3+
*/
4+
class X {
5+
enum class Y { First, Second }
6+
}
7+
8+
// EXIST: First
9+
// EXIST: Second

idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KDocCompletionTestGenerated.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,42 @@ public void testAllFilesPresentInKdoc() throws Exception {
3636
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/kdoc"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
3737
}
3838

39+
@TestMetadata("ExtensionsFQLink.kt")
40+
public void testExtensionsFQLink() throws Exception {
41+
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/ExtensionsFQLink.kt");
42+
doTest(fileName);
43+
}
44+
45+
@TestMetadata("ExtensionsForSubClassFQLink.kt")
46+
public void testExtensionsForSubClassFQLink() throws Exception {
47+
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/ExtensionsForSubClassFQLink.kt");
48+
doTest(fileName);
49+
}
50+
51+
@TestMetadata("FQLink.kt")
52+
public void testFQLink() throws Exception {
53+
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/FQLink.kt");
54+
doTest(fileName);
55+
}
56+
3957
@TestMetadata("Link.kt")
4058
public void testLink() throws Exception {
4159
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/Link.kt");
4260
doTest(fileName);
4361
}
4462

63+
@TestMetadata("MemberEnumEntryFQLink.kt")
64+
public void testMemberEnumEntryFQLink() throws Exception {
65+
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/MemberEnumEntryFQLink.kt");
66+
doTest(fileName);
67+
}
68+
69+
@TestMetadata("MemberEnumEntryLink.kt")
70+
public void testMemberEnumEntryLink() throws Exception {
71+
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/MemberEnumEntryLink.kt");
72+
doTest(fileName);
73+
}
74+
4575
@TestMetadata("MemberLink.kt")
4676
public void testMemberLink() throws Exception {
4777
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/MemberLink.kt");

0 commit comments

Comments
 (0)