diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt
index 10b127b2e4e..d0a088ed6c8 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt
@@ -397,6 +397,10 @@ class KtPsiFactory(private val project: Project) {
return createClass("class A()").getPrimaryConstructor()!!
}
+ fun createPrimaryConstructor(modifiers: String?): KtPrimaryConstructor {
+ return modifiers?.let { createClass("class A $modifiers constructor()").getPrimaryConstructor() } ?: createPrimaryConstructor()
+ }
+
fun createConstructorKeyword(): PsiElement =
createClass("class A constructor()").getPrimaryConstructor()!!.getConstructorKeyword()!!
diff --git a/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/after.kt.template b/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/after.kt.template
new file mode 100644
index 00000000000..d9ee4c351d2
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/after.kt.template
@@ -0,0 +1,3 @@
+class Foo(val x: Int = 4, y: Int) {
+ val z = y
+}
diff --git a/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/before.kt.template b/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/before.kt.template
new file mode 100644
index 00000000000..de2d3c26ff3
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/before.kt.template
@@ -0,0 +1,9 @@
+class Foo {
+ val z = y
+
+ val x: Int
+
+ constructor(x: Int = 4, y: Int) {
+ this.x = x
+ }
+}
diff --git a/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/description.html b/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/description.html
new file mode 100644
index 00000000000..d5fba0e6e1c
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertSecondaryConstructorToPrimaryIntention/description.html
@@ -0,0 +1,5 @@
+
+
+This intention converts secondary constructor to primary one.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 94c2a230eab..ab075c898fd 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1377,6 +1377,11 @@
Kotlin
+
+ org.jetbrains.kotlin.idea.intentions.ConvertSecondaryConstructorToPrimaryIntention
+ Kotlin
+
+
(
+ KtSecondaryConstructor::class.java,
+ "Convert to primary constructor"
+) {
+ private tailrec fun ConstructorDescriptor.isReachableByDelegationFrom(
+ constructor: ConstructorDescriptor, context: BindingContext, visited: Set = emptySet()
+ ): Boolean {
+ if (constructor == this) return true
+ if (constructor in visited) return false
+ val resolvedDelegationCall = context[BindingContext.CONSTRUCTOR_RESOLVED_DELEGATION_CALL, constructor] ?: return false
+ val delegationDescriptor = resolvedDelegationCall.candidateDescriptor
+ return isReachableByDelegationFrom(delegationDescriptor, context, visited + constructor)
+ }
+
+ override fun isApplicableTo(element: KtSecondaryConstructor, caretOffset: Int): Boolean {
+ val delegationCall = element.getDelegationCall()
+ if (delegationCall.isCallToThis) return false
+ val klass = element.containingClassOrObject ?: return false
+ if (klass.hasPrimaryConstructor()) return false
+
+ val context = klass.analyze()
+ val classDescriptor = context[BindingContext.CLASS, klass] ?: return false
+ val elementDescriptor = context[BindingContext.CONSTRUCTOR, element] ?: return false
+
+ for (constructorDescriptor in classDescriptor.constructors) {
+ if (constructorDescriptor == elementDescriptor) continue
+ if (!elementDescriptor.isReachableByDelegationFrom(constructorDescriptor, context)) return false
+ }
+
+ return true
+ }
+
+ private fun KtExpression.tryConvertToPropertyByParameterInitialization(
+ constructorDescriptor: ConstructorDescriptor, context: BindingContext
+ ): Pair? {
+ if (this !is KtBinaryExpression || operationToken != KtTokens.EQ) return null
+ val rightReference = right as? KtReferenceExpression ?: return null
+ val rightDescriptor = context[BindingContext.REFERENCE_TARGET, rightReference] as? ValueParameterDescriptor ?: return null
+ if (rightDescriptor.containingDeclaration != constructorDescriptor) return null
+ val left = left
+ val leftReference = when (left) {
+ is KtReferenceExpression ->
+ left
+ is KtDotQualifiedExpression ->
+ if (left.receiverExpression is KtThisExpression) left.selectorExpression as? KtReferenceExpression else null
+ else ->
+ null
+ }
+ val leftDescriptor = context[BindingContext.REFERENCE_TARGET, leftReference] as? PropertyDescriptor ?: return null
+ return rightDescriptor to leftDescriptor
+ }
+
+ override fun applyTo(element: KtSecondaryConstructor, editor: Editor?) {
+ val klass = element.containingClassOrObject as? KtClass ?: return
+ val context = klass.analyzeFully()
+ val constructorDescriptor = context[BindingContext.CONSTRUCTOR, element] ?: return
+ val factory = KtPsiFactory(klass)
+ val constructorInClass = klass.createPrimaryConstructorIfAbsent()
+ val constructor = factory.createPrimaryConstructor(element.modifierList?.text?.replace("\n", " "))
+ val parameterList = constructor.valueParameterList!!
+ val parameterToPropertyMap = mutableMapOf()
+ val initializer = factory.createAnonymousInitializer() as? KtClassInitializer
+ for (statement in element.bodyExpression?.statements ?: emptyList()) {
+ val (rightDescriptor, leftDescriptor) = statement.tryConvertToPropertyByParameterInitialization(constructorDescriptor, context)
+ ?: with (initializer) {
+ (initializer?.body as? KtBlockExpression)?.let {
+ it.addBefore(statement.copy(), it.rBrace)
+ it.addBefore(factory.createNewLine(), it.rBrace)
+ }
+ null to null
+ }
+ if (rightDescriptor == null || leftDescriptor == null) continue
+ parameterToPropertyMap[rightDescriptor] = leftDescriptor
+ }
+
+ for (parameter in element.valueParameters) {
+ val newParameter = factory.createParameter(parameter.text)
+ val parameterDescriptor = context[BindingContext.VALUE_PARAMETER, parameter]
+ val propertyDescriptor = parameterToPropertyMap[parameterDescriptor]
+ if (parameterDescriptor != null && propertyDescriptor != null) {
+ val property = DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor) as? KtProperty
+ if (property != null) {
+ if (propertyDescriptor.name == parameterDescriptor.name && propertyDescriptor.type == parameterDescriptor.type) {
+ val valOrVar = if (property.isVar) factory.createVarKeyword() else factory.createValKeyword()
+ newParameter.addBefore(valOrVar, newParameter.nameIdentifier)
+ val propertyModifiers = property.modifierList?.text
+ if (propertyModifiers != null) {
+ val newModifiers = factory.createModifierList(propertyModifiers)
+ newParameter.addBefore(newModifiers, newParameter.valOrVarKeyword)
+ }
+ property.delete()
+ }
+ else {
+ property.initializer = factory.createSimpleName(parameterDescriptor.name.asString())
+ }
+ }
+ }
+ parameterList.addParameter(newParameter)
+ }
+
+ val delegationCall = element.getDelegationCall()
+ val argumentList = delegationCall.valueArgumentList
+ if (!delegationCall.isImplicit && argumentList != null) {
+ for (superTypeListEntry in klass.getSuperTypeListEntries()) {
+ val typeReference = superTypeListEntry.typeReference ?: continue
+ val type = context[BindingContext.TYPE, typeReference]
+ if ((type?.constructor?.declarationDescriptor as? ClassDescriptor)?.kind == ClassKind.CLASS) {
+ val superTypeCallEntry = factory.createSuperTypeCallEntry("${typeReference.text}${argumentList.text}")
+ superTypeListEntry.replace(superTypeCallEntry)
+ break
+ }
+ }
+ }
+
+ constructorInClass.replace(constructor)
+ element.delete()
+ if ((initializer?.body as? KtBlockExpression)?.statements?.isNotEmpty() ?: false) {
+ initializer?.let { klass.addDeclaration(it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/.intention b/idea/testData/intentions/convertSecondaryConstructorToPrimary/.intention
new file mode 100644
index 00000000000..6e8ef9390a0
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.ConvertSecondaryConstructorToPrimaryIntention
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/defaultValueChain.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/defaultValueChain.kt
new file mode 100644
index 00000000000..e1f9bf03904
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/defaultValueChain.kt
@@ -0,0 +1,11 @@
+class DefaultValueChain {
+ val x1: Int
+ val x3: Int
+ val x5: Int
+
+ constructor(x1: Int, x2: Int = x1, x3: Int = x2, x4: Int = x3, x5: Int = x4) {
+ this.x1 = x1
+ this.x3 = x3
+ this.x5 = x5
+ }
+}
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/defaultValueChain.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/defaultValueChain.kt.after
new file mode 100644
index 00000000000..d7c7aaed1d0
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/defaultValueChain.kt.after
@@ -0,0 +1,3 @@
+class DefaultValueChain(val x1: Int, x2: Int = x1, val x3: Int = x2, x4: Int = x3, val x5: Int = x4) {
+
+}
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/init.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/init.kt
new file mode 100644
index 00000000000..c9ae0215d00
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/init.kt
@@ -0,0 +1,10 @@
+class ConvertToInit {
+ fun foo() {}
+
+ fun bar() {}
+
+ constructor() {
+ foo()
+ bar()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/init.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/init.kt.after
new file mode 100644
index 00000000000..106e0bbf1fb
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/init.kt.after
@@ -0,0 +1,11 @@
+class ConvertToInit() {
+ fun foo() {}
+
+ fun bar() {}
+
+ init {
+ foo()
+ bar()
+ }
+
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/initAndParams.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/initAndParams.kt
new file mode 100644
index 00000000000..7250e0fc3b5
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/initAndParams.kt
@@ -0,0 +1,15 @@
+class InitAndParams {
+ constructor(x: Int, z: Int) {
+ this.y = x
+ w = foo(y)
+ this.v = w + z
+ }
+
+ val y: Int
+
+ val w: Int
+
+ fun foo(arg: Int) = arg
+
+ val v: Int
+}
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/initAndParams.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/initAndParams.kt.after
new file mode 100644
index 00000000000..73754d2174c
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/initAndParams.kt.after
@@ -0,0 +1,15 @@
+class InitAndParams(x: Int, z: Int) {
+
+ val y: Int = x
+
+ val w: Int
+
+ fun foo(arg: Int) = arg
+
+ val v: Int
+
+ init {
+ w = foo(y)
+ this.v = w + z
+ }
+}
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/nonReachable.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/nonReachable.kt
new file mode 100644
index 00000000000..d979b9bc9ca
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/nonReachable.kt
@@ -0,0 +1,7 @@
+// IS_APPLICABLE: false
+
+class NonReachableConstructor {
+ constructor(x: String)
+
+ constructor(x: Int)
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/nonReachableLoop.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/nonReachableLoop.kt
new file mode 100644
index 00000000000..bb3a14b7b41
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/nonReachableLoop.kt
@@ -0,0 +1,11 @@
+// IS_APPLICABLE: false
+// ERROR: There's a cycle in the delegation calls chain
+// ERROR: There's a cycle in the delegation calls chain
+
+class NonReachableLoop {
+ constructor(x: String)
+
+ constructor(x: Int, y: Int): this(x + y)
+
+ constructor(x: Int): this(x, x)
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/protectedConstructor.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/protectedConstructor.kt
new file mode 100644
index 00000000000..19b55ff7c39
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/protectedConstructor.kt
@@ -0,0 +1,7 @@
+class Protected {
+ internal var s: String
+
+ protected constructor(s: String) {
+ this.s = s
+ }
+}
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/protectedConstructor.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/protectedConstructor.kt.after
new file mode 100644
index 00000000000..54577f05397
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/protectedConstructor.kt.after
@@ -0,0 +1,3 @@
+class Protected protected constructor(internal var s: String) {
+
+}
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/simple.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/simple.kt
new file mode 100644
index 00000000000..37103c20081
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/simple.kt
@@ -0,0 +1,3 @@
+class Simple {
+ constructor()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/simple.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/simple.kt.after
new file mode 100644
index 00000000000..c12aaa6795c
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/simple.kt.after
@@ -0,0 +1,2 @@
+class Simple() {
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/useParam.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/useParam.kt
new file mode 100644
index 00000000000..427bc65a48c
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/useParam.kt
@@ -0,0 +1,7 @@
+class UseParam {
+ constructor(x: Int) {
+ this.y = x
+ }
+
+ val y: Int
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/useParam.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/useParam.kt.after
new file mode 100644
index 00000000000..abd3b3c2245
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/useParam.kt.after
@@ -0,0 +1,4 @@
+class UseParam(x: Int) {
+
+ val y: Int = x
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/varArg.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/varArg.kt
new file mode 100644
index 00000000000..d2418f0e5c0
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/varArg.kt
@@ -0,0 +1,10 @@
+// WITH_RUNTIME
+
+class WithVarArg {
+
+ val x: List
+
+ constructor(vararg zz: String) {
+ x = listOf(*zz)
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/varArg.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/varArg.kt.after
new file mode 100644
index 00000000000..2db8b688dc4
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/varArg.kt.after
@@ -0,0 +1,11 @@
+// WITH_RUNTIME
+
+class WithVarArg(vararg zz: String) {
+
+ val x: List
+
+ init {
+ x = listOf(*zz)
+ }
+
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/varargVal.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/varargVal.kt
new file mode 100644
index 00000000000..943f906691e
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/varargVal.kt
@@ -0,0 +1,9 @@
+// WITH_RUNTIME
+
+class VarargVal {
+ val param: Array
+
+ constructor(vararg param: String) {
+ this.param = param
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/varargVal.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/varargVal.kt.after
new file mode 100644
index 00000000000..4d56d85d66e
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/varargVal.kt.after
@@ -0,0 +1,5 @@
+// WITH_RUNTIME
+
+class VarargVal(vararg val param: String) {
+
+}
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClass.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClass.kt
new file mode 100644
index 00000000000..1e577f0c97f
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClass.kt
@@ -0,0 +1,5 @@
+abstract class Base(val x: String)
+
+class Derived : Base {
+ constructor(x: String): super(x)
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClass.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClass.kt.after
new file mode 100644
index 00000000000..6ed250a2deb
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClass.kt.after
@@ -0,0 +1,4 @@
+abstract class Base(val x: String)
+
+class Derived(x: String) : Base(x) {
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClassNoArgs.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClassNoArgs.kt
new file mode 100644
index 00000000000..14e7d4bc0cd
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClassNoArgs.kt
@@ -0,0 +1,14 @@
+interface Interface
+
+interface Another
+
+abstract class Base
+
+class Derived : Interface, Base, Another {
+
+ val x: String
+
+ constructor(x: String): super() {
+ this.x = x
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClassNoArgs.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClassNoArgs.kt.after
new file mode 100644
index 00000000000..abf87fd4174
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClassNoArgs.kt.after
@@ -0,0 +1,9 @@
+interface Interface
+
+interface Another
+
+abstract class Base
+
+class Derived(val x: String) : Interface, Base(), Another {
+
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withComposedModifiers.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withComposedModifiers.kt
new file mode 100644
index 00000000000..440034d5787
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withComposedModifiers.kt
@@ -0,0 +1,12 @@
+annotation class AnnParam
+
+annotation class AnnProperty
+
+abstract class WithComposedModifiers {
+ @AnnProperty
+ open val x: Array
+
+ constructor(@AnnParam vararg x: String) {
+ this.x = x
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withComposedModifiers.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withComposedModifiers.kt.after
new file mode 100644
index 00000000000..c8625bcf83e
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withComposedModifiers.kt.after
@@ -0,0 +1,8 @@
+annotation class AnnParam
+
+annotation class AnnProperty
+
+abstract class WithComposedModifiers(@AnnParam vararg @AnnProperty
+open val x: String) {
+
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDelegation.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDelegation.kt
new file mode 100644
index 00000000000..acfe89cff45
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDelegation.kt
@@ -0,0 +1,7 @@
+// IS_APPLICABLE: false
+
+class WithDelegation {
+ constructor()
+
+ constructor(x: Int): this()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDifferentTypeProperty.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDifferentTypeProperty.kt
new file mode 100644
index 00000000000..5e26356a3a4
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDifferentTypeProperty.kt
@@ -0,0 +1,10 @@
+class WithDifferentTypeProperty {
+ val x: Number
+
+ val y: String
+
+ constructor(x: Int, z: String) {
+ this.x = x
+ this.y = z
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDifferentTypeProperty.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDifferentTypeProperty.kt.after
new file mode 100644
index 00000000000..8a07921e2a8
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withDifferentTypeProperty.kt.after
@@ -0,0 +1,6 @@
+class WithDifferentTypeProperty(x: Int, z: String) {
+ val x: Number = x
+
+ val y: String = z
+
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withModifiers.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withModifiers.kt
new file mode 100644
index 00000000000..aa69757d4dd
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withModifiers.kt
@@ -0,0 +1,6 @@
+annotation class Ann
+
+internal class WithModifiers {
+ @Ann
+ private constructor()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withModifiers.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withModifiers.kt.after
new file mode 100644
index 00000000000..8827e66e9fb
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withModifiers.kt.after
@@ -0,0 +1,4 @@
+annotation class Ann
+
+internal class WithModifiers @Ann private constructor() {
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withParameters.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withParameters.kt
new file mode 100644
index 00000000000..00418c7f6bc
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withParameters.kt
@@ -0,0 +1,3 @@
+class WithParameters {
+ constructor(x: Int = 42, y: String = "Hello")
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withParameters.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withParameters.kt.after
new file mode 100644
index 00000000000..a283fd3b291
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withParameters.kt.after
@@ -0,0 +1,2 @@
+class WithParameters(x: Int = 42, y: String = "Hello") {
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withPrimary.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withPrimary.kt
new file mode 100644
index 00000000000..8cc2985bb58
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withPrimary.kt
@@ -0,0 +1,6 @@
+// IS_APPLICABLE: false
+// ERROR: Primary constructor call expected
+
+class WithPrimary() {
+ constructor(x: Int)
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withProperties.kt b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withProperties.kt
new file mode 100644
index 00000000000..f58c97dadcd
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withProperties.kt
@@ -0,0 +1,11 @@
+class WithProperties {
+ val x: Int
+ val y: Int
+ private val z: Int
+
+ constructor(x: Int, y: Int = 7, z: Int = 13) {
+ this.x = x
+ this.y = y
+ this.z = z
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertSecondaryConstructorToPrimary/withProperties.kt.after b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withProperties.kt.after
new file mode 100644
index 00000000000..ffbe40907c6
--- /dev/null
+++ b/idea/testData/intentions/convertSecondaryConstructorToPrimary/withProperties.kt.after
@@ -0,0 +1,3 @@
+class WithProperties(val x: Int, val y: Int = 7, private val z: Int = 13) {
+
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/createFromUsage/createSecondaryConstructor/superCallNoClass.kt b/idea/testData/quickfix/createFromUsage/createSecondaryConstructor/superCallNoClass.kt
index e82afcef257..658bd7d912f 100644
--- a/idea/testData/quickfix/createFromUsage/createSecondaryConstructor/superCallNoClass.kt
+++ b/idea/testData/quickfix/createFromUsage/createSecondaryConstructor/superCallNoClass.kt
@@ -1,5 +1,6 @@
// "Create secondary constructor" "false"
// ERROR: Too many arguments for public constructor Any() defined in kotlin.Any
+// ACTION: Convert to primary constructor
// WITH_RUNTIME
interface T {
diff --git a/idea/testData/quickfix/insertDelegationCall/nonApplicableWithOneConstructor.kt b/idea/testData/quickfix/insertDelegationCall/nonApplicableWithOneConstructor.kt
index 6d7f76c957e..2504d6092a0 100644
--- a/idea/testData/quickfix/insertDelegationCall/nonApplicableWithOneConstructor.kt
+++ b/idea/testData/quickfix/insertDelegationCall/nonApplicableWithOneConstructor.kt
@@ -1,5 +1,6 @@
// "class org.jetbrains.kotlin.idea.quickfix.InsertDelegationCallQuickfix" "false"
// ACTION: Insert 'super()' call
+// ACTION: Convert to primary constructor
// ERROR: Explicit 'this' or 'super' call is required. There is no constructor in superclass that can be called without arguments
open class B(val x: Int)
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
index 69a7f133587..b95aad17552 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -4688,6 +4688,129 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
+ @TestMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class ConvertSecondaryConstructorToPrimary extends AbstractIntentionTest {
+ public void testAllFilesPresentInConvertSecondaryConstructorToPrimary() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertSecondaryConstructorToPrimary"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
+ }
+
+ @TestMetadata("defaultValueChain.kt")
+ public void testDefaultValueChain() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/defaultValueChain.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("init.kt")
+ public void testInit() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/init.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("initAndParams.kt")
+ public void testInitAndParams() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/initAndParams.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("nonReachable.kt")
+ public void testNonReachable() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/nonReachable.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("nonReachableLoop.kt")
+ public void testNonReachableLoop() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/nonReachableLoop.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("protectedConstructor.kt")
+ public void testProtectedConstructor() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/protectedConstructor.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("simple.kt")
+ public void testSimple() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/simple.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("useParam.kt")
+ public void testUseParam() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/useParam.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("varArg.kt")
+ public void testVarArg() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/varArg.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("varargVal.kt")
+ public void testVarargVal() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/varargVal.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withBaseClass.kt")
+ public void testWithBaseClass() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClass.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withBaseClassNoArgs.kt")
+ public void testWithBaseClassNoArgs() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withBaseClassNoArgs.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withComposedModifiers.kt")
+ public void testWithComposedModifiers() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withComposedModifiers.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withDelegation.kt")
+ public void testWithDelegation() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withDelegation.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withDifferentTypeProperty.kt")
+ public void testWithDifferentTypeProperty() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withDifferentTypeProperty.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withModifiers.kt")
+ public void testWithModifiers() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withModifiers.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withParameters.kt")
+ public void testWithParameters() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withParameters.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withPrimary.kt")
+ public void testWithPrimary() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withPrimary.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("withProperties.kt")
+ public void testWithProperties() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertSecondaryConstructorToPrimary/withProperties.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/intentions/convertToBlockBody")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)