Convert to secondary constructor: suggest on class name
#KT-40861 Fixed
This commit is contained in:
committed by
Dmitry Gridin
parent
2c5c15f503
commit
ce407471ec
+34
-31
@@ -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(
|
||||||
|
|||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// IS_APPLICABLE: false
|
||||||
|
class Foo() <caret>{
|
||||||
|
fun bar() {}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
class <caret>Foo(val x: Int)
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
class Foo {
|
||||||
|
val x: Int
|
||||||
|
|
||||||
|
constructor(x: Int) {
|
||||||
|
this.x = x
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
// IS_APPLICABLE: false
|
||||||
|
class Fo<caret>o constructor()
|
||||||
+2
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user