From af0c587b68503d3d06db0c63a66c2c2b940fe1dd Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Sun, 2 Oct 2022 07:38:47 +0900 Subject: [PATCH] [kotlin] ABSTRACT_SUPER_CALL quickfix: Add an import directive if needed ^KTIJ-23057 Fixed closes https://github.com/JetBrains/intellij-community/pull/2177 GitOrigin-RevId: a7d7e7e861dfd8a94d0e086142cd007cbd52f9ee --- .../idea/quickfix/AbstractSuperCallFix.kt | 53 ++++++++++++------- .../QuickFixMultiFileTestGenerated.java | 18 +++++++ .../abstractSuperCall4.after.kt | 12 +++++ .../abstractSuperCall4.before.Dependency1.kt | 5 ++ .../abstractSuperCall4.before.Dependency2.kt | 9 ++++ .../abstractSuperCall4.before.Main.kt | 11 ++++ ...stractSuperCallWithExplicitSuper3.after.kt | 12 +++++ ...llWithExplicitSuper3.before.Dependency1.kt | 5 ++ ...llWithExplicitSuper3.before.Dependency2.kt | 9 ++++ ...SuperCallWithExplicitSuper3.before.Main.kt | 11 ++++ 10 files changed, 125 insertions(+), 20 deletions(-) create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.after.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Dependency1.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Dependency2.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Main.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.after.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Dependency1.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Dependency2.kt create mode 100644 plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Main.kt diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AbstractSuperCallFix.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AbstractSuperCallFix.kt index 679e16f02b1c..0a648b2196a5 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AbstractSuperCallFix.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AbstractSuperCallFix.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateEqualsAndHashcodeAction import org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateEqualsAndHashcodeAction.Info import org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateToStringAction +import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinPsiOnlyQuickFixAction @@ -21,8 +22,10 @@ import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyHashCode import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyToString import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull import org.jetbrains.kotlin.utils.addToStdlib.safeAs private const val EQUALS = "equals" @@ -40,7 +43,7 @@ class AbstractSuperCallFix(element: KtNameReferenceExpression) : KotlinPsiOnlyQu override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { val expression = element ?: return false - return getSuperClassNameToReferTo(expression) != null + return getSuperClassFqNameToReferTo(expression) != null } override fun invoke(project: Project, editor: Editor?, file: KtFile) { @@ -70,18 +73,11 @@ class AbstractSuperCallFix(element: KtNameReferenceExpression) : KotlinPsiOnlyQu functionDescriptor.isAnyHashCode() -> replaceIfNotInObject() functionDescriptor.isAnyToString() -> replaceIfNotInObject() else -> { - getSuperClassNameToReferTo(expression)?.let { superClassName -> - expression.parentOfType() - ?.receiverExpression - ?.safeAs() - ?.specifySuperType(superClassName) - - KtPsiFactory(containingClass).createSuperTypeEntry(superClassName) - }?.let { - val alreadyExists = containingClass.superTypeListEntries.any { entry -> entry.text == it.text } - if (!alreadyExists) { - containingClass.addSuperTypeListEntry(it) - } + val superExpression = expression.getParentSuperExpression() + val superClassFqName = getSuperClassFqNameToReferTo(expression) + if (superExpression != null && superClassFqName != null) { + superExpression.specifySuperType(superClassFqName) + containingClass.addSuperTypeListEntryIfNotExists(superClassFqName) } } } @@ -94,8 +90,8 @@ class AbstractSuperCallFix(element: KtNameReferenceExpression) : KotlinPsiOnlyQu EQUALS -> KotlinBundle.message("equals.text") TO_STRING -> KotlinBundle.message("action.generate.tostring.name") else -> { - val nameToReferTo = getSuperClassNameToReferTo(expression) ?: error("isAvailable() was supposed to prevent null") - KotlinBundle.message("specify.super.type", nameToReferTo) + val nameToReferTo = getSuperClassFqNameToReferTo(expression) ?: error("isAvailable() was supposed to prevent null") + KotlinBundle.message("specify.super.type", nameToReferTo.shortName().asString()) } } } @@ -139,12 +135,26 @@ private fun Info.adjust( needEqualsActually, needHashCodeActually, this.classDescriptor, this.variablesForEquals, this.variablesForHashCode ) -private fun KtSuperExpression.specifySuperType(superType: String) { +private fun KtSuperExpression.specifySuperType(superType: FqName) { val label = labelQualifier?.text ?: "" - replace(KtPsiFactory(this).createExpression("super<$superType>$label")) + val replaced = replaced(KtPsiFactory(this).createExpression("super<${superType.asString()}>$label")) + ShortenReferences.DEFAULT.process(replaced) } -private fun getSuperClassNameToReferTo(expression: KtNameReferenceExpression): String? { +private fun KtClassOrObject.addSuperTypeListEntryIfNotExists(superType: FqName) { + val superTypeFullName = superType.asString() + val superTypeShortName = superType.shortName().asString() + val superTypeNames = setOf(superTypeShortName, superTypeFullName) + val superTypeListEntry = superTypeListEntries.firstOrNull { it.text in superTypeNames } + if (superTypeListEntry == null) { + val added = addSuperTypeListEntry(KtPsiFactory(this).createSuperTypeEntry(superTypeFullName)) + ShortenReferences.DEFAULT.process(added) + } else if (superTypeListEntry.text == superTypeFullName) { + ShortenReferences.DEFAULT.process(superTypeListEntry) + } +} + +private fun getSuperClassFqNameToReferTo(expression: KtNameReferenceExpression): FqName? { fun tryViaCalledFunction(): CallableDescriptor? = expression.resolveToCall()?.resultingDescriptor ?.overriddenDescriptors ?.find { it.safeAs()?.modality != Modality.ABSTRACT } @@ -157,5 +167,8 @@ private fun getSuperClassNameToReferTo(expression: KtNameReferenceExpression): S ?: tryViaContainingFunction() ?: return null - return callableToUseInstead.containingDeclaration.name.asString() -} \ No newline at end of file + return callableToUseInstead.containingDeclaration.fqNameOrNull() +} + +private fun KtNameReferenceExpression.getParentSuperExpression(): KtSuperExpression? = + parentOfType()?.receiverExpression?.safeAs() diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index d03b6cf94b30..502d37e4da69 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -2172,6 +2172,24 @@ public abstract class QuickFixMultiFileTestGenerated extends AbstractQuickFixMul } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("testData/quickfix/specifySuperExplicitly") + public static class SpecifySuperExplicitly extends AbstractQuickFixMultiFileTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTestWithExtraFile, this, testDataFilePath); + } + + @TestMetadata("abstractSuperCall4.before.Main.kt") + public void testAbstractSuperCall4() throws Exception { + runTest("testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Main.kt"); + } + + @TestMetadata("abstractSuperCallWithExplicitSuper3.before.Main.kt") + public void testAbstractSuperCallWithExplicitSuper3() throws Exception { + runTest("testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Main.kt"); + } + } + @RunWith(JUnit3RunnerWithInners.class) @TestMetadata("testData/quickfix/surroundWithNullCheck") public static class SurroundWithNullCheck extends AbstractQuickFixMultiFileTest { diff --git a/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.after.kt b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.after.kt new file mode 100644 index 000000000000..de5e90eeff86 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.after.kt @@ -0,0 +1,12 @@ +// "Specify super type 'Foo' explicitly" "true" +package three + +import one.Foo +import two.Derived + +class Derived3 : Derived(), Foo { + + override fun check(): String { + return super.check() + } +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Dependency1.kt b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Dependency1.kt new file mode 100644 index 000000000000..50e092d0b34d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Dependency1.kt @@ -0,0 +1,5 @@ +package one + +interface Foo { + fun check(): String = "OK" +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Dependency2.kt b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Dependency2.kt new file mode 100644 index 000000000000..c8263c10a628 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Dependency2.kt @@ -0,0 +1,9 @@ +package two + +import one.Foo + +abstract class Base { + abstract fun check(): String +} + +abstract class Derived : Base(), Foo diff --git a/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Main.kt b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Main.kt new file mode 100644 index 000000000000..c3719b9a26bc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCall4.before.Main.kt @@ -0,0 +1,11 @@ +// "Specify super type 'Foo' explicitly" "true" +package three + +import two.Derived + +class Derived3 : Derived() { + + override fun check(): String { + return super.check() + } +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.after.kt b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.after.kt new file mode 100644 index 000000000000..de5e90eeff86 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.after.kt @@ -0,0 +1,12 @@ +// "Specify super type 'Foo' explicitly" "true" +package three + +import one.Foo +import two.Derived + +class Derived3 : Derived(), Foo { + + override fun check(): String { + return super.check() + } +} diff --git a/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Dependency1.kt b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Dependency1.kt new file mode 100644 index 000000000000..50e092d0b34d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Dependency1.kt @@ -0,0 +1,5 @@ +package one + +interface Foo { + fun check(): String = "OK" +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Dependency2.kt b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Dependency2.kt new file mode 100644 index 000000000000..c8263c10a628 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Dependency2.kt @@ -0,0 +1,9 @@ +package two + +import one.Foo + +abstract class Base { + abstract fun check(): String +} + +abstract class Derived : Base(), Foo diff --git a/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Main.kt b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Main.kt new file mode 100644 index 000000000000..1c2b177e3907 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/quickfix/specifySuperExplicitly/abstractSuperCallWithExplicitSuper3.before.Main.kt @@ -0,0 +1,11 @@ +// "Specify super type 'Foo' explicitly" "true" +package three + +import two.Derived + +class Derived3 : Derived(), one.Foo { + + override fun check(): String { + return super.check() + } +}