From a8fdc4ef885e2b385a2cede99e0b2283736524ce Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Fri, 10 Mar 2023 15:55:35 +0100 Subject: [PATCH] [kotlin] Look up for kotlin top level properties/functions to add module dependency ^KTIJ-24921 GitOrigin-RevId: 1418d11d928825ae2574987ac2920370404eb540 --- .../daemon/impl/quickfix/OrderEntryFix.java | 16 ++++- .../KotlinAddOrderEntryActionFactory.kt | 58 +++++++++++++++++-- .../QuickFixMultiModuleTestGenerated.java | 23 ++++++++ .../addDependency/class/dependencies.txt | 3 + .../addDependency/class/jvm/Dependency.kt | 3 + .../addDependency/class/main/main.kt | 5 ++ .../topLevelFunction/dependencies.txt | 3 + .../topLevelFunction/jvm/Dependency.kt | 3 + .../topLevelFunction/main/main.kt | 5 ++ .../topLevelProperty/dependencies.txt | 3 + .../topLevelProperty/jvm/Dependency.kt | 3 + .../topLevelProperty/main/main.kt | 5 ++ .../idea/multiplatform/multiPlatformSetup.kt | 4 +- 13 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/dependencies.txt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/jvm/Dependency.kt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/main/main.kt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/dependencies.txt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/jvm/Dependency.kt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/main/main.kt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/dependencies.txt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/jvm/Dependency.kt create mode 100644 plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/main/main.kt diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java index f928b825f0ca..1f4feb802343 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java @@ -28,6 +28,7 @@ import com.intellij.psi.impl.light.LightJavaModule; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiShortNamesCache; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.Function; import com.intellij.util.SmartList; import com.intellij.util.ThreeState; import com.intellij.util.containers.ContainerUtil; @@ -87,8 +88,19 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix { invoke(project, null, descriptor.getPsiElement().getContainingFile()); } - @NotNull public static List<@NotNull LocalQuickFix> registerFixes(@NotNull PsiReference reference, @NotNull List registrar) { + return registerFixes(reference, registrar, shortReferenceName -> { + Project project = reference.getElement().getProject(); + return PsiShortNamesCache.getInstance(project).getClassesByName(shortReferenceName, GlobalSearchScope.allScope(project)); + }); + } + + @NotNull + public static List<@NotNull LocalQuickFix> registerFixes( + @NotNull PsiReference reference, + @NotNull List registrar, + @NotNull Function shortReferenceNameToClassesLookup + ) { PsiElement psiElement = reference.getElement(); String shortReferenceName = reference.getRangeInElement().substring(psiElement.getText()); @@ -119,7 +131,7 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix { return result; } - PsiClass[] classes = PsiShortNamesCache.getInstance(project).getClassesByName(shortReferenceName, GlobalSearchScope.allScope(project)); + PsiClass[] classes = shortReferenceNameToClassesLookup.fun(shortReferenceName); List allowedDependencies = filterAllowedDependencies(psiElement, classes); if (allowedDependencies.isEmpty()) { return result; diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddOrderEntryActionFactory.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddOrderEntryActionFactory.kt index ea66244a842c..0cd31f3bcd7d 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddOrderEntryActionFactory.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddOrderEntryActionFactory.kt @@ -5,18 +5,27 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiReference import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.PsiShortNamesCache +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.asJava.elements.KtLightMember +import org.jetbrains.kotlin.asJava.toLightElements import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.base.projectStructure.RootKindFilter import org.jetbrains.kotlin.idea.base.projectStructure.matches import org.jetbrains.kotlin.idea.base.util.runWhenSmart import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.stubindex.KotlinFunctionShortNameIndex +import org.jetbrains.kotlin.idea.stubindex.KotlinPropertyShortNameIndex +import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex +import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelPropertyFqnNameIndex import org.jetbrains.kotlin.idea.util.application.executeWriteCommand -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement import org.jetbrains.kotlin.psi.psiUtil.startOffset @@ -24,8 +33,10 @@ object KotlinAddOrderEntryActionFactory : KotlinIntentionActionsFactory() { override fun doCreateActions(diagnostic: Diagnostic): List { val simpleExpression = diagnostic.psiElement as? KtSimpleNameExpression ?: return emptyList() if (!RootKindFilter.projectSources.matches(simpleExpression)) return emptyList() + val project = simpleExpression.project - val refElement = simpleExpression.getQualifiedElement() + val importDirective = PsiTreeUtil.getParentOfType(simpleExpression, KtImportDirective::class.java) + val refElement: KtElement = simpleExpression.getQualifiedElement() val reference = object : PsiReferenceBase(refElement) { override fun resolve() = null @@ -40,7 +51,6 @@ object KotlinAddOrderEntryActionFactory : KotlinIntentionActionsFactory() { override fun getCanonicalText() = refElement.text override fun bindToElement(element: PsiElement): PsiElement { - val project = element.project project.runWhenSmart { project.executeWriteCommand("") { simpleExpression.mainReference.bindToElement(element, ShorteningMode.FORCED_SHORTENING) @@ -50,7 +60,45 @@ object KotlinAddOrderEntryActionFactory : KotlinIntentionActionsFactory() { } } + val registerFixes = OrderEntryFix.registerFixes(reference, mutableListOf()) { shortName -> + val scope = GlobalSearchScope.allScope(project) + val classesByName = PsiShortNamesCache.getInstance(project).getClassesByName(shortName, scope) + if (classesByName.isNotEmpty()) return@registerFixes classesByName + val importedFqName = importDirective?.importedFqName + if (importedFqName != null) { + PsiShortNamesCache.getInstance(project).getClassesByName(importedFqName.shortName().asString(), scope) + .takeUnless { it.isEmpty() } + ?.let { return@registerFixes it } + } + + val declarations = + run { + if (importedFqName != null && !importDirective.isAllUnder) { + KotlinTopLevelPropertyFqnNameIndex[importedFqName.asString(), project, scope] + } + else { + KotlinPropertyShortNameIndex[shortName, project, scope].filter { (it as? KtProperty)?.isTopLevel == true } + } + }.takeUnless { it.isEmpty() } ?: run { + if (importedFqName != null && !importDirective.isAllUnder) { + KotlinTopLevelFunctionFqnNameIndex[importedFqName.asString(), project, scope] + } + else { + KotlinFunctionShortNameIndex[shortName, project, scope].filter { (it as? KtNamedFunction)?.isTopLevel == true } + } + } + if (declarations.isNotEmpty()) { + val lightClasses = declarations + .flatMap { it.toLightElements() } + .mapNotNull { (it as? KtLightMember<*>)?.containingClass } + .toSet() + if (lightClasses.isNotEmpty()) { + return@registerFixes lightClasses.toTypedArray() + } + } + PsiClass.EMPTY_ARRAY + } @Suppress("UNCHECKED_CAST") - return OrderEntryFix.registerFixes(reference, mutableListOf()) as List? ?: emptyList() + return registerFixes as List? ?: emptyList() } } diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java index b76548ec527b..f61bd7d30d02 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java @@ -117,6 +117,29 @@ public abstract class QuickFixMultiModuleTestGenerated extends AbstractQuickFixM } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("testData/multiModuleQuickFix/addDependency") + public static class AddDependency extends AbstractQuickFixMultiModuleTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("class") + public void testClass() throws Exception { + runTest("testData/multiModuleQuickFix/addDependency/class/"); + } + + @TestMetadata("topLevelFunction") + public void testTopLevelFunction() throws Exception { + runTest("testData/multiModuleQuickFix/addDependency/topLevelFunction/"); + } + + @TestMetadata("topLevelProperty") + public void testTopLevelProperty() throws Exception { + runTest("testData/multiModuleQuickFix/addDependency/topLevelProperty/"); + } + } + @RunWith(JUnit3RunnerWithInners.class) @TestMetadata("testData/multiModuleQuickFix/addMissingActualMembers") public static class AddMissingActualMembers extends AbstractQuickFixMultiModuleTest { diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/dependencies.txt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/dependencies.txt new file mode 100644 index 000000000000..f5db539a03c7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/dependencies.txt @@ -0,0 +1,3 @@ +MODULE jvm { platform=[JVM] } +MODULE main { platform=[JVM (1.8)] } + diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/jvm/Dependency.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/jvm/Dependency.kt new file mode 100644 index 000000000000..9ae81975414d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/jvm/Dependency.kt @@ -0,0 +1,3 @@ +package bar.foo + +class Dependency \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/main/main.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/main/main.kt new file mode 100644 index 000000000000..6c758f4677be --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/class/main/main.kt @@ -0,0 +1,5 @@ +// "Add dependency on module 'jvm'" "true" +// DISABLE-ERRORS +package bar + +import bar.foo.Dependency \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/dependencies.txt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/dependencies.txt new file mode 100644 index 000000000000..f5db539a03c7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/dependencies.txt @@ -0,0 +1,3 @@ +MODULE jvm { platform=[JVM] } +MODULE main { platform=[JVM (1.8)] } + diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/jvm/Dependency.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/jvm/Dependency.kt new file mode 100644 index 000000000000..f70422ede101 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/jvm/Dependency.kt @@ -0,0 +1,3 @@ +package bar.foo + +fun fooMethod(): String = "foo" \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/main/main.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/main/main.kt new file mode 100644 index 000000000000..b86224767b85 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelFunction/main/main.kt @@ -0,0 +1,5 @@ +// "Add dependency on module 'jvm'" "true" +// DISABLE-ERRORS +package bar + +import bar.foo.fooMethod \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/dependencies.txt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/dependencies.txt new file mode 100644 index 000000000000..f5db539a03c7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/dependencies.txt @@ -0,0 +1,3 @@ +MODULE jvm { platform=[JVM] } +MODULE main { platform=[JVM (1.8)] } + diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/jvm/Dependency.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/jvm/Dependency.kt new file mode 100644 index 000000000000..0bebfdea0bba --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/jvm/Dependency.kt @@ -0,0 +1,3 @@ +package bar.foo + +val FOO: String = "foo" \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/main/main.kt b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/main/main.kt new file mode 100644 index 000000000000..6046c5ce791f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/multiModuleQuickFix/addDependency/topLevelProperty/main/main.kt @@ -0,0 +1,5 @@ +// "Add dependency on module 'jvm'" "true" +// DISABLE-ERRORS +package bar + +import bar.foo.FOO \ No newline at end of file diff --git a/plugins/kotlin/test-framework/test/org/jetbrains/kotlin/idea/multiplatform/multiPlatformSetup.kt b/plugins/kotlin/test-framework/test/org/jetbrains/kotlin/idea/multiplatform/multiPlatformSetup.kt index 9a35e8006a58..6f7f0ceed4a2 100644 --- a/plugins/kotlin/test-framework/test/org/jetbrains/kotlin/idea/multiplatform/multiPlatformSetup.kt +++ b/plugins/kotlin/test-framework/test/org/jetbrains/kotlin/idea/multiplatform/multiPlatformSetup.kt @@ -278,9 +278,9 @@ private fun parseModuleId(parts: List): ModuleId { } private fun parsePlatform(parts: List) = - platformNames.entries.single { (names, _) -> + platformNames.entries.singleOrNull { (names, _) -> names.any { name -> parts.any { part -> part.equals(name, ignoreCase = true) } } - }.value + }?.value ?: error("unable to lookup platform among $parts") private fun parseModuleName(parts: List) = when { parts.size > 1 -> parts.first()