Property <-> Function Conversion: Add/remove 'get'/'is' prefixes automatically
#KT-8812 Fixed
This commit is contained in:
+25
-8
@@ -45,8 +45,11 @@ import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
import java.util.*
|
||||
@@ -61,6 +64,11 @@ public class ConvertFunctionToPropertyIntention : JetSelfTargetingIntention<JetN
|
||||
): CallableRefactoring<FunctionDescriptor>(project, descriptor, context, getText()) {
|
||||
private val elementsToShorten = ArrayList<JetElement>()
|
||||
|
||||
private val newName: String by lazy {
|
||||
val name = callableDescriptor.name
|
||||
(SyntheticJavaPropertyDescriptor.propertyNameByGetMethodName(name) ?: name).asString()
|
||||
}
|
||||
|
||||
private fun convertFunction(originalFunction: JetNamedFunction, psiFactory: JetPsiFactory) {
|
||||
val function = originalFunction.copy() as JetNamedFunction
|
||||
|
||||
@@ -80,6 +88,7 @@ public class ConvertFunctionToPropertyIntention : JetSelfTargetingIntention<JetN
|
||||
function.addAfter(psiFactory.createParameterList("()"), insertAfter)
|
||||
function.addAfter(propertySample.getGetter()!!.getNamePlaceholder(), insertAfter)
|
||||
}
|
||||
function.setName(newName)
|
||||
|
||||
val property = originalFunction.replace(psiFactory.createProperty(function.getText())) as JetProperty
|
||||
if (needsExplicitType) {
|
||||
@@ -92,6 +101,7 @@ public class ConvertFunctionToPropertyIntention : JetSelfTargetingIntention<JetN
|
||||
val getterName = JvmAbi.getterName(callableDescriptor.getName().asString())
|
||||
val callables = getAffectedCallables(project, descriptorsForChange)
|
||||
val kotlinCalls = ArrayList<JetCallElement>()
|
||||
val kotlinRefsToRename = ArrayList<PsiReference>()
|
||||
val foreignRefs = ArrayList<PsiReference>()
|
||||
for (callable in callables) {
|
||||
if (callable !is PsiNamedElement) continue
|
||||
@@ -128,8 +138,9 @@ public class ConvertFunctionToPropertyIntention : JetSelfTargetingIntention<JetN
|
||||
val usages = ReferencesSearch.search(callable)
|
||||
for (usage in usages) {
|
||||
if (usage is JetSimpleNameReference) {
|
||||
val callElement = usage.expression.getParentOfTypeAndBranch<JetCallElement> { getCalleeExpression() }
|
||||
if (callElement != null) {
|
||||
val expression = usage.expression
|
||||
val callElement = expression.getParentOfTypeAndBranch<JetCallElement> { getCalleeExpression() }
|
||||
if (callElement != null && expression.getStrictParentOfType<JetCallableReferenceExpression>() == null) {
|
||||
if (callElement.getTypeArguments().isNotEmpty()) {
|
||||
conflicts.putValue(
|
||||
callElement,
|
||||
@@ -146,7 +157,9 @@ public class ConvertFunctionToPropertyIntention : JetSelfTargetingIntention<JetN
|
||||
}
|
||||
|
||||
kotlinCalls.add(callElement)
|
||||
continue
|
||||
}
|
||||
else {
|
||||
kotlinRefsToRename.add(usage)
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -158,13 +171,16 @@ public class ConvertFunctionToPropertyIntention : JetSelfTargetingIntention<JetN
|
||||
project.checkConflictsInteractively(conflicts) {
|
||||
project.executeWriteCommand(getText()) {
|
||||
val psiFactory = JetPsiFactory(project)
|
||||
val newGetterName = JvmAbi.getterName(newName)
|
||||
val newRefExpr = psiFactory.createExpression(newName)
|
||||
|
||||
kotlinCalls.forEach { it.replace(it.getCalleeExpression()!!) }
|
||||
foreignRefs.forEach { it.handleElementRename(getterName) }
|
||||
kotlinCalls.forEach { it.replace(newRefExpr) }
|
||||
kotlinRefsToRename.forEach { it.handleElementRename(newName) }
|
||||
foreignRefs.forEach { it.handleElementRename(newGetterName) }
|
||||
callables.forEach {
|
||||
when (it) {
|
||||
is JetNamedFunction -> convertFunction(it, psiFactory)
|
||||
is PsiMethod -> it.setName(getterName)
|
||||
is PsiMethod -> it.setName(newGetterName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,13 +203,14 @@ public class ConvertFunctionToPropertyIntention : JetSelfTargetingIntention<JetN
|
||||
return false
|
||||
}
|
||||
|
||||
val descriptor = element.analyze()[BindingContext.DECLARATION_TO_DESCRIPTOR, element] as? FunctionDescriptor ?: return false
|
||||
val descriptor = element.analyze(BodyResolveMode.PARTIAL)[BindingContext.DECLARATION_TO_DESCRIPTOR, element] as? FunctionDescriptor
|
||||
?: return false
|
||||
val returnType = descriptor.getReturnType() ?: return false
|
||||
return !KotlinBuiltIns.isUnit(returnType) && !KotlinBuiltIns.isNothing(returnType)
|
||||
}
|
||||
|
||||
override fun applyTo(element: JetNamedFunction, editor: Editor) {
|
||||
val context = element.analyze()
|
||||
val context = element.analyze(BodyResolveMode.PARTIAL)
|
||||
val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, element] as FunctionDescriptor
|
||||
Converter(element.getProject(), descriptor, context).run()
|
||||
}
|
||||
|
||||
+25
-13
@@ -38,11 +38,15 @@ import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.JetCallableReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.JetProperty
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import java.util.*
|
||||
|
||||
public class ConvertPropertyToFunctionIntention : JetSelfTargetingIntention<JetProperty>(javaClass(), "Convert property to function"), LowPriorityAction {
|
||||
@@ -51,6 +55,7 @@ public class ConvertPropertyToFunctionIntention : JetSelfTargetingIntention<JetP
|
||||
descriptor: CallableDescriptor,
|
||||
context: BindingContext
|
||||
): CallableRefactoring<CallableDescriptor>(project, descriptor, context, getText()) {
|
||||
private val newName: String = JvmAbi.getterName(callableDescriptor.name.asString())
|
||||
|
||||
private fun convertProperty(originalProperty: JetProperty, psiFactory: JetPsiFactory) {
|
||||
val property = originalProperty.copy() as JetProperty;
|
||||
@@ -74,16 +79,19 @@ public class ConvertPropertyToFunctionIntention : JetSelfTargetingIntention<JetP
|
||||
property.deleteChildRange(dropPropertyFrom, getter.getPrevSibling())
|
||||
}
|
||||
}
|
||||
property.setName(newName)
|
||||
|
||||
originalProperty.replace(psiFactory.createFunction(property.getText()))
|
||||
}
|
||||
|
||||
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
|
||||
val propertyName = callableDescriptor.getName().asString()
|
||||
val nameChanged = propertyName != newName
|
||||
val getterName = JvmAbi.getterName(callableDescriptor.getName().asString())
|
||||
val conflicts = MultiMap<PsiElement, String>()
|
||||
val callables = getAffectedCallables(project, descriptorsForChange)
|
||||
val kotlinRefs = ArrayList<JetSimpleNameExpression>()
|
||||
val foreignRefsToRename = ArrayList<PsiReference>()
|
||||
val kotlinRefsToReplaceWithCall = ArrayList<JetSimpleNameExpression>()
|
||||
val refsToRename = ArrayList<PsiReference>()
|
||||
val javaRefsToReplaceWithCall = ArrayList<PsiReferenceExpression>()
|
||||
for (callable in callables) {
|
||||
if (callable !is PsiNamedElement) continue
|
||||
@@ -112,9 +120,12 @@ public class ConvertPropertyToFunctionIntention : JetSelfTargetingIntention<JetP
|
||||
if (usage is JetReference) {
|
||||
if (usage is JetSimpleNameReference) {
|
||||
val expression = usage.expression
|
||||
if (expression.getCall(expression.analyze()) != null
|
||||
if (expression.getCall(expression.analyze(BodyResolveMode.PARTIAL)) != null
|
||||
&& expression.getStrictParentOfType<JetCallableReferenceExpression>() == null) {
|
||||
kotlinRefs.add(expression)
|
||||
kotlinRefsToReplaceWithCall.add(expression)
|
||||
}
|
||||
else if (nameChanged) {
|
||||
refsToRename.add(usage)
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -129,10 +140,7 @@ public class ConvertPropertyToFunctionIntention : JetSelfTargetingIntention<JetP
|
||||
|
||||
val refElement = usage.getElement()
|
||||
|
||||
if (refElement.getText().endsWith(getterName)) {
|
||||
foreignRefsToRename.add(usage)
|
||||
continue
|
||||
}
|
||||
if (refElement.getText().endsWith(getterName)) continue
|
||||
|
||||
if (usage is PsiJavaReference) {
|
||||
if (usage.resolve() is PsiField && usage is PsiReferenceExpression) {
|
||||
@@ -152,14 +160,18 @@ public class ConvertPropertyToFunctionIntention : JetSelfTargetingIntention<JetP
|
||||
project.executeWriteCommand(getText()) {
|
||||
val kotlinPsiFactory = JetPsiFactory(project)
|
||||
val javaPsiFactory = PsiElementFactory.SERVICE.getInstance(project)
|
||||
val newKotlinCallExpr = kotlinPsiFactory.createExpression("$newName()")
|
||||
|
||||
kotlinRefs.forEach { it.replace(kotlinPsiFactory.createExpressionByPattern("$0()", it)) }
|
||||
foreignRefsToRename.forEach { it.handleElementRename(propertyName) }
|
||||
javaRefsToReplaceWithCall.forEach { it.replace(javaPsiFactory.createExpressionFromText(it.getText() + "()", null)) }
|
||||
kotlinRefsToReplaceWithCall.forEach { it.replace(newKotlinCallExpr) }
|
||||
refsToRename.forEach { it.handleElementRename(newName) }
|
||||
javaRefsToReplaceWithCall.forEach {
|
||||
val getterRef = it.handleElementRename(newName)
|
||||
getterRef.replace(javaPsiFactory.createExpressionFromText("${getterRef.text}()", null))
|
||||
}
|
||||
callables.forEach {
|
||||
when (it) {
|
||||
is JetProperty -> convertProperty(it, kotlinPsiFactory)
|
||||
is PsiMethod -> it.setName(propertyName)
|
||||
is PsiMethod -> it.setName(newName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import static test.TestPackage.getFoo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
boolean b = getFoo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import static test.TestPackage.getFoo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
boolean b = getFoo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package test
|
||||
|
||||
fun <caret>getFoo(): Boolean = true
|
||||
@@ -0,0 +1,4 @@
|
||||
package test
|
||||
|
||||
val foo: Boolean
|
||||
get() = true
|
||||
@@ -0,0 +1,7 @@
|
||||
import static test.TestPackage.isFoo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
boolean b = isFoo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import static test.TestPackage.isFoo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
boolean b = isFoo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package test
|
||||
|
||||
fun <caret>isFoo(): Boolean = true
|
||||
@@ -0,0 +1,4 @@
|
||||
package test
|
||||
|
||||
val isFoo: Boolean
|
||||
get() = true
|
||||
@@ -0,0 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
package p
|
||||
|
||||
import p.getFoo
|
||||
|
||||
class A(val n: Int)
|
||||
|
||||
fun A.<caret>getFoo(): Boolean = n > 1
|
||||
|
||||
fun test() {
|
||||
val t = A::getFoo
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// WITH_RUNTIME
|
||||
package p
|
||||
|
||||
import p.foo
|
||||
|
||||
class A(val n: Int)
|
||||
|
||||
val A.foo: Boolean
|
||||
get() = n > 1
|
||||
|
||||
fun test() {
|
||||
val t = A::foo
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
package p
|
||||
|
||||
import p.isFoo
|
||||
|
||||
class A(val n: Int)
|
||||
|
||||
fun A.<caret>isFoo(): Boolean = n > 1
|
||||
|
||||
fun test() {
|
||||
val t = A::isFoo
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// WITH_RUNTIME
|
||||
package p
|
||||
|
||||
import p.isFoo
|
||||
|
||||
class A(val n: Int)
|
||||
|
||||
val A.isFoo: Boolean
|
||||
get() = n > 1
|
||||
|
||||
fun test() {
|
||||
val t = A::isFoo
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
class G {
|
||||
void test() {
|
||||
test.TestPackage.bar();
|
||||
test.TestPackage.getBar();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
package test
|
||||
|
||||
fun bar(): Int = 1
|
||||
fun getBar(): Int = 1
|
||||
@@ -1,9 +1,9 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(): Boolean {
|
||||
fun getFoo(): Boolean {
|
||||
return n > 1
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = A(1).foo()
|
||||
val t = A(1).getFoo()
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(): Boolean = n > 1
|
||||
fun getFoo(): Boolean = n > 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = A(1).foo()
|
||||
val t = A(1).getFoo()
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
class A(val n: Int) {
|
||||
fun foo(): Boolean = n > 1
|
||||
fun getFoo(): Boolean = n > 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = A(1).foo()
|
||||
val t = A(1).getFoo()
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import test.O;
|
||||
import static test.O.foo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
int n = O.foo;
|
||||
int m = foo;
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -1,9 +1,7 @@
|
||||
import test.O;
|
||||
import static test.O.foo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
int n = O.foo();
|
||||
int m = foo();
|
||||
int n = O.getFoo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import static test.O.foo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
int m = foo;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import static test.O.foo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
int m = getFoo();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
package test
|
||||
|
||||
object O {
|
||||
fun foo(): Int = 1
|
||||
fun getFoo(): Int = 1
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import static test.TestPackage.foo;
|
||||
import static test.TestPackage.getFoo;
|
||||
|
||||
class J {
|
||||
void test() {
|
||||
boolean b = foo();
|
||||
boolean b = getFoo();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
package test
|
||||
|
||||
fun foo(): Boolean = true
|
||||
fun getFoo(): Boolean = true
|
||||
@@ -1,12 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
package p
|
||||
|
||||
import p.foo
|
||||
import p.getFoo
|
||||
|
||||
class A(val n: Int)
|
||||
|
||||
fun A.foo(): Boolean = n > 1
|
||||
fun A.getFoo(): Boolean = n > 1
|
||||
|
||||
fun test() {
|
||||
val t = A::foo
|
||||
val t = A::getFoo
|
||||
}
|
||||
@@ -6,14 +6,14 @@ class JA extends A {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean foo() {
|
||||
boolean getFoo() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class JTest {
|
||||
void test() {
|
||||
boolean x = new A(1).foo();
|
||||
boolean y = new JA().foo();
|
||||
boolean x = new A(1).getFoo();
|
||||
boolean y = new JA().getFoo();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package test
|
||||
|
||||
interface T {
|
||||
fun foo(): Boolean
|
||||
fun getFoo(): Boolean
|
||||
}
|
||||
|
||||
open class A(val n: Int): T {
|
||||
override fun foo(): Boolean = n > 1
|
||||
override fun getFoo(): Boolean = n > 1
|
||||
}
|
||||
|
||||
class B: A(1) {
|
||||
override fun foo(): Boolean = true
|
||||
override fun getFoo(): Boolean = true
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val a = A(1).foo()
|
||||
val b = B().foo()
|
||||
val a = A(1).getFoo()
|
||||
val b = B().getFoo()
|
||||
}
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// WITH_RUNTIME
|
||||
fun String.foo(): String = if (isEmpty()) "" else substring(1).foo()
|
||||
fun String.getFoo(): String = if (isEmpty()) "" else substring(1).getFoo()
|
||||
+2
-2
@@ -3,11 +3,11 @@
|
||||
annotation class X(val s: String)
|
||||
|
||||
class A(val n: Int) {
|
||||
internal X("1") fun <T : Number> T.foo(): Boolean = toInt() - n > 1
|
||||
internal X("1") fun <T : Number> T.getFoo(): Boolean = toInt() - n > 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = with(A(1)) {
|
||||
2.5.foo()
|
||||
2.5.getFoo()
|
||||
}
|
||||
}
|
||||
@@ -3217,6 +3217,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaUsagesGet.kt")
|
||||
public void testJavaUsagesGet() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/javaUsagesGet.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaUsagesIs.kt")
|
||||
public void testJavaUsagesIs() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/javaUsagesIs.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("localFun.kt")
|
||||
public void testLocalFun() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/localFun.kt");
|
||||
@@ -3235,6 +3247,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("otherRefsGet.kt")
|
||||
public void testOtherRefsGet() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/otherRefsGet.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("otherRefsIs.kt")
|
||||
public void testOtherRefsIs() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/otherRefsIs.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("overrides.kt")
|
||||
public void testOverrides() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/overrides.kt");
|
||||
|
||||
Reference in New Issue
Block a user