diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/MovePropertyToConstructorIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/MovePropertyToConstructorIntention.kt index 181b7967fa6..185582ffa11 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/MovePropertyToConstructorIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/MovePropertyToConstructorIntention.kt @@ -28,14 +28,16 @@ import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.util.CommentSaver +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens.LATEINIT_KEYWORD import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.AnnotationChecker import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.types.KotlinType @@ -57,6 +59,7 @@ class MovePropertyToConstructorIntention : && element.setter == null && !element.hasModifier(LATEINIT_KEYWORD) && element.getStrictParentOfType() is KtClass + && (element.initializer?.isValidInConstructor() ?: true) override fun applyTo(element: KtProperty, editor: Editor?) { val parentClass = PsiTreeUtil.getParentOfType(element, KtClass::class.java) ?: return @@ -94,13 +97,14 @@ class MovePropertyToConstructorIntention : } } else { - val type = (element.resolveToDescriptor(BodyResolveMode.PARTIAL) as? PropertyDescriptor)?.type ?: return + val typeText = element.typeReference?.text ?: + (element.resolveToDescriptor(BodyResolveMode.PARTIAL) as? PropertyDescriptor)?.type?.render() ?: return val parameterText = buildString { element.modifierList?.getModifiersText()?.let(this::append) propertyAnnotationsText?.takeIf(String::isNotBlank)?.let { appendWithSpaceBefore(it) } appendWithSpaceBefore(element.valOrVarKeyword.text) element.name?.let { appendWithSpaceBefore(it) } - appendWithSpaceBefore(": ${type.fqNameSafeAsString()}") + appendWithSpaceBefore(": $typeText") element.initializer?.text?.let { append(" = $it") } } @@ -133,7 +137,7 @@ class MovePropertyToConstructorIntention : else text - private fun KotlinType.fqNameSafeAsString() = constructor.declarationDescriptor?.fqNameSafe?.asString() ?: "" + private fun KotlinType.render() = IdeDescriptorRenderers.SOURCE_CODE.renderType(this) private fun KtModifierList.getModifiersText() = getModifiers().joinToString(separator = " ") { it.text } @@ -141,4 +145,24 @@ class MovePropertyToConstructorIntention : node.getChildren(null).filter { it.elementType is KtModifierKeywordToken }.map { it.psi } private fun StringBuilder.appendWithSpaceBefore(str: String) = append(" " + str) -} + + private fun KtExpression.isValidInConstructor(): Boolean { + val containingClass = getStrictParentOfType() ?: return false + var isValid = true + this.accept(object : KtVisitorVoid() { + override fun visitKtElement(element: KtElement) { + element.acceptChildren(this) + } + + override fun visitReferenceExpression(expression: KtReferenceExpression) { + val context = expression.analyze(BodyResolveMode.PARTIAL) + val declarationDescriptor = expression.getResolvedCall(context)?.resultingDescriptor ?: return + if (declarationDescriptor.containingDeclaration == containingClass.descriptor) { + isValid = false + } + } + }) + + return isValid + } +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/functionReference.kt b/idea/testData/intentions/movePropertyToConstructor/functionReference.kt new file mode 100644 index 00000000000..d5b3a3c6307 --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/functionReference.kt @@ -0,0 +1,9 @@ +class Baz + +fun foo(a: Int, b: String, d: Baz) { + +} + +class TestClass { + val prop1 = ::foo +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/functionReference.kt.after b/idea/testData/intentions/movePropertyToConstructor/functionReference.kt.after new file mode 100644 index 00000000000..fcbafe0c758 --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/functionReference.kt.after @@ -0,0 +1,10 @@ +import kotlin.reflect.KFunction3 + +class Baz + +fun foo(a: Int, b: String, d: Baz) { + +} + +class TestClass(val prop1: KFunction3<@ParameterName(name = "a") Int, @ParameterName(name = "b") String, @ParameterName(name = "d") Baz, Unit> = ::foo) { +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/lambda.kt b/idea/testData/intentions/movePropertyToConstructor/lambda.kt new file mode 100644 index 00000000000..94860e9bbbf --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/lambda.kt @@ -0,0 +1,3 @@ +class TestClass { + val prop2 = { a: Int, b: String -> } +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/lambda.kt.after b/idea/testData/intentions/movePropertyToConstructor/lambda.kt.after new file mode 100644 index 00000000000..7a39ce1b1bd --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/lambda.kt.after @@ -0,0 +1,2 @@ +class TestClass(val prop2: (Int, String) -> Unit = { a: Int, b: String -> }) { +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/lambdaWithoutParameters.kt b/idea/testData/intentions/movePropertyToConstructor/lambdaWithoutParameters.kt new file mode 100644 index 00000000000..4425334c733 --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/lambdaWithoutParameters.kt @@ -0,0 +1,3 @@ +class TestClass { + val prop2 = { } +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/lambdaWithoutParameters.kt.after b/idea/testData/intentions/movePropertyToConstructor/lambdaWithoutParameters.kt.after new file mode 100644 index 00000000000..4f2428a7451 --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/lambdaWithoutParameters.kt.after @@ -0,0 +1,2 @@ +class TestClass(val prop2: () -> Unit = { }) { +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/map.kt b/idea/testData/intentions/movePropertyToConstructor/map.kt new file mode 100644 index 00000000000..1e279e0097f --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/map.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class Baz + +class TestClass { + val c = mapOf("foo" to Baz()) +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/map.kt.after b/idea/testData/intentions/movePropertyToConstructor/map.kt.after new file mode 100644 index 00000000000..5c4935f64c8 --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/map.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +class Baz + +class TestClass(val c: Map = mapOf("foo" to Baz())) { +} \ No newline at end of file diff --git a/idea/testData/intentions/movePropertyToConstructor/methodReference.kt b/idea/testData/intentions/movePropertyToConstructor/methodReference.kt new file mode 100644 index 00000000000..2a25e3edee0 --- /dev/null +++ b/idea/testData/intentions/movePropertyToConstructor/methodReference.kt @@ -0,0 +1,9 @@ +// IS_APPLICABLE: false + +class MySuperClass() { + val prop5 = this::test + + fun test() { + + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 9c1f6c969f8..4b24103ab71 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -11280,12 +11280,30 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("functionReference.kt") + public void testFunctionReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/functionReference.kt"); + doTest(fileName); + } + @TestMetadata("getter.kt") public void testGetter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/getter.kt"); doTest(fileName); } + @TestMetadata("lambda.kt") + public void testLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/lambda.kt"); + doTest(fileName); + } + + @TestMetadata("lambdaWithoutParameters.kt") + public void testLambdaWithoutParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/lambdaWithoutParameters.kt"); + doTest(fileName); + } + @TestMetadata("lateinit.kt") public void testLateinit() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/lateinit.kt"); @@ -11298,6 +11316,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("map.kt") + public void testMap() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/map.kt"); + doTest(fileName); + } + + @TestMetadata("methodReference.kt") + public void testMethodReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/methodReference.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/movePropertyToConstructor/simple.kt");