Intentions: Move member of companion object to corresponding class

#KT-9697 Fixed
This commit is contained in:
Alexey Sedunov
2016-02-03 14:31:55 +03:00
parent d13ac6b5a4
commit 5f037d372d
22 changed files with 310 additions and 3 deletions
@@ -0,0 +1,5 @@
class A {
<spot>fun foo() = 1</spot>
}
fun bar() = A.foo()
@@ -0,0 +1,7 @@
class A {
companion object {
<spot>fun foo() = 1</spot>
}
}
fun bar() = A.foo()
@@ -0,0 +1,5 @@
<html>
<body>
This intention moves member of companion object to the corresponding class.
</body>
</html>
+5
View File
@@ -1169,6 +1169,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.MoveMemberOutOfCompanionObjectIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
displayName="Convert object literal to lambda"
groupName="Kotlin"
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
import org.jetbrains.kotlin.idea.refactoring.runSynchronouslyWithProgress
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
class MoveMemberOutOfCompanionObjectIntention : SelfTargetingRangeIntention<KtNamedDeclaration>(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<PsiElement, String>()
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()
}
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.MoveMemberOutOfCompanionObjectIntention
@@ -0,0 +1,11 @@
class A {
companion object {
class <caret>B {
}
}
}
fun foo() {
A.Companion.B()
}
@@ -0,0 +1,9 @@
class A {
class B {
}
}
fun foo() {
A.B()
}
@@ -0,0 +1,15 @@
class A {
companion object {
class B {
}
fun <caret>foo() {
}
}
fun bar() {
foo()
}
}
@@ -0,0 +1,16 @@
class A {
companion object {
class B {
}
}
fun bar() {
foo()
}
fun foo() {
}
}
@@ -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 <caret>foo() {
}
}
fun bar() {
foo()
}
}
fun test() {
A.foo()
A.Companion.foo()
with(A) {
foo()
}
}
@@ -0,0 +1,13 @@
class A {
companion object {
class B {
}
val <caret>foo: Int = 1
}
fun bar() {
foo + 1
}
}
@@ -0,0 +1,14 @@
class A {
companion object {
class B {
}
}
fun bar() {
foo + 1
}
val foo: Int = 1
}
@@ -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 <caret>foo: Int = 1
}
fun bar() {
foo + 1
}
}
fun test() {
A.foo + 1
A.Companion.foo + 1
with(A) {
foo + 1
}
}
@@ -0,0 +1,9 @@
// IS_APPLICABLE: false
class A {
object O {
class <caret>B {
}
}
}
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
class A {
class <caret>B {
}
}
@@ -0,0 +1,18 @@
package a
class A {
companion object {
fun foo() {
}
}
class B {
}
}
fun foo() {
A.B()
}
@@ -0,0 +1,9 @@
package b;
import a.A;
class J {
void foo() {
A.B b = new A.B();
}
}
@@ -0,0 +1,17 @@
package a
class A {
companion object {
fun foo() {
}
class <caret>B {
}
}
}
fun foo() {
A.Companion.B()
}
@@ -0,0 +1,9 @@
package b;
import a.A;
class J {
void foo() {
A.Companion.B b = new A.Companion.B();
}
}
@@ -0,0 +1,5 @@
{
"mainFile": "a/A.kt",
"intentionClass": "org.jetbrains.kotlin.idea.intentions.MoveMemberOutOfCompanionObjectIntention",
"withRuntime": "true"
}
@@ -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<String, PsiFile> 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(), ", "));