Quick Fixes: Implement 'Move to constructor parameters' quick-fix
#KT-6604 In Progress
This commit is contained in:
@@ -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
|
||||
}
|
||||
+9
@@ -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)
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// "Move to constructor parameters" "true"
|
||||
open class A {
|
||||
<caret>val n: Int
|
||||
}
|
||||
|
||||
class B : A()
|
||||
|
||||
fun test() {
|
||||
val a = A()
|
||||
}
|
||||
+9
@@ -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)
|
||||
}
|
||||
+14
@@ -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)
|
||||
}
|
||||
+14
@@ -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()
|
||||
}
|
||||
Vendored
+12
@@ -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("")
|
||||
}
|
||||
+11
@@ -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)
|
||||
}
|
||||
+7
@@ -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
|
||||
}
|
||||
+7
@@ -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 }
|
||||
}
|
||||
+12
@@ -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("")
|
||||
}
|
||||
Vendored
+11
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user