diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index efdcca81732..4e18f322f61 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt index fdb36fea3ed..9a6e9633e5d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/InitializePropertyQuickFixFactory.kt @@ -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(property) { @@ -46,13 +58,77 @@ object InitializePropertyQuickFixFactory : KotlinIntentionActionsFactory() { } } - override fun doCreateActions(diagnostic: Diagnostic): List { - val property = diagnostic.psiElement as? KtProperty ?: return emptyList() + class MoveToConstructorParameters(property: KtProperty) : KotlinQuickFixAction(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): 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 { + val property = diagnostic.psiElement as? KtProperty ?: return emptyList() + if (property.receiverTypeReference != null) return emptyList() + + val actions = ArrayList(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 } } diff --git a/idea/testData/quickfix/moveToConstructorParameters/localVar.kt b/idea/testData/quickfix/moveToConstructorParameters/localVar.kt new file mode 100644 index 00000000000..aa94b9cc52d --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/localVar.kt @@ -0,0 +1,4 @@ +// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false" +fun test() { + val n: Int +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberExtensionProperty.kt b/idea/testData/quickfix/moveToConstructorParameters/memberExtensionProperty.kt new file mode 100644 index 00000000000..8be009cbae8 --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberExtensionProperty.kt @@ -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 { + val Int.n: Int +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNameClash.kt b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNameClash.kt new file mode 100644 index 00000000000..d90fc8cbf2b --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNameClash.kt @@ -0,0 +1,9 @@ +// "Move to constructor parameters" "true" +// SHOULD_FAIL_WITH: Duplicating parameter 'n' +open class A(n: Int) { + val n: Int +} + +fun test() { + val a = A(0) +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNoPrimaryConstructor.kt b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNoPrimaryConstructor.kt new file mode 100644 index 00000000000..ec349ef638c --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNoPrimaryConstructor.kt @@ -0,0 +1,10 @@ +// "Move to constructor parameters" "true" +open class A { + val n: Int +} + +class B : A() + +fun test() { + val a = A() +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNoPrimaryConstructor.kt.after b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNoPrimaryConstructor.kt.after new file mode 100644 index 00000000000..f450f75d1de --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassNoPrimaryConstructor.kt.after @@ -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) +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithConstructorDelegatingToSuper.kt b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithConstructorDelegatingToSuper.kt new file mode 100644 index 00000000000..8a91684bbbd --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithConstructorDelegatingToSuper.kt @@ -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 { + val n: Int + + constructor(): super(1) +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithImplicitlyDelegatingConstructor.kt b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithImplicitlyDelegatingConstructor.kt new file mode 100644 index 00000000000..19cfebf2f50 --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithImplicitlyDelegatingConstructor.kt @@ -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 { + val n: Int + + constructor() +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithPrimaryConstructor.kt b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithPrimaryConstructor.kt new file mode 100644 index 00000000000..5cec86b5c35 --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithPrimaryConstructor.kt @@ -0,0 +1,12 @@ +// "Move to constructor parameters" "true" +open class A(s: String) { + val n: Int + + constructor(a: Int): this("") +} + +class B : A("") + +fun test() { + val a = A("") +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithPrimaryConstructor.kt.after b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithPrimaryConstructor.kt.after new file mode 100644 index 00000000000..af37da18312 --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInClassWithPrimaryConstructor.kt.after @@ -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) +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInInterface.kt b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInInterface.kt new file mode 100644 index 00000000000..38756c5d1f0 --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInInterface.kt @@ -0,0 +1,7 @@ +// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false" +// ACTION: Make internal +// ACTION: Make private +// ACTION: Make protected +interface A { + val n: Int +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInObject.kt b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInObject.kt new file mode 100644 index 00000000000..911f4692dd2 --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyInObject.kt @@ -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 { + val n: Int +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/memberPropertyWithDelegateRuntime.kt b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyWithDelegateRuntime.kt new file mode 100644 index 00000000000..c3af89c98d1 --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/memberPropertyWithDelegateRuntime.kt @@ -0,0 +1,7 @@ +// "class org.jetbrains.kotlin.idea.quickfix.InitializePropertyQuickFixFactory$MoveToConstructorParameters" "false" +// ACTION: Make internal +// ACTION: Make private +// ACTION: Make protected +class A { + val n: Int by lazy { 0 } +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/propertyWithModifiersAndComments.kt b/idea/testData/quickfix/moveToConstructorParameters/propertyWithModifiersAndComments.kt new file mode 100644 index 00000000000..4b5faee333e --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/propertyWithModifiersAndComments.kt @@ -0,0 +1,12 @@ +// "Move to constructor parameters" "true" +annotation class foo + +open class A(s: String) { + private @foo val /*1*/ n: /* 2 */ Int +} + +class B : A("") + +fun test() { + val a = A("") +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/propertyWithModifiersAndComments.kt.after b/idea/testData/quickfix/moveToConstructorParameters/propertyWithModifiersAndComments.kt.after new file mode 100644 index 00000000000..55278139aaa --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/propertyWithModifiersAndComments.kt.after @@ -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) +} \ No newline at end of file diff --git a/idea/testData/quickfix/moveToConstructorParameters/topLevelProperty.kt b/idea/testData/quickfix/moveToConstructorParameters/topLevelProperty.kt new file mode 100644 index 00000000000..ff8c9ed8cdc --- /dev/null +++ b/idea/testData/quickfix/moveToConstructorParameters/topLevelProperty.kt @@ -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 +val n: Int \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java index f964b8c01f2..f7bf0470f90 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java @@ -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); } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 5d5e5549502..432444a59cd 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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)