diff --git a/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/after.kt.template b/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/after.kt.template
new file mode 100644
index 00000000000..6117b7cd4f1
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/after.kt.template
@@ -0,0 +1,2 @@
+sealed class Klass(private val s: String) {
+}
\ No newline at end of file
diff --git a/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/before.kt.template b/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/before.kt.template
new file mode 100644
index 00000000000..87261e861eb
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/before.kt.template
@@ -0,0 +1,2 @@
+open class Klass private constructor(private val s: String) {
+}
\ No newline at end of file
diff --git a/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/description.html b/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/description.html
new file mode 100644
index 00000000000..e60160ad7b5
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertClassToSealedClassIntention/description.html
@@ -0,0 +1,5 @@
+
+
+This intention converts an open or abstract class with only private constructors to a sealed class
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index bc37b8f544e..ffb28e9e267 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1593,6 +1593,11 @@
Kotlin
+
+ org.jetbrains.kotlin.idea.intentions.ConvertClassToSealedClassIntention
+ Kotlin
+
+
(KtClass::class.java, "Convert to sealed class") {
+
+ override fun applicabilityRange(element: KtClass): TextRange? {
+ if (element.modifierList == null) return null
+ if (!element.hasModifier(OPEN_KEYWORD) && !element.hasModifier(ABSTRACT_KEYWORD)) return null
+
+ val constructors = listOfNotNull(element.primaryConstructor) + element.secondaryConstructors
+ if (constructors.isEmpty()) return null
+ if (!constructors.all { it.hasModifier(PRIVATE_KEYWORD) && it.getAnnotationEntries().isEmpty() }) return null
+
+ val nameIdentifier = element.nameIdentifier ?: return null
+ return TextRange(element.startOffset, nameIdentifier.endOffset)
+ }
+
+ override fun applyTo(element: KtClass, editor: Editor?) {
+ element.modifierList?.run {
+ getModifier(OPEN_KEYWORD)?.delete()
+ getModifier(ABSTRACT_KEYWORD)?.delete()
+ }
+ element.addModifier(SEALED_KEYWORD)
+
+ element.primaryConstructor?.run {
+ if (element.secondaryConstructors.isEmpty() && valueParameters.isEmpty()) {
+ this.delete()
+ }
+ else {
+ val newConstructor = this.copy() as KtPrimaryConstructor
+ newConstructor.modifierList?.getModifier(PRIVATE_KEYWORD)?.delete()
+ newConstructor.getConstructorKeyword()?.delete()
+ this.replace(newConstructor)
+ }
+ }
+
+ element.secondaryConstructors.forEach {
+ it.modifierList?.getModifier(PRIVATE_KEYWORD)?.delete()
+ }
+ }
+
+}
diff --git a/idea/testData/intentions/convertClassToSealedClass/.intention b/idea/testData/intentions/convertClassToSealedClass/.intention
new file mode 100644
index 00000000000..6ab3dc22478
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.ConvertClassToSealedClassIntention
diff --git a/idea/testData/intentions/convertClassToSealedClass/abstract.kt b/idea/testData/intentions/convertClassToSealedClass/abstract.kt
new file mode 100644
index 00000000000..2bc8ad9f32d
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/abstract.kt
@@ -0,0 +1 @@
+abstract class Test private constructor()
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/abstract.kt.after b/idea/testData/intentions/convertClassToSealedClass/abstract.kt.after
new file mode 100644
index 00000000000..a0246442fe6
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/abstract.kt.after
@@ -0,0 +1 @@
+sealed class Test
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/noConstructor.kt b/idea/testData/intentions/convertClassToSealedClass/noConstructor.kt
new file mode 100644
index 00000000000..942d87066aa
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/noConstructor.kt
@@ -0,0 +1,2 @@
+// IS_APPLICABLE: false
+open class Test
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/notOpenAndAbstract.kt b/idea/testData/intentions/convertClassToSealedClass/notOpenAndAbstract.kt
new file mode 100644
index 00000000000..f33be4867cf
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/notOpenAndAbstract.kt
@@ -0,0 +1,2 @@
+// IS_APPLICABLE: false
+class Test private constructor()
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/notPrivatePrimaryConstructor.kt b/idea/testData/intentions/convertClassToSealedClass/notPrivatePrimaryConstructor.kt
new file mode 100644
index 00000000000..858806e4ff7
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/notPrivatePrimaryConstructor.kt
@@ -0,0 +1,2 @@
+// IS_APPLICABLE: false
+open class Test constructor()
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/notPrivateSecondaryConstructor.kt b/idea/testData/intentions/convertClassToSealedClass/notPrivateSecondaryConstructor.kt
new file mode 100644
index 00000000000..d72d7747407
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/notPrivateSecondaryConstructor.kt
@@ -0,0 +1,5 @@
+// IS_APPLICABLE: false
+open class Test private constructor() {
+ constructor(i: Int) : this() {
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/onlySecondaryConstructor.kt b/idea/testData/intentions/convertClassToSealedClass/onlySecondaryConstructor.kt
new file mode 100644
index 00000000000..a71f040f221
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/onlySecondaryConstructor.kt
@@ -0,0 +1,4 @@
+open class Test {
+ private constructor(i: Int) {
+ }
+}
diff --git a/idea/testData/intentions/convertClassToSealedClass/onlySecondaryConstructor.kt.after b/idea/testData/intentions/convertClassToSealedClass/onlySecondaryConstructor.kt.after
new file mode 100644
index 00000000000..b26f1f5d0d7
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/onlySecondaryConstructor.kt.after
@@ -0,0 +1,4 @@
+sealed class Test {
+ constructor(i: Int) {
+ }
+}
diff --git a/idea/testData/intentions/convertClassToSealedClass/primaryConstructor.kt b/idea/testData/intentions/convertClassToSealedClass/primaryConstructor.kt
new file mode 100644
index 00000000000..6cd92dbe528
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/primaryConstructor.kt
@@ -0,0 +1 @@
+open class Test private constructor()
diff --git a/idea/testData/intentions/convertClassToSealedClass/primaryConstructor.kt.after b/idea/testData/intentions/convertClassToSealedClass/primaryConstructor.kt.after
new file mode 100644
index 00000000000..a0246442fe6
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/primaryConstructor.kt.after
@@ -0,0 +1 @@
+sealed class Test
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithAnnotation.kt b/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithAnnotation.kt
new file mode 100644
index 00000000000..850a5c8dbc2
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithAnnotation.kt
@@ -0,0 +1,3 @@
+// IS_APPLICABLE: false
+annotation class Inject
+open class Test @Inject private constructor()
diff --git a/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithValueParameter.kt b/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithValueParameter.kt
new file mode 100644
index 00000000000..802ab01460c
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithValueParameter.kt
@@ -0,0 +1 @@
+open class Test private constructor(s: String)
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithValueParameter.kt.after b/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithValueParameter.kt.after
new file mode 100644
index 00000000000..9a0279eebb1
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithValueParameter.kt.after
@@ -0,0 +1 @@
+sealed class Test(s: String)
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/secondaryConstructor.kt b/idea/testData/intentions/convertClassToSealedClass/secondaryConstructor.kt
new file mode 100644
index 00000000000..22644d85d3d
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/secondaryConstructor.kt
@@ -0,0 +1,4 @@
+open class Test private constructor() {
+ private constructor(i: Int) : this() {
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/secondaryConstructor.kt.after b/idea/testData/intentions/convertClassToSealedClass/secondaryConstructor.kt.after
new file mode 100644
index 00000000000..5ea8e0e46f3
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/secondaryConstructor.kt.after
@@ -0,0 +1,4 @@
+sealed class Test() {
+ constructor(i: Int) : this() {
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertClassToSealedClass/secondaryConstructorWithAnnotation.kt b/idea/testData/intentions/convertClassToSealedClass/secondaryConstructorWithAnnotation.kt
new file mode 100644
index 00000000000..e7c60ecdbaf
--- /dev/null
+++ b/idea/testData/intentions/convertClassToSealedClass/secondaryConstructorWithAnnotation.kt
@@ -0,0 +1,6 @@
+// IS_APPLICABLE: false
+annotation class Inject
+open class Test private constructor() {
+ private @Inject constructor(i: Int) : this() {
+ }
+}
\ 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 19affd44868..1751978403f 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -3990,6 +3990,81 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
+ @TestMetadata("idea/testData/intentions/convertClassToSealedClass")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class ConvertClassToSealedClass extends AbstractIntentionTest {
+ @TestMetadata("abstract.kt")
+ public void testAbstract() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/abstract.kt");
+ doTest(fileName);
+ }
+
+ public void testAllFilesPresentInConvertClassToSealedClass() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertClassToSealedClass"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("noConstructor.kt")
+ public void testNoConstructor() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/noConstructor.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("notOpenAndAbstract.kt")
+ public void testNotOpenAndAbstract() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/notOpenAndAbstract.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("notPrivatePrimaryConstructor.kt")
+ public void testNotPrivatePrimaryConstructor() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/notPrivatePrimaryConstructor.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("notPrivateSecondaryConstructor.kt")
+ public void testNotPrivateSecondaryConstructor() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/notPrivateSecondaryConstructor.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("onlySecondaryConstructor.kt")
+ public void testOnlySecondaryConstructor() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/onlySecondaryConstructor.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("primaryConstructor.kt")
+ public void testPrimaryConstructor() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/primaryConstructor.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("primaryConstructorWithAnnotation.kt")
+ public void testPrimaryConstructorWithAnnotation() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithAnnotation.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("primaryConstructorWithValueParameter.kt")
+ public void testPrimaryConstructorWithValueParameter() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/primaryConstructorWithValueParameter.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("secondaryConstructor.kt")
+ public void testSecondaryConstructor() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/secondaryConstructor.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("secondaryConstructorWithAnnotation.kt")
+ public void testSecondaryConstructorWithAnnotation() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertClassToSealedClass/secondaryConstructorWithAnnotation.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/intentions/convertEnumToSealedClass")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)