diff --git a/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions.k2.xml b/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions.k2.xml
index 9037daa2d6c9..b197890aa8fb 100644
--- a/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions.k2.xml
+++ b/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions.k2.xml
@@ -436,5 +436,12 @@
group.names.kotlin
+
+ kotlin
+ org.jetbrains.kotlin.idea.k2.codeinsight.intentions.MoveMemberToTopLevelIntention
+ messages.KotlinBundle
+ group.names.kotlin
+
+
diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/MoveMemberToTopLevelIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/MoveMemberToTopLevelIntention.kt
new file mode 100644
index 000000000000..de10bce3fe5c
--- /dev/null
+++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/MoveMemberToTopLevelIntention.kt
@@ -0,0 +1,60 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package org.jetbrains.kotlin.idea.k2.codeinsight.intentions
+
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.util.TextRange
+import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
+import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingRangeIntention
+import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2MoveDescriptor
+import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2MoveOperationDescriptor
+import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2MoveSourceDescriptor
+import org.jetbrains.kotlin.idea.k2.refactoring.move.descriptor.K2MoveTargetDescriptor
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
+import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeAsciiOnly
+
+class MoveMemberToTopLevelIntention : SelfTargetingRangeIntention(
+ elementType = KtNamedDeclaration::class.java,
+ textGetter = KotlinBundle.lazyMessage("move.to.top.level")
+) {
+ override fun applicabilityRange(element: KtNamedDeclaration): TextRange? {
+ if (element !is KtNamedFunction && element !is KtProperty && element !is KtClassOrObject) return null
+ if (element.containingClassOrObject !is KtClassOrObject) return null
+ if (element is KtObjectDeclaration && element.isCompanion()) return null
+ return element.nameIdentifier?.textRange
+ }
+
+ override fun startInWriteAction(): Boolean = false
+
+ override fun applyTo(element: KtNamedDeclaration, editor: Editor?) {
+ val moveDescriptor = K2MoveDescriptor.Declarations(
+ element.project,
+ K2MoveSourceDescriptor.ElementSource(setOf(element)),
+ K2MoveTargetDescriptor.File(element.containingKtFile)
+ )
+
+ val containingClass = element.containingClassOrObject ?: return
+ val instanceName = containingClass.takeIf {
+ it !is KtObjectDeclaration &&
+ (element !is KtClass || element.isInner())
+ && element !is KtProperty
+ }?.name?.decapitalizeAsciiOnly()
+
+ // This intention used to also delete containing objects if they are empty after moving the declaration out, but
+ // this seems rather dangerous and only rarely useful, so it is not enabled in K2.
+ val processor = K2MoveOperationDescriptor.NestedDeclarations(
+ element.project,
+ listOf(moveDescriptor),
+ searchForText = false,
+ searchInComments = false,
+ searchReferences = true,
+ dirStructureMatchesPkg = false,
+ outerInstanceParameterName = instanceName,
+ moveCallBack = { }
+ ).refactoringProcessor()
+
+ // Need to set this for the conflict dialog to be shown
+ processor.setPrepareSuccessfulSwingThreadCallback { }
+ processor.run()
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java
index e327d993920f..7bec08ea89a2 100644
--- a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java
+++ b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java
@@ -10031,7 +10031,84 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest {
}
}
+ @RunWith(JUnit3RunnerWithInners.class)
+ @TestMetadata("../../../idea/tests/testData/intentions/moveMemberToTopLevel")
+ public static class MoveMemberToTopLevel extends AbstractK2IntentionTest {
+ @java.lang.Override
+ @org.jetbrains.annotations.NotNull
+ public final KotlinPluginMode getPluginMode() {
+ return KotlinPluginMode.K2;
+ }
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
+ }
+
+ @TestMetadata("abstractFunction.kt")
+ public void testAbstractFunction() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/abstractFunction.kt");
+ }
+
+ @TestMetadata("abstractProperty.kt")
+ public void testAbstractProperty() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/abstractProperty.kt");
+ }
+
+ @TestMetadata("function.kt")
+ public void testFunction() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/function.kt");
+ }
+
+ @TestMetadata("functionInCompanion.kt")
+ public void testFunctionInCompanion() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/functionInCompanion.kt");
+ }
+
+ @TestMetadata("property.kt")
+ public void testProperty() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/property.kt");
+ }
+
+ @TestMetadata("propertyInCompanion.kt")
+ public void testPropertyInCompanion() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/propertyInCompanion.kt");
+ }
+
+ @TestMetadata("redeclarationConflict.kt")
+ public void testRedeclarationConflict() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt");
+ }
+
+ @TestMetadata("redeclarationConflictK2.kt")
+ public void testRedeclarationConflictK2() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictK2.kt");
+ }
+
+ @TestMetadata("redeclarationConflictWithPackage.kt")
+ public void testRedeclarationConflictWithPackage() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt");
+ }
+
+ @TestMetadata("redeclarationConflictWithPackageK2.kt")
+ public void testRedeclarationConflictWithPackageK2() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackageK2.kt");
+ }
+
+ @TestMetadata("redeclarationPropertyConflict.kt")
+ public void testRedeclarationPropertyConflict() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt");
+ }
+
+ @TestMetadata("redeclarationPropertyConflictK2.kt")
+ public void testRedeclarationPropertyConflictK2() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflictK2.kt");
+ }
+
+ @TestMetadata("script.kts")
+ public void testScript() throws Exception {
+ runTest("../../../idea/tests/testData/intentions/moveMemberToTopLevel/script.kts");
+ }
+ }
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/intentions/anonymousFunctionToLambda")
diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/K1IntentionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/K1IntentionTestGenerated.java
index 156a003276f5..1e50892488a2 100644
--- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/K1IntentionTestGenerated.java
+++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/K1IntentionTestGenerated.java
@@ -15338,16 +15338,31 @@ public abstract class K1IntentionTestGenerated extends AbstractK1IntentionTest {
runTest("testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt");
}
+ @TestMetadata("redeclarationConflictK2.kt")
+ public void testRedeclarationConflictK2() throws Exception {
+ runTest("testData/intentions/moveMemberToTopLevel/redeclarationConflictK2.kt");
+ }
+
@TestMetadata("redeclarationConflictWithPackage.kt")
public void testRedeclarationConflictWithPackage() throws Exception {
runTest("testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt");
}
+ @TestMetadata("redeclarationConflictWithPackageK2.kt")
+ public void testRedeclarationConflictWithPackageK2() throws Exception {
+ runTest("testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackageK2.kt");
+ }
+
@TestMetadata("redeclarationPropertyConflict.kt")
public void testRedeclarationPropertyConflict() throws Exception {
runTest("testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt");
}
+ @TestMetadata("redeclarationPropertyConflictK2.kt")
+ public void testRedeclarationPropertyConflictK2() throws Exception {
+ runTest("testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflictK2.kt");
+ }
+
@TestMetadata("script.kts")
public void testScript() throws Exception {
runTest("testData/intentions/moveMemberToTopLevel/script.kts");
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/.k2Intention b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/.k2Intention
new file mode 100644
index 000000000000..ec0282b1cb65
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/.k2Intention
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.k2.codeinsight.intentions.MoveMemberToTopLevelIntention
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/property.kt.after.k2 b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/property.kt.after.k2
new file mode 100644
index 000000000000..5cc1a88b8182
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/property.kt.after.k2
@@ -0,0 +1,6 @@
+// WITH_STDLIB
+
+object A {
+}
+
+val foo: Int = 1
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt
index 3bf8edd15c3d..8cdd616f060c 100644
--- a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflict.kt
@@ -1,6 +1,7 @@
// SHOULD_FAIL_WITH: Package 'default' already contains function f1(Int)
// AFTER-WARNING: Parameter 'n' is never used
// AFTER-WARNING: Parameter 'n' is never used
+// IGNORE_K2
object Test {
fun f1(n: Int) {}
}
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictK2.kt b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictK2.kt
new file mode 100644
index 000000000000..83a024b4376f
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictK2.kt
@@ -0,0 +1,9 @@
+// SHOULD_FAIL_WITH: Following declarations would clash: to move function 'fun f1(n: Int)' and destination function 'fun f1(n: Int)' declared in scope default
+// AFTER-WARNING: Parameter 'n' is never used
+// AFTER-WARNING: Parameter 'n' is never used
+// IGNORE_K1
+object Test {
+ fun f1(n: Int) {}
+}
+
+fun f1(n: Int) {}
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt
index 4e4e5bbbd041..d02c62eaac16 100644
--- a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackage.kt
@@ -1,6 +1,7 @@
// SHOULD_FAIL_WITH: Package 'foo.bar' already contains function f1(Int)
// AFTER-WARNING: Parameter 'n' is never used
// AFTER-WARNING: Parameter 'n' is never used
+// IGNORE_K2
package foo.bar
object Test {
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackageK2.kt b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackageK2.kt
new file mode 100644
index 000000000000..1cd82fd89b6b
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationConflictWithPackageK2.kt
@@ -0,0 +1,11 @@
+// SHOULD_FAIL_WITH: Following declarations would clash: to move function 'fun f1(n: Int)' and destination function 'fun f1(n: Int)' declared in scope foo.bar
+// AFTER-WARNING: Parameter 'n' is never used
+// AFTER-WARNING: Parameter 'n' is never used
+// IGNORE_K1
+package foo.bar
+
+object Test {
+ fun f1(n: Int) {}
+}
+
+fun f1(n: Int) {}
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt
index d081c8145987..b60934523b19 100644
--- a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflict.kt
@@ -1,4 +1,5 @@
// SHOULD_FAIL_WITH: Package 'default' already contains property f1
+// IGNORE_K2
object Test {
val f1 = 1
}
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflictK2.kt b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflictK2.kt
new file mode 100644
index 000000000000..bb2d3a28a192
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/redeclarationPropertyConflictK2.kt
@@ -0,0 +1,7 @@
+// SHOULD_FAIL_WITH: Following declarations would clash: to move property 'val f1: Int' and destination property 'val f1: Int' declared in scope default
+// IGNORE_K1
+object Test {
+ val f1 = 1
+}
+
+val f1 = 1
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/script.kts b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/script.kts
index 35fba43f0d23..1a05aac73909 100644
--- a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/script.kts
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/script.kts
@@ -1,4 +1,5 @@
// RUNTIME_WITH_SCRIPT_RUNTIME
+// IGNORE_K2
object Foo {
fun foo() = 1
val a = 1
diff --git a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/script.kts.after b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/script.kts.after
index f20263622cd2..d1c0dc3457a3 100644
--- a/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/script.kts.after
+++ b/plugins/kotlin/idea/tests/testData/intentions/moveMemberToTopLevel/script.kts.after
@@ -1,4 +1,5 @@
// RUNTIME_WITH_SCRIPT_RUNTIME
+// IGNORE_K2
object Foo {
val a = 1
}