Smaller range + refactoring of ConvertMemberToExtension

This commit is contained in:
Valentin Kipyatkov
2015-05-11 20:09:51 +03:00
parent 04ec19cb4a
commit a23c6a2cdc
4 changed files with 85 additions and 110 deletions
@@ -330,8 +330,6 @@ hierarchy.legend.member.is.defined.in.class=Member is defined in the class
hierarchy.legend.member.defined.in.superclass=Member is not defined in the class but defined in superclass hierarchy.legend.member.defined.in.superclass=Member is not defined in the class but defined in superclass
hierarchy.legend.member.should.be.defined=Member should be defined since the class is not abstract hierarchy.legend.member.should.be.defined=Member should be defined since the class is not abstract
convert.to.extension=Convert to extension
suppress.warnings.family=Suppress Warnings suppress.warnings.family=Suppress Warnings
suppress.warning.for=Suppress ''{0}'' for {1} {2} suppress.warning.for=Suppress ''{0}'' for {1} {2}
@@ -17,79 +17,69 @@
package org.jetbrains.kotlin.idea.intentions.declarations package org.jetbrains.kotlin.idea.intentions.declarations
import com.intellij.codeInsight.CodeInsightUtilCore import com.intellij.codeInsight.CodeInsightUtilCore
import com.intellij.codeInsight.intention.impl.BaseIntentionAction
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.Function import com.intellij.util.Function
import com.intellij.util.IncorrectOperationException
import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.JetBundle import org.jetbrains.kotlin.idea.intentions.JetSelfTargetingRangeIntention
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.lexer.JetModifierKeywordToken import org.jetbrains.kotlin.lexer.JetModifierKeywordToken
import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR
public class ConvertMemberToExtension : BaseIntentionAction() { public class ConvertMemberToExtension : JetSelfTargetingRangeIntention<JetCallableDeclaration>(javaClass(), "Convert to extension") {
override fun applicabilityRange(element: JetCallableDeclaration): TextRange? {
override fun getText(): String { val classBody = element.getParent() as? JetClassBody ?: return null
return getFamilyName() if (classBody.getParent() !is JetClass) return null
} if (element.getReceiverTypeReference() != null) return null
when (element) {
override fun getFamilyName(): String { is JetProperty -> if (element.hasInitializer()) return null
return JetBundle.message("convert.to.extension") is JetSecondaryConstructor -> return null
}
override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean {
val declaration = getTarget(editor, file)
if (declaration is JetProperty) {
if (declaration.hasInitializer()) return false
} }
return declaration != null && declaration !is JetSecondaryConstructor && declaration.getParent() is JetClassBody && declaration.getParent().getParent() is JetClass && declaration.getReceiverTypeReference() == null return (element.getNameIdentifier() ?: return null).getTextRange()
} }
throws(IncorrectOperationException::class) override fun applyTo(element: JetCallableDeclaration, editor: Editor) {
override fun invoke(project: Project, editor: Editor, file: PsiFile) { val descriptor = element.resolveToDescriptor()
val member = getTarget(editor, file) val containingClass = descriptor.getContainingDeclaration() as ClassDescriptor
assert(member != null, "Must be checked by isAvailable")
val bindingContext = member!!.analyzeFully() val file = element.getContainingJetFile()
val memberDescriptor = bindingContext.get<PsiElement, DeclarationDescriptor>(DECLARATION_TO_DESCRIPTOR, member) ?: return val outermostParent = JetPsiUtil.getOutermostParent(element, file, false)
val containingClass = memberDescriptor.getContainingDeclaration() val receiver = containingClass.getDefaultType().toString() + "."
assert(containingClass is ClassDescriptor) { "Members must be contained in classes: \n" + "Descriptor: " + memberDescriptor + "\n" + "Declaration & context: " + member.getParent().getParent().getText() } val name = element.getNameIdentifier()!!.getText()
val outermostParent = JetPsiUtil.getOutermostParent(member, file, false) val valueParameterList = element.getValueParameterList()
val returnTypeRef = element.getTypeReference()
val receiver = (containingClass as ClassDescriptor).getDefaultType().toString() + "." val extensionText = modifiers(element) +
val identifier = member.getNameIdentifier() memberType(element) +
val name = if (identifier == null) "" else identifier.getText() " " +
typeParameters(element) +
receiver +
name +
(if (valueParameterList == null) "" else valueParameterList.getText()) +
(if (returnTypeRef != null) ": " + returnTypeRef.getText() else "") +
body(element)
val valueParameterList = member.getValueParameterList() val psiFactory = JetPsiFactory(element)
val returnTypeRef = member.getTypeReference()
val extensionText = modifiers(member) + memberType(member) + " " + typeParameters(member) + receiver + name + (if (valueParameterList == null) "" else valueParameterList.getText()) + (if (returnTypeRef != null) ": " + returnTypeRef.getText() else "") + body(member)
val psiFactory = JetPsiFactory(member)
val extension = psiFactory.createDeclaration<JetDeclaration>(extensionText) val extension = psiFactory.createDeclaration<JetDeclaration>(extensionText)
val added = file.addAfter(extension, outermostParent) val added = file.addAfter(extension, outermostParent)
file.addAfter(psiFactory.createNewLine(), outermostParent) file.addAfter(psiFactory.createNewLine(), outermostParent)
file.addAfter(psiFactory.createNewLine(), outermostParent) file.addAfter(psiFactory.createNewLine(), outermostParent)
member.delete() element.delete()
CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement<PsiElement>(added) CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement<PsiElement>(added)
val caretAnchor = added.getText().indexOf(CARET_ANCHOR) val caretAnchor = added.getText().indexOf(CARET_ANCHOR)
if (caretAnchor >= 0) { if (caretAnchor >= 0) {
val caretOffset = added.getTextRange().getStartOffset() + caretAnchor val caretOffset = added.getTextRange().getStartOffset() + caretAnchor
val anchor = PsiTreeUtil.findElementOfClassAtOffset<JetSimpleNameExpression>(file, caretOffset, javaClass<JetSimpleNameExpression>(), false) val anchor = PsiTreeUtil.findElementOfClassAtOffset(file, caretOffset, javaClass<JetSimpleNameExpression>(), false)
if (anchor != null && CARET_ANCHOR == anchor.getReferencedName()) { if (anchor != null && CARET_ANCHOR == anchor.getReferencedName()) {
val throwException = psiFactory.createExpression(THROW_UNSUPPORTED_OPERATION_EXCEPTION) val throwException = psiFactory.createExpression(THROW_UNSUPPORTED_OPERATION_EXCEPTION)
val replaced = anchor.replace(throwException) val replaced = anchor.replace(throwException)
@@ -100,76 +90,68 @@ public class ConvertMemberToExtension : BaseIntentionAction() {
} }
} }
companion object { private val CARET_ANCHOR = "____CARET_ANCHOR____"
private val THROW_UNSUPPORTED_OPERATION_EXCEPTION = " throw UnsupportedOperationException()"
public val CARET_ANCHOR: String = "____CARET_ANCHOR____" private fun memberType(member: JetCallableDeclaration): String {
public val THROW_UNSUPPORTED_OPERATION_EXCEPTION: String = " throw UnsupportedOperationException()" if (member is JetFunction) {
return "fun"
private fun getTarget(editor: Editor, file: PsiFile): JetCallableDeclaration? {
val element = file.findElementAt(editor.getCaretModel().getOffset())
return PsiTreeUtil.getParentOfType<JetCallableDeclaration>(element, javaClass<JetCallableDeclaration>(), false, javaClass<JetExpression>())
} }
return (member as JetProperty).getValOrVarNode().getText()
}
private fun memberType(member: JetCallableDeclaration): String { private fun modifiers(member: JetCallableDeclaration): String {
if (member is JetFunction) { val modifierList = member.getModifierList() ?: return ""
return "fun" 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() + " "
} }
return (member as JetProperty).getValOrVarNode().getText()
} }
return ""
}
private fun modifiers(member: JetCallableDeclaration): String { private fun typeParameters(member: JetCallableDeclaration): String {
val modifierList = member.getModifierList() ?: return "" val classElement = member.getParent().getParent()
for (modifierType in JetTokens.VISIBILITY_MODIFIERS.getTypes()) { assert(classElement is JetClass) { "Must be checked in isAvailable: " + classElement.getText() }
val modifier = modifierList.getModifier(modifierType as JetModifierKeywordToken)
if (modifier != null) { val allTypeParameters = ContainerUtil.concat<JetTypeParameter>((classElement as JetClass).getTypeParameters(), member.getTypeParameters())
return if (modifierType == JetTokens.PROTECTED_KEYWORD) "" else modifier.getText() + " " if (allTypeParameters.isEmpty()) return ""
} return "<" + StringUtil.join<JetTypeParameter>(allTypeParameters, object : Function<JetTypeParameter, String> {
override fun `fun`(parameter: JetTypeParameter): String {
return parameter.getText()
} }
}, ", ") + "> "
}
private fun body(member: JetCallableDeclaration): String {
if (member is JetProperty) {
return "\n" + getter(member) + "\n" + setter(member, !synthesizeBody(member.getGetter()))
}
else if (member is JetFunction) {
val bodyExpression = member.getBodyExpression() ?: return "{" + CARET_ANCHOR + "}"
if (!member.hasBlockBody()) return " = " + bodyExpression.getText()
return bodyExpression.getText()
}
else {
return "" return ""
} }
}
private fun typeParameters(member: JetCallableDeclaration): String { private fun getter(property: JetProperty): String {
val classElement = member.getParent().getParent() val getter = property.getGetter()
assert(classElement is JetClass) { "Must be checked in isAvailable: " + classElement.getText() } if (synthesizeBody(getter)) return "get() = " + CARET_ANCHOR
return getter!!.getText()
}
val allTypeParameters = ContainerUtil.concat<JetTypeParameter>((classElement as JetClass).getTypeParameters(), member.getTypeParameters()) private fun setter(property: JetProperty, allowCaretAnchor: Boolean): String {
if (allTypeParameters.isEmpty()) return "" if (!property.isVar()) return ""
return "<" + StringUtil.join<JetTypeParameter>(allTypeParameters, object : Function<JetTypeParameter, String> { val setter = property.getSetter()
override fun `fun`(parameter: JetTypeParameter): String { if (synthesizeBody(setter)) return "set(value) {" + (if (allowCaretAnchor) CARET_ANCHOR else THROW_UNSUPPORTED_OPERATION_EXCEPTION) + "}"
return parameter.getText() return setter!!.getText()
} }
}, ", ") + "> "
}
private fun body(member: JetCallableDeclaration): String { private fun synthesizeBody(getter: JetPropertyAccessor?): Boolean {
if (member is JetProperty) { return getter == null || getter.getBodyExpression() == null
return "\n" + getter(member) + "\n" + setter(member, !synthesizeBody(member.getGetter()))
}
else if (member is JetFunction) {
val bodyExpression = member.getBodyExpression() ?: return "{" + CARET_ANCHOR + "}"
if (!member.hasBlockBody()) return " = " + bodyExpression.getText()
return bodyExpression.getText()
}
else {
return ""
}
}
private fun getter(property: JetProperty): String {
val getter = property.getGetter()
if (synthesizeBody(getter)) return "get() = " + CARET_ANCHOR
return getter.getText()
}
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
}
} }
} }
@@ -1,3 +1,4 @@
// IS_APPLICABLE: false
// ERROR: Function declaration must have a name // ERROR: Function declaration must have a name
class Owner { class Owner {
fun <caret>() {} fun <caret>() {}
@@ -1,6 +0,0 @@
// ERROR: Function declaration must have a name
class Owner {
}
fun Owner.() {
}