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.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.warning.for=Suppress ''{0}'' for {1} {2}
@@ -17,79 +17,69 @@
package org.jetbrains.kotlin.idea.intentions.declarations
import com.intellij.codeInsight.CodeInsightUtilCore
import com.intellij.codeInsight.intention.impl.BaseIntentionAction
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.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.Function
import com.intellij.util.IncorrectOperationException
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
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.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR
public class ConvertMemberToExtension : BaseIntentionAction() {
override fun getText(): String {
return getFamilyName()
}
override fun getFamilyName(): String {
return JetBundle.message("convert.to.extension")
}
override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean {
val declaration = getTarget(editor, file)
if (declaration is JetProperty) {
if (declaration.hasInitializer()) return false
public class ConvertMemberToExtension : JetSelfTargetingRangeIntention<JetCallableDeclaration>(javaClass(), "Convert to extension") {
override fun applicabilityRange(element: JetCallableDeclaration): TextRange? {
val classBody = element.getParent() as? JetClassBody ?: return null
if (classBody.getParent() !is JetClass) return null
if (element.getReceiverTypeReference() != null) return null
when (element) {
is JetProperty -> if (element.hasInitializer()) return null
is JetSecondaryConstructor -> return null
}
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 invoke(project: Project, editor: Editor, file: PsiFile) {
val member = getTarget(editor, file)
assert(member != null, "Must be checked by isAvailable")
override fun applyTo(element: JetCallableDeclaration, editor: Editor) {
val descriptor = element.resolveToDescriptor()
val containingClass = descriptor.getContainingDeclaration() as ClassDescriptor
val bindingContext = member!!.analyzeFully()
val memberDescriptor = bindingContext.get<PsiElement, DeclarationDescriptor>(DECLARATION_TO_DESCRIPTOR, member) ?: return
val file = element.getContainingJetFile()
val outermostParent = JetPsiUtil.getOutermostParent(element, file, false)
val containingClass = memberDescriptor.getContainingDeclaration()
assert(containingClass is ClassDescriptor) { "Members must be contained in classes: \n" + "Descriptor: " + memberDescriptor + "\n" + "Declaration & context: " + member.getParent().getParent().getText() }
val receiver = containingClass.getDefaultType().toString() + "."
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 identifier = member.getNameIdentifier()
val name = if (identifier == null) "" else identifier.getText()
val extensionText = modifiers(element) +
memberType(element) +
" " +
typeParameters(element) +
receiver +
name +
(if (valueParameterList == null) "" else valueParameterList.getText()) +
(if (returnTypeRef != null) ": " + returnTypeRef.getText() else "") +
body(element)
val valueParameterList = member.getValueParameterList()
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 psiFactory = JetPsiFactory(element)
val extension = psiFactory.createDeclaration<JetDeclaration>(extensionText)
val added = file.addAfter(extension, outermostParent)
file.addAfter(psiFactory.createNewLine(), outermostParent)
file.addAfter(psiFactory.createNewLine(), outermostParent)
member.delete()
element.delete()
CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement<PsiElement>(added)
val caretAnchor = added.getText().indexOf(CARET_ANCHOR)
if (caretAnchor >= 0) {
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()) {
val throwException = psiFactory.createExpression(THROW_UNSUPPORTED_OPERATION_EXCEPTION)
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____"
public val THROW_UNSUPPORTED_OPERATION_EXCEPTION: String = " throw UnsupportedOperationException()"
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>())
private fun memberType(member: JetCallableDeclaration): String {
if (member is JetFunction) {
return "fun"
}
return (member as JetProperty).getValOrVarNode().getText()
}
private fun memberType(member: JetCallableDeclaration): String {
if (member is JetFunction) {
return "fun"
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() + " "
}
return (member as JetProperty).getValOrVarNode().getText()
}
return ""
}
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() + " "
}
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<JetTypeParameter>((classElement as JetClass).getTypeParameters(), member.getTypeParameters())
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 ""
}
}
private fun typeParameters(member: JetCallableDeclaration): String {
val classElement = member.getParent().getParent()
assert(classElement is JetClass) { "Must be checked in isAvailable: " + classElement.getText() }
private fun getter(property: JetProperty): String {
val getter = property.getGetter()
if (synthesizeBody(getter)) return "get() = " + CARET_ANCHOR
return getter!!.getText()
}
val allTypeParameters = ContainerUtil.concat<JetTypeParameter>((classElement as JetClass).getTypeParameters(), member.getTypeParameters())
if (allTypeParameters.isEmpty()) return ""
return "<" + StringUtil.join<JetTypeParameter>(allTypeParameters, object : Function<JetTypeParameter, String> {
override fun `fun`(parameter: JetTypeParameter): String {
return parameter.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 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 ""
}
}
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
}
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
class Owner {
fun <caret>() {}
@@ -1,6 +0,0 @@
// ERROR: Function declaration must have a name
class Owner {
}
fun Owner.() {
}