Rewritten KDoc-link resolve and completion to work more reasonably
This commit is contained in:
+15
-58
@@ -23,22 +23,17 @@ import com.intellij.codeInsight.lookup.LookupElementDecorator
|
||||
import com.intellij.patterns.PlatformPatterns.psiElement
|
||||
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.getKDocLinkMemberScope
|
||||
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
|
||||
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
|
||||
@@ -46,12 +41,8 @@ import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
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.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() {
|
||||
init {
|
||||
@@ -79,6 +70,7 @@ class KDocNameCompletionSession(
|
||||
toFromOriginalFileMapper: ToFromOriginalFileMapper,
|
||||
resultSet: CompletionResultSet
|
||||
) : CompletionSession(CompletionSessionConfiguration(parameters), parameters, toFromOriginalFileMapper, resultSet) {
|
||||
|
||||
override val descriptorKindFilter: DescriptorKindFilter? get() = null
|
||||
override val expectedInfos: Collection<ExpectedInfo> get() = emptyList()
|
||||
|
||||
@@ -100,63 +92,28 @@ class KDocNameCompletionSession(
|
||||
declarationDescriptor: DeclarationDescriptor) {
|
||||
val section = position.getContainingSection()
|
||||
val documentedParameters = section.findTagsByName("param").map { it.getSubjectName() }.toSet()
|
||||
val descriptors = getParamDescriptors(declarationDescriptor)
|
||||
getParamDescriptors(declarationDescriptor)
|
||||
.filter { it.name.asString() !in documentedParameters }
|
||||
|
||||
descriptors.forEach {
|
||||
collector.addElement(basicLookupElementFactory.createLookupElement(it, parametersAndTypeGrayed = true))
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectPackageViewDescriptors(qualifiedLink: List<String>, nameFilter: (Name) -> Boolean): Sequence<PackageViewDescriptor> {
|
||||
val fqName = FqName.fromSegments(qualifiedLink)
|
||||
return moduleDescriptor.getSubPackagesOf(fqName, nameFilter).asSequence()
|
||||
.map { moduleDescriptor.getPackage(it) }
|
||||
}
|
||||
|
||||
private fun collectDescriptorsFromScope(scope: LexicalScope, nameFilter: (Name) -> Boolean, collectFromParentScopes: Boolean): Sequence<DeclarationDescriptor> {
|
||||
val implicitReceivers = scope.getImplicitReceiversHierarchy().map { it.value }
|
||||
|
||||
fun isApplicable(descriptor: DeclarationDescriptor): Boolean {
|
||||
if (descriptor is CallableDescriptor) {
|
||||
val extensionReceiver = descriptor.extensionReceiverParameter
|
||||
if (extensionReceiver != null) {
|
||||
val substituted = descriptor.substituteExtensionIfCallable(implicitReceivers, bindingContext, DataFlowInfo.EMPTY,
|
||||
CallType.DEFAULT, moduleDescriptor)
|
||||
return substituted.isNotEmpty()
|
||||
.forEach {
|
||||
collector.addElement(basicLookupElementFactory.createLookupElement(it, parametersAndTypeGrayed = true))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val sequence = when {
|
||||
collectFromParentScopes -> scope.collectDescriptorsFiltered(nameFilter = nameFilter, changeNamesForAliased = true).asSequence()
|
||||
|
||||
scope is LexicalScope.Base -> scope.parent.getContributedDescriptors(nameFilter = nameFilter).asSequence()
|
||||
|
||||
else -> scope.getContributedDescriptors(nameFilter = nameFilter).asSequence() +
|
||||
scope.parent.collectDescriptorsFiltered(nameFilter = nameFilter).asSequence().filter { it.isExtension }
|
||||
}
|
||||
return sequence.filter(::isApplicable)
|
||||
}
|
||||
|
||||
private fun collectDescriptorsForLinkCompletion(declarationDescriptor: DeclarationDescriptor, kDocLink: KDocLink): Collection<DeclarationDescriptor> {
|
||||
val contextScope = getKDocLinkResolutionScope(resolutionFacade, declarationDescriptor)
|
||||
|
||||
private fun collectDescriptorsForLinkCompletion(declarationDescriptor: DeclarationDescriptor, kDocLink: KDocLink): Sequence<DeclarationDescriptor> {
|
||||
val qualifiedLink = kDocLink.getLinkText().split('.').dropLast(1)
|
||||
val nameFilter = descriptorNameFilter.toNameFilter()
|
||||
return if (qualifiedLink.isNotEmpty()) {
|
||||
if (qualifiedLink.isNotEmpty()) {
|
||||
val parentDescriptors = resolveKDocLink(bindingContext, resolutionFacade, declarationDescriptor, kDocLink.getTagIfSubject(), qualifiedLink)
|
||||
val childDescriptorsOfPartialLink = parentDescriptors.asSequence().flatMap {
|
||||
val scope = getKDocLinkResolutionScope(resolutionFacade, it)
|
||||
collectDescriptorsFromScope(scope, nameFilter, false)
|
||||
}
|
||||
|
||||
(collectPackageViewDescriptors(qualifiedLink, nameFilter) + childDescriptorsOfPartialLink)
|
||||
return parentDescriptors
|
||||
.flatMap {
|
||||
val scope = getKDocLinkMemberScope(it, contextScope)
|
||||
scope.getContributedDescriptors(nameFilter = nameFilter)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val scope = getKDocLinkResolutionScope(resolutionFacade, declarationDescriptor)
|
||||
(collectDescriptorsFromScope(scope, nameFilter, true)
|
||||
+ collectPackageViewDescriptors(qualifiedLink, nameFilter))
|
||||
return contextScope.collectDescriptorsFiltered(DescriptorKindFilter.ALL, nameFilter, changeNamesForAliased = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +121,7 @@ class KDocNameCompletionSession(
|
||||
collectDescriptorsForLinkCompletion(declarationDescriptor, kDocLink).forEach {
|
||||
val element = basicLookupElementFactory.createLookupElement(it, parametersAndTypeGrayed = true)
|
||||
collector.addElement(object : LookupElementDecorator<LookupElement>(element) {
|
||||
override fun handleInsert(context: InsertionContext?) {
|
||||
override fun handleInsert(context: InsertionContext) {
|
||||
// insert only plain name here, no qualifier/parentheses/etc.
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import kotlin.text.capitalize as xxx
|
||||
|
||||
/**
|
||||
* [String.x<caret>]
|
||||
*/
|
||||
fun foo(){}
|
||||
|
||||
// EXIST: { lookupString: "xxx", itemText: "xxx" }
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* [kotlin.<caret>]
|
||||
*/
|
||||
fun foo(){}
|
||||
|
||||
// EXIST: io
|
||||
// EXIST: Int
|
||||
+16
-3
@@ -1,6 +1,10 @@
|
||||
package a
|
||||
|
||||
class B {
|
||||
interface I
|
||||
|
||||
class A : I
|
||||
|
||||
class B : I {
|
||||
/**
|
||||
* [a.B.<caret>]
|
||||
*/
|
||||
@@ -10,11 +14,20 @@ class B {
|
||||
}
|
||||
|
||||
fun B.ext() {
|
||||
|
||||
}
|
||||
|
||||
val B.extVal: String
|
||||
get() = ""
|
||||
|
||||
fun A.wrongExt(){}
|
||||
|
||||
val A.wrongExtVal: String
|
||||
get() = ""
|
||||
|
||||
fun I.extForSuper(){}
|
||||
|
||||
// EXIST: ext
|
||||
// EXIST: extVal
|
||||
// EXIST: extVal
|
||||
// ABSENT: wrongExt
|
||||
// ABSENT: wrongExtVal
|
||||
// EXIST: extForSuper
|
||||
|
||||
+8
-8
@@ -1,19 +1,19 @@
|
||||
package z
|
||||
|
||||
/**
|
||||
* [<caret>]
|
||||
* [a<caret>]
|
||||
*/
|
||||
fun f(xyzzy: String) {
|
||||
fun az(ap: String) {
|
||||
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
fun aaa() {
|
||||
|
||||
}
|
||||
|
||||
class Z
|
||||
class aZ
|
||||
|
||||
// EXIST: z
|
||||
// EXIST: Z
|
||||
// EXIST: xyzzy
|
||||
// EXIST: bar
|
||||
// EXIST: az
|
||||
// EXIST: aZ
|
||||
// EXIST: ap
|
||||
// EXIST: aaa
|
||||
|
||||
+5
-4
@@ -1,8 +1,8 @@
|
||||
class Foo {
|
||||
/**
|
||||
* [<caret>]
|
||||
* [b<caret>]
|
||||
*/
|
||||
fun xyzzy() {
|
||||
fun baz() {
|
||||
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ class Foo {
|
||||
}
|
||||
}
|
||||
|
||||
fun Foo.quux() {
|
||||
fun Foo.boo() {
|
||||
}
|
||||
|
||||
// EXIST: bar
|
||||
// EXIST: quux
|
||||
// EXIST: baz
|
||||
// EXIST: boo
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
class Foo
|
||||
fun Foo.ext() = ""
|
||||
|
||||
|
||||
/**
|
||||
* [Foo.ext.<caret>]
|
||||
*/
|
||||
fun test() {}
|
||||
|
||||
// NOTHING_ELSE
|
||||
@@ -0,0 +1,9 @@
|
||||
class Foo
|
||||
|
||||
/**
|
||||
* [Foo.<caret>]
|
||||
*/
|
||||
fun test() {}
|
||||
|
||||
|
||||
// ABSENT: test
|
||||
+6
@@ -3046,6 +3046,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("KDocExtension.kt")
|
||||
public void testKDocExtension() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/importAliases/KDocExtension.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("PrefixUsed.kt")
|
||||
public void testPrefixUsed() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/importAliases/PrefixUsed.kt");
|
||||
|
||||
+21
-3
@@ -32,6 +32,12 @@ import java.util.regex.Pattern;
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class KDocCompletionTestGenerated extends AbstractJvmBasicCompletionTest {
|
||||
@TestMetadata("AfterPackageName.kt")
|
||||
public void testAfterPackageName() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/AfterPackageName.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInKdoc() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/kdoc"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
@@ -42,9 +48,9 @@ public class KDocCompletionTestGenerated extends AbstractJvmBasicCompletionTest
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ExtensionsForSubClassFQLink.kt")
|
||||
public void testExtensionsForSubClassFQLink() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/ExtensionsForSubClassFQLink.kt");
|
||||
@TestMetadata("ExtensionsForNestedClassFQLink.kt")
|
||||
public void testExtensionsForNestedClassFQLink() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/ExtensionsForNestedClassFQLink.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@@ -90,6 +96,18 @@ public class KDocCompletionTestGenerated extends AbstractJvmBasicCompletionTest
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NoCompletionAfterFunName.kt")
|
||||
public void testNoCompletionAfterFunName() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/NoCompletionAfterFunName.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NoTopLevelForQualified.kt")
|
||||
public void testNoTopLevelForQualified() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/NoTopLevelForQualified.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NotTagName.kt")
|
||||
public void testNotTagName() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/kdoc/NotTagName.kt");
|
||||
|
||||
Reference in New Issue
Block a user