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(
|
NOTHING_TO_OVERRIDE.registerFactory(
|
||||||
RemoveModifierFix.createRemoveModifierFromListOwnerFactory(OVERRIDE_KEYWORD),
|
RemoveModifierFix.createRemoveModifierFromListOwnerFactory(OVERRIDE_KEYWORD),
|
||||||
ChangeMemberFunctionSignatureFix,
|
ChangeMemberFunctionSignatureFix,
|
||||||
AddFunctionToSupertypeFix
|
AddFunctionToSupertypeFix,
|
||||||
|
AddPropertyToSupertypeFix
|
||||||
)
|
)
|
||||||
VIRTUAL_MEMBER_HIDDEN.registerFactory(AddModifierFix.createFactory(OVERRIDE_KEYWORD))
|
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")
|
@TestMetadata("idea/testData/quickfix/addReifiedToTypeParameterOfFunctionFix")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@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")
|
@TestMetadata("idea/testData/quickfix/addReifiedToTypeParameterOfFunctionFix")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user