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.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()
}
@@ -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,5 +1,5 @@
class G {
void test() {
test.TestPackage.bar();
test.TestPackage.getBar();
}
}
@@ -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,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;
}
}
@@ -0,0 +1,7 @@
import static test.O.foo;
class J {
void test() {
int m = getFoo();
}
}
@@ -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,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()
@@ -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");