Initial implementation of KT-8209 Support static member completion without writing the class name

This commit is contained in:
Valentin Kipyatkov
2015-11-02 18:07:30 +03:00
parent 7be9699263
commit ca437e51c4
21 changed files with 400 additions and 103 deletions
@@ -29,10 +29,10 @@ import java.util.*
class KtClassBody : KtElementImplStub<KotlinPlaceHolderStub<KtClassBody>>, KtDeclarationContainer {
constructor(node: ASTNode) : super(node)
override fun getParent() = parentByStub
constructor(stub: KotlinPlaceHolderStub<KtClassBody>) : super(stub, CLASS_BODY)
override fun getParent() = parentByStub
override fun getDeclarations() = Arrays.asList(*getStubOrPsiChildren(DECLARATION_TYPES, KtDeclaration.ARRAY_FACTORY))
override fun <R, D> accept(visitor: KtVisitor<R, D>, data: D) = visitor.visitClassBody(this, data)
@@ -196,7 +196,7 @@ class BasicCompletionSession(
packageNames.forEach { collector.addElement(basicLookupElementFactory.createLookupElementForPackage(it)) }
}
flushToResultSet()
NamedArgumentCompletion.complete(collector, expectedInfos)
@@ -227,6 +227,10 @@ class BasicCompletionSession(
flushToResultSet()
}
}
if (configuration.completeStaticMembers && callTypeAndReceiver is CallTypeAndReceiver.DEFAULT && prefix.isNotEmpty()) {
StaticMembersCompletion(collector, prefixMatcher, indicesHelper(false), lookupElementFactory).complete()
}
}
}
@@ -410,13 +414,13 @@ class BasicCompletionSession(
lookupElement
}
parameterNameAndTypeCompletion.addFromParametersInFile(position, resolutionFacade, isVisibleFilter)
parameterNameAndTypeCompletion.addFromParametersInFile(position, resolutionFacade, isVisibleFilterCheckAlways)
flushToResultSet()
parameterNameAndTypeCompletion.addFromImportedClasses(position, bindingContext, isVisibleFilter)
parameterNameAndTypeCompletion.addFromImportedClasses(position, bindingContext, isVisibleFilterCheckAlways)
flushToResultSet()
parameterNameAndTypeCompletion.addFromAllClasses(parameters, indicesHelper)
parameterNameAndTypeCompletion.addFromAllClasses(parameters, indicesHelper(false))
}
}
@@ -57,19 +57,23 @@ class CompletionSessionConfiguration(
val completeNonImportedDeclarations: Boolean,
val completeNonAccessibleDeclarations: Boolean,
val filterOutJavaGettersAndSetters: Boolean,
val completeJavaClassesNotToBeUsed: Boolean
val completeJavaClassesNotToBeUsed: Boolean,
val completeStaticMembers: Boolean
)
fun CompletionSessionConfiguration(parameters: CompletionParameters) = CompletionSessionConfiguration(
completeNonImportedDeclarations = parameters.invocationCount >= 2,
completeNonAccessibleDeclarations = parameters.invocationCount >= 2,
filterOutJavaGettersAndSetters = parameters.invocationCount < 2,
completeJavaClassesNotToBeUsed = parameters.invocationCount >= 2
completeJavaClassesNotToBeUsed = parameters.invocationCount >= 2,
completeStaticMembers = parameters.invocationCount >= 2
)
abstract class CompletionSession(protected val configuration: CompletionSessionConfiguration,
protected val parameters: CompletionParameters,
resultSet: CompletionResultSet) {
abstract class CompletionSession(
protected val configuration: CompletionSessionConfiguration,
protected val parameters: CompletionParameters,
resultSet: CompletionResultSet
) {
protected val position = parameters.getPosition()
private val file = position.getContainingFile() as KtFile
protected val resolutionFacade = file.getResolutionFacade()
@@ -124,7 +128,8 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
private fun ((Name) -> Boolean).or(otherFilter: (Name) -> Boolean): (Name) -> Boolean
= { this(it) || otherFilter(it) }
protected val isVisibleFilter: (DeclarationDescriptor) -> Boolean = { isVisibleDescriptor(it) }
protected val isVisibleFilter: (DeclarationDescriptor) -> Boolean = { isVisibleDescriptor(it, completeNonAccessible = configuration.completeNonAccessibleDeclarations) }
protected val isVisibleFilterCheckAlways: (DeclarationDescriptor) -> Boolean = { isVisibleDescriptor(it, completeNonAccessible = false) }
protected val referenceVariantsHelper = ReferenceVariantsHelper(bindingContext, resolutionFacade, moduleDescriptor, isVisibleFilter)
@@ -151,8 +156,10 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
override fun contains(file: VirtualFile) = super.contains(file) && file != parameters.getOriginalFile().getVirtualFile()
}
protected val indicesHelper: KotlinIndicesHelper
get() = KotlinIndicesHelper(resolutionFacade, searchScope, isVisibleFilter, true)
protected fun indicesHelper(mayIncludeInaccessible: Boolean): KotlinIndicesHelper {
val filter = if (mayIncludeInaccessible) isVisibleFilter else isVisibleFilterCheckAlways
return KotlinIndicesHelper(resolutionFacade, searchScope, filter, visibilityFilterMayIncludeAccessible = mayIncludeInaccessible)
}
protected val toFromOriginalFileMapper: ToFromOriginalFileMapper
= ToFromOriginalFileMapper(parameters.originalFile as KtFile, position.containingFile as KtFile, parameters.offset)
@@ -178,7 +185,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
get() = 0
}
private fun isVisibleDescriptor(descriptor: DeclarationDescriptor): Boolean {
private fun isVisibleDescriptor(descriptor: DeclarationDescriptor, completeNonAccessible: Boolean): Boolean {
if (!configuration.completeJavaClassesNotToBeUsed && descriptor is ClassDescriptor) {
val classification = descriptor.importableFqName?.let { importableFqNameClassifier.classify(it, isPackage = false) }
if (classification == ImportableFqNameClassifier.Classification.notToBeUsedInKotlin) return false
@@ -189,7 +196,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
if (descriptor is DeclarationDescriptorWithVisibility) {
val visible = descriptor.isVisible(inDescriptor, bindingContext, nameExpression)
if (visible) return true
return configuration.completeNonAccessibleDeclarations && !descriptor.isFromLibrary()
return completeNonAccessible && !descriptor.isFromLibrary()
}
return true
@@ -309,6 +316,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
var notImportedExtensions: Collection<CallableDescriptor> = emptyList()
if (callTypeAndReceiver.shouldCompleteCallableExtensions()) {
val nameFilter: (String) -> Boolean = { prefixMatcher.prefixMatches(it) }
val indicesHelper = indicesHelper(true)
val extensions = if (runtimeReceiver != null)
indicesHelper.getCallableTopLevelExtensions(callTypeAndReceiver, listOf(runtimeReceiver.type), nameFilter)
else
@@ -384,7 +392,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
}
protected fun getTopLevelCallables(): Collection<DeclarationDescriptor> {
return indicesHelper.getTopLevelCallables({ prefixMatcher.prefixMatches(it) })
return indicesHelper(true).getTopLevelCallables({ prefixMatcher.prefixMatches(it) })
.filterShadowedNonImported()
}
@@ -399,7 +407,7 @@ abstract class CompletionSession(protected val configuration: CompletionSessionC
}
protected fun addClassesFromIndex(kindFilter: (ClassKind) -> Boolean) {
AllClassesCompletion(parameters, indicesHelper, prefixMatcher, resolutionFacade, kindFilter)
AllClassesCompletion(parameters, indicesHelper(true), prefixMatcher, resolutionFacade, kindFilter)
.collect(
{ descriptor -> collector.addElement(basicLookupElementFactory.createLookupElement(descriptor), notImported = true) },
{ javaClass -> collector.addElement(basicLookupElementFactory.createLookupElementForJavaClass(javaClass), notImported = true) }
@@ -27,8 +27,7 @@ import com.intellij.patterns.ElementPattern
import com.intellij.patterns.StandardPatterns
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.completion.handlers.CastReceiverInsertHandler
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
@@ -40,6 +39,7 @@ import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
@@ -69,7 +69,8 @@ enum class ItemPriority {
DEFAULT,
IMPLEMENT,
OVERRIDE,
NAMED_PARAMETER
NAMED_PARAMETER,
STATIC_MEMBER
}
val ITEM_PRIORITY_KEY = Key<ItemPriority>("ITEM_PRIORITY_KEY")
@@ -152,7 +153,7 @@ fun InsertionContext.isAfterDot(): Boolean {
val chars = getDocument().getCharsSequence()
while (offset > 0) {
offset--
val c = chars.charAt(offset)
val c = chars[offset]
if (!Character.isWhitespace(c)) {
return c == '.'
}
@@ -335,7 +336,7 @@ private open class BaseTypeLookupElement(type: KotlinType, baseLookupElement: Lo
override fun handleInsert(context: InsertionContext) {
context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), fullText)
context.setTailOffset(context.getStartOffset() + fullText.length())
context.setTailOffset(context.getStartOffset() + fullText.length)
shortenReferences(context, context.getStartOffset(), context.getTailOffset())
}
}
@@ -350,3 +351,49 @@ fun <T> ElementPattern<T>.andNot(rhs: ElementPattern<T>) = StandardPatterns.and(
fun <T> ElementPattern<T>.or(rhs: ElementPattern<T>) = StandardPatterns.or(this, rhs)
fun singleCharPattern(char: Char) = StandardPatterns.character().equalTo(char)
fun LookupElement.decorateAsStaticMember(
memberDescriptor: DeclarationDescriptor,
classDescriptor: ClassDescriptor,
classNameAsLookupString: Boolean
): LookupElement {
val qualifierPresentation = classDescriptor.getName().asString()
val qualifierText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor)
return object: LookupElementDecorator<LookupElement>(this) {
override fun getAllLookupStrings(): Set<String> {
return if (classNameAsLookupString) setOf(delegate.lookupString, qualifierPresentation) else super.getAllLookupStrings()
}
override fun renderElement(presentation: LookupElementPresentation) {
delegate.renderElement(presentation)
presentation.itemText = qualifierPresentation + "." + presentation.itemText
val tailText = " (" + DescriptorUtils.getFqName(classDescriptor.containingDeclaration) + ")"
if (memberDescriptor is FunctionDescriptor) {
presentation.appendTailText(tailText, true)
}
else {
presentation.setTailText(tailText, true)
}
if (presentation.typeText.isNullOrEmpty()) {
presentation.typeText = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(classDescriptor.defaultType)
}
}
override fun handleInsert(context: InsertionContext) {
val prefix = qualifierText + "."
val offset = context.startOffset
context.document.insertString(offset, prefix)
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, offset + prefix.length)
shortenReferences(context, offset, offset + prefix.length)
PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(context.document)
super.handleInsert(context)
}
}
}
@@ -257,13 +257,15 @@ public class KotlinCompletionContributor : CompletionContributor() {
}
val somethingAdded = session.complete()
if (!somethingAdded && parameters.getInvocationCount() < 2) {
if (!somethingAdded && parameters.invocationCount < 2) {
// Rerun completion if nothing was found
val newConfiguration = CompletionSessionConfiguration(
completeNonImportedDeclarations = true,
completeNonAccessibleDeclarations = false,
filterOutJavaGettersAndSetters = false,
completeJavaClassesNotToBeUsed = false)
completeJavaClassesNotToBeUsed = false,
completeStaticMembers = parameters.invocationCount > 0
)
BasicCompletionSession(newConfiguration, parameters, result).complete()
}
}
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
class StaticMembersCompletion(
private val collector: LookupElementsCollector,
private val prefixMatcher: PrefixMatcher,
private val indicesHelper: KotlinIndicesHelper,
private val lookupElementFactory: LookupElementFactory
) {
//TODO: SAM-adapters
//TODO: different priority&behaviour when static import from the same class exist
//TODO: filter out those that are visible from bases/imports etc
//TODO: filter out those that are accessible from SmartCompletion.additionalItems
fun complete() {
val descriptorKindFilter = DescriptorKindFilter.CALLABLES exclude DescriptorKindExclude.Extensions
val nameFilter: (String) -> Boolean = { prefixMatcher.prefixMatches(it) }
for (member in indicesHelper.getJavaStaticMembers(descriptorKindFilter, nameFilter)) {
collector.addElement(createLookupElement(member) ?: continue)
}
for (member in indicesHelper.getObjectMembers(descriptorKindFilter, nameFilter)) {
collector.addElement(createLookupElement(member) ?: continue)
}
}
private fun createLookupElement(descriptor: CallableDescriptor): LookupElement? {
var classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return null
if (classDescriptor.isCompanionObject) {
classDescriptor = classDescriptor.containingDeclaration as? ClassDescriptor ?: return null
}
return lookupElementFactory.createLookupElement(descriptor, useReceiverTypes = false)
.decorateAsStaticMember(descriptor, classDescriptor, classNameAsLookupString = false)
.assignPriority(ItemPriority.STATIC_MEMBER)
.suppressAutoInsertion()
}
}
@@ -16,24 +16,17 @@
package org.jetbrains.kotlin.idea.completion.smart
import com.intellij.codeInsight.completion.CompletionInitializationContext
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.idea.completion.LookupElementFactory
import org.jetbrains.kotlin.idea.completion.decorateAsStaticMember
import org.jetbrains.kotlin.idea.completion.fuzzyType
import org.jetbrains.kotlin.idea.completion.shortenReferences
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.fuzzyReturnType
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
@@ -107,45 +100,10 @@ class StaticMembers(
}
private fun createLookupElements(memberDescriptor: DeclarationDescriptor, classDescriptor: ClassDescriptor): Collection<LookupElement> {
val lookupElements = lookupElementFactory.createLookupElementsInSmartCompletion(memberDescriptor, bindingContext, useReceiverTypes = false)
val qualifierPresentation = classDescriptor.getName().asString()
val qualifierText = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor)
return lookupElements.map {
object: LookupElementDecorator<LookupElement>(it) {
override fun getAllLookupStrings() = setOf(delegate.lookupString, qualifierPresentation)
override fun renderElement(presentation: LookupElementPresentation) {
getDelegate().renderElement(presentation)
presentation.setItemText(qualifierPresentation + "." + presentation.getItemText())
val tailText = " (" + DescriptorUtils.getFqName(classDescriptor.getContainingDeclaration()) + ")"
if (memberDescriptor is FunctionDescriptor) {
presentation.appendTailText(tailText, true)
}
else {
presentation.setTailText(tailText, true)
}
if (presentation.getTypeText().isNullOrEmpty()) {
presentation.setTypeText(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(classDescriptor.getDefaultType()))
}
return lookupElementFactory.createLookupElementsInSmartCompletion(memberDescriptor, bindingContext, useReceiverTypes = false)
.map {
it.decorateAsStaticMember(memberDescriptor, classDescriptor, classNameAsLookupString = true)
.assignSmartCompletionPriority(SmartCompletionItemPriority.STATIC_MEMBER)
}
override fun handleInsert(context: InsertionContext) {
val prefix = qualifierText + "."
val offset = context.startOffset
context.document.insertString(offset, prefix)
context.offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, offset + prefix.length)
shortenReferences(context, offset, offset + prefix.length)
PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(context.document)
super.handleInsert(context)
}
}.assignSmartCompletionPriority(SmartCompletionItemPriority.STATIC_MEMBER)
}
}
}
@@ -0,0 +1,8 @@
fun foo() {
SOU<caret>
}
// INVOCATION_COUNT: 2
// EXIST_JAVA_ONLY: { allLookupStrings: "SOUTH, getSOUTH", itemText: "GridBagConstraints.SOUTH", tailText: " (java.awt)", typeText: "Int", attributes: "" }
// EXIST_JAVA_ONLY: { allLookupStrings: "SOUTHWEST, getSOUTHWEST", itemText: "GridBagConstraints.SOUTHWEST", tailText: " (java.awt)", typeText: "Int", attributes: "" }
// ABSENT: serialVersionUID
@@ -0,0 +1,8 @@
fun foo() {
invoke<caret>
}
// INVOCATION_COUNT: 2
// EXIST_JAVA_ONLY: { allLookupStrings: "invokeLater", itemText: "SwingUtilities.invokeLater", tailText: "(Runnable!) (javax.swing)", typeText: "Unit", attributes: "" }
// EXIST_JAVA_ONLY: { allLookupStrings: "invokeAndWait", itemText: "SwingUtilities.invokeAndWait", tailText: "(Runnable!) (javax.swing)", typeText: "Unit", attributes: "" }
// ABSENT: { itemText: "SwingUtilities.convertScreenLocationToParent" }
@@ -0,0 +1,19 @@
package test
object KotlinObject {
fun funFromObject() { }
private fun privateFun(){}
}
class KotlinClass {
companion object SomeName {
fun funFromCompanionObject() { }
private fun privateFun(){}
}
}
class AnotherKotlinClass {
private companion object {
fun funFromPrivateCompanionObject() { }
}
}
@@ -0,0 +1,11 @@
package first
fun testFun() {
funFromO<caret>
}
// INVOCATION_COUNT: 2
// EXIST: { allLookupStrings: "funFromObject", itemText: "KotlinObject.funFromObject", tailText: "() (test)", typeText: "Unit", attributes: "" }
// EXIST: { allLookupStrings: "funFromCompanionObject", itemText: "KotlinClass.funFromCompanionObject", tailText: "() (test)", typeText: "Unit", attributes: "" }
// ABSENT: privateFun
// ABSENT: funFromPrivateCompanionObject
@@ -0,0 +1,6 @@
fun foo() {
invoke<caret>
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: "SwingUtilities.invokeLater"
@@ -0,0 +1,8 @@
import javax.swing.SwingUtilities
fun foo() {
SwingUtilities.invokeLater(<caret>)
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: "SwingUtilities.invokeLater"
@@ -0,0 +1,6 @@
fun foo() {
fromLit<caret>
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: "Regex.fromLiteral"
@@ -0,0 +1,8 @@
import kotlin.text.Regex
fun foo() {
Regex.fromLiteral(<caret>)
}
// INVOCATION_COUNT: 2
// ELEMENT_TEXT: "Regex.fromLiteral"
@@ -2095,6 +2095,27 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/staticMembers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StaticMembers extends AbstractJSBasicCompletionTest {
public void testAllFilesPresentInStaticMembers() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/staticMembers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("JavaStaticFields.kt")
public void testJavaStaticFields() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticFields.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethods.kt")
public void testJavaStaticMethods() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethods.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/substitutedSignature")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -2095,6 +2095,27 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/staticMembers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StaticMembers extends AbstractJvmBasicCompletionTest {
public void testAllFilesPresentInStaticMembers() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/staticMembers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("JavaStaticFields.kt")
public void testJavaStaticFields() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticFields.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethods.kt")
public void testJavaStaticMethods() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/staticMembers/JavaStaticMethods.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/substitutedSignature")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -281,6 +281,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
doTest(fileName);
}
@TestMetadata("ObjectMembers")
public void testObjectMembers() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/ObjectMembers/");
doTest(fileName);
}
@TestMetadata("ParameterNameAndTypeNestedClasses")
public void testParameterNameAndTypeNestedClasses() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/multifile/ParameterNameAndTypeNestedClasses/");
@@ -530,6 +530,27 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
}
}
@TestMetadata("idea/idea-completion/testData/handlers/basic/staticMembers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StaticMembers extends AbstractBasicCompletionHandlerTest {
public void testAllFilesPresentInStaticMembers() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/handlers/basic/staticMembers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("classObjectMethod.kt")
public void testClassObjectMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/staticMembers/classObjectMethod.kt");
doTest(fileName);
}
@TestMetadata("JavaStaticMethod.kt")
public void testJavaStaticMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/staticMembers/JavaStaticMethod.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/handlers/basic/stringTemplate")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -17,10 +17,16 @@
package org.jetbrains.kotlin.idea.core
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiModifier
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.psi.stubs.StringStubIndexExtension
import com.intellij.util.Processor
import com.intellij.util.indexing.IdFilter
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.getJavaFieldDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
@@ -29,14 +35,16 @@ import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.idea.util.receiverTypes
import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.isAnnotatedAsHidden
import org.jetbrains.kotlin.resolve.lazy.ResolveSessionUtils
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
@@ -46,7 +54,8 @@ public class KotlinIndicesHelper(
private val resolutionFacade: ResolutionFacade,
private val scope: GlobalSearchScope,
visibilityFilter: (DeclarationDescriptor) -> Boolean,
applyExcludeSettings: Boolean
applyExcludeSettings: Boolean = true,
private val visibilityFilterMayIncludeAccessible: Boolean = false
) {
private val moduleDescriptor = resolutionFacade.moduleDescriptor
@@ -60,20 +69,15 @@ public class KotlinIndicesHelper(
}
public fun getTopLevelCallablesByName(name: String): Collection<CallableDescriptor> {
val declarations = HashSet<KtNamedDeclaration>()
val declarations = HashSet<KtCallableDeclaration>()
declarations.addTopLevelNonExtensionCallablesByName(KotlinFunctionShortNameIndex.getInstance(), name)
declarations.addTopLevelNonExtensionCallablesByName(KotlinPropertyShortNameIndex.getInstance(), name)
return declarations.flatMap {
if (it.getContainingJetFile().isCompiled()) { //TODO: it's temporary while resolveToDescriptor does not work for compiled declarations
resolutionFacade.resolveImportReference(moduleDescriptor, it.getFqName()!!).filterIsInstance<CallableDescriptor>()
}
else {
(resolutionFacade.resolveToDescriptor(it) as? CallableDescriptor).singletonOrEmptyList()
}
}.filter { it.getExtensionReceiverParameter() == null && descriptorFilter(it) }
return declarations
.flatMap { it.resolveToDescriptorsWithHack() }
.filter { it.extensionReceiverParameter == null && descriptorFilter(it) }
}
private fun MutableSet<KtNamedDeclaration>.addTopLevelNonExtensionCallablesByName(
private fun MutableSet<KtCallableDeclaration>.addTopLevelNonExtensionCallablesByName(
index: StringStubIndexExtension<out KtCallableDeclaration>,
name: String
) {
@@ -84,7 +88,10 @@ public class KotlinIndicesHelper(
return (KotlinTopLevelFunctionFqnNameIndex.getInstance().getAllKeys(project).asSequence() +
KotlinTopLevelPropertyFqnNameIndex.getInstance().getAllKeys(project).asSequence())
.map { FqName(it) }
.filter { nameFilter(it.shortName().asString()) }
.filter {
ProgressManager.checkCanceled()
nameFilter(it.shortName().asString())
}
.toSet()
.flatMap { findTopLevelCallables(it).filter(descriptorFilter) }
}
@@ -115,8 +122,9 @@ public class KotlinIndicesHelper(
val declarations = index.getAllKeys(project)
.asSequence()
.filter {
ProgressManager.checkCanceled()
KotlinTopLevelExtensionsByReceiverTypeIndex.receiverTypeNameFromKey(it) in receiverTypeNames
&& nameFilter(KotlinTopLevelExtensionsByReceiverTypeIndex.callableNameFromKey(it))
&& nameFilter(KotlinTopLevelExtensionsByReceiverTypeIndex.callableNameFromKey(it))
}
.flatMap { index.get(it, project, scope).asSequence() }
@@ -140,24 +148,12 @@ public class KotlinIndicesHelper(
val result = LinkedHashSet<CallableDescriptor>()
fun processDescriptor(descriptor: CallableDescriptor) {
if (descriptorFilter(descriptor)) {
if (descriptor.extensionReceiverParameter != null && descriptorFilter(descriptor)) {
result.addAll(descriptor.substituteExtensionIfCallable(receiverTypes, callType))
}
}
for (declaration in declarations) {
if (declaration.getContainingJetFile().isCompiled()) {
//TODO: it's temporary while resolveToDescriptor does not work for compiled declarations
for (descriptor in resolutionFacade.resolveImportReference(moduleDescriptor, declaration.getFqName()!!)) {
if (descriptor is CallableDescriptor && descriptor.getExtensionReceiverParameter() != null) {
processDescriptor(descriptor)
}
}
}
else {
processDescriptor(resolutionFacade.resolveToDescriptor(declaration) as CallableDescriptor)
}
}
declarations.forEach { it.resolveToDescriptorsWithHack().forEach(::processDescriptor) }
return result
}
@@ -172,7 +168,10 @@ public class KotlinIndicesHelper(
public fun getKotlinClasses(nameFilter: (String) -> Boolean, kindFilter: (ClassKind) -> Boolean): Collection<ClassDescriptor> {
return KotlinFullClassNameIndex.getInstance().getAllKeys(project).asSequence()
.map { FqName(it) }
.filter { nameFilter(it.shortName().asString()) }
.filter {
ProgressManager.checkCanceled()
nameFilter(it.shortName().asString())
}
.toList()
.flatMap { getClassDescriptorsByFQName(it, kindFilter) }
}
@@ -190,6 +189,73 @@ public class KotlinIndicesHelper(
.filter(descriptorFilter)
}
public fun getObjectMembers(descriptorKindFilter: DescriptorKindFilter, nameFilter: (String) -> Boolean): Collection<CallableDescriptor> {
val result = LinkedHashSet<CallableDescriptor>()
fun addFromIndex(index: StringStubIndexExtension<out KtCallableDeclaration>) {
for (name in index.getAllKeys(project)) {
ProgressManager.checkCanceled()
if (!nameFilter(name)) continue
for (declaration in index.get(name, project, scope)) {
if (declaration.parent.parent !is KtObjectDeclaration) continue
if (!visibilityFilterMayIncludeAccessible && declaration.hasModifier(KtTokens.PRIVATE_KEYWORD)) continue
declaration.resolveToDescriptorsWithHack().filterTo(result) { descriptorKindFilter.accepts(it) && descriptorFilter(it) }
}
}
}
if (descriptorKindFilter.kindMask.and(DescriptorKindFilter.FUNCTIONS_MASK) != 0) {
addFromIndex(KotlinFunctionShortNameIndex.getInstance())
}
if (descriptorKindFilter.kindMask.and(DescriptorKindFilter.VARIABLES_MASK) != 0) {
addFromIndex(KotlinPropertyShortNameIndex.getInstance())
}
return result
}
public fun getJavaStaticMembers(descriptorKindFilter: DescriptorKindFilter, nameFilter: (String) -> Boolean): Collection<CallableDescriptor> {
val result = LinkedHashSet<CallableDescriptor>()
val idFilter = IdFilter.getProjectIdFilter(resolutionFacade.project, false)
val shortNamesCache = PsiShortNamesCache.getInstance(project)
val methodNamesProcessor = Processor<String> { name ->
ProgressManager.checkCanceled()
if (!nameFilter(name)) return@Processor true
for (method in shortNamesCache.getMethodsByName(name, scope)) {
if (!method.hasModifierProperty(PsiModifier.STATIC)) continue
if (!visibilityFilterMayIncludeAccessible && method.hasModifierProperty(PsiModifier.PRIVATE)) continue
val descriptor = method.getJavaMethodDescriptor() ?: continue
if (descriptorKindFilter.accepts(descriptor) && descriptorFilter(descriptor)) {
result.add(descriptor)
}
}
true
}
shortNamesCache.processAllMethodNames(methodNamesProcessor, scope, idFilter)
val fieldNamesProcessor = Processor<String> { name ->
ProgressManager.checkCanceled()
if (!nameFilter(name)) return@Processor true
for (field in shortNamesCache.getFieldsByName(name, scope)) {
if (!field.hasModifierProperty(PsiModifier.STATIC)) continue
if (!visibilityFilterMayIncludeAccessible && field.hasModifierProperty(PsiModifier.PRIVATE)) continue
val descriptor = field.getJavaFieldDescriptor() ?: continue
if (descriptorKindFilter.accepts(descriptor) && descriptorFilter(descriptor)) {
result.add(descriptor)
}
}
true
}
shortNamesCache.processAllFieldNames(fieldNamesProcessor, scope, idFilter)
return result
}
private fun findTopLevelCallables(fqName: FqName): Collection<CallableDescriptor> {
return resolutionFacade.resolveImportReference(moduleDescriptor, fqName)
.filterIsInstance<CallableDescriptor>()
@@ -202,5 +268,14 @@ public class KotlinIndicesHelper(
return CodeInsightSettings.getInstance().EXCLUDED_PACKAGES
.any { excluded -> fqName == excluded || (fqName.startsWith(excluded) && fqName[excluded.length()] == '.') }
}
private fun KtCallableDeclaration.resolveToDescriptorsWithHack(): Collection<CallableDescriptor> {
if (getContainingJetFile().isCompiled()) { //TODO: it's temporary while resolveToDescriptor does not work for compiled declarations
return resolutionFacade.resolveImportReference(moduleDescriptor, fqName!!).filterIsInstance<CallableDescriptor>()
}
else {
return (resolutionFacade.resolveToDescriptor(this) as? CallableDescriptor).singletonOrEmptyList()
}
}
}
@@ -149,7 +149,7 @@ internal abstract class AutoImportFixBase<T: KtExpression>(expression: T) :
val result = ArrayList<DeclarationDescriptor>()
val indicesHelper = KotlinIndicesHelper(element.getResolutionFacade(), searchScope, ::isVisible, true)
val indicesHelper = KotlinIndicesHelper(element.getResolutionFacade(), searchScope, ::isVisible)
val expression = element
if (expression is KtSimpleNameExpression) {