[kotlin] Look up for kotlin top level properties/functions to add module dependency

^KTIJ-24921

GitOrigin-RevId: 1418d11d928825ae2574987ac2920370404eb540
This commit is contained in:
Vladimir Dolzhenko
2023-03-10 18:05:28 +00:00
committed by intellij-monorepo-bot
parent c4b1c60067
commit a8fdc4ef88
13 changed files with 125 additions and 9 deletions
@@ -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<? super IntentionAction> 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<? super IntentionAction> registrar,
@NotNull Function<? super String, PsiClass[]> 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<PsiClass> allowedDependencies = filterAllowedDependencies(psiElement, classes);
if (allowedDependencies.isEmpty()) {
return result;
@@ -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<IntentionAction> {
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<KtElement>(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<IntentionAction>? ?: emptyList()
return registerFixes as List<IntentionAction>? ?: emptyList()
}
}
@@ -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 {
@@ -0,0 +1,3 @@
MODULE jvm { platform=[JVM] }
MODULE main { platform=[JVM (1.8)] }
@@ -0,0 +1,3 @@
package bar.foo
class Dependency
@@ -0,0 +1,5 @@
// "Add dependency on module 'jvm'" "true"
// DISABLE-ERRORS
package bar
import bar.<caret>foo.Dependency
@@ -0,0 +1,3 @@
MODULE jvm { platform=[JVM] }
MODULE main { platform=[JVM (1.8)] }
@@ -0,0 +1,3 @@
package bar.foo
fun fooMethod(): String = "foo"
@@ -0,0 +1,5 @@
// "Add dependency on module 'jvm'" "true"
// DISABLE-ERRORS
package bar
import bar.<caret>foo.fooMethod
@@ -0,0 +1,3 @@
MODULE jvm { platform=[JVM] }
MODULE main { platform=[JVM (1.8)] }
@@ -0,0 +1,3 @@
package bar.foo
val FOO: String = "foo"
@@ -0,0 +1,5 @@
// "Add dependency on module 'jvm'" "true"
// DISABLE-ERRORS
package bar
import bar.<caret>foo.FOO
@@ -278,9 +278,9 @@ private fun parseModuleId(parts: List<String>): ModuleId {
}
private fun parsePlatform(parts: List<String>) =
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<String>) = when {
parts.size > 1 -> parts.first()