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)