Extract Function: Add support of initializer expressions

This commit is contained in:
Alexey Sedunov
2014-06-26 18:50:22 +04:00
parent 026bc05a86
commit 0c49d48a48
30 changed files with 302 additions and 12 deletions
@@ -53,6 +53,11 @@ import com.intellij.openapi.ui.popup.Balloon.Position
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.lang.psi.JetDeclaration
import java.util.Collections
import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.lang.psi.JetParameterList
import org.jetbrains.jet.lang.psi.JetClassInitializer
import org.jetbrains.jet.lang.psi.JetFunctionLiteralExpression
import org.jetbrains.jet.lang.psi.JetFunctionLiteral
public class ExtractKotlinFunctionHandler(public val allContainersEnabled: Boolean = false) : RefactoringActionHandler {
fun doInvoke(
@@ -178,7 +183,13 @@ fun selectElements(
val declaration = element.getParentByType(javaClass<JetDeclaration>(), strict)
if (declaration == null) return Collections.emptyList()
val parent = declaration.getParent()
val parent = declaration.getParent()?.let {
when (it) {
is JetProperty -> it.getParent()
is JetParameterList -> it.getParent()?.getParent()
else -> it
}
}
return when (parent) {
is JetFile -> Collections.singletonList(parent)
is JetClassBody -> {
@@ -189,7 +200,13 @@ fun selectElements(
.dropWhile { it !is JetClassBody }
}
else -> {
val targetContainer = parent?.getParentByType(javaClass<JetDeclarationWithBody>())?.getBodyExpression()
val enclosingDeclaration =
PsiTreeUtil.getNonStrictParentOfType(parent, javaClass<JetDeclarationWithBody>(), javaClass<JetClassInitializer>())
val targetContainer = when (enclosingDeclaration) {
is JetDeclarationWithBody -> enclosingDeclaration.getBodyExpression()
is JetClassInitializer -> enclosingDeclaration.getBody()
else -> null
}
if (targetContainer is JetBlockExpression) Collections.singletonList(targetContainer) else Collections.emptyList()
}
}
@@ -49,6 +49,7 @@ import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor
import org.jetbrains.jet.lang.psi.JetPsiFactory
import org.jetbrains.jet.lang.resolve.BindingContextUtils
import org.jetbrains.jet.lang.psi.JetFunctionLiteral
import org.jetbrains.jet.lang.psi.JetClassInitializer
import org.jetbrains.jet.lang.resolve.bindingContextUtil.getResolvedCall
import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils
@@ -79,7 +80,9 @@ class ExtractionData(
) {
val project: Project = originalFile.getProject()
val insertBefore: Boolean = targetSibling.getParentByType(javaClass<JetDeclaration>(), true) is JetDeclarationWithBody
val insertBefore: Boolean = targetSibling.getParentByType(javaClass<JetDeclaration>(), true)?.let {
it is JetDeclarationWithBody || it is JetClassInitializer
} ?: false
fun getExpressions(): List<JetExpression> = originalElements.filterIsInstance(javaClass<JetExpression>())
@@ -541,17 +541,28 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_EXPRESSION))
}
val noContainerError = AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_CONTAINER))
val commonParent = PsiTreeUtil.findCommonParent(originalElements)!!
val enclosingDeclaration = commonParent.getParentByType(javaClass<JetDeclarationWithBody>())
if (enclosingDeclaration == null) {
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(ErrorMessage.NO_CONTAINER))
val enclosingDeclaration = commonParent.getParentByType(javaClass<JetDeclaration>(), true)
val bodyElement = when (enclosingDeclaration) {
is JetDeclarationWithBody -> enclosingDeclaration.getBodyExpression()
is JetWithExpressionInitializer -> enclosingDeclaration.getInitializer()
is JetParameter -> enclosingDeclaration.getDefaultValue()
is JetClassInitializer -> enclosingDeclaration.getBody()
is JetClass -> {
if (commonParent.isInsideOf(enclosingDeclaration.getDelegationSpecifiers())) enclosingDeclaration else return noContainerError
}
else -> return noContainerError
}
val bindingContext = originalFile.getLazyResolveSession().resolveToElement(bodyElement)
val resolveSession = originalFile.getLazyResolveSession()
val bindingContext = resolveSession.resolveToElement(enclosingDeclaration.getBodyExpression())
val pseudocode = PseudocodeUtil.generatePseudocode(enclosingDeclaration, bindingContext)
val pseudocodeDeclaration = PsiTreeUtil.getParentOfType(
commonParent, javaClass<JetDeclarationWithBody>(), javaClass<JetClassOrObject>()
) ?: commonParent.getParentByType(javaClass<JetProperty>())
?: return noContainerError
val pseudocode = PseudocodeUtil.generatePseudocode(pseudocodeDeclaration, bindingContext)
val localInstructions = getLocalInstructions(pseudocode)
val replacementMap = HashMap<Int, Replacement>()
@@ -587,7 +598,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
val functionNameValidator =
JetNameValidatorImpl(
targetSibling.getParent(),
targetSibling,
if (targetSibling is JetClassInitializer) targetSibling.getParent() else targetSibling,
JetNameValidatorImpl.Target.FUNCTIONS_AND_CLASSES
)
val functionName = JetNameSuggester.suggestNames(controlFlow.returnType, functionNameValidator, DEFAULT_FUNCTION_NAME).first()
@@ -0,0 +1,3 @@
class A(val a: Int, val b: Int) {
val foo: Int get() = <selection>a + b</selection> - 1
}
@@ -0,0 +1,7 @@
class A(val a: Int, val b: Int) {
val foo: Int get() = i() - 1
fun i(): Int {
return a + b
}
}
@@ -0,0 +1,3 @@
val a = 1
val b = 1
val foo: Int get() = <selection>a + b</selection> - 1
@@ -0,0 +1,7 @@
val a = 1
val b = 1
val foo: Int get() = i() - 1
fun i(): Int {
return a + b
}
@@ -0,0 +1,6 @@
// PARAM_TYPES: kotlin.Int
class A(val a: Int, b: Int) {
{
println(<selection>a + b</selection> - 1)
}
}
@@ -0,0 +1,10 @@
// PARAM_TYPES: kotlin.Int
class A(val a: Int, b: Int) {
{
println(i(b) - 1)
}
fun i(b: Int): Int {
return a + b
}
}
@@ -0,0 +1,3 @@
// PARAM_TYPES: kotlin.Int
val n = 1
class A(val a: Int, val b: Int = <selection>a + n</selection>)
@@ -0,0 +1,7 @@
// PARAM_TYPES: kotlin.Int
val n = 1
class A(val a: Int, val b: Int = i(a))
fun i(a: Int): Int {
return a + n
}
@@ -0,0 +1,5 @@
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
fun bar(n: Int) {
fun foo(a: Int, b: Int) = <selection>a + b - n</selection> - 1
}
@@ -0,0 +1,9 @@
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
fun bar(n: Int) {
fun i(a: Int, b: Int): Int {
return a + b - n
}
fun foo(a: Int, b: Int) = i(a, b) - 1
}
@@ -0,0 +1,4 @@
// PARAM_TYPES: kotlin.Int
fun bar(n: Int) {
fun foo(a: Int, b: Int = <selection>a + n</selection>) = a + b - n - 1
}
@@ -0,0 +1 @@
Cannot extract method since following types are not denotable in the target scope: [ERROR : ]
@@ -0,0 +1,5 @@
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
class A(val n: Int) {
fun foo(a: Int, b: Int) = <selection>a + b - n</selection> - 1
}
@@ -0,0 +1,9 @@
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
class A(val n: Int) {
fun foo(a: Int, b: Int) = i(a, b) - 1
fun i(a: Int, b: Int): Int {
return a + b - n
}
}
@@ -0,0 +1,4 @@
// PARAM_TYPES: kotlin.Int
class A(val n: Int) {
fun foo(a: Int, b: Int = <selection>a + n</selection>) = a + b - n - 1
}
@@ -0,0 +1,8 @@
// PARAM_TYPES: kotlin.Int
class A(val n: Int) {
fun foo(a: Int, b: Int = i(a)) = a + b - n - 1
fun i(a: Int): Int {
return a + n
}
}
@@ -0,0 +1,3 @@
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
fun foo(a: Int, b: Int) = <selection>a + b</selection> - 1
@@ -0,0 +1,7 @@
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
fun foo(a: Int, b: Int) = i(a, b) - 1
fun i(a: Int, b: Int): Int {
return a + b
}
@@ -0,0 +1,3 @@
// PARAM_TYPES: kotlin.Int
val n = 1
fun foo(a: Int, b: Int = <selection>a + n</selection>) = a + b - 1
@@ -0,0 +1,7 @@
// PARAM_TYPES: kotlin.Int
val n = 1
fun foo(a: Int, b: Int = i(a)) = a + b - 1
fun i(a: Int): Int {
return a + n
}
@@ -0,0 +1,3 @@
fun bar(a: Int, b: Int) {
val foo = <selection>a + b</selection> - 1
}
@@ -0,0 +1,7 @@
fun bar(a: Int, b: Int) {
fun i(): Int {
return a + b
}
val foo = i() - 1
}
@@ -0,0 +1,4 @@
// PARAM_TYPES: kotlin.Int
class A(val a: Int, b: Int) {
val foo = <selection>a + b</selection> - 1
}
@@ -0,0 +1,8 @@
// PARAM_TYPES: kotlin.Int
class A(val a: Int, b: Int) {
val foo = i(b) - 1
fun i(b: Int): Int {
return a + b
}
}
@@ -0,0 +1,3 @@
val a = 1
val b = 1
val foo = <selection>a + b</selection> - 1
@@ -0,0 +1,7 @@
val a = 1
val b = 1
val foo = i() - 1
fun i(): Int {
return a + b
}
@@ -211,7 +211,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
}
@TestMetadata("idea/testData/refactoring/extractFunction")
@InnerTestClasses({ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class})
@InnerTestClasses({ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class})
public static class ExtractFunction extends AbstractJetExtractionTest {
public void testAllFilesPresentInExtractFunction() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction"), Pattern.compile("^(.+)\\.kt$"), true);
@@ -685,6 +685,121 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
}
@TestMetadata("idea/testData/refactoring/extractFunction/initializers")
@InnerTestClasses({Initializers.Accessors.class, Initializers.Classes.class, Initializers.Functions.class, Initializers.Properties.class})
public static class Initializers extends AbstractJetExtractionTest {
public void testAllFilesPresentInInitializers() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/initializers"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("idea/testData/refactoring/extractFunction/initializers/accessors")
public static class Accessors extends AbstractJetExtractionTest {
public void testAllFilesPresentInAccessors() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/initializers/accessors"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("memberProperty.kt")
public void testMemberProperty() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/accessors/memberProperty.kt");
}
@TestMetadata("topLevelProperty.kt")
public void testTopLevelProperty() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/accessors/topLevelProperty.kt");
}
}
@TestMetadata("idea/testData/refactoring/extractFunction/initializers/classes")
public static class Classes extends AbstractJetExtractionTest {
public void testAllFilesPresentInClasses() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/initializers/classes"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("classInitializer.kt")
public void testClassInitializer() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/classes/classInitializer.kt");
}
@TestMetadata("classParameters.kt")
public void testClassParameters() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/classes/classParameters.kt");
}
}
@TestMetadata("idea/testData/refactoring/extractFunction/initializers/functions")
public static class Functions extends AbstractJetExtractionTest {
public void testAllFilesPresentInFunctions() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/initializers/functions"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("localFunction.kt")
public void testLocalFunction() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/functions/localFunction.kt");
}
@TestMetadata("localFunctionParameters.kt")
public void testLocalFunctionParameters() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/functions/localFunctionParameters.kt");
}
@TestMetadata("memberFunction.kt")
public void testMemberFunction() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/functions/memberFunction.kt");
}
@TestMetadata("memberFunctionParameters.kt")
public void testMemberFunctionParameters() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/functions/memberFunctionParameters.kt");
}
@TestMetadata("topLevelFunction.kt")
public void testTopLevelFunction() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/functions/topLevelFunction.kt");
}
@TestMetadata("topLevelFunctionParameters.kt")
public void testTopLevelFunctionParameters() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/functions/topLevelFunctionParameters.kt");
}
}
@TestMetadata("idea/testData/refactoring/extractFunction/initializers/properties")
public static class Properties extends AbstractJetExtractionTest {
public void testAllFilesPresentInProperties() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/initializers/properties"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("localProperty.kt")
public void testLocalProperty() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/properties/localProperty.kt");
}
@TestMetadata("memberProperty.kt")
public void testMemberProperty() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/properties/memberProperty.kt");
}
@TestMetadata("topLevelProperty.kt")
public void testTopLevelProperty() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/initializers/properties/topLevelProperty.kt");
}
}
public static Test innerSuite() {
TestSuite suite = new TestSuite("Initializers");
suite.addTestSuite(Initializers.class);
suite.addTestSuite(Accessors.class);
suite.addTestSuite(Classes.class);
suite.addTestSuite(Functions.class);
suite.addTestSuite(Properties.class);
return suite;
}
}
@TestMetadata("idea/testData/refactoring/extractFunction/parameters")
@InnerTestClasses({Parameters.CandidateTypes.class, Parameters.ExtractSuper.class, Parameters.ExtractThis.class, Parameters.It.class, Parameters.Misc.class, Parameters.NonDenotableTypes.class})
public static class Parameters extends AbstractJetExtractionTest {
@@ -1022,6 +1137,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
suite.addTestSuite(Basic.class);
suite.addTest(ControlFlow.innerSuite());
suite.addTestSuite(DefaultContainer.class);
suite.addTest(Initializers.innerSuite());
suite.addTest(Parameters.innerSuite());
suite.addTestSuite(TypeParameters.class);
return suite;