Convert to secondary constructor: suggest on class name

#KT-40861 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-09-08 09:26:19 +09:00
committed by Dmitry Gridin
parent 2c5c15f503
commit ce407471ec
7 changed files with 70 additions and 31 deletions
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.intentions package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -21,21 +22,23 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.CONSTRUCTOR import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.CONSTRUCTOR
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.parents import org.jetbrains.kotlin.resolve.descriptorUtil.parents
class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtPrimaryConstructor>( class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingRangeIntention<KtClass>(
KtPrimaryConstructor::class.java, KtClass::class.java,
KotlinBundle.lazyMessage("convert.to.secondary.constructor") KotlinBundle.lazyMessage("convert.to.secondary.constructor")
) { ) {
override fun isApplicableTo(element: KtPrimaryConstructor, caretOffset: Int): Boolean { override fun applicabilityRange(element: KtClass): TextRange? {
val containingClass = element.containingClassOrObject as? KtClass ?: return false val primaryCtor = element.primaryConstructor ?: return null
if (containingClass.isAnnotation() || containingClass.isData() val startOffset =
|| containingClass.superTypeListEntries.any { it is KtDelegatedSuperTypeEntry } (if (primaryCtor.getConstructorKeyword() != null) primaryCtor else element.nameIdentifier)?.startOffset ?: return null
) return false if (element.isAnnotation() || element.isData() || element.superTypeListEntries.any { it is KtDelegatedSuperTypeEntry }) return null
return element.valueParameters.all { !it.hasValOrVar() || (it.name != null && it.annotationEntries.isEmpty()) } if (primaryCtor.valueParameters.any { it.hasValOrVar() && (it.name == null || it.annotationEntries.isNotEmpty()) }) return null
return TextRange(startOffset, primaryCtor.endOffset)
} }
private fun KtReferenceExpression.isIndependent(classDescriptor: ClassDescriptor, context: BindingContext): Boolean = private fun KtReferenceExpression.isIndependent(classDescriptor: ClassDescriptor, context: BindingContext): Boolean =
@@ -54,15 +57,15 @@ class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtP
return !propertyInitializer.anyDescendantOfType<KtReferenceExpression> { !it.isIndependent(classDescriptor, context) } return !propertyInitializer.anyDescendantOfType<KtReferenceExpression> { !it.isIndependent(classDescriptor, context) }
} }
override fun applyTo(element: KtPrimaryConstructor, editor: Editor?) { override fun applyTo(element: KtClass, editor: Editor?) {
val klass = element.containingClassOrObject as? KtClass ?: return val primaryCtor = element.primaryConstructor ?: return
if (klass.isAnnotation()) return if (element.isAnnotation()) return
val context = klass.analyze() val context = element.analyze()
val factory = KtPsiFactory(klass) val factory = KtPsiFactory(element)
val commentSaver = CommentSaver(element) val commentSaver = CommentSaver(primaryCtor)
val initializerMap = mutableMapOf<KtProperty, String>() val initializerMap = mutableMapOf<KtProperty, String>()
for (property in klass.getProperties()) { for (property in element.getProperties()) {
if (property.isIndependent(klass, context)) continue if (property.isIndependent(element, context)) continue
if (property.typeReference == null) { if (property.typeReference == null) {
with(SpecifyTypeExplicitlyIntention()) { with(SpecifyTypeExplicitlyIntention()) {
if (applicabilityRange(property) != null) { if (applicabilityRange(property) != null) {
@@ -77,10 +80,10 @@ class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtP
} }
val constructor = factory.createSecondaryConstructor( val constructor = factory.createSecondaryConstructor(
CallableBuilder(CONSTRUCTOR).apply { CallableBuilder(CONSTRUCTOR).apply {
element.modifierList?.let { modifier(it.text) } primaryCtor.modifierList?.let { modifier(it.text) }
typeParams() typeParams()
name() name()
for (valueParameter in element.valueParameters) { for (valueParameter in primaryCtor.valueParameters) {
val annotations = valueParameter.annotationEntries.joinToString(separator = " ") { it.text } val annotations = valueParameter.annotationEntries.joinToString(separator = " ") { it.text }
val vararg = if (valueParameter.isVarArg) VARARG_KEYWORD.value else "" val vararg = if (valueParameter.isVarArg) VARARG_KEYWORD.value else ""
param( param(
@@ -89,18 +92,18 @@ class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtP
) )
} }
noReturnType() noReturnType()
for (superTypeEntry in klass.superTypeListEntries) { for (superTypeEntry in element.superTypeListEntries) {
if (superTypeEntry is KtSuperTypeCallEntry) { if (superTypeEntry is KtSuperTypeCallEntry) {
superDelegation(superTypeEntry.valueArgumentList?.text ?: "") superDelegation(superTypeEntry.valueArgumentList?.text ?: "")
superTypeEntry.replace(factory.createSuperTypeEntry(superTypeEntry.typeReference!!.text)) superTypeEntry.replace(factory.createSuperTypeEntry(superTypeEntry.typeReference!!.text))
} }
} }
val valueParameterInitializers = val valueParameterInitializers =
element.valueParameters.asSequence().filter { it.hasValOrVar() }.joinToString(separator = "\n") { primaryCtor.valueParameters.asSequence().filter { it.hasValOrVar() }.joinToString(separator = "\n") {
val name = it.name!! val name = it.name!!
"this.$name = $name" "this.$name = $name"
} }
val classBodyInitializers = klass.declarations.asSequence().filter { val classBodyInitializers = element.declarations.asSequence().filter {
(it is KtProperty && initializerMap[it] != null) || it is KtAnonymousInitializer (it is KtProperty && initializerMap[it] != null) || it is KtAnonymousInitializer
}.joinToString(separator = "\n") { }.joinToString(separator = "\n") {
if (it is KtProperty) { if (it is KtProperty) {
@@ -112,8 +115,8 @@ class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtP
"" ""
} }
} else { } else {
((it as KtAnonymousInitializer).body as? KtBlockExpression)?.statements?.joinToString(separator = "\n") { ((it as KtAnonymousInitializer).body as? KtBlockExpression)?.statements?.joinToString(separator = "\n") { stmt ->
it.text stmt.text
} ?: "" } ?: ""
} }
} }
@@ -124,20 +127,20 @@ class ConvertPrimaryConstructorToSecondaryIntention : SelfTargetingIntention<KtP
}.asString() }.asString()
) )
val lastEnumEntry = klass.declarations.lastOrNull { it is KtEnumEntry } as? KtEnumEntry val lastEnumEntry = element.declarations.lastOrNull { it is KtEnumEntry } as? KtEnumEntry
val secondaryConstructor = val secondaryConstructor =
lastEnumEntry?.let { klass.addDeclarationAfter(constructor, it) } ?: klass.addDeclarationBefore(constructor, null) lastEnumEntry?.let { element.addDeclarationAfter(constructor, it) } ?: element.addDeclarationBefore(constructor, null)
commentSaver.restore(secondaryConstructor) commentSaver.restore(secondaryConstructor)
convertValueParametersToProperties(element, klass, factory, lastEnumEntry) convertValueParametersToProperties(primaryCtor, element, factory, lastEnumEntry)
if (klass.isEnum()) { if (element.isEnum()) {
addSemicolonIfNotExist(klass, factory, lastEnumEntry) addSemicolonIfNotExist(element, factory, lastEnumEntry)
} }
for (anonymousInitializer in klass.getAnonymousInitializers()) { for (anonymousInitializer in element.getAnonymousInitializers()) {
anonymousInitializer.delete() anonymousInitializer.delete()
} }
element.delete() primaryCtor.delete()
} }
private fun convertValueParametersToProperties( private fun convertValueParametersToProperties(
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
class Foo() <caret>{
fun bar() {}
}
@@ -0,0 +1 @@
class <caret>Foo(val x: Int)
@@ -0,0 +1,7 @@
class Foo {
val x: Int
constructor(x: Int) {
this.x = x
}
}
@@ -0,0 +1,2 @@
// IS_APPLICABLE: false
class Fo<caret>o constructor()
@@ -0,0 +1,2 @@
// IS_APPLICABLE: false
class <caret>Foo
@@ -5962,6 +5962,26 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
runTest("idea/testData/intentions/convertPrimaryConstructorToSecondary/annotationClass.kt"); runTest("idea/testData/intentions/convertPrimaryConstructorToSecondary/annotationClass.kt");
} }
@TestMetadata("caretOnClassBody.kt")
public void testCaretOnClassBody() throws Exception {
runTest("idea/testData/intentions/convertPrimaryConstructorToSecondary/caretOnClassBody.kt");
}
@TestMetadata("caretOnClassName.kt")
public void testCaretOnClassName() throws Exception {
runTest("idea/testData/intentions/convertPrimaryConstructorToSecondary/caretOnClassName.kt");
}
@TestMetadata("caretOnClassNameWithConstructorKeyword.kt")
public void testCaretOnClassNameWithConstructorKeyword() throws Exception {
runTest("idea/testData/intentions/convertPrimaryConstructorToSecondary/caretOnClassNameWithConstructorKeyword.kt");
}
@TestMetadata("caretOnClassNameWithoutPrimaryConstructor.kt")
public void testCaretOnClassNameWithoutPrimaryConstructor() throws Exception {
runTest("idea/testData/intentions/convertPrimaryConstructorToSecondary/caretOnClassNameWithoutPrimaryConstructor.kt");
}
@TestMetadata("dataClass.kt") @TestMetadata("dataClass.kt")
public void testDataClass() throws Exception { public void testDataClass() throws Exception {
runTest("idea/testData/intentions/convertPrimaryConstructorToSecondary/dataClass.kt"); runTest("idea/testData/intentions/convertPrimaryConstructorToSecondary/dataClass.kt");