diff --git a/ChangeLog.md b/ChangeLog.md
index 1c996de9f18..207e913e659 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -476,6 +476,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
- [`KT-15068`](https://youtrack.jetbrains.com/issue/KT-15068) Implement intention which rename file according to the top-level class name
- Implement quickfix which enables/disables coroutine support in module or project
+- [`KT-15056`](https://youtrack.jetbrains.com/issue/KT-15056) Implement intention which converts object literal to class
## 1.0.6
diff --git a/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/after.kt.template b/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/after.kt.template
new file mode 100644
index 00000000000..2615d1d542e
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/after.kt.template
@@ -0,0 +1,9 @@
+open class K
+
+fun foo(n: Int) {
+ val x = O(n)
+}
+
+class O(private val n: Int) : K() {
+ fun bar() = n
+}
\ No newline at end of file
diff --git a/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/before.kt.template b/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/before.kt.template
new file mode 100644
index 00000000000..befc623d785
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/before.kt.template
@@ -0,0 +1,7 @@
+open class K
+
+fun foo(n: Int) {
+ val x = object : K() {
+ fun bar() = n
+ }
+}
\ No newline at end of file
diff --git a/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/description.html b/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/description.html
new file mode 100644
index 00000000000..f36f0e99a02
--- /dev/null
+++ b/idea/resources/intentionDescriptions/ConvertObjectLiteralToClassIntention/description.html
@@ -0,0 +1,5 @@
+
+
+This intention converts object literal to class and replaces object literal expression with constructor call.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 63608a8caf1..9628e54eb6d 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1485,6 +1485,11 @@
Kotlin
+
+ org.jetbrains.kotlin.idea.intentions.ConvertObjectLiteralToClassIntention
+ Kotlin
+
+
(
+ KtObjectLiteralExpression::class.java,
+ "Convert object literal to class"
+) {
+ override fun applicabilityRange(element: KtObjectLiteralExpression) = element.objectDeclaration.getObjectKeyword()?.textRange
+
+ override fun startInWriteAction() = false
+
+ private fun doApply(editor: Editor, element: KtObjectLiteralExpression, targetParent: KtElement) {
+ val project = element.project
+
+ val scope = element.getResolutionScope()
+ val context = element.analyze(BodyResolveMode.PARTIAL)
+ val objectLiteralType = element.getType(context)
+ val validator: (String) -> Boolean = { scope.findClassifier(Name.identifier(it), NoLookupLocation.FROM_IDE) == null }
+ val className = if (objectLiteralType != null) {
+ KotlinNameSuggester.suggestNamesByType(objectLiteralType, validator, "O").first()
+ }
+ else {
+ KotlinNameSuggester.suggestNameByName("O", validator)
+ }
+
+ val psiFactory = KtPsiFactory(element)
+
+ val targetSibling = element.parentsWithSelf.first { it.parent == targetParent }
+
+ val objectDeclaration = element.objectDeclaration
+ val newClass = psiFactory.createClass("class $className")
+ objectDeclaration.getSuperTypeList()?.let {
+ newClass.add(psiFactory.createColon())
+ newClass.add(it)
+ }
+ objectDeclaration.getBody()?.let {
+ newClass.add(it)
+ }
+
+ project.executeWriteCommand(text) {
+ ExtractionEngine(
+ object : ExtractionEngineHelper(text) {
+ override fun configureAndRun(
+ project: Project,
+ editor: Editor,
+ descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
+ onFinish: (ExtractionResult) -> Unit
+ ) {
+ val descriptor = descriptorWithConflicts.descriptor.copy(suggestedNames = className.singletonList())
+ doRefactor(
+ ExtractionGeneratorConfiguration(descriptor, ExtractionGeneratorOptions.DEFAULT),
+ onFinish
+ )
+ }
+ }
+ ).run(editor, ExtractionData(element.containingKtFile, element.toRange(), targetSibling)) {
+ val functionDeclaration = it.declaration as KtFunction
+ if (functionDeclaration.valueParameters.isNotEmpty()) {
+ val valKeyword = psiFactory.createValKeyword()
+ newClass
+ .createPrimaryConstructorParameterListIfAbsent()
+ .replaced(functionDeclaration.valueParameterList!!)
+ .parameters
+ .forEach {
+ it.addAfter(valKeyword, null)
+ it.addModifier(KtTokens.PRIVATE_KEYWORD)
+ }
+ }
+ functionDeclaration.replaced(newClass).apply {
+ primaryConstructor?.let { CodeStyleManager.getInstance(project).reformat(it) }
+ }
+ }
+ }
+ }
+
+ override fun applyTo(element: KtObjectLiteralExpression, editor: Editor?) {
+ if (editor == null) return
+
+ val containers = element.getExtractionContainers(strict = true, includeAll = true)
+
+ if (ApplicationManager.getApplication().isUnitTestMode) {
+ return doApply(editor, element, containers.last())
+ }
+
+ chooseContainerElementIfNecessary(
+ containers,
+ editor,
+ if (containers.first() is KtFile) "Select target file" else "Select target code block / file",
+ true,
+ { it },
+ { doApply(editor, element, it) }
+ )
+ }
+}
diff --git a/idea/testData/intentions/convertObjectLiteralToClass/.intention b/idea/testData/intentions/convertObjectLiteralToClass/.intention
new file mode 100644
index 00000000000..ba8f864844c
--- /dev/null
+++ b/idea/testData/intentions/convertObjectLiteralToClass/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.ConvertObjectLiteralToClassIntention
\ No newline at end of file
diff --git a/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoCapture.kt b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoCapture.kt
new file mode 100644
index 00000000000..7d1bf534efb
--- /dev/null
+++ b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoCapture.kt
@@ -0,0 +1,7 @@
+open class K
+
+fun foo(n: Int) {
+ val x = object : K() {
+ fun bar() = 1
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoCapture.kt.after b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoCapture.kt.after
new file mode 100644
index 00000000000..d056a578aa0
--- /dev/null
+++ b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoCapture.kt.after
@@ -0,0 +1,9 @@
+open class K
+
+fun foo(n: Int) {
+ val x = O()
+}
+
+class O : K() {
+ fun bar() = 1
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoSupers.kt b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoSupers.kt
new file mode 100644
index 00000000000..f142e3b210e
--- /dev/null
+++ b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoSupers.kt
@@ -0,0 +1,5 @@
+fun foo(n: Int) {
+ val x = object {
+ fun bar() = n
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoSupers.kt.after b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoSupers.kt.after
new file mode 100644
index 00000000000..a7e6a9c60dc
--- /dev/null
+++ b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoSupers.kt.after
@@ -0,0 +1,7 @@
+fun foo(n: Int) {
+ val x = O(n)
+}
+
+class O(private val n: Int) {
+ fun bar() = n
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralWithCapture.kt b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralWithCapture.kt
new file mode 100644
index 00000000000..0f647d8cba9
--- /dev/null
+++ b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralWithCapture.kt
@@ -0,0 +1,7 @@
+open class K
+
+fun foo(n: Int) {
+ val x = object : K() {
+ fun bar() = n
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralWithCapture.kt.after b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralWithCapture.kt.after
new file mode 100644
index 00000000000..8ac91c2d271
--- /dev/null
+++ b/idea/testData/intentions/convertObjectLiteralToClass/objectLiteralWithCapture.kt.after
@@ -0,0 +1,9 @@
+open class K
+
+fun foo(n: Int) {
+ val x = O(n)
+}
+
+class O(private val n: Int) : K() {
+ fun bar() = n
+}
\ No newline at end of file
diff --git a/idea/testData/quickfix/modifiers/noAbstractForAnonymousObject.kt b/idea/testData/quickfix/modifiers/noAbstractForAnonymousObject.kt
index 9835de5b2dc..f13947c5cf3 100644
--- a/idea/testData/quickfix/modifiers/noAbstractForAnonymousObject.kt
+++ b/idea/testData/quickfix/modifiers/noAbstractForAnonymousObject.kt
@@ -1,6 +1,7 @@
// "Make 'object : T {}' abstract" "false"
// ACTION: Implement members
// ACTION: Split property declaration
+// ACTION: Convert object literal to class
// ERROR: Object must be declared abstract or implement abstract member public abstract fun foo(): Unit defined in T
interface T {
fun foo()
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
index 36704fcf13c..d00ab0d93ee 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -4680,6 +4680,33 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
+ @TestMetadata("idea/testData/intentions/convertObjectLiteralToClass")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class ConvertObjectLiteralToClass extends AbstractIntentionTest {
+ public void testAllFilesPresentInConvertObjectLiteralToClass() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertObjectLiteralToClass"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("objectLiteralNoCapture.kt")
+ public void testObjectLiteralNoCapture() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoCapture.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("objectLiteralNoSupers.kt")
+ public void testObjectLiteralNoSupers() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoSupers.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("objectLiteralWithCapture.kt")
+ public void testObjectLiteralWithCapture() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertObjectLiteralToClass/objectLiteralWithCapture.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/intentions/convertParameterToReceiver")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)