refactor ConvertFunctionToPropertyIntention and fix 'Convert function to property shouldn't insert explicit type if it was inferred previously' (#1011)
Fixes #KT-14820
This commit is contained in:
committed by
Dmitry Jemerov
parent
927cd04405
commit
4cbdbaa057
@@ -612,7 +612,7 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
|
||||
private fun placeKeyword() {
|
||||
assert(state == State.MODIFIERS)
|
||||
|
||||
if (sb.isNotEmpty()) {
|
||||
if (!sb.endsWith(" ")) {
|
||||
sb.append(" ")
|
||||
}
|
||||
val keyword = when (target) {
|
||||
@@ -625,9 +625,9 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
|
||||
state = State.RECEIVER
|
||||
}
|
||||
|
||||
private fun bodyPrefix() = when (target) {
|
||||
private fun bodyPrefix(breakLine: Boolean = true) = when (target) {
|
||||
Target.FUNCTION, Target.CONSTRUCTOR -> ""
|
||||
Target.READ_ONLY_PROPERTY -> "\nget()"
|
||||
Target.READ_ONLY_PROPERTY -> (if (breakLine) "\n" else " ") + "get()"
|
||||
}
|
||||
|
||||
fun modifier(modifier: String): CallableBuilder {
|
||||
@@ -733,6 +733,16 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
|
||||
return this
|
||||
}
|
||||
|
||||
fun getterExpression(expression: String, breakLine: Boolean = true): CallableBuilder {
|
||||
assert(target == Target.READ_ONLY_PROPERTY)
|
||||
assert(state == State.BODY || state == State.TYPE_CONSTRAINTS)
|
||||
|
||||
sb.append(bodyPrefix(breakLine)).append(" = ").append(expression)
|
||||
state = State.DONE
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun initializer(body: String): CallableBuilder {
|
||||
assert(target == Target.READ_ONLY_PROPERTY && (state == State.BODY || state == State.TYPE_CONSTRAINTS))
|
||||
|
||||
|
||||
+35
-26
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,7 +21,10 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
|
||||
@@ -32,6 +35,8 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
|
||||
import org.jetbrains.kotlin.idea.refactoring.*
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
@@ -40,9 +45,11 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.READ_ONLY_PROPERTY
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStartOffsetIn
|
||||
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.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
@@ -58,8 +65,9 @@ class ConvertFunctionToPropertyIntention : SelfTargetingIntention<KtNamedFunctio
|
||||
|
||||
private inner class Converter(
|
||||
project: Project,
|
||||
private val editor: Editor?,
|
||||
descriptor: FunctionDescriptor
|
||||
): CallableRefactoring<FunctionDescriptor>(project, descriptor, text) {
|
||||
) : CallableRefactoring<FunctionDescriptor>(project, descriptor, text) {
|
||||
private val elementsToShorten = ArrayList<KtElement>()
|
||||
|
||||
private val newName: String by lazy {
|
||||
@@ -68,30 +76,31 @@ class ConvertFunctionToPropertyIntention : SelfTargetingIntention<KtNamedFunctio
|
||||
}
|
||||
|
||||
private fun convertFunction(originalFunction: KtNamedFunction, psiFactory: KtPsiFactory) {
|
||||
val function = originalFunction.copy() as KtNamedFunction
|
||||
val propertyString = KtPsiFactory.CallableBuilder(READ_ONLY_PROPERTY).apply {
|
||||
// make sure to capture all comments and line breaks
|
||||
modifier(originalFunction.text.substring(0, originalFunction.funKeyword!!.getStartOffsetIn(originalFunction)))
|
||||
typeParams(originalFunction.typeParameters.map { it.text })
|
||||
originalFunction.receiverTypeReference?.let { receiver(it.text) }
|
||||
name(newName)
|
||||
originalFunction.getReturnTypeReference()?.let { returnType(it.text) }
|
||||
typeConstraints(originalFunction.typeConstraints.map { it.text })
|
||||
|
||||
val propertySample = psiFactory.createProperty("val foo: Int get() = 1")
|
||||
if (originalFunction.equalsToken != null) {
|
||||
getterExpression(originalFunction.bodyExpression!!.text, breakLine = originalFunction.typeReference != null)
|
||||
}
|
||||
else {
|
||||
(originalFunction.bodyExpression as? KtBlockExpression)?.let { body ->
|
||||
transform {
|
||||
append("\nget() ")
|
||||
append(body.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.asString()
|
||||
|
||||
val needsExplicitType = function.typeReference == null
|
||||
if (needsExplicitType) {
|
||||
originalFunction.typeFqNameToAdd?.let { function.setTypeReference(psiFactory.createType(it)) }
|
||||
}
|
||||
val replaced = originalFunction.replaced(psiFactory.createDeclaration<KtProperty>(propertyString))
|
||||
|
||||
function.funKeyword!!.replace(propertySample.valOrVarKeyword)
|
||||
function.valueParameterList?.delete()
|
||||
val insertAfter = (function.equalsToken ?: function.bodyExpression)
|
||||
?.siblings(forward = false, withItself = false)
|
||||
?.firstOrNull { it !is PsiWhiteSpace }
|
||||
if (insertAfter != null) {
|
||||
function.addAfter(psiFactory.createParameterList("()"), insertAfter)
|
||||
function.addAfter(propertySample.getter!!.namePlaceholder, insertAfter)
|
||||
}
|
||||
function.setName(newName)
|
||||
|
||||
val property = originalFunction.replace(psiFactory.createProperty(function.text)) as KtProperty
|
||||
if (needsExplicitType) {
|
||||
elementsToShorten.add(property.typeReference!!)
|
||||
}
|
||||
editor?.caretModel?.moveToOffset(replaced.nameIdentifier!!.endOffset)
|
||||
}
|
||||
|
||||
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
|
||||
@@ -211,6 +220,6 @@ class ConvertFunctionToPropertyIntention : SelfTargetingIntention<KtNamedFunctio
|
||||
override fun applyTo(element: KtNamedFunction, editor: Editor?) {
|
||||
val context = element.analyze(BodyResolveMode.PARTIAL)
|
||||
val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, element] as FunctionDescriptor
|
||||
Converter(element.project, descriptor).run()
|
||||
Converter(element.project, editor, descriptor).run()
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -3,12 +3,11 @@ interface T {
|
||||
}
|
||||
|
||||
class A(val n: Int) {
|
||||
val foo: T
|
||||
get() = object : T {
|
||||
override fun bar() {
|
||||
val foo get() = object : T {
|
||||
override fun bar() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
|
||||
+1
-2
@@ -2,8 +2,7 @@ fun test() {
|
||||
class X
|
||||
|
||||
class A(val n: Int) {
|
||||
val foo: X
|
||||
get() = X()
|
||||
val foo get() = X()
|
||||
}
|
||||
|
||||
val t = A(1).foo
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
class A(val n: Int) {
|
||||
val foo: Boolean
|
||||
get() = n > 1
|
||||
val foo get() = n > 1
|
||||
}
|
||||
|
||||
fun test() {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
annotation class X(val s: String)
|
||||
|
||||
@X("")
|
||||
fun foo<caret>(): String = ""
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
annotation class X(val s: String)
|
||||
|
||||
@X("")
|
||||
val foo<caret>: String
|
||||
get() = ""
|
||||
@@ -0,0 +1,8 @@
|
||||
annotation class X(val s: String)
|
||||
|
||||
// 1
|
||||
@X("") // 2
|
||||
/* 3 */ fun foo<caret>(): String {
|
||||
// 4
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
annotation class X(val s: String)
|
||||
|
||||
// 1
|
||||
@X("") // 2
|
||||
/* 3 */ val foo<caret>: String
|
||||
get() {
|
||||
// 4
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
fun foo<caret>() = ""
|
||||
@@ -0,0 +1 @@
|
||||
val foo<caret> get() = ""
|
||||
@@ -4151,12 +4151,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertFunctionToProperty"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("annotationLineBreak.kt")
|
||||
public void testAnnotationLineBreak() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/annotationLineBreak.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("blockBody.kt")
|
||||
public void testBlockBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/blockBody.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("comments.kt")
|
||||
public void testComments() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/comments.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("existingPropertyConflict.kt")
|
||||
public void testExistingPropertyConflict() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/existingPropertyConflict.kt");
|
||||
@@ -4223,6 +4235,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noExplicitType.kt")
|
||||
public void testNoExplicitType() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/noExplicitType.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nothingFun.kt")
|
||||
public void testNothingFun() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionToProperty/nothingFun.kt");
|
||||
|
||||
Reference in New Issue
Block a user