diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPropertyAccessor.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPropertyAccessor.java index f809269355b..b62b93dcd56 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPropertyAccessor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetPropertyAccessor.java @@ -59,9 +59,14 @@ public class JetPropertyAccessor extends JetDeclarationStub parameters = parameterList.getParameters(); if (parameters.isEmpty()) return null; diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt index 0aa98376356..7b4012f0076 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.ShortenReferences import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf @@ -40,6 +41,13 @@ fun JetCallableDeclaration.setType(type: JetType) { ShortenReferences.DEFAULT.process(getTypeReference()!!) } +fun JetCallableDeclaration.setReceiverType(type: JetType) { + if (type.isError()) return + val typeReference = JetPsiFactory(getProject()).createType(IdeDescriptorRenderers.SOURCE_CODE.renderType(type)) + setReceiverTypeReference(typeReference) + ShortenReferences.DEFAULT.process(getReceiverTypeReference()!!) +} + fun JetContainerNode.description(): String? { when (getNode().getElementType()) { JetNodeTypes.THEN -> return "if" diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/declarations/ConvertMemberToExtension.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/declarations/ConvertMemberToExtension.kt index de9b820ecba..b16406c9e30 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/declarations/ConvertMemberToExtension.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/declarations/ConvertMemberToExtension.kt @@ -16,18 +16,15 @@ package org.jetbrains.kotlin.idea.intentions.declarations -import com.intellij.codeInsight.CodeInsightUtilCore import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.util.TextRange -import com.intellij.openapi.util.text.StringUtil -import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.util.Function -import com.intellij.util.containers.ContainerUtil +import com.intellij.psi.PsiDocumentManager import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingRangeIntention -import org.jetbrains.kotlin.lexer.JetModifierKeywordToken +import org.jetbrains.kotlin.idea.intentions.setReceiverType +import org.jetbrains.kotlin.idea.quickfix.moveCaret import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.psi.* @@ -51,108 +48,96 @@ public class ConvertMemberToExtension : JetSelfTargetingRangeIntention(extensionText) - val added = file.addAfter(extension, outermostParent) + val extension = file.addAfter(element, outermostParent) as JetCallableDeclaration file.addAfter(psiFactory.createNewLine(), outermostParent) file.addAfter(psiFactory.createNewLine(), outermostParent) element.delete() - CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(added) + extension.setReceiverType(containingClass.getDefaultType()) - val caretAnchor = added.getText().indexOf(CARET_ANCHOR) - if (caretAnchor >= 0) { - val caretOffset = added.getTextRange().getStartOffset() + caretAnchor - val anchor = PsiTreeUtil.findElementOfClassAtOffset(file, caretOffset, javaClass(), false) - if (anchor != null && CARET_ANCHOR == anchor.getReferencedName()) { - val throwException = psiFactory.createExpression(THROW_UNSUPPORTED_OPERATION_EXCEPTION) - val replaced = anchor.replace(throwException) - val range = replaced.getTextRange() - editor.getCaretModel().moveToOffset(range.getStartOffset()) - editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset()) + if (typeParameterList != null) { + if (extension.getTypeParameterList() != null) { + extension.getTypeParameterList()!!.replace(typeParameterList) + } + else { + extension.addBefore(typeParameterList, extension.getReceiverTypeReference()) + extension.addBefore(psiFactory.createWhiteSpace(), extension.getReceiverTypeReference()) } } - } - private val CARET_ANCHOR = "____CARET_ANCHOR____" - private val THROW_UNSUPPORTED_OPERATION_EXCEPTION = " throw UnsupportedOperationException()" + extension.getModifierList()?.getModifier(JetTokens.PROTECTED_KEYWORD)?.delete() + extension.getModifierList()?.getModifier(JetTokens.ABSTRACT_KEYWORD)?.delete() - private fun memberType(member: JetCallableDeclaration): String { - if (member is JetFunction) { - return "fun" - } - return (member as JetProperty).getValOrVarNode().getText() - } + var bodyToSelect: JetExpression? = null - private fun modifiers(member: JetCallableDeclaration): String { - val modifierList = member.getModifierList() ?: return "" - for (modifierType in JetTokens.VISIBILITY_MODIFIERS.getTypes()) { - val modifier = modifierList.getModifier(modifierType as JetModifierKeywordToken) - if (modifier != null) { - return if (modifierType == JetTokens.PROTECTED_KEYWORD) "" else modifier.getText() + " " + fun selectBody(declaration: JetDeclarationWithBody) { + if (bodyToSelect == null) { + val body = declaration.getBodyExpression() + bodyToSelect = if (body is JetBlockExpression) body.getStatements().single() as JetExpression else body } } - return "" - } - private fun typeParameters(member: JetCallableDeclaration): String { - val classElement = member.getParent().getParent() - assert(classElement is JetClass) { "Must be checked in isAvailable: " + classElement.getText() } - - val allTypeParameters = ContainerUtil.concat((classElement as JetClass).getTypeParameters(), member.getTypeParameters()) - if (allTypeParameters.isEmpty()) return "" - return "<" + StringUtil.join(allTypeParameters, object : Function { - override fun `fun`(parameter: JetTypeParameter): String { - return parameter.getText() + when (extension) { + is JetFunction -> { + if (!extension.hasBody()) { + extension.add(psiFactory.createFunctionBody(THROW_UNSUPPORTED_OPERATION_EXCEPTION)) + selectBody(extension) + } } - }, ", ") + "> " - } - private fun body(member: JetCallableDeclaration): String { - if (member is JetProperty) { - return "\n" + getter(member) + "\n" + setter(member, !synthesizeBody(member.getGetter())) + is JetProperty -> { + val templateProperty = psiFactory.createDeclaration("var v: Any\nget()=$THROW_UNSUPPORTED_OPERATION_EXCEPTION\nset(value){$THROW_UNSUPPORTED_OPERATION_EXCEPTION}") + val templateGetter = templateProperty.getGetter()!! + val templateSetter = templateProperty.getSetter()!! + + var getter = extension.getGetter() + if (getter == null) { + getter = extension.addAfter(templateGetter, extension.getTypeReference()) as JetPropertyAccessor + extension.addBefore(psiFactory.createNewLine(), getter) + selectBody(getter) + } + else if (!getter.hasBody()) { + getter = getter.replace(templateGetter) as JetPropertyAccessor + selectBody(getter) + } + + if (extension.isVar()) { + var setter = extension.getSetter() + if (setter == null) { + setter = extension.addAfter(templateSetter, getter) as JetPropertyAccessor + extension.addBefore(psiFactory.createNewLine(), setter) + selectBody(setter) + } + else if (!setter.hasBody()) { + setter = setter.replace(templateSetter) as JetPropertyAccessor + selectBody(setter) + } + } + } } - else if (member is JetFunction) { - val bodyExpression = member.getBodyExpression() ?: return "{" + CARET_ANCHOR + "}" - if (!member.hasBlockBody()) return " = " + bodyExpression.getText() - return bodyExpression.getText() - } - else { - return "" + + if (bodyToSelect != null) { + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()) + + val range = bodyToSelect!!.getTextRange() + editor.moveCaret(range.getStartOffset(), ScrollType.CENTER) + editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset()) } } - private fun getter(property: JetProperty): String { - val getter = property.getGetter() - if (synthesizeBody(getter)) return "get() = " + CARET_ANCHOR - return getter!!.getText() - } + private val THROW_UNSUPPORTED_OPERATION_EXCEPTION = "throw UnsupportedOperationException()" - private fun setter(property: JetProperty, allowCaretAnchor: Boolean): String { - if (!property.isVar()) return "" - val setter = property.getSetter() - if (synthesizeBody(setter)) return "set(value) {" + (if (allowCaretAnchor) CARET_ANCHOR else THROW_UNSUPPORTED_OPERATION_EXCEPTION) + "}" - return setter!!.getText() - } - - private fun synthesizeBody(getter: JetPropertyAccessor?): Boolean { - return getter == null || getter.getBodyExpression() == null + private fun newTypeParameterList(member: JetCallableDeclaration): JetTypeParameterList? { + val classElement = member.getParent().getParent() as JetClass + val classParams = classElement.getTypeParameters() + if (classParams.isEmpty()) return null + val allTypeParameters = classParams + member.getTypeParameters() + val text = allTypeParameters.map { it.getText() }.joinToString(",", "<", ">") + return JetPsiFactory(member).createDeclaration("fun $text foo()").getTypeParameterList() } } diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/KDoc.kt b/idea/testData/intentions/declarations/convertMemberToExtension/KDoc.kt new file mode 100644 index 00000000000..aedc7f7b2d9 --- /dev/null +++ b/idea/testData/intentions/declarations/convertMemberToExtension/KDoc.kt @@ -0,0 +1,8 @@ +class Foo +{ + /** + * Doc + */ + public val count: Int + get() = 1 +} \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/KDoc.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/KDoc.kt.after new file mode 100644 index 00000000000..b7cc428f749 --- /dev/null +++ b/idea/testData/intentions/declarations/convertMemberToExtension/KDoc.kt.after @@ -0,0 +1,9 @@ +class Foo +{ +} + +/** + * Doc + */ +public val Foo.count: Int + get() = 1 \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/explicitUnit.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/explicitUnit.kt.after index c838f1b1c26..7b88283da29 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/explicitUnit.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/explicitUnit.kt.after @@ -1,5 +1,4 @@ class Owner { } -fun Owner.f(): Unit { -} \ No newline at end of file +fun Owner.f(): Unit {} \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/genericFunInGenericClass.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/genericFunInGenericClass.kt.after index c1ee18f6097..217e1bb8e21 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/genericFunInGenericClass.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/genericFunInGenericClass.kt.after @@ -1,4 +1,4 @@ abstract class Owner { } -fun Owner.f(t: T, r: R): R = r +fun Owner.f(t: T, r: R): R = r diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/genericFunParamAfterName.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/genericFunParamAfterName.kt.after index ac6c26ebb88..07e5db3ee96 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/genericFunParamAfterName.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/genericFunParamAfterName.kt.after @@ -1,4 +1,4 @@ abstract class Owner { } -fun Owner.f(t: T): T = t \ No newline at end of file +fun Owner.f(t: T): T = t \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/simple.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/simple.kt.after index 026942eb60e..7f647b217f3 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/simple.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/simple.kt.after @@ -1,5 +1,4 @@ class Owner { } -fun Owner.f() { -} \ No newline at end of file +fun Owner.f() {} \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetter.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetter.kt.after index d0fc5b137c8..4d472c6c322 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetter.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetter.kt.after @@ -2,4 +2,4 @@ class Owner { } val Owner.p: Int - get() = 1 \ No newline at end of file + get() = 1 \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetterBlockBody.kt b/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetterBlockBody.kt index cb4b4c3b9b9..203b59591c1 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetterBlockBody.kt +++ b/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetterBlockBody.kt @@ -1,4 +1,4 @@ class Owner { val p: Int - get() { return 1} + get() { return 1} } \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetterBlockBody.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetterBlockBody.kt.after index 275d011bde3..9685b52acab 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetterBlockBody.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/valWithGetterBlockBody.kt.after @@ -2,6 +2,4 @@ class Owner { } val Owner.p: Int - get() { - return 1 - } \ No newline at end of file + get() { return 1} \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/varWithDefaultSetter.kt b/idea/testData/intentions/declarations/convertMemberToExtension/varWithDefaultSetter.kt index 41872aa9f2f..8c2615106de 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/varWithDefaultSetter.kt +++ b/idea/testData/intentions/declarations/convertMemberToExtension/varWithDefaultSetter.kt @@ -1,6 +1,6 @@ // ERROR: Property must be initialized class Owner { var p: Int - get() = 1 - set + get() = 1 + set } diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetter.kt b/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetter.kt index 5fda1810465..781171bd07b 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetter.kt +++ b/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetter.kt @@ -1,5 +1,5 @@ // ERROR: Property must be initialized class Owner { var p: Int - get() { return 1 } + get() { return 1 } } diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetter.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetter.kt.after index 04dfe6da5cb..c9a4f8a3a4d 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetter.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetter.kt.after @@ -3,9 +3,7 @@ class Owner { } var Owner.p: Int - get() { - return 1 - } + get() { return 1 } set(value) { throw UnsupportedOperationException() } diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetterAndSetter.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetterAndSetter.kt.after index e627a520bd8..feb0cffecbc 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetterAndSetter.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/varWithGetterAndSetter.kt.after @@ -2,6 +2,5 @@ class Owner { } var Owner.p: Int - get() = 1 - set(v) { - } \ No newline at end of file + get() = 1 + set(v) {} \ No newline at end of file diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/varWithSetter.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/varWithSetter.kt.after index 7504abc00a8..70911b3ef9f 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/varWithSetter.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/varWithSetter.kt.after @@ -4,5 +4,4 @@ class Owner { var Owner.p: Int get() = throw UnsupportedOperationException() - set(v) { - } + set(v) {} diff --git a/idea/testData/intentions/declarations/convertMemberToExtension/withType.kt.after b/idea/testData/intentions/declarations/convertMemberToExtension/withType.kt.after index fc4fa717dc6..1adf1cb12fa 100644 --- a/idea/testData/intentions/declarations/convertMemberToExtension/withType.kt.after +++ b/idea/testData/intentions/declarations/convertMemberToExtension/withType.kt.after @@ -1,6 +1,4 @@ class Owner { } -fun Owner.f(): Int { - return 1 -} +fun Owner.f(): Int { return 1 } diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 39dc280de46..c6c391a9bbe 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -3897,6 +3897,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("KDoc.kt") + public void testKDoc() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/KDoc.kt"); + doTest(fileName); + } + @TestMetadata("localFunction.kt") public void testLocalFunction() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/declarations/convertMemberToExtension/localFunction.kt");