Convert Member to Extension Intention: Drop open/final modifiers, add imports when necessary, transform Java-site calls
#KT-8876 Fixed
This commit is contained in:
+47
-1
@@ -20,14 +20,23 @@ import com.intellij.codeInsight.intention.LowPriorityAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ScrollType
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiMethodCallExpression
|
||||
import com.intellij.psi.PsiReferenceExpression
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.util.SmartList
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
|
||||
import org.jetbrains.kotlin.idea.intentions.setReceiverType
|
||||
import org.jetbrains.kotlin.idea.quickfix.moveCaret
|
||||
import org.jetbrains.kotlin.idea.quickfix.unblockDocument
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
class ConvertMemberToExtensionIntention : SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Convert member to extension"), LowPriorityAction {
|
||||
override fun applicabilityRange(element: KtCallableDeclaration): TextRange? {
|
||||
@@ -49,11 +58,25 @@ class ConvertMemberToExtensionIntention : SelfTargetingRangeIntention<KtCallable
|
||||
val containingClass = descriptor.containingDeclaration as ClassDescriptor
|
||||
|
||||
val file = element.getContainingKtFile()
|
||||
val project = file.project
|
||||
val outermostParent = KtPsiUtil.getOutermostParent(element, file, false)
|
||||
|
||||
val ktFilesToAddImports = SmartList<KtFile>()
|
||||
val javaCallsToFix = SmartList<PsiMethodCallExpression>()
|
||||
for (ref in ReferencesSearch.search(element)) {
|
||||
when (ref) {
|
||||
is KtReference -> {
|
||||
val refFile = ref.element.getContainingKtFile()
|
||||
if (refFile != file) {
|
||||
ktFilesToAddImports.add(refFile)
|
||||
}
|
||||
}
|
||||
is PsiReferenceExpression -> javaCallsToFix.addIfNotNull(ref.parent as? PsiMethodCallExpression)
|
||||
}
|
||||
}
|
||||
|
||||
val typeParameterList = newTypeParameterList(element)
|
||||
|
||||
val project = element.project
|
||||
val psiFactory = KtPsiFactory(element)
|
||||
|
||||
val extension = file.addAfter(element, outermostParent) as KtCallableDeclaration
|
||||
@@ -75,6 +98,8 @@ class ConvertMemberToExtensionIntention : SelfTargetingRangeIntention<KtCallable
|
||||
|
||||
extension.modifierList?.getModifier(KtTokens.PROTECTED_KEYWORD)?.delete()
|
||||
extension.modifierList?.getModifier(KtTokens.ABSTRACT_KEYWORD)?.delete()
|
||||
extension.modifierList?.getModifier(KtTokens.OPEN_KEYWORD)?.delete()
|
||||
extension.modifierList?.getModifier(KtTokens.FINAL_KEYWORD)?.delete()
|
||||
|
||||
var bodyToSelect: KtExpression? = null
|
||||
|
||||
@@ -125,6 +150,27 @@ class ConvertMemberToExtensionIntention : SelfTargetingRangeIntention<KtCallable
|
||||
}
|
||||
}
|
||||
|
||||
if (ktFilesToAddImports.isNotEmpty()) {
|
||||
val newDescriptor = extension.resolveToDescriptor()
|
||||
val importInsertHelper = ImportInsertHelper.getInstance(project)
|
||||
for (ktFileToAddImport in ktFilesToAddImports) {
|
||||
importInsertHelper.importDescriptor(ktFileToAddImport, newDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
if (javaCallsToFix.isNotEmpty()) {
|
||||
val lightMethod = extension.toLightMethods().first()
|
||||
for (javaCallToFix in javaCallsToFix) {
|
||||
javaCallToFix.methodExpression.qualifierExpression?.let {
|
||||
val argumentList = javaCallToFix.argumentList
|
||||
argumentList.addBefore(it, argumentList.expressions.firstOrNull())
|
||||
}
|
||||
|
||||
val newRef = javaCallToFix.methodExpression.bindToElement(lightMethod)
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(newRef)
|
||||
}
|
||||
}
|
||||
|
||||
editor?.apply {
|
||||
unblockDocument()
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
class Owner {
|
||||
final fun <caret>f() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
class Owner {
|
||||
}
|
||||
|
||||
fun Owner.f() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
open class Owner {
|
||||
open fun <caret>f() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
open class Owner {
|
||||
}
|
||||
|
||||
fun Owner.f() {
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"mainFile": "x/test.kt",
|
||||
"intentionClass": "org.jetbrains.kotlin.idea.intentions.declarations.ConvertMemberToExtensionIntention",
|
||||
"withRuntime": "true"
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package x
|
||||
|
||||
interface A {
|
||||
}
|
||||
|
||||
fun A.foo(n: Int) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package y;
|
||||
|
||||
import x.A;
|
||||
import x.TestKt;
|
||||
|
||||
class J {
|
||||
static void foo(A a) {
|
||||
TestKt.foo(a, 1);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package y
|
||||
|
||||
import x.A
|
||||
import x.foo
|
||||
|
||||
fun bar(a: A) {
|
||||
a.foo(1)
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package x
|
||||
|
||||
interface A {
|
||||
fun <caret>foo(n: Int)
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package y;
|
||||
|
||||
import x.A;
|
||||
|
||||
class J {
|
||||
static void foo(A a) {
|
||||
a.foo(1);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package y
|
||||
|
||||
import x.A
|
||||
|
||||
fun bar(a: A) {
|
||||
a.foo(1)
|
||||
}
|
||||
@@ -4741,6 +4741,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("final.kt")
|
||||
public void testFinal() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/final.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("funcitonNoName.kt")
|
||||
public void testFuncitonNoName() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/funcitonNoName.kt");
|
||||
@@ -4825,6 +4831,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("open.kt")
|
||||
public void testOpen() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/open.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("outsideFunction.kt")
|
||||
public void testOutsideFunction() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/outsideFunction.kt");
|
||||
|
||||
@@ -35,6 +35,12 @@ public class MultiFileIntentionTestGenerated extends AbstractMultiFileIntentionT
|
||||
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/multiFileIntentions"), Pattern.compile("^(.+)\\.test$"));
|
||||
}
|
||||
|
||||
@TestMetadata("convertMemberToExtension/addImports/addImports.test")
|
||||
public void testConvertMemberToExtension_addImports_AddImports() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/convertMemberToExtension/addImports/addImports.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("implementAbstractMember/implementFunctionInJava/implementAllInJava.test")
|
||||
public void testImplementAbstractMember_implementFunctionInJava_ImplementAllInJava() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/implementAllInJava.test");
|
||||
|
||||
Reference in New Issue
Block a user