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:
@@ -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
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
+8
@@ -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) {}
|
||||
+6
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user