Implement an intention action to move a companion object member to top level

#KT-18828 Fixed

(cherry picked from commit 11f8f8b)
This commit is contained in:
shiraji
2017-10-21 04:56:07 +03:00
committed by Alexey Sedunov
parent 7044e46756
commit 31a1fb916a
19 changed files with 221 additions and 22 deletions
@@ -28,10 +28,14 @@ import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.checker.KotlinTypeCheckerImpl
import org.jetbrains.kotlin.types.typeUtil.equalTypesOrNulls
fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, descriptor2: DeclarationDescriptor?): Boolean {
fun descriptorsEqualWithSubstitution(
descriptor1: DeclarationDescriptor?,
descriptor2: DeclarationDescriptor?,
checkOriginals: Boolean = true
): Boolean {
if (descriptor1 == descriptor2) return true
if (descriptor1 == null || descriptor2 == null) return false
if (descriptor1.original != descriptor2.original) return false
if (checkOriginals && descriptor1.original != descriptor2.original) return false
if (descriptor1 !is CallableDescriptor) return true
descriptor2 as CallableDescriptor
+6 -1
View File
@@ -1645,7 +1645,12 @@
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.MoveMemberToTopLevelIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
displayName="Object literal can be converted to lambda"
groupPath="Kotlin"
groupName="Style issues"
@@ -17,31 +17,15 @@
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.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getUsageContext
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.synthetics.findClassDescriptor
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.util.findCallableMemberBySignature
abstract class MoveMemberOutOfObjectIntention(text: String) : SelfTargetingRangeIntention<KtNamedDeclaration>(KtNamedDeclaration::class.java, text) {
override fun startInWriteAction() = false
@@ -0,0 +1,67 @@
/*
* Copyright 2010-2017 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.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
import org.jetbrains.kotlin.util.descriptorsEqualWithSubstitution
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
class MoveMemberToTopLevelIntention : MoveMemberOutOfObjectIntention("Move to top level") {
override fun addConflicts(element: KtNamedDeclaration, conflicts: MultiMap<PsiElement, String>) {
val packageViewDescriptor = element.findModuleDescriptor().getPackage(element.containingKtFile.packageFqName)
val packageDescriptor = packageViewDescriptor.fragments.firstIsInstance<LazyPackageDescriptor>()
val memberScope = packageDescriptor.getMemberScope()
val packageName = packageViewDescriptor.fqName.asString().takeIf { it.isNotBlank() } ?: "default"
val name = element.name ?: return
val isRedeclaration = when (element) {
is KtProperty -> memberScope.getVariableNames().any { name == it.identifier }
is KtFunction -> {
memberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_IDE).filter {
descriptorsEqualWithSubstitution(element.descriptor, it, false)
}.isNotEmpty()
}
else -> false
}
if (isRedeclaration) {
conflicts.putValue(element, "Package '$packageName' already contains ${RefactoringUIUtil.getDescription(element, false)}")
}
}
override fun getDestination(element: KtNamedDeclaration) = element.containingKtFile
override fun applicabilityRange(element: KtNamedDeclaration): TextRange? {
if (element !is KtNamedFunction && element !is KtProperty) return null
element.containingClassOrObject as? KtObjectDeclaration ?: return null
return element.nameIdentifier?.textRange
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.MoveMemberToTopLevelIntention
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
abstract class A {
abstract fun <caret> foo()
}
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
abstract class A {
abstract val <caret> foo: Int
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
object A {
private fun <caret>foo() = 1
}
@@ -0,0 +1,3 @@
// WITH_RUNTIME
private fun foo() = 1
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class A {
companion object {
private fun <caret>foo() = 1
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
class A {
}
private fun foo() = 1
@@ -0,0 +1,5 @@
// WITH_RUNTIME
object A {
val <caret>foo: Int = 1
}
@@ -0,0 +1,3 @@
// WITH_RUNTIME
val foo: Int = 1
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class A {
companion object {
val <caret>foo: Int = 1
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
class A {
}
val foo: Int = 1
@@ -0,0 +1,6 @@
// SHOULD_FAIL_WITH: Package 'default' already contains function f1(Int)
object Test {
fun <caret>f1(n: Int) {}
}
fun f1(n: Int) {}
@@ -0,0 +1,8 @@
// SHOULD_FAIL_WITH: Package 'foo.bar' already contains function f1(Int)
package foo.bar
object Test {
fun <caret>f1(n: Int) {}
}
fun f1(n: Int) {}
@@ -0,0 +1,6 @@
// SHOULD_FAIL_WITH: Package 'default' already contains property f1
object Test {
val <caret>f1 = 1
}
val f1 = 1
@@ -11706,6 +11706,72 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/moveMemberToTopLevel")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class MoveMemberToTopLevel extends AbstractIntentionTest {
@TestMetadata("abstractFunction.kt")
public void testAbstractFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/abstractFunction.kt");
doTest(fileName);
}
@TestMetadata("abstractProperty.kt")
public void testAbstractProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/abstractProperty.kt");
doTest(fileName);
}
public void testAllFilesPresentInMoveMemberToTopLevel() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/moveMemberToTopLevel"),
Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("function.kt")
public void testFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/function.kt");
doTest(fileName);
}
@TestMetadata("functionInCompanion.kt")
public void testFunctionInCompanion() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt");
doTest(fileName);
}
@TestMetadata("property.kt")
public void testProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/property.kt");
doTest(fileName);
}
@TestMetadata("propertyInCompanion.kt")
public void testPropertyInCompanion() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt");
doTest(fileName);
}
@TestMetadata("redeclarationConflict.kt")
public void testRedeclarationConflict() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt");
doTest(fileName);
}
@TestMetadata("redeclarationConflictWithPackage.kt")
public void testRedeclarationConflictWithPackage() throws Exception {
String fileName =
KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt");
doTest(fileName);
}
@TestMetadata("redeclarationPropertyConflict.kt")
public void testRedeclarationPropertyConflict() throws Exception {
String fileName =
KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/moveOutOfCompanion")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)