[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
This commit is contained in:
Toshiaki Kameyama
2022-10-28 10:15:28 +00:00
committed by intellij-monorepo-bot
parent 10f8b83aa5
commit af0c587b68
10 changed files with 125 additions and 20 deletions
@@ -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<KtDotQualifiedExpression>()
?.receiverExpression
?.safeAs<KtSuperExpression>()
?.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<SimpleFunctionDescriptor>()?.modality != Modality.ABSTRACT }
@@ -157,5 +167,8 @@ private fun getSuperClassNameToReferTo(expression: KtNameReferenceExpression): S
?: tryViaContainingFunction()
?: return null
return callableToUseInstead.containingDeclaration.name.asString()
}
return callableToUseInstead.containingDeclaration.fqNameOrNull()
}
private fun KtNameReferenceExpression.getParentSuperExpression(): KtSuperExpression? =
parentOfType<KtDotQualifiedExpression>()?.receiverExpression?.safeAs<KtSuperExpression>()
@@ -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 {
@@ -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<Foo>.check()
}
}
@@ -0,0 +1,5 @@
package one
interface Foo {
fun check(): String = "OK"
}
@@ -0,0 +1,9 @@
package two
import one.Foo
abstract class Base {
abstract fun check(): String
}
abstract class Derived : Base(), Foo
@@ -0,0 +1,11 @@
// "Specify super type 'Foo' explicitly" "true"
package three
import two.Derived
class Derived3 : Derived() {
override fun check(): String {
return super.<caret>check()
}
}
@@ -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<Foo>.check()
}
}
@@ -0,0 +1,5 @@
package one
interface Foo {
fun check(): String = "OK"
}
@@ -0,0 +1,9 @@
package two
import one.Foo
abstract class Base {
abstract fun check(): String
}
abstract class Derived : Base(), Foo
@@ -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<Derived>.<caret>check()
}
}