Intention to convert a member to an extension

This commit is contained in:
Andrey Breslav
2013-08-26 20:27:27 +04:00
parent acfb85e3fd
commit c02f0a7d04
52 changed files with 472 additions and 3 deletions
@@ -183,6 +183,9 @@ public interface JetTokens {
OPEN_KEYWORD, INNER_KEYWORD, ANNOTATION_KEYWORD, OVERRIDE_KEYWORD, PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD,
PROTECTED_KEYWORD, OUT_KEYWORD, IN_KEYWORD, FINAL_KEYWORD, VARARG_KEYWORD, INLINE_KEYWORD, REIFIED_KEYWORD
);
TokenSet VISIBILITY_MODIFIERS = TokenSet.create(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD);
TokenSet WHITESPACES = TokenSet.create(TokenType.WHITE_SPACE);
/**
@@ -371,6 +371,7 @@ public class GenerateTests {
testModel("idea/testData/intentions/branched/when/eliminateSubject", "doTestEliminateWhenSubject"),
testModel("idea/testData/intentions/declarations/split", "doTestSplitProperty"),
testModel("idea/testData/intentions/declarations/join", "doTestJoinProperty"),
testModel("idea/testData/intentions/declarations/convertMemberToExtension", "doTestConvertMemberToExtension"),
testModel("idea/testData/intentions/removeUnnecessaryParentheses", "doTestRemoveUnnecessaryParentheses")
);
@@ -0,0 +1,6 @@
class Foo<T> {
}
<spot>fun <T> Foo<T>.get(): T {
return t
}</spot>
@@ -0,0 +1,5 @@
class Foo<T> {
<spot> fun get(): T {
return t
}</spot>
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts a member of a class or trait into an extension to this class/trait.
</body>
</html>
+5
View File
@@ -405,6 +405,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.jet.plugin.intentions.declarations.ConvertMemberToExtension</className>
<category>Kotlin</category>
</intentionAction>
<project.converterProvider implementation="org.jetbrains.jet.plugin.converters.JetRunConfigurationSettingsFormatConverterProvider"/>
</extensions>
@@ -269,4 +269,6 @@ super.methods.action.key.find.usages=find usages of
find.what.implementing.methods.checkbox=&Implementing functions
find.what.overriding.methods.checkbox=Over&riding functions
find.options.include.overloaded.methods.checkbox=Include o&verloaded functions and extensions
find.options.include.overloaded.methods.checkbox=Include o&verloaded functions and extensions
convert.to.extension=Convert to extension
@@ -0,0 +1,152 @@
/*
* Copyright 2010-2013 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.jet.plugin.intentions.declarations;
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.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.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lexer.JetToken;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.JetBundle;
import org.jetbrains.jet.plugin.caches.resolve.KotlinCacheManagerUtil;
import java.util.List;
public class ConvertMemberToExtension extends BaseIntentionAction {
@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
) {
JetNamedFunction function = getTarget(editor, file);
return function != null
&& function.getParent() instanceof JetClassBody
&& function.getParent().getParent() instanceof JetClass
&& function.getReceiverTypeRef() == null;
}
private static JetNamedFunction getTarget(Editor editor, PsiFile file) {
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
return PsiTreeUtil.getParentOfType(element, JetNamedFunction.class, false, JetExpression.class);
}
@Override
public void invoke(
@NotNull Project project, Editor editor, PsiFile file
) throws IncorrectOperationException {
JetNamedFunction member = getTarget(editor, file);
assert member != null : "Must be checked by isAvailable";
BindingContext bindingContext = KotlinCacheManagerUtil.getDeclarationsBindingContext(member);
SimpleFunctionDescriptor memberDescriptor = bindingContext.get(BindingContext.FUNCTION, member);
if (memberDescriptor == null) return;
assert memberDescriptor.getReceiverParameter() == null : "Must have checked that there's no receiver earlier\n" +
"Descriptor: " + memberDescriptor + "\n" +
"Declaration: " + member.getText();
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.getReturnTypeRef();
String receiverAndTheRest = receiver +
name +
(valueParameterList == null ? "" : valueParameterList.getText()) +
(returnTypeRef != null ? ": " + returnTypeRef.getText() : "") +
textOfBody(member);
String extensionText = modifiers(member) + "fun " + typeParameters(member) + receiverAndTheRest;
JetNamedFunction extension = JetPsiFactory.createFunction(project, extensionText);
file.addAfter(extension, outermostParent);
file.addAfter(JetPsiFactory.createNewLine(project), outermostParent);
file.addAfter(JetPsiFactory.createNewLine(project), outermostParent);
member.delete();
}
private static String modifiers(JetNamedFunction member) {
JetModifierList modifierList = member.getModifierList();
if (modifierList == null) return "";
for (IElementType modifierType : JetTokens.VISIBILITY_MODIFIERS.getTypes()) {
PsiElement modifier = modifierList.getModifier((JetToken) modifierType);
if (modifier != null) {
return modifierType == JetTokens.PROTECTED_KEYWORD ? "" : modifier.getText() + " ";
}
}
return "";
}
private static String typeParameters(JetNamedFunction 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 textOfBody(JetNamedFunction member) {
JetExpression bodyExpression = member.getBodyExpression();
if (bodyExpression == null) return "{}";
if (!member.hasBlockBody()) return " = " + bodyExpression.getText();
return bodyExpression.getText();
}
}
@@ -0,0 +1,3 @@
abstract class Owner {
abstract fun <caret>f()
}
@@ -0,0 +1,5 @@
abstract class Owner {
}
fun Owner.f() {
}
@@ -0,0 +1,3 @@
class Owner {
fun <caret>f(): Unit {}
}
@@ -0,0 +1,5 @@
class Owner {
}
fun Owner.f(): Unit {
}
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
abstract class Owner {
fun Int.<caret>f() {}
}
@@ -0,0 +1,3 @@
class Owner {
fun <caret>f(p: () -> Unit): (Int) -> String {}
}
@@ -0,0 +1,5 @@
class Owner {
}
fun Owner.f(p: () -> Unit): (Int) -> String {
}
@@ -0,0 +1,3 @@
abstract class Owner<T> {
fun <caret>f(t: T): T = t
}
@@ -0,0 +1,4 @@
abstract class Owner<T> {
}
fun <T> Owner<T>.f(t: T): T = t
@@ -0,0 +1,3 @@
abstract class Owner {
fun <T> <caret>f(t: T): T = t
}
@@ -0,0 +1,4 @@
abstract class Owner {
}
fun <T> Owner.f(t: T): T = t
@@ -0,0 +1,3 @@
abstract class Owner<T> {
fun <caret>f<R>(t: T, r: R): R = t
}
@@ -0,0 +1,4 @@
abstract class Owner<T> {
}
fun <T, R> Owner<T>.f(t: T, r: R): R = t
@@ -0,0 +1,3 @@
abstract class Owner<T, X: List<T>> {
fun <R: T> <caret>f(t: T, r: R): R = t
}
@@ -0,0 +1,4 @@
abstract class Owner<T, X: List<T>> {
}
fun <T, X : List<T>, R : T> Owner<T, X>.f(t: T, r: R): R = t
@@ -0,0 +1,3 @@
abstract class Owner {
fun <caret>f<T>(t: T): T = t
}
@@ -0,0 +1,4 @@
abstract class Owner {
}
fun <T> Owner.f(t: T): T = t
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
abstract class Owner {
fun f() {<caret>}
}
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
object Owner {
fun <caret>f() {}
}
@@ -0,0 +1,3 @@
abstract class Owner {
internal fun <caret>f() = 1
}
@@ -0,0 +1,4 @@
abstract class Owner {
}
internal fun Owner.f() = 1
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
class Owner {
fun f() {
fun <caret>f() {}
}
}
@@ -0,0 +1,3 @@
class Owner {
fun <caret>f() = 1
}
@@ -0,0 +1,4 @@
class Owner {
}
fun Owner.f() = 1
@@ -0,0 +1,3 @@
class Owner {
fun <caret>f(): Int = 1
}
@@ -0,0 +1,4 @@
class Owner {
}
fun Owner.f(): Int = 1
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
abstract class <caret>Owner {
abstract fun f()
}
@@ -0,0 +1,3 @@
abstract class Owner {
private fun <caret>f() = 1
}
@@ -0,0 +1,4 @@
abstract class Owner {
}
private fun Owner.f() = 1
@@ -0,0 +1,3 @@
abstract class Owner {
protected fun <caret>f() = 1
}
@@ -0,0 +1,4 @@
abstract class Owner {
}
fun Owner.f() = 1
@@ -0,0 +1,3 @@
abstract class Owner {
public fun <caret>f() = 1
}
@@ -0,0 +1,4 @@
abstract class Owner {
}
public fun Owner.f() = 1
@@ -0,0 +1,3 @@
class Owner {
fun <caret>f() {}
}
@@ -0,0 +1,5 @@
class Owner {
}
fun Owner.f() {
}
@@ -0,0 +1,2 @@
// IS_APPLICABLE: false
fun <caret>f() {}
@@ -0,0 +1,3 @@
class Owner {
fun <caret>f(p: jet.Int): jet.String {}
}
@@ -0,0 +1,5 @@
class Owner {
}
fun Owner.f(p: jet.Int): jet.String {
}
@@ -0,0 +1,3 @@
class Owner {
fun <caret>f(p: Foo): bar.Baz {}
}
@@ -0,0 +1,5 @@
class Owner {
}
fun Owner.f(p: Foo): bar.Baz {
}
@@ -0,0 +1,3 @@
class Owner {
fun <caret>f(): Int {}
}
@@ -0,0 +1,5 @@
class Owner {
}
fun Owner.f(): Int {
}
@@ -23,8 +23,8 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.testFramework.LightCodeInsightTestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.InTextDirectivesUtils;
import org.jetbrains.jet.plugin.intentions.RemoveUnnecessaryParenthesesIntention;
import org.jetbrains.jet.plugin.intentions.branchedTransformations.intentions.*;
import org.jetbrains.jet.plugin.intentions.declarations.ConvertMemberToExtension;
import org.jetbrains.jet.plugin.intentions.declarations.SplitPropertyDeclarationIntention;
import java.io.File;
@@ -106,6 +106,10 @@ public abstract class AbstractCodeTransformationTest extends LightCodeInsightTes
doTestIntention(path, new RemoveUnnecessaryParenthesesIntention());
}
public void doTestConvertMemberToExtension(@NotNull String path) throws Exception {
doTestIntention(path, new ConvertMemberToExtension());
}
private void doTestIntention(@NotNull String path, @NotNull IntentionAction intentionAction) throws Exception {
configureByFile(path);
@@ -30,7 +30,7 @@ import org.jetbrains.jet.plugin.intentions.AbstractCodeTransformationTest;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@InnerTestClasses({CodeTransformationsTestGenerated.IfToAssignment.class, CodeTransformationsTestGenerated.IfToReturn.class, CodeTransformationsTestGenerated.IfToReturnAsymmetrically.class, CodeTransformationsTestGenerated.WhenToAssignment.class, CodeTransformationsTestGenerated.WhenToReturn.class, CodeTransformationsTestGenerated.AssignmentToIf.class, CodeTransformationsTestGenerated.AssignmentToWhen.class, CodeTransformationsTestGenerated.PropertyToIf.class, CodeTransformationsTestGenerated.PropertyToWhen.class, CodeTransformationsTestGenerated.ReturnToIf.class, CodeTransformationsTestGenerated.ReturnToWhen.class, CodeTransformationsTestGenerated.IfToWhen.class, CodeTransformationsTestGenerated.WhenToIf.class, CodeTransformationsTestGenerated.Flatten.class, CodeTransformationsTestGenerated.IntroduceSubject.class, CodeTransformationsTestGenerated.EliminateSubject.class, CodeTransformationsTestGenerated.Split.class, CodeTransformationsTestGenerated.Join.class, CodeTransformationsTestGenerated.RemoveUnnecessaryParentheses.class})
@InnerTestClasses({CodeTransformationsTestGenerated.IfToAssignment.class, CodeTransformationsTestGenerated.IfToReturn.class, CodeTransformationsTestGenerated.IfToReturnAsymmetrically.class, CodeTransformationsTestGenerated.WhenToAssignment.class, CodeTransformationsTestGenerated.WhenToReturn.class, CodeTransformationsTestGenerated.AssignmentToIf.class, CodeTransformationsTestGenerated.AssignmentToWhen.class, CodeTransformationsTestGenerated.PropertyToIf.class, CodeTransformationsTestGenerated.PropertyToWhen.class, CodeTransformationsTestGenerated.ReturnToIf.class, CodeTransformationsTestGenerated.ReturnToWhen.class, CodeTransformationsTestGenerated.IfToWhen.class, CodeTransformationsTestGenerated.WhenToIf.class, CodeTransformationsTestGenerated.Flatten.class, CodeTransformationsTestGenerated.IntroduceSubject.class, CodeTransformationsTestGenerated.EliminateSubject.class, CodeTransformationsTestGenerated.Split.class, CodeTransformationsTestGenerated.Join.class, CodeTransformationsTestGenerated.ConvertMemberToExtension.class, CodeTransformationsTestGenerated.RemoveUnnecessaryParentheses.class})
public class CodeTransformationsTestGenerated extends AbstractCodeTransformationTest {
@TestMetadata("idea/testData/intentions/branched/folding/ifToAssignment")
public static class IfToAssignment extends AbstractCodeTransformationTest {
@@ -791,6 +791,134 @@ public class CodeTransformationsTestGenerated extends AbstractCodeTransformation
}
@TestMetadata("idea/testData/intentions/declarations/convertMemberToExtension")
public static class ConvertMemberToExtension extends AbstractCodeTransformationTest {
@TestMetadata("abstract.kt")
public void testAbstract() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/abstract.kt");
}
public void testAllFilesPresentInConvertMemberToExtension() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/intentions/declarations/convertMemberToExtension"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("explicitUnit.kt")
public void testExplicitUnit() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/explicitUnit.kt");
}
@TestMetadata("extension.kt")
public void testExtension() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/extension.kt");
}
@TestMetadata("functionType.kt")
public void testFunctionType() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/functionType.kt");
}
@TestMetadata("genericClass.kt")
public void testGenericClass() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/genericClass.kt");
}
@TestMetadata("genericFun.kt")
public void testGenericFun() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/genericFun.kt");
}
@TestMetadata("genericFunInGenericClass.kt")
public void testGenericFunInGenericClass() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/genericFunInGenericClass.kt");
}
@TestMetadata("genericFunInGenericClassWithUpperBounds.kt")
public void testGenericFunInGenericClassWithUpperBounds() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/genericFunInGenericClassWithUpperBounds.kt");
}
@TestMetadata("genericFunParamAfterName.kt")
public void testGenericFunParamAfterName() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/genericFunParamAfterName.kt");
}
@TestMetadata("inFunctionBody.kt")
public void testInFunctionBody() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/inFunctionBody.kt");
}
@TestMetadata("inObject.kt")
public void testInObject() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/inObject.kt");
}
@TestMetadata("internal.kt")
public void testInternal() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/internal.kt");
}
@TestMetadata("localFunction.kt")
public void testLocalFunction() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/localFunction.kt");
}
@TestMetadata("nonBlockBodyNoType.kt")
public void testNonBlockBodyNoType() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/nonBlockBodyNoType.kt");
}
@TestMetadata("nonBlockBodyWithType.kt")
public void testNonBlockBodyWithType() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/nonBlockBodyWithType.kt");
}
@TestMetadata("outsideFunction.kt")
public void testOutsideFunction() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/outsideFunction.kt");
}
@TestMetadata("private.kt")
public void testPrivate() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/private.kt");
}
@TestMetadata("protected.kt")
public void testProtected() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/protected.kt");
}
@TestMetadata("public.kt")
public void testPublic() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/public.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/simple.kt");
}
@TestMetadata("topLevelFunction.kt")
public void testTopLevelFunction() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/topLevelFunction.kt");
}
@TestMetadata("typeFqName.kt")
public void testTypeFqName() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/typeFqName.kt");
}
@TestMetadata("unknownType.kt")
public void testUnknownType() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/unknownType.kt");
}
@TestMetadata("withType.kt")
public void testWithType() throws Exception {
doTestConvertMemberToExtension("idea/testData/intentions/declarations/convertMemberToExtension/withType.kt");
}
}
@TestMetadata("idea/testData/intentions/removeUnnecessaryParentheses")
public static class RemoveUnnecessaryParentheses extends AbstractCodeTransformationTest {
public void testAllFilesPresentInRemoveUnnecessaryParentheses() throws Exception {
@@ -879,6 +1007,7 @@ public class CodeTransformationsTestGenerated extends AbstractCodeTransformation
suite.addTestSuite(EliminateSubject.class);
suite.addTestSuite(Split.class);
suite.addTestSuite(Join.class);
suite.addTestSuite(ConvertMemberToExtension.class);
suite.addTestSuite(RemoveUnnecessaryParentheses.class);
return suite;
}