Quick Fixes: Implement 'Move to constructor parameters' quick-fix

#KT-6604 In Progress
This commit is contained in:
Alexey Sedunov
2015-11-12 16:25:15 +03:00
parent f66af7bdd4
commit 138370ce5b
19 changed files with 319 additions and 9 deletions
@@ -408,6 +408,10 @@ public fun KtDeclaration.visibilityModifierType(): KtModifierKeywordToken?
public fun KtStringTemplateExpression.isPlain() = entries.all { it is KtLiteralStringTemplateEntry }
public val KtDeclaration.containingClassOrObject: KtClassOrObject?
get() = (parent as? KtClassBody)?.parent as? KtClassOrObject
public fun KtExpression.getOutermostParenthesizerOrThis(): KtExpression {
return (parentsWithSelf zip parents).firstOrNull {
val (element, parent) = it
@@ -17,18 +17,30 @@
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.command.impl.FinishMarkAction
import com.intellij.openapi.command.impl.StartMarkAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.search.searches.MethodReferencesSearch
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import java.util.*
object InitializePropertyQuickFixFactory : KotlinIntentionActionsFactory() {
class AddInitializerFix(property: KtProperty) : KotlinQuickFixAction<KtProperty>(property) {
@@ -46,13 +58,77 @@ object InitializePropertyQuickFixFactory : KotlinIntentionActionsFactory() {
}
}
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val property = diagnostic.psiElement as? KtProperty ?: return emptyList()
class MoveToConstructorParameters(property: KtProperty) : KotlinQuickFixAction<KtProperty>(property) {
override fun getText() = "Move to constructor parameters"
override fun getFamilyName() = text
if (property.receiverTypeReference == null) {
return listOf(AddInitializerFix(property))
private fun configureChangeSignature(propertyDescriptor: PropertyDescriptor): KotlinChangeSignatureConfiguration {
return object : KotlinChangeSignatureConfiguration {
override fun configure(originalDescriptor: KotlinMethodDescriptor): KotlinMethodDescriptor {
return originalDescriptor.modify {
val initializerText = CodeInsightUtils.defaultInitializer(propertyDescriptor.type) ?: "null"
val newParam = KotlinParameterInfo(
callableDescriptor = originalDescriptor.baseDescriptor,
name = propertyDescriptor.name.asString(),
type = propertyDescriptor.type,
valOrVar = element.valOrVarKeyword.toValVar(),
modifierList = element.modifierList,
defaultValueForCall = KtPsiFactory(element.project).createExpression(initializerText)
)
it.addParameter(newParam)
}
}
override fun performSilently(affectedFunctions: Collection<PsiElement>): Boolean {
return affectedFunctions.flatMap { it.toLightMethods() }.all { MethodReferencesSearch.search(it).findFirst() == null }
}
}
}
return emptyList()
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val klass = element.containingClassOrObject ?: return
val propertyDescriptor = element.resolveToDescriptorIfAny() as? PropertyDescriptor ?: return
StartMarkAction.canStart(project)?.let { return }
val startMarkAction = StartMarkAction.start(editor, project, text)
try {
val parameterToInsert = KtPsiFactory(project).createParameter(element.text)
runWriteAction { element.delete() }
val classDescriptor = klass.resolveToDescriptorIfAny() as? ClassDescriptorWithResolutionScopes ?: return
val constructorDescriptor = classDescriptor.unsubstitutedPrimaryConstructor ?: return
val constructorPointer = constructorDescriptor.source.getPsi()?.createSmartPointer()
val config = configureChangeSignature(propertyDescriptor)
val changeSignature = { runChangeSignature(project, constructorDescriptor, config, element, text) }
changeSignature.runRefactoringWithPostprocessing(project, "refactoring.changeSignature") {
val constructorOrClass = constructorPointer?.element
val constructor = constructorOrClass as? KtConstructor<*> ?: (constructorOrClass as? KtClass)?.getPrimaryConstructor()
constructor?.getValueParameters()?.lastOrNull()?.replace(parameterToInsert)
}
}
finally {
FinishMarkAction.finish(project, editor, startMarkAction)
}
}
}
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val property = diagnostic.psiElement as? KtProperty ?: return emptyList()
if (property.receiverTypeReference != null) return emptyList()
val actions = ArrayList<IntentionAction>(2)
actions.add(AddInitializerFix(property))
(property.containingClassOrObject as? KtClass)?.let { klass ->
if (klass.isAnnotation() || klass.isInterface()) return@let
if (property.accessors.isNotEmpty()) return@let
if (klass.getSecondaryConstructors().any { !it.getDelegationCall().isCallToThis }) return@let
actions.add(MoveToConstructorParameters(property))
}
return actions
}
}
@@ -0,0 +1,4 @@
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false"
fun test() {
<caret>val n: Int
}
@@ -0,0 +1,9 @@
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false"
// ACTION: Make 'n' abstract
// ACTION: Make internal
// ACTION: Make private
// ACTION: Make protected
// ERROR: Property must be initialized or be abstract
class A {
<caret>val Int.n: Int
}
@@ -0,0 +1,9 @@
// "Move to constructor parameters" "true"
// SHOULD_FAIL_WITH: Duplicating parameter 'n'
open class A(n: Int) {
<caret>val n: Int
}
fun test() {
val a = A(0)
}
@@ -0,0 +1,10 @@
// "Move to constructor parameters" "true"
open class A {
<caret>val n: Int
}
class B : A()
fun test() {
val a = A()
}
@@ -0,0 +1,9 @@
// "Move to constructor parameters" "true"
open class A(val n: Int) {
}
class B : A(0)
fun test() {
val a = A(0)
}
@@ -0,0 +1,14 @@
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false"
// ACTION: Initialize property 'n'
// ACTION: Make 'n' abstract
// ACTION: Make internal
// ACTION: Make private
// ACTION: Make protected
// ERROR: Property must be initialized or be abstract
open class A(x: Int)
class B : A {
<caret>val n: Int
constructor(): super(1)
}
@@ -0,0 +1,14 @@
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false"
// ACTION: Initialize property 'n'
// ACTION: Make 'n' abstract
// ACTION: Make internal
// ACTION: Make private
// ACTION: Make protected
// ERROR: Property must be initialized or be abstract
open class A
class B {
<caret>val n: Int
constructor()
}
@@ -0,0 +1,12 @@
// "Move to constructor parameters" "true"
open class A(s: String) {
<caret>val n: Int
constructor(a: Int): this("")
}
class B : A("")
fun test() {
val a = A("")
}
@@ -0,0 +1,11 @@
// "Move to constructor parameters" "true"
open class A(s: String, val n: Int) {
constructor(a: Int): this("", 0)
}
class B : A("", 0)
fun test() {
val a = A("", 0)
}
@@ -0,0 +1,7 @@
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false"
// ACTION: Make internal
// ACTION: Make private
// ACTION: Make protected
interface A {
<caret>val n: Int
}
@@ -0,0 +1,9 @@
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false"
// ACTION: Initialize property 'n'
// ACTION: Make 'n' abstract
// ACTION: Make internal
// ACTION: Make private
// ERROR: Property must be initialized or be abstract
object A {
<caret>val n: Int
}
@@ -0,0 +1,7 @@
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false"
// ACTION: Make internal
// ACTION: Make private
// ACTION: Make protected
class A {
<caret>val n: Int by lazy { 0 }
}
@@ -0,0 +1,12 @@
// "Move to constructor parameters" "true"
annotation class foo
open class A(s: String) {
<caret>private @foo val /*1*/ n: /* 2 */ Int
}
class B : A("")
fun test() {
val a = A("")
}
@@ -0,0 +1,11 @@
// "Move to constructor parameters" "true"
annotation class foo
open class A(s: String, private @foo val /*1*/ n: /* 2 */ Int) {
}
class B : A("", 0)
fun test() {
val a = A("", 0)
}
@@ -0,0 +1,6 @@
// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false"
// ACTION: Initialize property 'n'
// ACTION: Make internal
// ACTION: Make private
// ERROR: Property must be initialized
<caret>val n: Int
@@ -96,8 +96,10 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase {
@Override
public void run() {
String fileText = "";
String expectedErrorMessage = "";
try {
fileText = FileUtil.loadFile(testFile, CharsetToolkit.UTF8_CHARSET);
expectedErrorMessage = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// SHOULD_FAIL_WITH: ");
String contents = StringUtil.convertLineSeparators(fileText);
quickFixTestCase.configureFromFileText(testFile.getName(), contents);
quickFixTestCase.bringRealEditorBack();
@@ -105,6 +107,8 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase {
checkForUnexpectedActions();
applyAction(contents, quickFixTestCase, testName, testFullPath);
assertEmpty(expectedErrorMessage);
}
catch (FileComparisonFailure e) {
throw e;
@@ -113,10 +117,11 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase {
throw e;
}
catch (Throwable e) {
e.printStackTrace();
fail(testName);
}
finally {
if (!e.getMessage().equals(expectedErrorMessage)) {
e.printStackTrace();
fail(testName);
}
} finally {
ConfigLibraryUtil.unconfigureLibrariesByDirective(getModule(), fileText);
}
}
@@ -4924,6 +4924,87 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/moveToConstructorParameters")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class MoveToConstructorParameters extends AbstractQuickFixTest {
public void testAllFilesPresentInMoveToConstructorParameters() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/moveToConstructorParameters"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("localVar.kt")
public void testLocalVar() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/localVar.kt");
doTest(fileName);
}
@TestMetadata("memberExtensionProperty.kt")
public void testMemberExtensionProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberExtensionProperty.kt");
doTest(fileName);
}
@TestMetadata("memberPropertyInClassNameClash.kt")
public void testMemberPropertyInClassNameClash() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNameClash.kt");
doTest(fileName);
}
@TestMetadata("memberPropertyInClassNoPrimaryConstructor.kt")
public void testMemberPropertyInClassNoPrimaryConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNoPrimaryConstructor.kt");
doTest(fileName);
}
@TestMetadata("memberPropertyInClassWithConstructorDelegatingToSuper.kt")
public void testMemberPropertyInClassWithConstructorDelegatingToSuper() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithConstructorDelegatingToSuper.kt");
doTest(fileName);
}
@TestMetadata("memberPropertyInClassWithImplicitlyDelegatingConstructor.kt")
public void testMemberPropertyInClassWithImplicitlyDelegatingConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithImplicitlyDelegatingConstructor.kt");
doTest(fileName);
}
@TestMetadata("memberPropertyInClassWithPrimaryConstructor.kt")
public void testMemberPropertyInClassWithPrimaryConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithPrimaryConstructor.kt");
doTest(fileName);
}
@TestMetadata("memberPropertyInInterface.kt")
public void testMemberPropertyInInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberPropertyInInterface.kt");
doTest(fileName);
}
@TestMetadata("memberPropertyInObject.kt")
public void testMemberPropertyInObject() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberPropertyInObject.kt");
doTest(fileName);
}
@TestMetadata("memberPropertyWithDelegateRuntime.kt")
public void testMemberPropertyWithDelegateRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/memberPropertyWithDelegateRuntime.kt");
doTest(fileName);
}
@TestMetadata("propertyWithModifiersAndComments.kt")
public void testPropertyWithModifiersAndComments() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/propertyWithModifiersAndComments.kt");
doTest(fileName);
}
@TestMetadata("topLevelProperty.kt")
public void testTopLevelProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/moveToConstructorParameters/topLevelProperty.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/quickfix/nullables")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)