[kotlin] Added K2 intention to move members to top level

^KTIJ-32001 fixed

GitOrigin-RevId: 550fb97d92e32144bb2ab4a6a7e1461f956e0e56
This commit is contained in:
Frederik Haselmeier
2024-11-26 11:10:16 +00:00
committed by intellij-monorepo-bot
parent e91c6650db
commit 62ebbe5db8
14 changed files with 198 additions and 0 deletions
@@ -436,5 +436,12 @@
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
<intentionAction>
<language>kotlin</language>
<className>org.jetbrains.kotlin.idea.k2.codeinsight.intentions.MoveMemberToTopLevelIntention</className>
<bundleName>messages.KotlinBundle</bundleName>
<categoryKey>group.names.kotlin</categoryKey>
</intentionAction>
</extensions>
</idea-plugin>
@@ -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<KtNamedDeclaration>(
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()
}
}
@@ -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")
@@ -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");
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.k2.codeinsight.intentions.MoveMemberToTopLevelIntention
@@ -0,0 +1,6 @@
// WITH_STDLIB
object A {
}
val foo: Int = 1
@@ -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 <caret>f1(n: Int) {}
}
@@ -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 <caret>f1(n: Int) {}
}
fun f1(n: Int) {}
@@ -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 {
@@ -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 <caret>f1(n: Int) {}
}
fun f1(n: Int) {}
@@ -1,4 +1,5 @@
// SHOULD_FAIL_WITH: Package 'default' already contains property f1
// IGNORE_K2
object Test {
val <caret>f1 = 1
}
@@ -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 <caret>f1 = 1
}
val f1 = 1
@@ -1,4 +1,5 @@
// RUNTIME_WITH_SCRIPT_RUNTIME
// IGNORE_K2
object Foo {
fun <caret>foo() = 1
val a = 1
@@ -1,4 +1,5 @@
// RUNTIME_WITH_SCRIPT_RUNTIME
// IGNORE_K2
object Foo {
val a = 1
}