KT-9839: convert primary constructor to secondary one: leave independent properties in class body as is

(cherry picked from commit b90414a)
This commit is contained in:
Mikhail Glukhikh
2016-09-29 15:49:21 +03:00
committed by Mikhail Glukhikh
parent 7f50e6e70e
commit 28b70faa99
8 changed files with 102 additions and 1 deletions
@@ -17,12 +17,19 @@
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.lexer.KtTokens.THIS_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.VARARG_KEYWORD
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.CONSTRUCTOR
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtPrimaryConstructor>(
KtPrimaryConstructor::class.java,
@@ -31,15 +38,33 @@ class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtP
override fun isApplicableTo(element: KtPrimaryConstructor, caretOffset: Int) =
element.containingClassOrObject is KtClass && element.valueParameters.all { !it.hasValOrVar() || it.annotationEntries.isEmpty() }
private fun KtReferenceExpression.isIndependent(classDescriptor: ClassDescriptor, context: BindingContext): Boolean {
val referencedDescriptor = context[BindingContext.REFERENCE_TARGET, this] ?: return false
return when (referencedDescriptor) {
is ValueParameterDescriptor ->
(referencedDescriptor.containingDeclaration as? ConstructorDescriptor)?.containingDeclaration != classDescriptor
else ->
classDescriptor !in referencedDescriptor.parents
}
}
private fun KtProperty.isIndependent(klass: KtClass, context: BindingContext): Boolean {
val propertyInitializer = initializer ?: return true
val classDescriptor = context[BindingContext.CLASS, klass] ?: return false
return !propertyInitializer.anyDescendantOfType<KtReferenceExpression> { !it.isIndependent(classDescriptor, context) }
}
override fun applyTo(element: KtPrimaryConstructor, editor: Editor?) {
val klass = element.containingClassOrObject as? KtClass ?: return
val context = klass.analyze()
val factory = KtPsiFactory(klass)
val initializerMap = mutableMapOf<KtProperty, String>()
for (property in klass.getProperties()) {
if (property.typeReference == null) {
SpecifyTypeExplicitlyIntention().applyTo(property, editor)
}
val initializer = property.initializer ?: continue
if (property.isIndependent(klass, context)) continue
val initializer = property.initializer!!
initializerMap[property] = initializer.text
initializer.delete()
property.equalsToken!!.delete()
@@ -0,0 +1,5 @@
class Hello<caret>(val x: String) {
val y = "Hello"
val z = x + y
}
@@ -0,0 +1,12 @@
class Hello {
val x: String
constructor(x: String) {
this.x = x
this.z = x + y
}
val y: String = "Hello"
val z: String
}
@@ -0,0 +1,9 @@
object Storage {
val hello = "Hello"
}
class Hello<caret>(val x: String) {
val y = Storage.hello
val z = x + y
}
@@ -0,0 +1,16 @@
object Storage {
val hello = "Hello"
}
class Hello {
val x: String
constructor(x: String) {
this.x = x
this.z = x + y
}
val y: String = Storage.hello
val z: String
}
@@ -0,0 +1,5 @@
fun foo(x: Int) {
class Local<caret>(val y: Int) {
val z = x
}
}
@@ -0,0 +1,11 @@
fun foo(x: Int) {
class Local {
val y: Int
constructor(y: Int) {
this.y = y
}
val z: Int = x
}
}
@@ -4390,6 +4390,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("independentProperty.kt")
public void testIndependentProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/independentProperty.kt");
doTest(fileName);
}
@TestMetadata("independentProperty2.kt")
public void testIndependentProperty2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/independentProperty2.kt");
doTest(fileName);
}
@TestMetadata("independentProperty3.kt")
public void testIndependentProperty3() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/independentProperty3.kt");
doTest(fileName);
}
@TestMetadata("initAndParams.kt")
public void testInitAndParams() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertPrimaryConstructorToSecondary/initAndParams.kt");