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(), ", "));