diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt index dce1aa4fff0..d11df0a84b8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt @@ -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)) diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionToPropertyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionToPropertyIntention.kt index 88e0a8cb721..e61179546cf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionToPropertyIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionToPropertyIntention.kt @@ -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(project, descriptor, text) { + ) : CallableRefactoring(project, descriptor, text) { private val elementsToShorten = ArrayList() private val newName: String by lazy { @@ -68,30 +76,31 @@ class ConvertFunctionToPropertyIntention : SelfTargetingIntention + 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(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) { @@ -211,6 +220,6 @@ class ConvertFunctionToPropertyIntention : SelfTargetingIntention 1 + val foo get() = n > 1 } fun test() { diff --git a/idea/testData/intentions/convertFunctionToProperty/annotationLineBreak.kt b/idea/testData/intentions/convertFunctionToProperty/annotationLineBreak.kt new file mode 100644 index 00000000000..9e8ebd3d08e --- /dev/null +++ b/idea/testData/intentions/convertFunctionToProperty/annotationLineBreak.kt @@ -0,0 +1,4 @@ +annotation class X(val s: String) + +@X("") +fun foo(): String = "" \ No newline at end of file diff --git a/idea/testData/intentions/convertFunctionToProperty/annotationLineBreak.kt.after b/idea/testData/intentions/convertFunctionToProperty/annotationLineBreak.kt.after new file mode 100644 index 00000000000..fe2ea66a51c --- /dev/null +++ b/idea/testData/intentions/convertFunctionToProperty/annotationLineBreak.kt.after @@ -0,0 +1,5 @@ +annotation class X(val s: String) + +@X("") +val foo: String + get() = "" \ No newline at end of file diff --git a/idea/testData/intentions/convertFunctionToProperty/comments.kt b/idea/testData/intentions/convertFunctionToProperty/comments.kt new file mode 100644 index 00000000000..d740d950d99 --- /dev/null +++ b/idea/testData/intentions/convertFunctionToProperty/comments.kt @@ -0,0 +1,8 @@ +annotation class X(val s: String) + +// 1 +@X("") // 2 +/* 3 */ fun foo(): String { + // 4 + return "" +} \ No newline at end of file diff --git a/idea/testData/intentions/convertFunctionToProperty/comments.kt.after b/idea/testData/intentions/convertFunctionToProperty/comments.kt.after new file mode 100644 index 00000000000..7a5ee0f5050 --- /dev/null +++ b/idea/testData/intentions/convertFunctionToProperty/comments.kt.after @@ -0,0 +1,9 @@ +annotation class X(val s: String) + +// 1 +@X("") // 2 +/* 3 */ val foo: String + get() { + // 4 + return "" + } \ No newline at end of file diff --git a/idea/testData/intentions/convertFunctionToProperty/noExplicitType.kt b/idea/testData/intentions/convertFunctionToProperty/noExplicitType.kt new file mode 100644 index 00000000000..1f317f101e6 --- /dev/null +++ b/idea/testData/intentions/convertFunctionToProperty/noExplicitType.kt @@ -0,0 +1 @@ +fun foo() = "" \ No newline at end of file diff --git a/idea/testData/intentions/convertFunctionToProperty/noExplicitType.kt.after b/idea/testData/intentions/convertFunctionToProperty/noExplicitType.kt.after new file mode 100644 index 00000000000..6053491ce31 --- /dev/null +++ b/idea/testData/intentions/convertFunctionToProperty/noExplicitType.kt.after @@ -0,0 +1 @@ +val foo get() = "" \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index b3976218139..4fb3801fb6a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -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");