[kotlin] Added ability to move nested declarations using K2 move refactoring

The nested declarations are first converted to be effectively static if they capture an outer class.

^KTIJ-28862 fixed

GitOrigin-RevId: c035ba0350702f8934a1258b408521afc8736ea8
This commit is contained in:
Frederik Haselmeier
2024-11-26 11:10:16 +00:00
committed by intellij-monorepo-bot
parent d3f50d6114
commit e91c6650db
65 changed files with 712 additions and 59 deletions
@@ -930,7 +930,7 @@ text.move.declaration.no.support.for.companion.objects=Move declaration is not s
text.move.declaration.no.support.for.enums=Move declaration is not supported for enum entries
text.move.file.no.support.for.file.target=Move files is not supported for non-directory target
text.move.declaration.no.support.for.nested.declarations=Move declaration is not supported for nested declarations
text.move.declaration.only.support.for.nested.classes=Move declaration is not supported for nested declarations other than nested classes
text.move.declaration.only.support.for.some.nested.declarations=Move declaration is not supported for nested declarations other than nested classes, functions and properties
text.move.declaration.only.support.for.single.elements=Move declaration is not supported for multiple nested declarations
text.move.declaration.no.support.for.multi.file=Moving declarations from different files is not supported
text.move.declaration.supports.only.top.levels.and.nested.classes=Move declaration is only supported for top-level declarations and nested classes
@@ -1071,6 +1071,56 @@ public abstract class MoveTestGenerated extends AbstractMoveTest {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToObject/moveToObject.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/deepInnerToTopLevel/deepInnerToTopLevel.test")
public void testKotlin_moveMethod_moveToTopLevel_deepInnerToTopLevel_DeepInnerToTopLevel() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/deepInnerToTopLevel/deepInnerToTopLevel.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/dropEmptyCompanion/dropEmptyCompanion.test")
public void testKotlin_moveMethod_moveToTopLevel_dropEmptyCompanion_DropEmptyCompanion() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/dropEmptyCompanion/dropEmptyCompanion.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/externalFunctionUsageContextReceiver/externalFunctionUsageContextReceiver.test")
public void testKotlin_moveMethod_moveToTopLevel_externalFunctionUsageContextReceiver_ExternalFunctionUsageContextReceiver() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/externalFunctionUsageContextReceiver/externalFunctionUsageContextReceiver.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/externalFunctionUsageFromJava/externalFunctionUsageFromJava.test")
public void testKotlin_moveMethod_moveToTopLevel_externalFunctionUsageFromJava_ExternalFunctionUsageFromJava() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/externalFunctionUsageFromJava/externalFunctionUsageFromJava.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/externalFunctionUsage/externalFunctionUsage.test")
public void testKotlin_moveMethod_moveToTopLevel_externalFunctionUsage_ExternalFunctionUsage() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/externalFunctionUsage/externalFunctionUsage.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/implicitReceiver/implicitReceiver.test")
public void testKotlin_moveMethod_moveToTopLevel_implicitReceiver_ImplicitReceiver() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/implicitReceiver/implicitReceiver.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/implicitRefToCompanionObject/implicitRefToCompanionObject.test")
public void testKotlin_moveMethod_moveToTopLevel_implicitRefToCompanionObject_ImplicitRefToCompanionObject() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/implicitRefToCompanionObject/implicitRefToCompanionObject.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/nameClash/nameClash.test")
public void testKotlin_moveMethod_moveToTopLevel_nameClash_NameClash() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/nameClash/nameClash.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/outerInstanceAddParameter/outerInstanceAddParameter.test")
public void testKotlin_moveMethod_moveToTopLevel_outerInstanceAddParameter_OuterInstanceAddParameter() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/outerInstanceAddParameter/outerInstanceAddParameter.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/outerInstanceDontAddParameter/outerInstanceDontAddParameter.test")
public void testKotlin_moveMethod_moveToTopLevel_outerInstanceDontAddParameter_OuterInstanceDontAddParameter() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/outerInstanceDontAddParameter/outerInstanceDontAddParameter.test");
}
@TestMetadata("kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test")
public void testKotlin_moveNestedClass_callableReferences_nestedToAnotherClass_NestedToAnotherClass() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test");
@@ -1215,5 +1265,15 @@ public abstract class MoveTestGenerated extends AbstractMoveTest {
public void testKotlin_moveNestedClass_protectedClass_ProtectedClass() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveNestedClass/protectedClass/protectedClass.test");
}
@TestMetadata("kotlin/moveProperty/moveToTopLevel/moveWithInstanceReference/moveWithInstanceReference.test")
public void testKotlin_moveProperty_moveToTopLevel_moveWithInstanceReference_MoveWithInstanceReference() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveProperty/moveToTopLevel/moveWithInstanceReference/moveWithInstanceReference.test");
}
@TestMetadata("kotlin/moveProperty/moveToTopLevel/moveWithoutInstanceReference/moveWithoutInstanceReference.test")
public void testKotlin_moveProperty_moveToTopLevel_moveWithoutInstanceReference_MoveWithoutInstanceReference() throws Exception {
runTest("testData/refactoring/moveNested/kotlin/moveProperty/moveToTopLevel/moveWithoutInstanceReference/moveWithoutInstanceReference.test");
}
}
}
@@ -0,0 +1,6 @@
// WITH_STDLIB
object A {
}
private fun foo() = 1
@@ -1,8 +1,10 @@
// IGNORE_K1
// "Propagate 'MyExperimentalAPI' opt-in requirement to containing class 'Derived'" "false"
// COMPILER_ARGUMENTS: -opt-in=kotlin.RequiresOptIn
// WITH_STDLIB
// ACTION: Enable a trailing comma by default in the formatter
// ACTION: Go To Super Method
// ACTION: Move to top level
// ACTION: Opt in for 'MyExperimentalAPI' in containing file 'override.kt'
// ACTION: Opt in for 'MyExperimentalAPI' in module 'light_idea_test_case'
// ACTION: Opt in for 'MyExperimentalAPI' on 'foo'
@@ -0,0 +1,6 @@
package bar
fun foo(test: Test.Test2.Test3): Int {
println(this@Test2)
return Test.a + test.b
}
@@ -0,0 +1,10 @@
package bar
class Test {
val a: Int = 5
inner class Test2 {
inner class Test3 {
val b: Int = 5
}
}
}
@@ -0,0 +1,14 @@
package bar
class Test {
val a: Int = 5
inner class Test2 {
inner class Test3 {
val b: Int = 5
fun foo<caret>(): Int {
println(this@Test2)
return a + b
}
}
}
}
@@ -0,0 +1,2 @@
Indirect outer instances won't be extracted: a
Indirect outer instances won't be extracted: this@Test2
@@ -0,0 +1,8 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"outerInstanceParameter": "test",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,7 @@
package bar
class Test {
companion object {
fun foo<caret>(): Int = 5
}
}
@@ -0,0 +1,7 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,5 @@
package bar
fun foo(test: Test, b: Int): Int {
return test.a + b
}
@@ -0,0 +1,9 @@
package bar
class Test {
val a: Int = 5
}
fun outside(test: Test): Int {
return foo(test, 5)
}
@@ -0,0 +1,12 @@
package bar
class Test {
val a: Int = 5
fun foo<caret>(b: Int): Int {
return a + b
}
}
fun outside(test: Test): Int {
return test.foo(5)
}
@@ -0,0 +1,8 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"outerInstanceParameter": "test",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,5 @@
package bar
fun foo(test: Test, b: Int): Int {
return test.a + b
}
@@ -0,0 +1,9 @@
package bar
class Test {
val a: Int = 5
}
fun Test.outside(): Int {
return foo(this, 5)
}
@@ -0,0 +1,12 @@
package bar
class Test {
val a: Int = 5
fun foo<caret>(b: Int): Int {
return a + b
}
}
fun Test.outside(): Int {
return foo(5)
}
@@ -0,0 +1,8 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"outerInstanceParameter": "test",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,7 @@
package bar;
class JavaClass {
public void test() {
new Test().foo(5);
}
}
@@ -0,0 +1,5 @@
package bar
fun foo(test: Test, b: Int): Int {
return test.a + b
}
@@ -0,0 +1,7 @@
package bar;
class JavaClass {
public void test() {
new Test().foo(5);
}
}
@@ -0,0 +1,8 @@
package bar
class Test {
val a: Int = 5
fun foo<caret>(b: Int): Int {
return a + b
}
}
@@ -0,0 +1,8 @@
{
"mainFile": "bar/test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"outerInstanceParameter": "test",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,5 @@
package bar
fun foo(test: Test): Int {
return test.test()
}
@@ -0,0 +1,6 @@
package bar
class Test {
fun test(): Int = 5
}
@@ -0,0 +1,9 @@
package bar
class Test {
fun test(): Int = 5
fun foo<caret>(): Int {
return test()
}
}
@@ -0,0 +1,8 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"outerInstanceParameter": "test",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,6 @@
package bar
fun foo(): Int {
// TODO: The .Companion parts here can be removed after KT-64842 is fixed
return Test.Companion.a + Test.Companion.a
}
@@ -0,0 +1,7 @@
package bar
class Test {
companion object {
val a: Int = 5
}
}
@@ -0,0 +1,11 @@
package bar
class Test {
companion object {
val a: Int = 5
fun foo<caret>(): Int {
// TODO: The .Companion parts here can be removed after KT-64842 is fixed
return a + a
}
}
}
@@ -0,0 +1,7 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,5 @@
package bar
fun foo(test: Test, b: Int): Int {
return test.a + test.a
}
@@ -0,0 +1,9 @@
package bar
class Test {
val a: Int = 5
}
fun foo(test: Test, b: Int): Int {
return 5
}
@@ -0,0 +1,12 @@
package bar
class Test {
val a: Int = 5
fun foo<caret>(b: Int): Int {
return a + a
}
}
fun foo(test: Test, b: Int): Int {
return 5
}
@@ -0,0 +1 @@
Following declarations would clash: to move class test.A.C and destination class test.C declared in scope test
@@ -0,0 +1,8 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"outerInstanceParameter": "test",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,5 @@
package bar
fun foo(test: Test): Int {
return test.a + test.a
}
@@ -0,0 +1,8 @@
package bar
class Test {
val a: Int = 5
fun foo<caret>(): Int {
return a + a
}
}
@@ -0,0 +1,8 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"outerInstanceParameter": "test",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,5 @@
package bar
fun foo(): Int {
return Test.a + Test.a
}
@@ -0,0 +1,8 @@
package bar
class Test {
val a: Int
fun foo<caret>(): Int {
return a + a
}
}
@@ -0,0 +1,7 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,6 @@
package bar
class Test {
val b: Int = 5
val foo<caret>: Int = b + b
}
@@ -0,0 +1 @@
Usages of outer class instance inside of property 'foo' won't be processed
@@ -0,0 +1,7 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -0,0 +1,5 @@
package bar
class Test {
val foo<caret>: Int = 5
}
@@ -0,0 +1,7 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_DECLARATION",
"withRuntime": "true",
"enabledInK1": "false",
"enabledInK2": "true"
}
@@ -46,7 +46,7 @@ abstract class K2BaseMoveDeclarationsRefactoringProcessor<T : DeclarationsMoveDe
}
}
protected open fun collectConflicts(moveDescriptor: K2MoveDescriptor, allUsages: MutableSet<UsageInfo>) {}
protected open fun collectConflicts(moveDescriptor: K2MoveDescriptor,allUsages: MutableSet<UsageInfo>) {}
override fun findUsages(): Array<UsageInfo> {
if (!operationDescriptor.searchReferences) return emptyArray()
@@ -97,7 +97,7 @@ abstract class K2BaseMoveDeclarationsRefactoringProcessor<T : DeclarationsMoveDe
}
}
protected val conflicts = MultiMap<PsiElement, String>()
protected val conflicts: MultiMap<PsiElement, String> = MultiMap<PsiElement, String>()
override fun preprocessUsages(refUsages: Ref<Array<UsageInfo>>): Boolean {
val usages = refUsages.get()
@@ -9,6 +9,7 @@ import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.renderer.types.impl.KaTypeRendererForSource
import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.idea.base.analysis.api.utils.shortenReferences
@@ -26,14 +27,35 @@ class K2MoveNestedDeclarationsRefactoringProcessor(
operationDescriptor: K2MoveOperationDescriptor.NestedDeclarations,
) : K2BaseMoveDeclarationsRefactoringProcessor<K2MoveOperationDescriptor.NestedDeclarations>(operationDescriptor) {
private fun findInternalUsages(moveSource: K2MoveSourceDescriptor<*>): List<UsageInfo> {
val classToMove = moveSource.elements.singleOrNull() as? KtClass ?: return emptyList()
return collectOuterInstanceReferences(classToMove)
val declarationToMove = moveSource.elements.singleOrNull() as? KtNamedDeclaration ?: return emptyList()
return collectOuterInstanceReferences(declarationToMove)
}
override fun getUsages(moveDescriptor: K2MoveDescriptor): List<UsageInfo> {
return super.getUsages(moveDescriptor) + findInternalUsages(moveDescriptor.source)
}
private enum class MoveType {
CLASS, PROPERTY, FUNCTION, UNKNOWN
}
private val moveType: MoveType by lazy {
when (operationDescriptor.sourceElements.singleOrNull()) {
is KtClass -> MoveType.CLASS
is KtProperty -> MoveType.PROPERTY
is KtNamedFunction -> MoveType.FUNCTION
else -> MoveType.UNKNOWN
}
}
private val elementToMove = operationDescriptor.sourceElements.single()
private fun willLoseOuterInstanceReference(): Boolean {
// For properties, any outer instance reference is a conflict because we do not process them.
val canReferenceOuterInstance = moveType != MoveType.PROPERTY && operationDescriptor.outerInstanceParameterName != null
// For anything else, it is a conflict if it is contained in a class rather than an object
return !canReferenceOuterInstance && elementToMove.containingClassOrObject !is KtObjectDeclaration
}
override fun collectConflicts(
moveDescriptor: K2MoveDescriptor,
allUsages: MutableSet<UsageInfo>
@@ -55,7 +77,18 @@ class K2MoveNestedDeclarationsRefactoringProcessor(
true
}
is OuterInstanceReferenceUsageInfo -> usage.reportConflictIfAny(conflicts)
is OuterInstanceReferenceUsageInfo -> {
if (moveType == MoveType.PROPERTY) {
// For properties, any outer instance reference is a conflict because we do not process them.
conflicts.putValue(
element,
KotlinBundle.message("usages.of.outer.class.instance.inside.of.property.0.won.t.be.processed", elementToMove.nameAsSafeName.asString())
)
true
} else {
usage.reportConflictIfAny(conflicts)
}
}
else -> false
}
@@ -73,17 +106,17 @@ class K2MoveNestedDeclarationsRefactoringProcessor(
val outerInstanceParameterName = operationDescriptor.outerInstanceParameterName ?: return
val psiFactory = KtPsiFactory(project)
val newOuterInstanceRef = psiFactory.createExpression(outerInstanceParameterName)
val classToMove = moveSource.elements.singleOrNull() as? KtClass
val declarationToMove = moveSource.elements.singleOrNull() as? KtNamedDeclaration
for (usage in usages) {
if (usage is MoveRenameUsageInfo) {
val referencedNestedClass = usage.referencedElement?.unwrapped as? KtClassOrObject
if (referencedNestedClass == classToMove) {
val outerClass = referencedNestedClass?.containingClassOrObject
val referencedNestedDeclaration = usage.referencedElement?.unwrapped as? KtNamedDeclaration
if (declarationToMove != null && referencedNestedDeclaration == declarationToMove) {
val outerClass = referencedNestedDeclaration.containingClassOrObject
val lightOuterClass = outerClass?.toLightClass()
if (lightOuterClass != null) {
MoveInnerClassUsagesHandler.EP_NAME
.forLanguage(usage.element?.language ?: return)
.forLanguage(usage.element?.language ?: continue)
?.correctInnerClassUsage(usage, lightOuterClass, outerInstanceParameterName)
}
}
@@ -106,26 +139,49 @@ class K2MoveNestedDeclarationsRefactoringProcessor(
moveDescriptor: K2MoveDescriptor,
originalDeclaration: KtNamedDeclaration
) {
val containingClass = originalDeclaration.containingClassOrObject
val psiFactory = KtPsiFactory(originalDeclaration.project)
val outerInstanceParameterName = operationDescriptor.outerInstanceParameterName
with(originalDeclaration) {
operationDescriptor.newClassName?.let { setName(it) }
if (this is KtClass) {
// TODO: Potentially allow for moving into classes
if (hasModifier(KtTokens.INNER_KEYWORD)) removeModifier(KtTokens.INNER_KEYWORD)
if (hasModifier(KtTokens.PROTECTED_KEYWORD)) removeModifier(KtTokens.PROTECTED_KEYWORD)
when (this) {
is KtClass -> {
// TODO: Potentially allow for moving into classes
if (hasModifier(KtTokens.INNER_KEYWORD)) removeModifier(KtTokens.INNER_KEYWORD)
if (hasModifier(KtTokens.PROTECTED_KEYWORD)) removeModifier(KtTokens.PROTECTED_KEYWORD)
operationDescriptor.outerInstanceParameterName?.let { outerInstanceParameterName ->
val containingClass = containingClassOrObject ?: return
analyze(originalDeclaration) {
// Use the fully qualified type because we have not moved it to the new location yet
val type = containingClass.classSymbol?.defaultType?.render(
renderer = KaTypeRendererForSource.WITH_QUALIFIED_NAMES,
position = Variance.INVARIANT
) ?: return
val parameter = KtPsiFactory(project).createParameter("private val $outerInstanceParameterName: $type")
createPrimaryConstructorParameterListIfAbsent().addParameter(parameter)
if (outerInstanceParameterName != null) {
val containingClass = containingClassOrObject ?: return
analyze(originalDeclaration) {
// Use the fully qualified type because we have not moved it to the new location yet
val type = containingClass.classSymbol?.defaultType?.render(
renderer = KaTypeRendererForSource.WITH_QUALIFIED_NAMES,
position = Variance.INVARIANT
) ?: return
val parameter = KtPsiFactory(project).createParameter("private val $outerInstanceParameterName: $type")
createPrimaryConstructorParameterListIfAbsent().addParameter(parameter)
}
}
}
is KtNamedFunction -> {
if (outerInstanceParameterName != null) {
val outerInstanceType = analyze(originalDeclaration) {
val type = (containingClass?.symbol as? KaClassSymbol)?.defaultType ?: return@analyze null
type.render(KaTypeRendererForSource.WITH_QUALIFIED_NAMES, Variance.INVARIANT)
}
if (outerInstanceType == null) return
valueParameterList?.addParameterBefore(
psiFactory.createParameter(
"${outerInstanceParameterName}: $outerInstanceType"
),
valueParameterList?.parameters?.firstOrNull()
)
}
}
is KtProperty -> {
}
}
}
}
@@ -136,9 +192,16 @@ class K2MoveNestedDeclarationsRefactoringProcessor(
newDeclaration: PsiElement
) {
val outerInstanceParameterName = operationDescriptor.outerInstanceParameterName ?: return
if (originalDeclaration !is KtClass || newDeclaration !is KtClass) return
val primaryConstructor = newDeclaration.primaryConstructor ?: return
val addedParameter = primaryConstructor.valueParameters.firstOrNull { it.name == outerInstanceParameterName } ?: return
shortenReferences(addedParameter)
when (newDeclaration) {
is KtClass -> {
val primaryConstructor = newDeclaration.primaryConstructor ?: return
val addedParameter = primaryConstructor.valueParameters.firstOrNull { it.name == outerInstanceParameterName } ?: return
shortenReferences(addedParameter)
}
is KtNamedFunction -> {
val addedParameter = newDeclaration.valueParameterList?.parameters?.firstOrNull() ?: return
shortenReferences(addedParameter)
}
}
}
}
@@ -190,7 +190,7 @@ internal fun checkNameClashConflicts(
"text.declarations.clash.move.0.destination.1.declared.in.scope.2",
renderedDeclaration,
conflictingSymbol.renderForConflict(),
conflictingScope.renderForConflict(),
conflictingScope.renderForConflict().ifBlank { "default" },
)
conflicts.putValue(declaration, message)
}
@@ -229,7 +229,7 @@ sealed class K2MoveModel {
override val target: K2MoveTargetModel.File,
override val inSourceRoot: Boolean,
outerClassName: String?,
internal val isInnerClass: Boolean,
internal val needsInstanceReference: Boolean,
override val moveCallBack: MoveCallback? = null
) : K2MoveModel() {
@@ -237,11 +237,11 @@ sealed class K2MoveModel {
return super.isValidRefactoring() && isValidDeclarationsRefactoring(source, target)
}
var passOuterClass: Boolean = isInnerClass
private var passOuterClass: Boolean = needsInstanceReference
var outerClassInstanceParameterName: String = outerClassName?.decapitalizeAsciiOnly() ?: "instance"
override fun buildPanel(panel: Panel) = with(panel) {
if (isInnerClass) {
if (needsInstanceReference) {
lateinit var selected: ComponentPredicate
row {
selected = checkBox(KotlinBundle.message("pass.outer.class.instance.as.parameter"))
@@ -270,7 +270,7 @@ sealed class K2MoveModel {
searchReferences = searchReferences.state,
dirStructureMatchesPkg = true,
newClassName = null,
outerInstanceParameterName = outerClassInstanceParameterName,
outerInstanceParameterName = outerClassInstanceParameterName.takeIf { needsInstanceReference },
moveCallBack = moveCallBack
)
}
@@ -323,15 +323,16 @@ sealed class K2MoveModel {
if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, elements.toList(), true)) return null
if (elementsToMove.any { it.parentOfType<KtNamedDeclaration>(withSelf = false) != null }) {
if (elementsToMove.size != 1) {
val singleElementToMove = elementsToMove.singleOrNull()
if (singleElementToMove == null) {
val message = RefactoringBundle.getCannotRefactorMessage(
KotlinBundle.message("text.move.declaration.only.support.for.single.elements")
)
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
return null
} else if (elementsToMove.single() !is KtClassOrObject) {
} else if (singleElementToMove !is KtClassOrObject && singleElementToMove !is KtNamedFunction && singleElementToMove !is KtProperty) {
val message = RefactoringBundle.getCannotRefactorMessage(
KotlinBundle.message("text.move.declaration.only.support.for.nested.classes")
KotlinBundle.message("text.move.declaration.only.support.for.some.nested.declarations")
)
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
return null
@@ -409,18 +410,20 @@ sealed class K2MoveModel {
val psiDirectory = containingFile.containingDirectory ?: error("No directory found")
K2MoveTargetModel.File(sourceFileName(), containingFile.packageFqName, psiDirectory)
}
val singleClassToMove = (elementsToMove.singleOrNull() as? KtClassOrObject)
val singleDeclarationToMove = (elementsToMove.singleOrNull() as? KtNamedDeclaration)
.takeIf { it !is KtObjectDeclaration || !it.isCompanion() }
val outerClassName = (singleClassToMove?.parent?.parent as? KtClassOrObject?)?.name
val outerClassName = (singleDeclarationToMove?.parent?.parent as? KtClassOrObject?)?.name
if (singleClassToMove?.containingClassOrObject != null) {
if (singleDeclarationToMove?.containingClassOrObject != null) {
val needsInstanceReference = (singleDeclarationToMove is KtClass && singleDeclarationToMove.isInner()) ||
(singleDeclarationToMove is KtNamedFunction && singleDeclarationToMove.containingClassOrObject !is KtObjectDeclaration)
NestedDeclarations(
project = project,
source = source,
target = target,
inSourceRoot = inSourceRoot,
isInnerClass = singleClassToMove is KtClass && singleClassToMove.isInner(),
outerClassName = outerClassName,
needsInstanceReference = needsInstanceReference,
outerClassName = outerClassName.takeIf { needsInstanceReference },
moveCallBack = moveCallBack
)
} else {
@@ -21,8 +21,8 @@ import org.jetbrains.kotlin.idea.k2.refactoring.move.processor.K2MoveNestedDecla
import org.jetbrains.kotlin.idea.refactoring.runRefactoringTest
import org.jetbrains.kotlin.idea.util.sourceRoot
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
abstract class AbstractK2MoveNestedTest : AbstractMultifileMoveRefactoringTest() {
@@ -37,9 +37,9 @@ internal object K2MoveNestedRefactoringAction : KotlinMoveRefactoringAction {
val project = mainFile.project
val type = config.getString("type")
when (type) {
"MOVE_KOTLIN_NESTED_CLASS" -> {
"MOVE_KOTLIN_NESTED_CLASS", "MOVE_KOTLIN_NESTED_DECLARATION" -> {
val project = mainFile.project
val elementToMove = elementsAtCaret.single().getNonStrictParentOfType<KtClassOrObject>()!!
val elementToMove = elementsAtCaret.single().getNonStrictParentOfType<KtNamedDeclaration>()!!
val fileName = (elementToMove.name!!) + ".kt"
val targetPackageFqName = config.getNullableString("targetPackage")?.let {
FqName(it)
@@ -11,9 +11,7 @@ import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
import org.jetbrains.kotlin.idea.k2.refactoring.move.ui.K2MoveModel
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.*
class K2MoveModelTest : KotlinLightCodeInsightFixtureTestCase() {
override val pluginMode: KotlinPluginMode
@@ -336,6 +334,23 @@ class K2MoveModelTest : KotlinLightCodeInsightFixtureTestCase() {
}
}
fun `test move multiple nested class should fail`() {
myFixture.configureByText(KotlinFileType.INSTANCE, """
package foo
class Outer<caret> {
class Foo { }
class Bar { }
}
""".trimIndent())
val outerClass = myFixture.elementAtCaret as KtClass
val nestedClasses = outerClass.declarations.filterIsInstance<KtClass>()
assertEquals(2, nestedClasses.size)
assertThrows(RefactoringErrorHintException::class.java) {
K2MoveModel.create(nestedClasses.toTypedArray(), null)
}
}
fun `test move nested class`() {
myFixture.configureByText(KotlinFileType.INSTANCE, """
package foo
@@ -354,8 +369,7 @@ class K2MoveModelTest : KotlinLightCodeInsightFixtureTestCase() {
assert(sourceElement is KtClass && sourceElement.name == "Bar")
val targetElement = moveDeclarationsModel.target.pkgName
assertEquals("foo", targetElement.asString())
assertEquals(false, moveDeclarationsModel.passOuterClass)
assertEquals(false, moveDeclarationsModel.isInnerClass)
assertEquals(false, moveDeclarationsModel.needsInstanceReference)
}
fun `test move nested inner class`() {
@@ -376,26 +390,71 @@ class K2MoveModelTest : KotlinLightCodeInsightFixtureTestCase() {
assert(sourceElement is KtClass && sourceElement.name == "Bar")
val targetElement = moveDeclarationsModel.target.pkgName
assertEquals("foo", targetElement.asString())
assertEquals(true, moveDeclarationsModel.passOuterClass)
assertEquals("outerFoo", moveDeclarationsModel.outerClassInstanceParameterName)
assertEquals(true, moveDeclarationsModel.isInnerClass)
assertEquals(true, moveDeclarationsModel.needsInstanceReference)
}
fun `test move instance method should fail`() {
fun `test move instance method`() {
myFixture.configureByText(KotlinFileType.INSTANCE, """
package foo
class Foo {
class OuterFoo {
fun fo<caret>o() { }
}
""".trimIndent())
val instanceMethod = myFixture.elementAtCaret as KtNamedDeclaration
val moveModel = K2MoveModel.create(arrayOf(instanceMethod), null)
assertInstanceOf<K2MoveModel.NestedDeclarations>(moveModel)
assertTrue(moveModel!!.isValidRefactoring())
val moveDeclarationsModel = moveModel as K2MoveModel.NestedDeclarations
assertSize(1, moveDeclarationsModel.source.elements)
val sourceElement = moveDeclarationsModel.source.elements.firstOrNull()
assert(sourceElement is KtFunction && sourceElement.name == "foo")
val targetElement = moveDeclarationsModel.target.pkgName
assertEquals("foo", targetElement.asString())
assertEquals("outerFoo", moveDeclarationsModel.outerClassInstanceParameterName)
assertEquals(true, moveDeclarationsModel.needsInstanceReference)
}
fun `test move multiple instance methods should fail`() {
myFixture.configureByText(KotlinFileType.INSTANCE, """
package foo
class Outer<caret> {
fun foo() {}
fun bar() {}
}
""".trimIndent())
val outerClass = myFixture.elementAtCaret as KtClass
val nestedFunctions = outerClass.declarations.filterIsInstance<KtFunction>()
assertEquals(2, nestedFunctions.size)
assertThrows(RefactoringErrorHintException::class.java) {
K2MoveModel.create(arrayOf(instanceMethod), null)
K2MoveModel.create(nestedFunctions.toTypedArray(), null)
}
}
fun `test move companion object method should fail`() {
fun `test move object method`() {
myFixture.configureByText(KotlinFileType.INSTANCE, """
package foo
object OuterFoo {
fun fo<caret>o() { }
}
""".trimIndent())
val instanceMethod = myFixture.elementAtCaret as KtNamedDeclaration
val moveModel = K2MoveModel.create(arrayOf(instanceMethod), null)
assertInstanceOf<K2MoveModel.NestedDeclarations>(moveModel)
assertTrue(moveModel!!.isValidRefactoring())
val moveDeclarationsModel = moveModel as K2MoveModel.NestedDeclarations
assertSize(1, moveDeclarationsModel.source.elements)
val sourceElement = moveDeclarationsModel.source.elements.firstOrNull()
assert(sourceElement is KtFunction && sourceElement.name == "foo")
val targetElement = moveDeclarationsModel.target.pkgName
assertEquals("foo", targetElement.asString())
assertEquals(false, moveDeclarationsModel.needsInstanceReference)
}
fun `test move companion object method`() {
myFixture.configureByText(KotlinFileType.INSTANCE, """
package foo
@@ -405,9 +464,54 @@ class K2MoveModelTest : KotlinLightCodeInsightFixtureTestCase() {
}
}
""".trimIndent())
val companionObjectMethod = myFixture.elementAtCaret as KtNamedDeclaration
val instanceMethod = myFixture.elementAtCaret as KtNamedDeclaration
val moveModel = K2MoveModel.create(arrayOf(instanceMethod), null)
assertInstanceOf<K2MoveModel.NestedDeclarations>(moveModel)
assertTrue(moveModel!!.isValidRefactoring())
val moveDeclarationsModel = moveModel as K2MoveModel.NestedDeclarations
assertSize(1, moveDeclarationsModel.source.elements)
val sourceElement = moveDeclarationsModel.source.elements.firstOrNull()
assert(sourceElement is KtFunction && sourceElement.name == "foo")
val targetElement = moveDeclarationsModel.target.pkgName
assertEquals("foo", targetElement.asString())
assertEquals(false, moveDeclarationsModel.needsInstanceReference)
}
fun `test move member property`() {
myFixture.configureByText(KotlinFileType.INSTANCE, """
package foo
class Foo {
val foo<caret> = 5
}
""".trimIndent())
val instanceProperty = myFixture.elementAtCaret as KtNamedDeclaration
val moveModel = K2MoveModel.create(arrayOf(instanceProperty), null)
assertInstanceOf<K2MoveModel.NestedDeclarations>(moveModel)
assertTrue(moveModel!!.isValidRefactoring())
val moveDeclarationsModel = moveModel as K2MoveModel.NestedDeclarations
assertSize(1, moveDeclarationsModel.source.elements)
val sourceElement = moveDeclarationsModel.source.elements.firstOrNull()
assert(sourceElement is KtProperty && sourceElement.name == "foo")
val targetElement = moveDeclarationsModel.target.pkgName
assertEquals("foo", targetElement.asString())
assertEquals(false, moveDeclarationsModel.needsInstanceReference)
}
fun `test move multiple member properties should fal`() {
myFixture.configureByText(KotlinFileType.INSTANCE, """
package foo
class Outer<caret> {
val foo = 5
val bar = 5
}
""".trimIndent())
val outerClass = myFixture.elementAtCaret as KtClass
val nestedProperties = outerClass.declarations.filterIsInstance<KtProperty>()
assertEquals(2, nestedProperties.size)
assertThrows(RefactoringErrorHintException::class.java) {
K2MoveModel.create(arrayOf(companionObjectMethod), null)
K2MoveModel.create(nestedProperties.toTypedArray(), null)
}
}
@@ -195,6 +195,56 @@ public class K2MoveNestedTestGenerated extends AbstractK2MoveNestedTest {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToObject/moveToObject.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/deepInnerToTopLevel/deepInnerToTopLevel.test")
public void testKotlin_moveMethod_moveToTopLevel_deepInnerToTopLevel_DeepInnerToTopLevel() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/deepInnerToTopLevel/deepInnerToTopLevel.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/dropEmptyCompanion/dropEmptyCompanion.test")
public void testKotlin_moveMethod_moveToTopLevel_dropEmptyCompanion_DropEmptyCompanion() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/dropEmptyCompanion/dropEmptyCompanion.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/externalFunctionUsageContextReceiver/externalFunctionUsageContextReceiver.test")
public void testKotlin_moveMethod_moveToTopLevel_externalFunctionUsageContextReceiver_ExternalFunctionUsageContextReceiver() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/externalFunctionUsageContextReceiver/externalFunctionUsageContextReceiver.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/externalFunctionUsageFromJava/externalFunctionUsageFromJava.test")
public void testKotlin_moveMethod_moveToTopLevel_externalFunctionUsageFromJava_ExternalFunctionUsageFromJava() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/externalFunctionUsageFromJava/externalFunctionUsageFromJava.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/externalFunctionUsage/externalFunctionUsage.test")
public void testKotlin_moveMethod_moveToTopLevel_externalFunctionUsage_ExternalFunctionUsage() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/externalFunctionUsage/externalFunctionUsage.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/implicitReceiver/implicitReceiver.test")
public void testKotlin_moveMethod_moveToTopLevel_implicitReceiver_ImplicitReceiver() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/implicitReceiver/implicitReceiver.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/implicitRefToCompanionObject/implicitRefToCompanionObject.test")
public void testKotlin_moveMethod_moveToTopLevel_implicitRefToCompanionObject_ImplicitRefToCompanionObject() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/implicitRefToCompanionObject/implicitRefToCompanionObject.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/nameClash/nameClash.test")
public void testKotlin_moveMethod_moveToTopLevel_nameClash_NameClash() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/nameClash/nameClash.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/outerInstanceAddParameter/outerInstanceAddParameter.test")
public void testKotlin_moveMethod_moveToTopLevel_outerInstanceAddParameter_OuterInstanceAddParameter() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/outerInstanceAddParameter/outerInstanceAddParameter.test");
}
@TestMetadata("kotlin/moveMethod/moveToTopLevel/outerInstanceDontAddParameter/outerInstanceDontAddParameter.test")
public void testKotlin_moveMethod_moveToTopLevel_outerInstanceDontAddParameter_OuterInstanceDontAddParameter() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveMethod/moveToTopLevel/outerInstanceDontAddParameter/outerInstanceDontAddParameter.test");
}
@TestMetadata("kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test")
public void testKotlin_moveNestedClass_callableReferences_nestedToAnotherClass_NestedToAnotherClass() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test");
@@ -339,4 +389,14 @@ public class K2MoveNestedTestGenerated extends AbstractK2MoveNestedTest {
public void testKotlin_moveNestedClass_protectedClass_ProtectedClass() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveNestedClass/protectedClass/protectedClass.test");
}
@TestMetadata("kotlin/moveProperty/moveToTopLevel/moveWithInstanceReference/moveWithInstanceReference.test")
public void testKotlin_moveProperty_moveToTopLevel_moveWithInstanceReference_MoveWithInstanceReference() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveProperty/moveToTopLevel/moveWithInstanceReference/moveWithInstanceReference.test");
}
@TestMetadata("kotlin/moveProperty/moveToTopLevel/moveWithoutInstanceReference/moveWithoutInstanceReference.test")
public void testKotlin_moveProperty_moveToTopLevel_moveWithoutInstanceReference_MoveWithoutInstanceReference() throws Exception {
runTest("../../idea/tests/testData/refactoring/moveNested/kotlin/moveProperty/moveToTopLevel/moveWithoutInstanceReference/moveWithoutInstanceReference.test");
}
}
@@ -156,7 +156,7 @@ internal fun MutableTWorkspace.generateK2IntentionTests() {
//model("${idea}intentions/loopToCallChain/filter", pattern = pattern, isIgnored = true)
//model("${idea}intentions/loopToCallChain/introduceIndex", pattern = pattern, isIgnored = true)
//model("${idea}intentions/loopToCallChain/indexOf", pattern = pattern, isIgnored = true)
model("${idea}intentions/moveMemberToTopLevel", pattern = pattern, isIgnored = true)
model("${idea}intentions/moveMemberToTopLevel", pattern = pattern)
model("${idea}intentions/anonymousFunctionToLambda", pattern = pattern)
model("${idea}intentions/copyConcatenatedStringToClipboard", pattern = pattern, isIgnored = true)
model("${idea}intentions/inlayHints", pattern = pattern, isIgnored = true)