Rewritten KDoc-link resolve and completion to work more reasonably

This commit is contained in:
Valentin Kipyatkov
2017-08-08 09:57:47 +03:00
parent 998814b1a1
commit e52c3b4c81
19 changed files with 333 additions and 143 deletions
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
@@ -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");
@@ -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");