From 998814b1a16640bb72f73c5ce96f24b1d52d244d Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 28 Jul 2017 18:23:08 +0300 Subject: [PATCH] Support for import aliases in both resolve and completion in KDoc --- .../kotlin/idea/kdoc/resolveKDocLink.kt | 47 +++++++++++-------- .../completion/KDocCompletionContributor.kt | 33 ++++++------- .../testData/basic/java/importAliases/KDoc.kt | 8 ++++ .../handlers/basic/importAliases/KDoc.kt | 8 ++++ .../basic/importAliases/KDoc.kt.after | 8 ++++ .../test/JvmBasicCompletionTestGenerated.java | 6 +++ .../BasicCompletionHandlerTestGenerated.java | 6 +++ .../testData/kdoc/resolve/ImportAliasClass.kt | 9 ++++ .../idea/kdoc/KdocResolveTestGenerated.java | 6 +++ 9 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 idea/idea-completion/testData/basic/java/importAliases/KDoc.kt create mode 100644 idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt create mode 100644 idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt.after create mode 100644 idea/testData/kdoc/resolve/ImportAliasClass.kt diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt index ca90e53f273..d8db6ca8ec4 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt @@ -21,9 +21,11 @@ import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.util.getFileResolutionScope +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtQualifiedExpression @@ -34,9 +36,10 @@ import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.scopes.* -import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered -import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope +import org.jetbrains.kotlin.resolve.scopes.utils.* import org.jetbrains.kotlin.resolve.source.PsiSourceElement +import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.kotlin.utils.addIfNotNull fun resolveKDocLink(context: BindingContext, resolutionFacade: ResolutionFacade, @@ -86,31 +89,27 @@ fun resolveKDocSampleLink(context: BindingContext, return resolveDefaultKDocLink(context, resolutionFacade, fromDescriptor, qualifiedName) } - - - -private fun resolveDefaultKDocLink(context: BindingContext, - resolutionFacade: ResolutionFacade, - fromDescriptor: DeclarationDescriptor, - qualifiedName: List): Collection { +private fun resolveDefaultKDocLink( + context: BindingContext, + resolutionFacade: ResolutionFacade, + fromDescriptor: DeclarationDescriptor, + qualifiedName: List +): Collection { val scope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor) if (qualifiedName.size == 1) { - val shortName = qualifiedName.single() - val descriptorsByName = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == shortName }) - // Try to find a matching local descriptor (parameter or type parameter) first. + val shortName = Name.identifier(qualifiedName.single()) + val descriptorsByName = SmartList() + scope.collectAllByName(shortName, descriptorsByName) + + // Try to find a matching local descriptor (parameter or type parameter) first val localDescriptors = descriptorsByName.filter { it.containingDeclaration == fromDescriptor } - if (localDescriptors.isNotEmpty()) return localDescriptors - val moduleDescriptor = fromDescriptor.module + descriptorsByName.addIfNotNull(fromDescriptor.module.getPackage(FqName.topLevel(shortName))) - val packagesByName = moduleDescriptor.getSubPackagesOf(FqName.ROOT, { true }) - .filter { it.asString() == shortName } - .map { moduleDescriptor.getPackage(it) } - - return descriptorsByName + packagesByName + return descriptorsByName } val moduleDescriptor = fromDescriptor.module @@ -125,11 +124,19 @@ private fun resolveDefaultKDocLink(context: BindingContext, if (descriptor == null) return emptyList() if (memberName != null) { val memberScope = getKDocLinkResolutionScope(resolutionFacade, descriptor) - return memberScope.collectDescriptorsFiltered(nameFilter = { it == memberName }) + return memberScope.collectAllByName(memberName) } return listOf(descriptor) } +private fun LexicalScope.collectAllByName(shortName: Name, toCollection: MutableCollection = SmartList()): Collection { + toCollection.addIfNotNull(findClassifier(shortName, NoLookupLocation.FROM_IDE)) + toCollection.addIfNotNull(findPackage(shortName)) + toCollection.addAll(collectFunctions(shortName, NoLookupLocation.FROM_IDE)) + toCollection.addAll(collectVariables(shortName, NoLookupLocation.FROM_IDE)) + return toCollection +} + private fun getPackageInnerScope(descriptor: PackageFragmentDescriptor): MemberScope { return descriptor.containingDeclaration.getPackage(descriptor.fqName).memberScope } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KDocCompletionContributor.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KDocCompletionContributor.kt index 3ffb20ed26a..a450a3c5871 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KDocCompletionContributor.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KDocCompletionContributor.kt @@ -48,13 +48,12 @@ 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) @@ -109,13 +108,13 @@ class KDocNameCompletionSession( } } - fun collectPackageViewDescriptors(qualifiedLink: List, nameFilter: (Name) -> Boolean): Sequence { - val fqName = if (qualifiedLink.isEmpty()) FqName.ROOT else FqName.fromSegments(qualifiedLink) + private fun collectPackageViewDescriptors(qualifiedLink: List, nameFilter: (Name) -> Boolean): Sequence { + val fqName = FqName.fromSegments(qualifiedLink) return moduleDescriptor.getSubPackagesOf(fqName, nameFilter).asSequence() .map { moduleDescriptor.getPackage(it) } } - fun collectDescriptorsFromScope(scope: LexicalScope, nameFilter: (Name) -> Boolean, collectFormParentScopes: Boolean): Sequence { + private fun collectDescriptorsFromScope(scope: LexicalScope, nameFilter: (Name) -> Boolean, collectFromParentScopes: Boolean): Sequence { val implicitReceivers = scope.getImplicitReceiversHierarchy().map { it.value } fun isApplicable(descriptor: DeclarationDescriptor): Boolean { @@ -130,21 +129,19 @@ class KDocNameCompletionSession( return true } - @Suppress("IfThenToElvis") - return ( - if (collectFormParentScopes) - scope.collectDescriptorsFiltered(nameFilter = nameFilter).asSequence() - else if (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 || it.isExtensionProperty }) - ).filter(::isApplicable) + 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) } - fun collectDescriptorsForLinkCompletion(declarationDescriptor: DeclarationDescriptor, kDocLink: KDocLink): Sequence { + private fun collectDescriptorsForLinkCompletion(declarationDescriptor: DeclarationDescriptor, kDocLink: KDocLink): Sequence { val qualifiedLink = kDocLink.getLinkText().split('.').dropLast(1) val nameFilter = descriptorNameFilter.toNameFilter() return if (qualifiedLink.isNotEmpty()) { @@ -184,7 +181,7 @@ object KDocTagCompletionProvider : CompletionProvider() { StandardPatterns.character().javaIdentifierPart() or singleCharPattern('@'), StandardPatterns.character().javaIdentifierStart() or singleCharPattern('@')) - if (prefix.length > 0 && !prefix.startsWith('@')) { + if (prefix.isNotEmpty() && !prefix.startsWith('@')) { return } val kdocOwner = parameters.position.getNonStrictParentOfType()?.getOwner() diff --git a/idea/idea-completion/testData/basic/java/importAliases/KDoc.kt b/idea/idea-completion/testData/basic/java/importAliases/KDoc.kt new file mode 100644 index 00000000000..b2455ac8d4e --- /dev/null +++ b/idea/idea-completion/testData/basic/java/importAliases/KDoc.kt @@ -0,0 +1,8 @@ +import java.sql.Date as SqlDate + +/** + * [Sql] + */ +fun foo(){} + +// EXIST: { lookupString: "SqlDate", itemText: "SqlDate", tailText: " (java.sql.Date)" } diff --git a/idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt b/idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt new file mode 100644 index 00000000000..877c2096527 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt @@ -0,0 +1,8 @@ +import java.sql.Date as SqlDate + +/** + * [Sql] + */ +fun foo(){} + +// ELEMENT: SqlDate diff --git a/idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt.after b/idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt.after new file mode 100644 index 00000000000..9d03fe2e2a6 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt.after @@ -0,0 +1,8 @@ +import java.sql.Date as SqlDate + +/** + * [SqlDate] + */ +fun foo(){} + +// ELEMENT: SqlDate diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index e1a48534146..6a7b8baec4f 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -3040,6 +3040,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT doTest(fileName); } + @TestMetadata("KDoc.kt") + public void testKDoc() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/importAliases/KDoc.kt"); + doTest(fileName); + } + @TestMetadata("PrefixUsed.kt") public void testPrefixUsed() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/java/importAliases/PrefixUsed.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java index 791cee912f9..5e6a913dc14 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java @@ -488,6 +488,12 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion doTest(fileName); } + @TestMetadata("KDoc.kt") + public void testKDoc() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/importAliases/KDoc.kt"); + doTest(fileName); + } + @TestMetadata("TopLevelFun.kt") public void testTopLevelFun() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/importAliases/TopLevelFun.kt"); diff --git a/idea/testData/kdoc/resolve/ImportAliasClass.kt b/idea/testData/kdoc/resolve/ImportAliasClass.kt new file mode 100644 index 00000000000..64efb15606a --- /dev/null +++ b/idea/testData/kdoc/resolve/ImportAliasClass.kt @@ -0,0 +1,9 @@ +import java.sql.Date as SqlDate + +/** + * This is [SqlDate] + */ +class Foo { +} + +// REF: (java.sql).Date diff --git a/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java index fe2173ef668..eef22202367 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java @@ -73,6 +73,12 @@ public class KdocResolveTestGenerated extends AbstractReferenceResolveTest { doTest(fileName); } + @TestMetadata("ImportAliasClass.kt") + public void testImportAliasClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/ImportAliasClass.kt"); + doTest(fileName); + } + @TestMetadata("ImportedClassReference.kt") public void testImportedClassReference() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/ImportedClassReference.kt");