Create from Usage: Implement 'Create property as constructor parameter' quick-fix

#KT-8426 Fixed
This commit is contained in:
Alexey Sedunov
2015-10-19 11:32:33 +03:00
parent bdd495460b
commit 929d06c86f
45 changed files with 422 additions and 30 deletions
@@ -21,9 +21,13 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionFactoryWithDelegate
import org.jetbrains.kotlin.idea.quickfix.LowPriorityQuickFixWithDelegateFactory
import org.jetbrains.kotlin.idea.quickfix.QuickFixWithDelegateFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageFixBase
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableInfo
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.PropertyInfo
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterFromUsageFix
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
import java.util.*
public abstract class CreateCallableMemberFromUsageFactory<E : KtElement>(
val extensionsSupported: Boolean = true
@@ -32,7 +36,7 @@ public abstract class CreateCallableMemberFromUsageFactory<E : KtElement>(
originalElementPointer: SmartPsiElementPointer<E>,
lowPriority: Boolean,
quickFixDataFactory: () -> List<CallableInfo>?,
quickFixFactory: (E, List<CallableInfo>) -> CreateCallableFromUsageFixBase<E>
quickFixFactory: (E, List<CallableInfo>) -> CreateFromUsageFixBase<E>?
): QuickFixWithDelegateFactory {
val delegateFactory = {
val data = quickFixDataFactory().orEmpty()
@@ -52,14 +56,24 @@ public abstract class CreateCallableMemberFromUsageFactory<E : KtElement>(
diagnostic: Diagnostic,
quickFixDataFactory: () -> List<CallableInfo>?
): List<QuickFixWithDelegateFactory> {
val memberFix = newCallableQuickFix(originalElementPointer, false, quickFixDataFactory) { element, data ->
CreateCallableFromUsageFix(element, data)
}
if (!extensionsSupported) return listOf(memberFix)
val fixes = ArrayList<QuickFixWithDelegateFactory>(3)
val extensionFix = newCallableQuickFix(originalElementPointer, true, quickFixDataFactory) { element, data ->
CreateExtensionCallableFromUsageFix(element, data)
newCallableQuickFix(originalElementPointer, false, quickFixDataFactory) { element, data ->
CreateCallableFromUsageFix(element, data)
}.let { fixes.add(it) }
newCallableQuickFix(originalElementPointer, false, quickFixDataFactory) f@ { element, data ->
(data.singleOrNull() as? PropertyInfo)?.let {
CreateParameterFromUsageFix.createFixForPrimaryConstructorPropertyParameter(element, it)
}
}.let { fixes.add(it) }
if (extensionsSupported) {
newCallableQuickFix(originalElementPointer, true, quickFixDataFactory) { element, data ->
CreateExtensionCallableFromUsageFix(element, data)
}.let { fixes.add(it) }
}
return listOf(memberFix, extensionFix)
return fixes
}
}
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
@@ -52,11 +51,8 @@ object CreateParameterByRefActionFactory : CreateParameterFromUsageFactory<KtSim
return refExpr
}
override fun extractFixData(
element: KtSimpleNameExpression,
diagnostic: Diagnostic
): CreateParameterData<KtSimpleNameExpression>? {
val result = (diagnostic.psiFile as? KtFile)?.analyzeFullyAndGetResult() ?: return null
fun extractFixData(element: KtSimpleNameExpression): CreateParameterData<KtSimpleNameExpression>? {
val result = (element.containingFile as? KtFile)?.analyzeFullyAndGetResult() ?: return null
val context = result.bindingContext
val moduleDescriptor = result.moduleDescriptor
@@ -129,6 +125,8 @@ object CreateParameterByRefActionFactory : CreateParameterFromUsageFactory<KtSim
element
)
}
override fun extractFixData(element: KtSimpleNameExpression, diagnostic: Diagnostic) = extractFixData(element)
}
fun KotlinType.hasTypeParametersToAdd(functionDescriptor: FunctionDescriptor, context: BindingContext): Boolean {
@@ -19,20 +19,30 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.refactoring.canRefactor
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageFixBase
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableBuilderConfiguration
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallablePlacement
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.PropertyInfo
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.createBuilder
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.source.getPsi
public class CreateParameterFromUsageFix<E : KtElement>(
public open class CreateParameterFromUsageFix<E : KtElement>(
val functionDescriptor: FunctionDescriptor,
val parameterInfo: JetParameterInfo,
val defaultValueContext: E
) : CreateFromUsageFixBase<E>(defaultValueContext) {
override fun getText(): String {
return JetBundle.message("create.parameter.from.usage", parameterInfo.name)
return with(parameterInfo) {
if (valOrVar != JetValVar.None) "Create property '$name' as constructor parameter" else "Create parameter '$name'"
}
}
override fun startInWriteAction() = false
@@ -48,4 +58,46 @@ public class CreateParameterFromUsageFix<E : KtElement>(
runChangeSignature(project, functionDescriptor, config, defaultValueContext, text)
}
}
companion object {
public fun <E : KtElement> createFixForPrimaryConstructorPropertyParameter(
element: E,
info: PropertyInfo
) : CreateParameterFromUsageFix<E>? {
val receiverClassDescriptor: ClassDescriptor
val builder = CallableBuilderConfiguration(listOf(info), element, element.getContainingJetFile(), null, false).createBuilder()
val receiverTypeCandidate = builder.computeTypeCandidates(info.receiverTypeInfo).firstOrNull()
if (receiverTypeCandidate != null) {
builder.placement = CallablePlacement.WithReceiver(receiverTypeCandidate)
receiverClassDescriptor = receiverTypeCandidate.theType.constructor.declarationDescriptor as? ClassDescriptor ?: return null
}
else {
if (element !is KtSimpleNameExpression) return null
val classOrObject = element.getStrictParentOfType<KtClassOrObject>() ?: return null
receiverClassDescriptor = classOrObject.resolveToDescriptorIfAny() as? ClassDescriptor ?: return null
val paramInfo = CreateParameterByRefActionFactory.extractFixData(element)?.parameterInfo
if (paramInfo?.callableDescriptor == receiverClassDescriptor.unsubstitutedPrimaryConstructor) return null
}
if (receiverClassDescriptor.kind != ClassKind.CLASS) return null
val receiverClass = receiverClassDescriptor.source.getPsi() as? KtClass ?: return null
if (!receiverClass.canRefactor()) return null
val constructorDescriptor = receiverClassDescriptor.unsubstitutedPrimaryConstructor ?: return null
val paramType = info.returnTypeInfo.getPossibleTypes(builder).firstOrNull() ?: return null
if (paramType.hasTypeParametersToAdd(constructorDescriptor, builder.currentFileContext)) return null
val paramInfo = JetParameterInfo(
callableDescriptor = constructorDescriptor,
name = info.name,
type = paramType,
valOrVar = if (info.writable) JetValVar.Var else JetValVar.Val
)
return CreateParameterFromUsageFix(constructorDescriptor, paramInfo, element)
}
}
}
@@ -1,6 +1,7 @@
// "Create class 'A'" "false"
// ACTION: Create extension property 'A'
// ACTION: Create member property 'A'
// ACTION: Create property 'A' as constructor parameter
// ERROR: Unresolved reference: A
package p
@@ -1,6 +1,7 @@
// "Create enum constant 'A'" "false"
// ACTION: Create extension property 'A'
// ACTION: Create member property 'A'
// ACTION: Create property 'A' as constructor parameter
// ERROR: Unresolved reference: A
package p
@@ -1,6 +1,7 @@
// "Create object 'A'" "false"
// ACTION: Create extension property 'A'
// ACTION: Create member property 'A'
// ACTION: Create property 'A' as constructor parameter
// ERROR: Unresolved reference: A
package p
@@ -1,6 +1,7 @@
// "Create local variable 'foo'" "false"
// ACTION: Create extension property 'foo'
// ACTION: Create member property 'foo'
// ACTION: Create property 'foo' as constructor parameter
// ERROR: Unresolved reference: foo
class A
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
class A {
fun test(n: Int) {
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
class A(var foo: Int) {
fun test(n: Int) {
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
class A {
val test: Int get() {
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
class A(val foo: Int) {
val test: Int get() {
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
class A<T> {
val test: T get() {
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val foo: T) {
val test: T get() {
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
class A {
val test: Int get() = <caret>foo
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
class A(val foo: Int) {
val test: Int get() = foo
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
// ERROR: No value passed for parameter foo
// ERROR: No value passed for parameter foo
// ERROR: No value passed for parameter foo
@@ -1,4 +1,4 @@
// "Create parameter 'foo'" "true"
// "Create property 'foo' as constructor parameter" "true"
// ERROR: No value passed for parameter foo
// ERROR: No value passed for parameter foo
// ERROR: No value passed for parameter foo
@@ -1,6 +1,7 @@
// "Create parameter 'foo'" "false"
// ACTION: Create extension property 'foo'
// ACTION: Create member property 'foo'
// ACTION: Create property 'foo' as constructor parameter
// ERROR: Unresolved reference: foo
class A
@@ -0,0 +1,11 @@
// "Create property 'foo' as constructor parameter" "false"
// ACTION: Convert to expression body
// ACTION: Create parameter 'foo'
// ACTION: Create local variable 'foo'
// ERROR: Unresolved reference: foo
fun test() {
fun nestedTest(): Int {
return <caret>foo
}
}
@@ -0,0 +1,9 @@
// "Create property 'foo' as constructor parameter" "true"
class A {
class B {
fun test(): Int {
return <caret>foo
}
}
}
@@ -0,0 +1,9 @@
// "Create property 'foo' as constructor parameter" "true"
class A {
class B(val foo: Int) {
fun test(): Int {
return foo
}
}
}
@@ -0,0 +1,10 @@
// "Create property 'foo' as constructor parameter" "true"
class A {
class B {
fun test(): Int {
<caret>foo = 1
return foo
}
}
}
@@ -0,0 +1,10 @@
// "Create property 'foo' as constructor parameter" "true"
class A {
class B(var foo: Int) {
fun test(): Int {
<caret>foo = 1
return foo
}
}
}
@@ -0,0 +1,14 @@
// "Create property 'foo' as constructor parameter" "false"
// ACTION: Convert to expression body
// ACTION: Create local variable 'foo'
// ACTION: Create parameter 'foo'
// ACTION: Create property 'foo'
// ERROR: Unresolved reference: foo
class A {
object B {
fun test(): Int {
return <caret>foo
}
}
}
@@ -0,0 +1,8 @@
// "Create member property 'foo' as constructor parameter" "false"
// ACTION: Create member property 'foo'
// ERROR: Unresolved reference: foo
fun test() {
val a: Int = J.<caret>foo
}
@@ -0,0 +1,7 @@
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val n: T) {
fun test(): A<Int> {
return this.<caret>foo
}
}
@@ -0,0 +1,7 @@
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val n: T, val foo: A<Int>) {
fun test(): A<Int> {
return this.foo
}
}
@@ -0,0 +1,7 @@
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val n: T)
fun <U> A<U>.test(): A<Int> {
return this.<caret>foo
}
@@ -0,0 +1,7 @@
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val n: T, val foo: A<Int>)
fun <U> A<U>.test(): A<Int> {
return this.<caret>foo
}
@@ -0,0 +1,9 @@
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val n: T) {
inner class B<U>(val m: U) {
fun test(): A<Int> {
return this.<caret>foo
}
}
}
@@ -0,0 +1,9 @@
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val n: T) {
inner class B<U>(val m: U, val foo: A<Int>) {
fun test(): A<Int> {
return this.foo
}
}
}
@@ -0,0 +1,9 @@
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val n: T) {
inner class B<U>(val m: U) {
fun test(): A<Int> {
return this@A.<caret>foo
}
}
}
@@ -0,0 +1,9 @@
// "Create property 'foo' as constructor parameter" "true"
class A<T>(val n: T, val foo: A<Int>) {
inner class B<U>(val m: U) {
fun test(): A<Int> {
return this@A.foo
}
}
}
@@ -0,0 +1,10 @@
// "Create property 'foo' as constructor parameter" "false"
// ACTION: Create property 'foo'
// ACTION: Convert to expression body
// ACTION: Create local variable 'foo'
// ACTION: Create parameter 'foo'
// ERROR: Unresolved reference: foo
fun test(): Int {
return <caret>foo
}
@@ -0,0 +1,14 @@
// "Create member property 'foo' as constructor parameter" "false"
// ACTION: Create member property 'foo'
// ACTION: Create extension property 'foo'
// ERROR: Unresolved reference: foo
class A<T>(val n: T) {
companion object {
}
}
fun test() {
val a: Int = A.<caret>foo
}
@@ -0,0 +1,9 @@
// "Create property 'foo' as constructor parameter" "false"
// ACTION: Create member property 'foo'
// ACTION: Convert to expression body
// ACTION: Create extension property 'foo'
// ERROR: Unresolved reference: foo
fun test(): String? {
return A().<caret>foo
}
@@ -0,0 +1,9 @@
// "Create property 'foo' as constructor parameter" "false"
// ACTION: Create extension property 'foo'
// ERROR: Unresolved reference: foo
class A<T>(val n: T)
fun test() {
val a: A<Int> = 2.<caret>foo
}
@@ -0,0 +1,8 @@
// "Create property 'foo' as constructor parameter" "true"
// ERROR: No value passed for parameter foo
class A<T>(val n: T)
fun test() {
val a: A<Int> = A(1).<caret>foo
}
@@ -0,0 +1,8 @@
// "Create property 'foo' as constructor parameter" "true"
// ERROR: No value passed for parameter foo
class A<T>(val n: T, val foo: A<Int>)
fun test() {
val a: A<Int> = A(1, ).foo
}
@@ -0,0 +1,8 @@
// "Create property 'foo' as constructor parameter" "true"
// ERROR: No value passed for parameter foo
class A<T>(val n: T)
fun test() {
A(1).<caret>foo = "1"
}
@@ -0,0 +1,8 @@
// "Create property 'foo' as constructor parameter" "true"
// ERROR: No value passed for parameter foo
class A<T>(val n: T, var foo: String)
fun test() {
A(1, ).foo = "1"
}
@@ -849,6 +849,27 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
}
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class PrimaryParameter extends AbstractQuickFixMultiFileTest {
public void testAllFilesPresentInPrimaryParameter() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter"), Pattern.compile("^(\\w+)\\.before\\.Main\\.\\w+$"), true);
}
@TestMetadata("staticValOnJavaClass.before.Main.kt")
public void testStaticValOnJavaClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/staticValOnJavaClass.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("valOnJavaType.before.Main.kt")
public void testValOnJavaType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/valOnJavaType.before.Main.kt");
doTestWithExtraFile(fileName);
}
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createVariable/property")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -2835,6 +2835,93 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class PrimaryParameter extends AbstractQuickFixTest {
public void testAllFilesPresentInPrimaryParameter() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
@TestMetadata("localValNoReceiver.kt")
public void testLocalValNoReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/localValNoReceiver.kt");
doTest(fileName);
}
@TestMetadata("memberValNoReceiver.kt")
public void testMemberValNoReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/memberValNoReceiver.kt");
doTest(fileName);
}
@TestMetadata("memberVarNoReceiver.kt")
public void testMemberVarNoReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/memberVarNoReceiver.kt");
doTest(fileName);
}
@TestMetadata("objectMemberValNoReceiver.kt")
public void testObjectMemberValNoReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/objectMemberValNoReceiver.kt");
doTest(fileName);
}
@TestMetadata("thisInClass.kt")
public void testThisInClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/thisInClass.kt");
doTest(fileName);
}
@TestMetadata("thisInExtension.kt")
public void testThisInExtension() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/thisInExtension.kt");
doTest(fileName);
}
@TestMetadata("thisInNestedClass1.kt")
public void testThisInNestedClass1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/thisInNestedClass1.kt");
doTest(fileName);
}
@TestMetadata("thisInNestedClass2.kt")
public void testThisInNestedClass2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/thisInNestedClass2.kt");
doTest(fileName);
}
@TestMetadata("topLevelValNoReceiver.kt")
public void testTopLevelValNoReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/topLevelValNoReceiver.kt");
doTest(fileName);
}
@TestMetadata("valOnCompanionObject.kt")
public void testValOnCompanionObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/valOnCompanionObject.kt");
doTest(fileName);
}
@TestMetadata("valOnLibType.kt")
public void testValOnLibType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/valOnLibType.kt");
doTest(fileName);
}
@TestMetadata("valOnUserType.kt")
public void testValOnUserType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/valOnUserType.kt");
doTest(fileName);
}
@TestMetadata("varOnUserType.kt")
public void testVarOnUserType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/primaryParameter/varOnUserType.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/quickfix/createFromUsage/createVariable/property")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)