Add the quickfix to add property to supertype #KT-17004 Fixed
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.codeInsight.intention.LowPriorityAction
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.ui.popup.ListPopupStep
|
||||
import com.intellij.openapi.ui.popup.PopupStep
|
||||
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
|
||||
import com.intellij.util.PlatformIcons
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.Modality.ABSTRACT
|
||||
import org.jetbrains.kotlin.descriptors.Modality.SEALED
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.implicitModality
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.modalityModifier
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
|
||||
class AddPropertyToSupertypeFix private constructor(
|
||||
element: KtProperty,
|
||||
private val properties: List<PropertyData>
|
||||
) : KotlinQuickFixAction<KtProperty>(element), LowPriorityAction {
|
||||
|
||||
private class PropertyData(val signaturePreview: String, val sourceCode: String, val targetClass: KtClass)
|
||||
|
||||
override fun getText(): String {
|
||||
val single = properties.singleOrNull()
|
||||
return if (single != null) actionName(single) else "Add property to supertype..."
|
||||
}
|
||||
|
||||
override fun getFamilyName() = "Add property to supertype"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
if (properties.size == 1 || editor == null || !editor.component.isShowing) {
|
||||
addProperty(properties.first(), project)
|
||||
} else {
|
||||
JBPopupFactory.getInstance().createListPopup(createPropertyPopup(project)).showInBestPositionFor(editor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addProperty(propertyData: PropertyData, project: Project) {
|
||||
project.executeWriteCommand("Add Property to Type") {
|
||||
val classBody = propertyData.targetClass.getOrCreateBody()
|
||||
|
||||
val propertyElement = KtPsiFactory(project).createProperty(propertyData.sourceCode)
|
||||
val insertedPropertyElement = classBody.addBefore(propertyElement, classBody.rBrace) as KtProperty
|
||||
|
||||
ShortenReferences.DEFAULT.process(insertedPropertyElement)
|
||||
val modifierToken = insertedPropertyElement.modalityModifier()?.node?.elementType as? KtModifierKeywordToken
|
||||
?: return@executeWriteCommand
|
||||
if (insertedPropertyElement.implicitModality() == modifierToken) {
|
||||
RemoveModifierFix(insertedPropertyElement, modifierToken, true).invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPropertyPopup(project: Project): ListPopupStep<*> {
|
||||
return object : BaseListPopupStep<PropertyData>("Choose Type", properties) {
|
||||
override fun isAutoSelectionEnabled() = false
|
||||
|
||||
override fun onChosen(selectedValue: PropertyData, finalChoice: Boolean): PopupStep<*>? {
|
||||
if (finalChoice) {
|
||||
addProperty(selectedValue, project)
|
||||
}
|
||||
return PopupStep.FINAL_CHOICE
|
||||
}
|
||||
|
||||
override fun getIconFor(value: PropertyData) = PlatformIcons.PROPERTY_ICON
|
||||
override fun getTextFor(value: PropertyData) = actionName(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun actionName(propertyData: PropertyData) = "Add '${propertyData.signaturePreview}' to '${propertyData.targetClass.name}'"
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val property = diagnostic.psiElement as? KtProperty ?: return null
|
||||
|
||||
val descriptors = generatePropertiesToAdd(property)
|
||||
if (descriptors.isEmpty()) return null
|
||||
|
||||
val project = diagnostic.psiFile.project
|
||||
val propertyData = descriptors.mapNotNull { createPropertyData(it, property.initializer, project) }
|
||||
if (propertyData.isEmpty()) return null
|
||||
|
||||
return AddPropertyToSupertypeFix(property, propertyData)
|
||||
}
|
||||
|
||||
private fun createPropertyData(
|
||||
propertyDescriptor: PropertyDescriptor,
|
||||
initializer: KtExpression?,
|
||||
project: Project
|
||||
): PropertyData? {
|
||||
val classDescriptor = propertyDescriptor.containingDeclaration as ClassDescriptor
|
||||
var signaturePreview = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.render(propertyDescriptor)
|
||||
if (classDescriptor.kind == ClassKind.INTERFACE) {
|
||||
signaturePreview = signaturePreview.substring("abstract ".length)
|
||||
}
|
||||
|
||||
var sourceCode = IdeDescriptorRenderers.SOURCE_CODE.render(propertyDescriptor)
|
||||
if (classDescriptor.kind == ClassKind.CLASS && classDescriptor.modality == Modality.OPEN && initializer != null) {
|
||||
sourceCode += " = ${initializer.text}"
|
||||
}
|
||||
|
||||
val targetClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, classDescriptor) as? KtClass ?: return null
|
||||
return PropertyData(signaturePreview, sourceCode, targetClass)
|
||||
}
|
||||
|
||||
private fun generatePropertiesToAdd(propertyElement: KtProperty): List<PropertyDescriptor> {
|
||||
val propertyDescriptor =
|
||||
propertyElement.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? PropertyDescriptor ?: return emptyList()
|
||||
val classDescriptor = propertyDescriptor.containingDeclaration as? ClassDescriptor ?: return emptyList()
|
||||
|
||||
return getSuperClasses(classDescriptor)
|
||||
.asSequence()
|
||||
.filterNot { KotlinBuiltIns.isAnyOrNullableAny(it.defaultType) }
|
||||
.map { generatePropertySignatureForType(propertyDescriptor, it) }
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun getSuperClasses(classDescriptor: ClassDescriptor): List<ClassDescriptor> {
|
||||
val supertypes = classDescriptor.defaultType.supertypes().toMutableList().sortSubtypesFirst()
|
||||
return supertypes.mapNotNull { it.constructor.declarationDescriptor as? ClassDescriptor }
|
||||
}
|
||||
|
||||
private fun MutableList<KotlinType>.sortSubtypesFirst(): List<KotlinType> {
|
||||
// TODO: rewrite this
|
||||
val typeChecker = KotlinTypeChecker.DEFAULT
|
||||
for (i in 1 until size) {
|
||||
val currentType = this[i]
|
||||
for (j in 0 until i) {
|
||||
if (typeChecker.isSubtypeOf(currentType, this[j])) {
|
||||
this.removeAt(i)
|
||||
this.add(j, currentType)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun generatePropertySignatureForType(
|
||||
propertyDescriptor: PropertyDescriptor,
|
||||
typeDescriptor: ClassDescriptor
|
||||
): PropertyDescriptor {
|
||||
val containerModality = typeDescriptor.modality
|
||||
val modality = if (containerModality == SEALED || containerModality == ABSTRACT) ABSTRACT else propertyDescriptor.modality
|
||||
return propertyDescriptor.newCopyBuilder()
|
||||
.setOwner(typeDescriptor)
|
||||
.setModality(modality)
|
||||
.setVisibility(propertyDescriptor.visibility)
|
||||
.setKind(CallableMemberDescriptor.Kind.DECLARATION)
|
||||
.setCopyOverrides(false)
|
||||
.build()!!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,8 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
NOTHING_TO_OVERRIDE.registerFactory(
|
||||
RemoveModifierFix.createRemoveModifierFromListOwnerFactory(OVERRIDE_KEYWORD),
|
||||
ChangeMemberFunctionSignatureFix,
|
||||
AddFunctionToSupertypeFix
|
||||
AddFunctionToSupertypeFix,
|
||||
AddPropertyToSupertypeFix
|
||||
)
|
||||
VIRTUAL_MEMBER_HIDDEN.registerFactory(AddModifierFix.createFactory(OVERRIDE_KEYWORD))
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add 'abstract val hoge: Int' to 'Foo'" "true"
|
||||
abstract class Foo
|
||||
|
||||
class Bar: Foo() {
|
||||
override<caret> val hoge = 3
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Add 'abstract val hoge: Int' to 'Foo'" "true"
|
||||
abstract class Foo {
|
||||
abstract val hoge: Int
|
||||
}
|
||||
|
||||
class Bar: Foo() {
|
||||
override val hoge = 3
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add 'val hoge: Int' to 'Foo'" "true"
|
||||
interface Foo
|
||||
|
||||
class Bar: Foo {
|
||||
override<caret> val hoge = 3
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Add 'val hoge: Int' to 'Foo'" "true"
|
||||
interface Foo {
|
||||
val hoge: Int
|
||||
}
|
||||
|
||||
class Bar: Foo {
|
||||
override val hoge = 3
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add 'open val hoge: Int' to 'Foo'" "true"
|
||||
open class Foo
|
||||
|
||||
class Bar: Foo() {
|
||||
override<caret> val hoge = 3
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Add 'open val hoge: Int' to 'Foo'" "true"
|
||||
open class Foo {
|
||||
open val hoge: Int = 3
|
||||
}
|
||||
|
||||
class Bar: Foo() {
|
||||
override val hoge = 3
|
||||
}
|
||||
+13
@@ -221,6 +221,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addPropertyToSupertype")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddPropertyToSupertype extends AbstractQuickFixMultiFileTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAddPropertyToSupertype() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addPropertyToSupertype"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addReifiedToTypeParameterOfFunctionFix")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -821,6 +821,34 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addPropertyToSupertype")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddPropertyToSupertype extends AbstractQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
@TestMetadata("abstractClass.kt")
|
||||
public void testAbstractClass() throws Exception {
|
||||
runTest("idea/testData/quickfix/addPropertyToSupertype/abstractClass.kt");
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAddPropertyToSupertype() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addPropertyToSupertype"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("interface.kt")
|
||||
public void testInterface() throws Exception {
|
||||
runTest("idea/testData/quickfix/addPropertyToSupertype/interface.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("openClass.kt")
|
||||
public void testOpenClass() throws Exception {
|
||||
runTest("idea/testData/quickfix/addPropertyToSupertype/openClass.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addReifiedToTypeParameterOfFunctionFix")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user