Property <-> Function Conversion: Add/remove 'get'/'is' prefixes automatically

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