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.Editor
|
||||||
import com.intellij.openapi.editor.ScrollType
|
import com.intellij.openapi.editor.ScrollType
|
||||||
import com.intellij.openapi.util.TextRange
|
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.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
|
import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
|
||||||
import org.jetbrains.kotlin.idea.intentions.setReceiverType
|
import org.jetbrains.kotlin.idea.intentions.setReceiverType
|
||||||
import org.jetbrains.kotlin.idea.quickfix.moveCaret
|
import org.jetbrains.kotlin.idea.quickfix.moveCaret
|
||||||
import org.jetbrains.kotlin.idea.quickfix.unblockDocument
|
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.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
class ConvertMemberToExtensionIntention : SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Convert member to extension"), LowPriorityAction {
|
class ConvertMemberToExtensionIntention : SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Convert member to extension"), LowPriorityAction {
|
||||||
override fun applicabilityRange(element: KtCallableDeclaration): TextRange? {
|
override fun applicabilityRange(element: KtCallableDeclaration): TextRange? {
|
||||||
@@ -49,11 +58,25 @@ class ConvertMemberToExtensionIntention : SelfTargetingRangeIntention<KtCallable
|
|||||||
val containingClass = descriptor.containingDeclaration as ClassDescriptor
|
val containingClass = descriptor.containingDeclaration as ClassDescriptor
|
||||||
|
|
||||||
val file = element.getContainingKtFile()
|
val file = element.getContainingKtFile()
|
||||||
|
val project = file.project
|
||||||
val outermostParent = KtPsiUtil.getOutermostParent(element, file, false)
|
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 typeParameterList = newTypeParameterList(element)
|
||||||
|
|
||||||
val project = element.project
|
|
||||||
val psiFactory = KtPsiFactory(element)
|
val psiFactory = KtPsiFactory(element)
|
||||||
|
|
||||||
val extension = file.addAfter(element, outermostParent) as KtCallableDeclaration
|
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.PROTECTED_KEYWORD)?.delete()
|
||||||
extension.modifierList?.getModifier(KtTokens.ABSTRACT_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
|
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 {
|
editor?.apply {
|
||||||
unblockDocument()
|
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);
|
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")
|
@TestMetadata("funcitonNoName.kt")
|
||||||
public void testFuncitonNoName() throws Exception {
|
public void testFuncitonNoName() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/funcitonNoName.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/funcitonNoName.kt");
|
||||||
@@ -4825,6 +4831,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("outsideFunction.kt")
|
||||||
public void testOutsideFunction() throws Exception {
|
public void testOutsideFunction() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/outsideFunction.kt");
|
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$"));
|
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")
|
@TestMetadata("implementAbstractMember/implementFunctionInJava/implementAllInJava.test")
|
||||||
public void testImplementAbstractMember_implementFunctionInJava_ImplementAllInJava() throws Exception {
|
public void testImplementAbstractMember_implementFunctionInJava_ImplementAllInJava() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/implementAllInJava.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/implementAbstractMember/implementFunctionInJava/implementAllInJava.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user