diff --git a/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/after.kt.template b/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/after.kt.template
new file mode 100644
index 00000000000..f751eaa0c32
--- /dev/null
+++ b/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/after.kt.template
@@ -0,0 +1,5 @@
+class A {
+ fun foo() = 1
+}
+
+fun bar() = A.foo()
\ No newline at end of file
diff --git a/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/before.kt.template b/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/before.kt.template
new file mode 100644
index 00000000000..8ee0d5bd2e7
--- /dev/null
+++ b/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/before.kt.template
@@ -0,0 +1,7 @@
+class A {
+ companion object {
+ fun foo() = 1
+ }
+}
+
+fun bar() = A.foo()
\ No newline at end of file
diff --git a/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/description.html b/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/description.html
new file mode 100644
index 00000000000..edd07f36f2f
--- /dev/null
+++ b/idea/resources/intentionDescriptions/MoveMemberOutOfCompanionObjectIntention/description.html
@@ -0,0 +1,5 @@
+
+
+This intention moves member of companion object to the corresponding class.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index be7a2599e0b..6546292e3ba 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1169,6 +1169,11 @@
Kotlin
+
+ org.jetbrains.kotlin.idea.intentions.MoveMemberOutOfCompanionObjectIntention
+ Kotlin
+
+
(KtNamedDeclaration::class.java,
+ "Move out of companion object") {
+ override fun applicabilityRange(element: KtNamedDeclaration): TextRange? {
+ if (element !is KtNamedFunction && element !is KtProperty && element !is KtClassOrObject) return null
+ val container = element.containingClassOrObject
+ if (!(container is KtObjectDeclaration && container.isCompanion())) return null
+ if (container.containingClassOrObject == null) return null
+ return element.nameIdentifier?.textRange
+ }
+
+ override fun applyTo(element: KtNamedDeclaration, editor: Editor?) {
+ val project = element.project
+
+ val companionObject = element.containingClassOrObject!!
+ val targetClass = companionObject.containingClassOrObject!!
+
+ fun deleteCompanionIfEmpty() {
+ if (companionObject.declarations.isEmpty()) {
+ companionObject.delete()
+ }
+ }
+
+ if (element is KtClassOrObject) {
+ val moveDescriptor = MoveDeclarationsDescriptor(listOf(element),
+ KotlinMoveTargetForExistingElement(targetClass),
+ MoveDeclarationsDelegate.NestedClass())
+ MoveKotlinDeclarationsProcessor(project, moveDescriptor).run()
+ deleteCompanionIfEmpty()
+ return
+ }
+
+ val externalRefs = project.runSynchronouslyWithProgress("Searching for ${element.name}", true) {
+ ReferencesSearch.search(element).filter {
+ val refElement = it.element ?: return@filter false
+ !targetClass.isAncestor(refElement) || companionObject.isAncestor(refElement)
+ }
+ } ?: return
+
+ val conflicts = MultiMap()
+ for (ref in externalRefs) {
+ val refElement = ref.element ?: continue
+ conflicts.putValue(refElement, "Class instance required: ${refElement.text}")
+ }
+
+ project.checkConflictsInteractively(conflicts) {
+ Mover.Default(element, targetClass)
+ deleteCompanionIfEmpty()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/.intention b/idea/testData/intentions/moveOutOfCompanion/.intention
new file mode 100644
index 00000000000..fabfb96ed5b
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/.intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.intentions.MoveMemberOutOfCompanionObjectIntention
diff --git a/idea/testData/intentions/moveOutOfCompanion/moveAndDropCompanion.kt b/idea/testData/intentions/moveOutOfCompanion/moveAndDropCompanion.kt
new file mode 100644
index 00000000000..f8db1f2df82
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/moveAndDropCompanion.kt
@@ -0,0 +1,11 @@
+class A {
+ companion object {
+ class B {
+
+ }
+ }
+}
+
+fun foo() {
+ A.Companion.B()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/moveAndDropCompanion.kt.after b/idea/testData/intentions/moveOutOfCompanion/moveAndDropCompanion.kt.after
new file mode 100644
index 00000000000..cf48decaa8e
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/moveAndDropCompanion.kt.after
@@ -0,0 +1,9 @@
+class A {
+ class B {
+
+ }
+}
+
+fun foo() {
+ A.B()
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/moveFunction.kt b/idea/testData/intentions/moveOutOfCompanion/moveFunction.kt
new file mode 100644
index 00000000000..55b49450d67
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/moveFunction.kt
@@ -0,0 +1,15 @@
+class A {
+ companion object {
+ class B {
+
+ }
+
+ fun foo() {
+
+ }
+ }
+
+ fun bar() {
+ foo()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/moveFunction.kt.after b/idea/testData/intentions/moveOutOfCompanion/moveFunction.kt.after
new file mode 100644
index 00000000000..612031c3ece
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/moveFunction.kt.after
@@ -0,0 +1,16 @@
+class A {
+ companion object {
+ class B {
+
+ }
+
+ }
+
+ fun bar() {
+ foo()
+ }
+
+ fun foo() {
+
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/moveFunctionWithExternalRefs.kt b/idea/testData/intentions/moveOutOfCompanion/moveFunctionWithExternalRefs.kt
new file mode 100644
index 00000000000..0f3552b3843
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/moveFunctionWithExternalRefs.kt
@@ -0,0 +1,27 @@
+// WITH_RUNTIME
+// SHOULD_FAIL_WITH: Class instance required: foo, Class instance required: foo, Class instance required: foo, Class instance required: foo
+class A {
+ companion object {
+ class B {
+ init {
+ foo()
+ }
+ }
+
+ fun foo() {
+
+ }
+ }
+
+ fun bar() {
+ foo()
+ }
+}
+
+fun test() {
+ A.foo()
+ A.Companion.foo()
+ with(A) {
+ foo()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/moveProperty.kt b/idea/testData/intentions/moveOutOfCompanion/moveProperty.kt
new file mode 100644
index 00000000000..023b21dd0a1
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/moveProperty.kt
@@ -0,0 +1,13 @@
+class A {
+ companion object {
+ class B {
+
+ }
+
+ val foo: Int = 1
+ }
+
+ fun bar() {
+ foo + 1
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/moveProperty.kt.after b/idea/testData/intentions/moveOutOfCompanion/moveProperty.kt.after
new file mode 100644
index 00000000000..628e45e0910
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/moveProperty.kt.after
@@ -0,0 +1,14 @@
+class A {
+ companion object {
+ class B {
+
+ }
+
+ }
+
+ fun bar() {
+ foo + 1
+ }
+
+ val foo: Int = 1
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/movePropertyWithExternalRefs.kt b/idea/testData/intentions/moveOutOfCompanion/movePropertyWithExternalRefs.kt
new file mode 100644
index 00000000000..aa80a9a3a2f
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/movePropertyWithExternalRefs.kt
@@ -0,0 +1,25 @@
+// WITH_RUNTIME
+// SHOULD_FAIL_WITH: Class instance required: foo, Class instance required: foo, Class instance required: foo, Class instance required: foo
+class A {
+ companion object {
+ class B {
+ init {
+ foo + 1
+ }
+ }
+
+ val foo: Int = 1
+ }
+
+ fun bar() {
+ foo + 1
+ }
+}
+
+fun test() {
+ A.foo + 1
+ A.Companion.foo + 1
+ with(A) {
+ foo + 1
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/notCompanion1.kt b/idea/testData/intentions/moveOutOfCompanion/notCompanion1.kt
new file mode 100644
index 00000000000..5292daf77f1
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/notCompanion1.kt
@@ -0,0 +1,9 @@
+// IS_APPLICABLE: false
+
+class A {
+ object O {
+ class B {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/intentions/moveOutOfCompanion/notCompanion2.kt b/idea/testData/intentions/moveOutOfCompanion/notCompanion2.kt
new file mode 100644
index 00000000000..be7b391766f
--- /dev/null
+++ b/idea/testData/intentions/moveOutOfCompanion/notCompanion2.kt
@@ -0,0 +1,7 @@
+// IS_APPLICABLE: false
+
+class A {
+ class B {
+
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/after/a/A.kt b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/after/a/A.kt
new file mode 100644
index 00000000000..2ed0ad52b8d
--- /dev/null
+++ b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/after/a/A.kt
@@ -0,0 +1,18 @@
+package a
+
+class A {
+ companion object {
+ fun foo() {
+
+ }
+
+ }
+
+ class B {
+
+ }
+}
+
+fun foo() {
+ A.B()
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/after/b/J.java b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/after/b/J.java
new file mode 100644
index 00000000000..fae7c1cb6cc
--- /dev/null
+++ b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/after/b/J.java
@@ -0,0 +1,9 @@
+package b;
+
+import a.A;
+
+class J {
+ void foo() {
+ A.B b = new A.B();
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/before/a/A.kt b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/before/a/A.kt
new file mode 100644
index 00000000000..fd2e914ac72
--- /dev/null
+++ b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/before/a/A.kt
@@ -0,0 +1,17 @@
+package a
+
+class A {
+ companion object {
+ fun foo() {
+
+ }
+
+ class B {
+
+ }
+ }
+}
+
+fun foo() {
+ A.Companion.B()
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/before/b/J.java b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/before/b/J.java
new file mode 100644
index 00000000000..db6a0812c95
--- /dev/null
+++ b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/before/b/J.java
@@ -0,0 +1,9 @@
+package b;
+
+import a.A;
+
+class J {
+ void foo() {
+ A.Companion.B b = new A.Companion.B();
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/moveClass.test b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/moveClass.test
new file mode 100644
index 00000000000..a0edee76934
--- /dev/null
+++ b/idea/testData/multiFileIntentions/moveOutOfCompanion/moveClass/moveClass.test
@@ -0,0 +1,5 @@
+{
+ "mainFile": "a/A.kt",
+ "intentionClass": "org.jetbrains.kotlin.idea.intentions.MoveMemberOutOfCompanionObjectIntention",
+ "withRuntime": "true"
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.java
index b8b8bb8d008..2868e65917c 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.java
@@ -154,7 +154,7 @@ public abstract class AbstractIntentionTest extends KotlinCodeInsightTestCase {
assertEquals("Intention text mismatch.", intentionTextString, intentionAction.getText());
}
- String shouldFailString = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// SHOULD_FAIL_WITH: ");
+ String shouldFailString = StringUtil.join(InTextDirectivesUtils.findListWithPrefixes(fileText, "// SHOULD_FAIL_WITH: "), ", ");
try {
if (isApplicableExpected) {
@@ -171,7 +171,7 @@ public abstract class AbstractIntentionTest extends KotlinCodeInsightTestCase {
}
);
// Don't bother checking if it should have failed.
- if (shouldFailString == null) {
+ if (shouldFailString.isEmpty()) {
for (Map.Entry entry: pathToFile.entrySet()) {
//noinspection AssignmentToStaticFieldFromInstanceMethod
myFile = entry.getValue();
@@ -186,7 +186,7 @@ public abstract class AbstractIntentionTest extends KotlinCodeInsightTestCase {
}
}
}
- assertNull("Expected test to fail.", shouldFailString);
+ assertEquals("Expected test to fail.", "", shouldFailString);
}
catch (BaseRefactoringProcessor.ConflictsInTestsException e) {
assertEquals("Failure message mismatch.", shouldFailString, StringUtil.join(e.getMessages(), ", "));