Converted ConvertMemberToExtension to Kotlin

This commit is contained in:
Valentin Kipyatkov
2015-05-11 19:51:25 +03:00
parent 518ea85c87
commit 04ec19cb4a
2 changed files with 175 additions and 211 deletions
@@ -1,211 +0,0 @@
/*
* Copyright 2010-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.tree.IElementType;
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.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.ResolvePackage;
import org.jetbrains.kotlin.lexer.JetModifierKeywordToken;
import org.jetbrains.kotlin.lexer.JetTokens;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import java.util.List;
import static org.jetbrains.kotlin.psi.PsiPackage.JetPsiFactory;
import static org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR;
public class ConvertMemberToExtension extends BaseIntentionAction {
public static final String CARET_ANCHOR = "____CARET_ANCHOR____";
public static final String THROW_UNSUPPORTED_OPERATION_EXCEPTION = " throw UnsupportedOperationException()";
@NotNull
@Override
public String getText() {
return getFamilyName();
}
@NotNull
@Override
public String getFamilyName() {
return JetBundle.message("convert.to.extension");
}
@Override
public boolean isAvailable(
@NotNull Project project, Editor editor, PsiFile file
) {
JetCallableDeclaration declaration = getTarget(editor, file);
if (declaration instanceof JetProperty) {
if (((JetProperty) declaration).hasInitializer()) return false;
}
return declaration != null
&& !(declaration instanceof JetSecondaryConstructor)
&& declaration.getParent() instanceof JetClassBody
&& declaration.getParent().getParent() instanceof JetClass
&& declaration.getReceiverTypeReference() == null;
}
private static JetCallableDeclaration getTarget(Editor editor, PsiFile file) {
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
return PsiTreeUtil.getParentOfType(element, JetCallableDeclaration.class, false, JetExpression.class);
}
@Override
public void invoke(
@NotNull Project project, Editor editor, PsiFile file
) throws IncorrectOperationException {
JetCallableDeclaration member = getTarget(editor, file);
assert member != null : "Must be checked by isAvailable";
BindingContext bindingContext = ResolvePackage.analyzeFully(member);
DeclarationDescriptor memberDescriptor = bindingContext.get(DECLARATION_TO_DESCRIPTOR, member);
if (memberDescriptor == null) return;
DeclarationDescriptor containingClass = memberDescriptor.getContainingDeclaration();
assert containingClass instanceof ClassDescriptor : "Members must be contained in classes: \n" +
"Descriptor: " + memberDescriptor + "\n" +
"Declaration & context: " + member.getParent().getParent().getText();
PsiElement outermostParent = JetPsiUtil.getOutermostParent(member, file, false);
String receiver = ((ClassDescriptor) containingClass).getDefaultType() + ".";
PsiElement identifier = member.getNameIdentifier();
String name = identifier == null ? "" : identifier.getText();
JetParameterList valueParameterList = member.getValueParameterList();
JetTypeReference returnTypeRef = member.getTypeReference();
String extensionText = modifiers(member) +
memberType(member) + " " +
typeParameters(member) +
receiver +
name +
(valueParameterList == null ? "" : valueParameterList.getText()) +
(returnTypeRef != null ? ": " + returnTypeRef.getText() : "") +
body(member);
JetPsiFactory psiFactory = JetPsiFactory(member);
JetDeclaration extension = psiFactory.<JetDeclaration>createDeclaration(extensionText);
PsiElement added = file.addAfter(extension, outermostParent);
file.addAfter(psiFactory.createNewLine(), outermostParent);
file.addAfter(psiFactory.createNewLine(), outermostParent);
member.delete();
CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(added);
int caretAnchor = added.getText().indexOf(CARET_ANCHOR);
if (caretAnchor >= 0) {
int caretOffset = added.getTextRange().getStartOffset() + caretAnchor;
JetSimpleNameExpression anchor = PsiTreeUtil.findElementOfClassAtOffset(file, caretOffset, JetSimpleNameExpression.class, false);
if (anchor != null && CARET_ANCHOR.equals(anchor.getReferencedName())) {
JetExpression throwException = psiFactory.createExpression(THROW_UNSUPPORTED_OPERATION_EXCEPTION);
PsiElement replaced = anchor.replace(throwException);
TextRange range = replaced.getTextRange();
editor.getCaretModel().moveToOffset(range.getStartOffset());
editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
}
}
}
private static String memberType(JetCallableDeclaration member) {
if (member instanceof JetFunction) {
return "fun";
}
return ((JetProperty) member).getValOrVarNode().getText();
}
private static String modifiers(JetCallableDeclaration member) {
JetModifierList modifierList = member.getModifierList();
if (modifierList == null) return "";
for (IElementType modifierType : JetTokens.VISIBILITY_MODIFIERS.getTypes()) {
PsiElement modifier = modifierList.getModifier((JetModifierKeywordToken) modifierType);
if (modifier != null) {
return modifierType == JetTokens.PROTECTED_KEYWORD ? "" : modifier.getText() + " ";
}
}
return "";
}
private static String typeParameters(JetCallableDeclaration member) {
PsiElement classElement = member.getParent().getParent();
assert classElement instanceof JetClass : "Must be checked in isAvailable: " + classElement.getText();
List<JetTypeParameter> allTypeParameters = ContainerUtil.concat(
((JetClass) classElement).getTypeParameters(),
member.getTypeParameters());
if (allTypeParameters.isEmpty()) return "";
return "<" + StringUtil.join(allTypeParameters, new Function<JetTypeParameter, String>() {
@Override
public String fun(JetTypeParameter parameter) {
return parameter.getText();
}
}, ", ") + "> ";
}
private static String body(JetCallableDeclaration member) {
if (member instanceof JetProperty) {
JetProperty property = (JetProperty) member;
return "\n" + getter(property) + "\n" + setter(property, !synthesizeBody(property.getGetter()));
}
else if (member instanceof JetFunction) {
JetFunction function = (JetFunction) member;
JetExpression bodyExpression = function.getBodyExpression();
if (bodyExpression == null) return "{" + CARET_ANCHOR + "}";
if (!function.hasBlockBody()) return " = " + bodyExpression.getText();
return bodyExpression.getText();
}
else {
return "";
}
}
private static String getter(JetProperty property) {
JetPropertyAccessor getter = property.getGetter();
if (synthesizeBody(getter)) return "get() = " + CARET_ANCHOR;
return getter.getText();
}
private static String setter(JetProperty property, boolean allowCaretAnchor) {
if (!property.isVar()) return "";
JetPropertyAccessor setter = property.getSetter();
if (synthesizeBody(setter)) return "set(value) {" + (allowCaretAnchor ? CARET_ANCHOR : THROW_UNSUPPORTED_OPERATION_EXCEPTION) + "}";
return setter.getText();
}
private static boolean synthesizeBody(@Nullable JetPropertyAccessor getter) {
return getter == null || getter.getBodyExpression() == null;
}
}
@@ -0,0 +1,175 @@
/*
* Copyright 2010-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.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.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
}
return declaration != null && declaration !is JetSecondaryConstructor && declaration.getParent() is JetClassBody && declaration.getParent().getParent() is JetClass && declaration.getReceiverTypeReference() == null
}
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")
val bindingContext = member!!.analyzeFully()
val memberDescriptor = bindingContext.get<PsiElement, DeclarationDescriptor>(DECLARATION_TO_DESCRIPTOR, member) ?: return
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 outermostParent = JetPsiUtil.getOutermostParent(member, file, false)
val receiver = (containingClass as ClassDescriptor).getDefaultType().toString() + "."
val identifier = member.getNameIdentifier()
val name = if (identifier == null) "" else identifier.getText()
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 extension = psiFactory.createDeclaration<JetDeclaration>(extensionText)
val added = file.addAfter(extension, outermostParent)
file.addAfter(psiFactory.createNewLine(), outermostParent)
file.addAfter(psiFactory.createNewLine(), outermostParent)
member.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)
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())
}
}
}
companion object {
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 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 ""
}
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 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
}
}
}