From ae9eb19f3932af15590fb6cea9b1d356a14d0768 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 13 Aug 2014 18:33:52 +0400 Subject: [PATCH] Auto-import popup to import global properties #KT-4541 Fixed #KT-3097 Fixed --- ...pLevelExtensionPropertyShortNameIndex.java | 51 +++++++++++++++++++ ...velNonExtensionPropertyShortNameIndex.java | 51 +++++++++++++++++++ .../stubindex/StubIndexServiceImpl.java | 16 ++++-- idea/src/META-INF/plugin.xml | 2 + .../jet/plugin/caches/KotlinIndicesHelper.kt | 41 ++++++++++----- .../jet/plugin/quickfix/AutoImportFix.kt | 6 +-- .../afterLibraryPropertyRuntime.kt | 9 ++++ .../beforeLibraryPropertyRuntime.kt | 7 +++ .../autoImports/propertyImport.after.kt | 10 ++++ .../autoImports/propertyImport.before.Main.kt | 8 +++ .../propertyImport.before.data.Sample.kt | 3 ++ .../QuickFixMultiFileTestGenerated.java | 5 ++ .../quickfix/QuickFixTestGenerated.java | 5 ++ 13 files changed, 194 insertions(+), 20 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/JetTopLevelExtensionPropertyShortNameIndex.java create mode 100644 idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/JetTopLevelNonExtensionPropertyShortNameIndex.java create mode 100644 idea/testData/quickfix/autoImports/afterLibraryPropertyRuntime.kt create mode 100644 idea/testData/quickfix/autoImports/beforeLibraryPropertyRuntime.kt create mode 100644 idea/testData/quickfix/autoImports/propertyImport.after.kt create mode 100644 idea/testData/quickfix/autoImports/propertyImport.before.Main.kt create mode 100644 idea/testData/quickfix/autoImports/propertyImport.before.data.Sample.kt diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/JetTopLevelExtensionPropertyShortNameIndex.java b/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/JetTopLevelExtensionPropertyShortNameIndex.java new file mode 100644 index 00000000000..65db798d5c4 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/JetTopLevelExtensionPropertyShortNameIndex.java @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2014 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.jet.plugin.stubindex; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.stubs.StringStubIndexExtension; +import com.intellij.psi.stubs.StubIndexKey; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.psi.JetProperty; + +import java.util.Collection; + +public class JetTopLevelExtensionPropertyShortNameIndex extends StringStubIndexExtension { + private static final StubIndexKey KEY = KotlinIndexUtil.createIndexKey(JetTopLevelExtensionPropertyShortNameIndex.class); + + private static final JetTopLevelExtensionPropertyShortNameIndex ourInstance = new JetTopLevelExtensionPropertyShortNameIndex(); + + @NotNull + public static JetTopLevelExtensionPropertyShortNameIndex getInstance() { + return ourInstance; + } + + private JetTopLevelExtensionPropertyShortNameIndex() {} + + @NotNull + @Override + public StubIndexKey getKey() { + return KEY; + } + + @NotNull + @Override + public Collection get(String s, Project project, @NotNull GlobalSearchScope scope) { + return super.get(s, project, JetSourceFilterScope.kotlinSourcesAndLibraries(scope, project)); + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/JetTopLevelNonExtensionPropertyShortNameIndex.java b/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/JetTopLevelNonExtensionPropertyShortNameIndex.java new file mode 100644 index 00000000000..cc4785e5357 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/JetTopLevelNonExtensionPropertyShortNameIndex.java @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2014 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.jet.plugin.stubindex; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.stubs.StringStubIndexExtension; +import com.intellij.psi.stubs.StubIndexKey; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.psi.JetProperty; + +import java.util.Collection; + +public class JetTopLevelNonExtensionPropertyShortNameIndex extends StringStubIndexExtension { + private static final StubIndexKey KEY = KotlinIndexUtil.createIndexKey(JetTopLevelNonExtensionPropertyShortNameIndex.class); + + private static final JetTopLevelNonExtensionPropertyShortNameIndex ourInstance = new JetTopLevelNonExtensionPropertyShortNameIndex(); + + @NotNull + public static JetTopLevelNonExtensionPropertyShortNameIndex getInstance() { + return ourInstance; + } + + private JetTopLevelNonExtensionPropertyShortNameIndex() {} + + @NotNull + @Override + public StubIndexKey getKey() { + return KEY; + } + + @NotNull + @Override + public Collection get(String s, Project project, @NotNull GlobalSearchScope scope) { + return super.get(s, project, JetSourceFilterScope.kotlinSourcesAndLibraries(scope, project)); + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/StubIndexServiceImpl.java b/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/StubIndexServiceImpl.java index aabf3d520ce..600c90ae73c 100644 --- a/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/StubIndexServiceImpl.java +++ b/idea/idea-analysis/src/org/jetbrains/jet/plugin/stubindex/StubIndexServiceImpl.java @@ -123,9 +123,19 @@ public class StubIndexServiceImpl implements StubIndexService { @Override public void indexProperty(PsiJetPropertyStub stub, IndexSink sink) { - String propertyName = stub.getName(); - if (propertyName != null) { - sink.occurrence(JetPropertyShortNameIndex.getInstance().getKey(), propertyName); + String name = stub.getName(); + if (name != null) { + if (stub.isTopLevel()) { + // Collection only top level functions as only they are expected in completion without explicit import + if (!stub.hasReceiverTypeRef()) { + sink.occurrence(JetTopLevelNonExtensionPropertyShortNameIndex.getInstance().getKey(), name); + } + else { + sink.occurrence(JetTopLevelExtensionPropertyShortNameIndex.getInstance().getKey(), name); + } + } + + sink.occurrence(JetPropertyShortNameIndex.getInstance().getKey(), name); } // can have special fq name in case of syntactically incorrect function with no name if (stub.isTopLevel()) { diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 15026ca1ce4..73e803dda25 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -366,8 +366,10 @@ + + diff --git a/idea/src/org/jetbrains/jet/plugin/caches/KotlinIndicesHelper.kt b/idea/src/org/jetbrains/jet/plugin/caches/KotlinIndicesHelper.kt index bac73239da6..d9d4d689cc2 100644 --- a/idea/src/org/jetbrains/jet/plugin/caches/KotlinIndicesHelper.kt +++ b/idea/src/org/jetbrains/jet/plugin/caches/KotlinIndicesHelper.kt @@ -28,8 +28,6 @@ import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.resolve.name.Name import org.jetbrains.jet.plugin.stubindex.JetTopLevelNonExtensionFunctionShortNameIndex import org.jetbrains.jet.lang.psi.JetFile -import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor -import java.util.ArrayList import org.jetbrains.jet.plugin.stubindex.JetTopLevelFunctionsFqnNameIndex import org.jetbrains.jet.plugin.stubindex.JetTopLevelPropertiesFqnNameIndex import org.jetbrains.jet.lang.psi.JetSimpleNameExpression @@ -45,6 +43,9 @@ import org.jetbrains.jet.lang.resolve.QualifiedExpressionResolver import org.jetbrains.jet.lang.resolve.BindingTraceContext import org.jetbrains.jet.lang.descriptors.CallableDescriptor import com.intellij.openapi.project.Project +import java.util.HashSet +import org.jetbrains.jet.lang.descriptors.PropertyDescriptor +import org.jetbrains.jet.plugin.stubindex.JetTopLevelNonExtensionPropertyShortNameIndex public class KotlinIndicesHelper(private val project: Project) { public fun getTopLevelObjects(nameFilter: (String) -> Boolean, resolveSession: ResolveSessionForBodies, scope: GlobalSearchScope): Collection { @@ -75,21 +76,21 @@ public class KotlinIndicesHelper(private val project: Project) { return result } - public fun getTopLevelFunctionDescriptorsByName(name: String, context: JetExpression /*TODO: to be dropped*/, resolveSession: ResolveSessionForBodies, scope: GlobalSearchScope): Collection { - + public fun getTopLevelCallablesByName(name: String, context: JetExpression /*TODO: to be dropped*/, resolveSession: ResolveSessionForBodies, scope: GlobalSearchScope): Collection { val jetScope = resolveSession.resolveToElement(context).get(BindingContext.RESOLUTION_SCOPE, context) ?: return listOf() - val result = hashSetOf() + val result = HashSet() //TODO: this code is temporary and is to be dropped when compiled top level functions are indexed val identifier = Name.identifier(name) for (fqName in JetFromJavaDescriptorHelper.getTopLevelCallableFqNames(project, scope, false)) { if (fqName.lastSegmentIs(identifier)) { - findTopLevelCallables(fqName, context, jetScope, resolveSession).filterIsInstanceTo(result, javaClass()) + result.addAll(findTopLevelCallables(fqName, context, jetScope, resolveSession)) } } result.addSourceTopLevelFunctions(name, resolveSession, scope) + result.addSourceTopLevelProperties(name, resolveSession, scope) return result } @@ -109,24 +110,36 @@ public class KotlinIndicesHelper(private val project: Project) { } } - public fun getTopLevelCallables(nameFilter: (String) -> Boolean, context: JetExpression /*TODO: to be dropped*/, resolveSession: ResolveSessionForBodies, scope: GlobalSearchScope): Collection { - val result = ArrayList() + private fun MutableCollection.addSourceTopLevelProperties(name: String, resolveSession: ResolveSessionForBodies, scope: GlobalSearchScope) { + val identifier = Name.identifier(name) + val affectedPackages = JetTopLevelNonExtensionPropertyShortNameIndex.getInstance().get(name, project, scope) + .map { it.getContainingFile() } + .filterIsInstance(javaClass()) + .map { it.getPackageFqName() } + .toSet() + for (affectedPackage in affectedPackages) { + val packageDescriptor = resolveSession.getModuleDescriptor().getPackage(affectedPackage) + ?: error("There's a property in stub index with invalid package: $affectedPackage") + addAll(packageDescriptor.getMemberScope().getProperties(identifier)) + } + } + + public fun getTopLevelCallables(nameFilter: (String) -> Boolean, context: JetExpression /*TODO: to be dropped*/, resolveSession: ResolveSessionForBodies, scope: GlobalSearchScope): Collection { val sourceNames = JetTopLevelFunctionsFqnNameIndex.getInstance().getAllKeys(project).stream() + JetTopLevelPropertiesFqnNameIndex.getInstance().getAllKeys(project).stream() val allFqNames = sourceNames.map { FqName(it) } + JetFromJavaDescriptorHelper.getTopLevelCallableFqNames(project, scope, false).stream() val jetScope = resolveSession.resolveToElement(context).get(BindingContext.RESOLUTION_SCOPE, context) ?: return listOf() - allFqNames.filter { nameFilter(it.shortName().asString()) } - .toSet() - .flatMapTo(result) { findTopLevelCallables(it, context, jetScope, resolveSession) } - return result + return allFqNames.filter { nameFilter(it.shortName().asString()) } + .toSet() + .flatMap { findTopLevelCallables(it, context, jetScope, resolveSession) } } public fun getCallableExtensions(nameFilter: (String) -> Boolean, expression: JetSimpleNameExpression, resolveSession: ResolveSessionForBodies, - scope: GlobalSearchScope): Collection { + scope: GlobalSearchScope): Collection { val context = resolveSession.resolveToElement(expression) val receiverExpression = expression.getReceiverExpression() ?: return listOf() val expressionType = context.get(BindingContext.EXPRESSION_TYPE, receiverExpression) @@ -166,7 +179,7 @@ public class KotlinIndicesHelper(private val project: Project) { return ResolveSessionUtils.getClassDescriptorsByFqName(analyzer, classFQName) } - private fun findTopLevelCallables(fqName: FqName, context: JetExpression, jetScope: JetScope, resolveSession: ResolveSessionForBodies): Collection { + private fun findTopLevelCallables(fqName: FqName, context: JetExpression, jetScope: JetScope, resolveSession: ResolveSessionForBodies): Collection { val importDirective = JetPsiFactory(context.getProject()).createImportDirective(ImportPath(fqName, false)) val allDescriptors = QualifiedExpressionResolver().analyseImportReference(importDirective, jetScope, BindingTraceContext(), resolveSession.getModuleDescriptor()) return allDescriptors.filterIsInstance(javaClass()).filter { it.getReceiverParameter() == null } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/AutoImportFix.kt b/idea/src/org/jetbrains/jet/plugin/quickfix/AutoImportFix.kt index 6967e1416bf..096ddf2a3a3 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/AutoImportFix.kt +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/AutoImportFix.kt @@ -112,7 +112,7 @@ public class AutoImportFix(element: JetSimpleNameExpression) : JetHintAction - = KotlinIndicesHelper(project).getTopLevelFunctionDescriptorsByName(name, context, resolveSession, searchScope) + private fun getTopLevelCallables(name: String, context: JetExpression, searchScope: GlobalSearchScope, resolveSession: ResolveSessionForBodies, project: Project): Collection + = KotlinIndicesHelper(project).getTopLevelCallablesByName(name, context, resolveSession, searchScope) .map { DescriptorUtils.getFqNameSafe(it) } .toSet() diff --git a/idea/testData/quickfix/autoImports/afterLibraryPropertyRuntime.kt b/idea/testData/quickfix/autoImports/afterLibraryPropertyRuntime.kt new file mode 100644 index 00000000000..a9249582c7b --- /dev/null +++ b/idea/testData/quickfix/autoImports/afterLibraryPropertyRuntime.kt @@ -0,0 +1,9 @@ +// "Import" "true" + +package test + +import kotlin.concurrent.currentThread + +fun foo() { + currentThread +} diff --git a/idea/testData/quickfix/autoImports/beforeLibraryPropertyRuntime.kt b/idea/testData/quickfix/autoImports/beforeLibraryPropertyRuntime.kt new file mode 100644 index 00000000000..b0ef1512c70 --- /dev/null +++ b/idea/testData/quickfix/autoImports/beforeLibraryPropertyRuntime.kt @@ -0,0 +1,7 @@ +// "Import" "true" + +package test + +fun foo() { + currentThread +} diff --git a/idea/testData/quickfix/autoImports/propertyImport.after.kt b/idea/testData/quickfix/autoImports/propertyImport.after.kt new file mode 100644 index 00000000000..3d51be1cde4 --- /dev/null +++ b/idea/testData/quickfix/autoImports/propertyImport.after.kt @@ -0,0 +1,10 @@ +// "Import" "true" +// ERROR: Unresolved reference: someTestProp + +package test + +import test.data.someTestProp + +fun foo() { + someTestProp +} diff --git a/idea/testData/quickfix/autoImports/propertyImport.before.Main.kt b/idea/testData/quickfix/autoImports/propertyImport.before.Main.kt new file mode 100644 index 00000000000..fcbd4283897 --- /dev/null +++ b/idea/testData/quickfix/autoImports/propertyImport.before.Main.kt @@ -0,0 +1,8 @@ +// "Import" "true" +// ERROR: Unresolved reference: someTestProp + +package test + +fun foo() { + someTestProp +} diff --git a/idea/testData/quickfix/autoImports/propertyImport.before.data.Sample.kt b/idea/testData/quickfix/autoImports/propertyImport.before.data.Sample.kt new file mode 100644 index 00000000000..20d96fb280a --- /dev/null +++ b/idea/testData/quickfix/autoImports/propertyImport.before.data.Sample.kt @@ -0,0 +1,3 @@ +package test.data + +val someTestProp = 1 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java index 5f6c3f74f52..3be6fa55225 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java @@ -154,6 +154,11 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes doTestWithExtraFile("idea/testData/quickfix/autoImports/postfixOperator.before.Main.kt"); } + @TestMetadata("propertyImport.before.Main.kt") + public void testPropertyImport() throws Exception { + doTestWithExtraFile("idea/testData/quickfix/autoImports/propertyImport.before.Main.kt"); + } + @TestMetadata("timesAssign.before.Main.kt") public void testTimesAssign() throws Exception { doTestWithExtraFile("idea/testData/quickfix/autoImports/timesAssign.before.Main.kt"); diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java index 9d046ac1834..e0cbc66775f 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java @@ -308,6 +308,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest("idea/testData/quickfix/autoImports/beforeCheckNoStackOverflowInImportInnerClassInCurrentFile.kt"); } + @TestMetadata("beforeLibraryPropertyRuntime.kt") + public void testLibraryPropertyRuntime() throws Exception { + doTest("idea/testData/quickfix/autoImports/beforeLibraryPropertyRuntime.kt"); + } + @TestMetadata("beforeLibraryTopLevelFunctionImportRuntime.kt") public void testLibraryTopLevelFunctionImportRuntime() throws Exception { doTest("idea/testData/quickfix/autoImports/beforeLibraryTopLevelFunctionImportRuntime.kt");