Better implementation of "Move lambda outside parenthesis"

This commit is contained in:
Valentin Kipyatkov
2015-05-22 13:54:52 +03:00
parent 3bca1d1f83
commit d7c64d44b6
10 changed files with 57 additions and 49 deletions
@@ -21,19 +21,18 @@ import com.intellij.psi.PsiWhiteSpace
public class JetFunctionLiteralArgument(node: ASTNode) : JetValueArgument(node), FunctionLiteralArgument { public class JetFunctionLiteralArgument(node: ASTNode) : JetValueArgument(node), FunctionLiteralArgument {
private fun assertFL() = throw AssertionError("Function literal argument doesn't contain function literal expression: " + override fun getArgumentExpression() = super<JetValueArgument>.getArgumentExpression()!!
"${super<JetValueArgument>.getArgumentExpression()?.getText()} (it should be guaranteed by parser)")
override fun getArgumentExpression() = super<JetValueArgument>.getArgumentExpression() ?: assertFL() override fun getFunctionLiteral(): JetFunctionLiteralExpression = getArgumentExpression().unpackFunctionLiteral()!!
override fun getFunctionLiteral(): JetFunctionLiteralExpression = unpackFunctionLiteral(getArgumentExpression())
private fun unpackFunctionLiteral(expression: JetExpression?): JetFunctionLiteralExpression =
when (expression) {
is JetFunctionLiteralExpression -> expression
is JetLabeledExpression -> unpackFunctionLiteral(expression.getBaseExpression())
is JetAnnotatedExpression -> unpackFunctionLiteral(expression.getBaseExpression())
else -> assertFL()
}
} }
public fun JetExpression.unpackFunctionLiteral(): JetFunctionLiteralExpression? {
return when (this) {
is JetFunctionLiteralExpression -> this
is JetLabeledExpression -> getBaseExpression()?.unpackFunctionLiteral()
is JetAnnotatedExpression -> getBaseExpression()?.unpackFunctionLiteral()
else -> null
}
}
@@ -95,7 +95,10 @@ public fun JetPsiFactory.createExpressionByPattern(pattern: String, vararg args:
CodeEditUtil.setNodeGeneratedRecursively(expression.getNode(), false) CodeEditUtil.setNodeGeneratedRecursively(expression.getNode(), false)
for ((pointer, n) in pointers) { for ((pointer, n) in pointers) {
val element = pointer.getElement()!! var element = pointer.getElement()!!
if (element is JetFunctionLiteral) {
element = element.getParent() as JetFunctionLiteralExpression
}
element.replace(args[n] as PsiElement) element.replace(args[n] as PsiElement)
} }
@@ -20,7 +20,7 @@ import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.util.psiModificationUtil.moveLambdaOutsideParentheses import org.jetbrains.kotlin.idea.util.psiModificationUtil.moveFunctionLiteralOutsideParentheses
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
@@ -29,9 +29,10 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
public class MoveLambdaOutsideParenthesesIntention : JetSelfTargetingIntention<JetCallExpression>(javaClass(), "Move lambda argument out of parentheses") { public class MoveLambdaOutsideParenthesesIntention : JetSelfTargetingIntention<JetCallExpression>(javaClass(), "Move lambda argument out of parentheses") {
override fun isApplicableTo(element: JetCallExpression, caretOffset: Int): Boolean { override fun isApplicableTo(element: JetCallExpression, caretOffset: Int): Boolean {
val argument = element.getValueArgumentsInParentheses().lastOrNull() ?: return false if (element.getFunctionLiteralArguments().isNotEmpty()) return false
val argument = element.getValueArguments().lastOrNull() ?: return false
val expression = argument.getArgumentExpression() ?: return false val expression = argument.getArgumentExpression() ?: return false
val functionLiteral = getFunctionLiteral(expression) ?: return false val functionLiteral = expression.unpackFunctionLiteral() ?: return false
val callee = element.getCalleeExpression() val callee = element.getCalleeExpression()
if (callee is JetSimpleNameExpression) { if (callee is JetSimpleNameExpression) {
@@ -54,15 +55,7 @@ public class MoveLambdaOutsideParenthesesIntention : JetSelfTargetingIntention<J
return !bodyRange.containsInside(caretOffset) return !bodyRange.containsInside(caretOffset)
} }
private fun getFunctionLiteral(expression: JetExpression?): JetFunctionLiteral? {
return when (expression) {
is JetFunctionLiteralExpression -> expression.getFunctionLiteral()
is JetLabeledExpression -> getFunctionLiteral(expression.getBaseExpression())
else -> null
}
}
override fun applyTo(element: JetCallExpression, editor: Editor) { override fun applyTo(element: JetCallExpression, editor: Editor) {
element.moveLambdaOutsideParentheses() element.moveFunctionLiteralOutsideParentheses()
} }
} }
@@ -391,8 +391,9 @@ public class JetFunctionCallUsage extends JetUsageInfo<JetCallElement> {
element.deleteChildRange(KotlinPackage.first(lambdaArguments), KotlinPackage.last(lambdaArguments)); element.deleteChildRange(KotlinPackage.first(lambdaArguments), KotlinPackage.last(lambdaArguments));
} }
//TODO: this is not correct!
JetValueArgument lastArgument = KotlinPackage.lastOrNull(newArgumentList.getArguments()); JetValueArgument lastArgument = KotlinPackage.lastOrNull(newArgumentList.getArguments());
boolean hasTrailingLambdaInArgumentListAfter = lastArgument != null && lastArgument.getArgumentExpression() instanceof JetFunctionLiteralExpression; boolean hasTrailingLambdaInArgumentListAfter = lastArgument != null && PsiPackage.unpackFunctionLiteral(lastArgument.getArgumentExpression()) != null;
arguments = (JetValueArgumentList) arguments.replace(newArgumentList); arguments = (JetValueArgumentList) arguments.replace(newArgumentList);
@@ -430,11 +431,11 @@ public class JetFunctionCallUsage extends JetUsageInfo<JetCallElement> {
} }
if (hasTrailingLambdaInArgumentListAfter) { if (hasTrailingLambdaInArgumentListAfter) {
JetCallElement newCallElement = JetCallExpression newCallExpression =
(JetCallElement) (newElement instanceof JetQualifiedExpression (JetCallExpression) (newElement instanceof JetQualifiedExpression
? ((JetQualifiedExpression) newElement).getSelectorExpression() ? ((JetQualifiedExpression) newElement).getSelectorExpression()
: newElement); : newElement);
PsiModificationUtilPackage.moveLambdaOutsideParentheses(newCallElement); PsiModificationUtilPackage.moveFunctionLiteralOutsideParentheses(newCallExpression);
} }
} }
@@ -54,7 +54,7 @@ fun JetFunctionLiteralArgument.moveInsideParenthesesAndReplaceWith(
psiFactory.createArgument(replacement) psiFactory.createArgument(replacement)
} }
val functionLiteralArgument = newCallExpression.getFunctionLiteralArguments().head!! val functionLiteralArgument = newCallExpression.getFunctionLiteralArguments().firstOrNull()!!
val valueArgumentList = newCallExpression.getValueArgumentList() ?: psiFactory.createCallArguments("()") val valueArgumentList = newCallExpression.getValueArgumentList() ?: psiFactory.createCallArguments("()")
valueArgumentList.addArgument(argument) valueArgumentList.addArgument(argument)
@@ -69,20 +69,20 @@ fun JetFunctionLiteralArgument.moveInsideParenthesesAndReplaceWith(
return oldCallExpression.replace(newCallExpression) as JetCallExpression return oldCallExpression.replace(newCallExpression) as JetCallExpression
} }
fun JetCallElement.moveLambdaOutsideParentheses() { fun JetCallExpression.moveFunctionLiteralOutsideParentheses() {
val args = getValueArgumentsInParentheses() assert(getFunctionLiteralArguments().isEmpty())
val functionLiteral = args.last!!.getArgumentExpression()?.getText() val argumentList = getValueArgumentList()!!
val calleeText = getCalleeExpression()?.getText() val argument = argumentList.getArguments().last()
if (calleeText == null || functionLiteral == null) return val expression = argument.getArgumentExpression()!!
assert(expression.unpackFunctionLiteral() != null)
val params = args.subList(0, args.size - 1).map { it.asElement().getText() ?: "" }.joinToString(", ", "(", ")") val dummyCall = JetPsiFactory(this).createExpressionByPattern("foo()$0={}$", expression) as JetCallExpression
val functionLiteralArgument = dummyCall.getFunctionLiteralArguments().single()
val newCall = this.add(functionLiteralArgument)
if (params == "()") { if (argumentList.getArguments().size() > 1) {
"$calleeText $functionLiteral" argumentList.removeArgument(argument)
} }
else { else {
"$calleeText$params $functionLiteral" argumentList.delete()
} }
replace(JetPsiFactory(this).createExpression(newCall))
} }
@@ -3,7 +3,7 @@
// ERROR: Unresolved reference: it // ERROR: Unresolved reference: it
fun foo() { fun foo() {
bar <caret>{ it } bar<caret> { it }
} }
fun bar(a: Int = 0, f: (Int) -> Int) { } fun bar(a: Int = 0, f: (Int) -> Int) { }
@@ -1,3 +1,3 @@
fun foo(p: (Int, () -> Int) -> Unit) { fun foo(p: (Int, () -> Int) -> Unit) {
p(1) <caret>{ 2 } p(1<caret>) { 2 }
} }
@@ -0,0 +1,6 @@
// IS_APPLICABLE: false
fun foo() {
bar(<caret>{ it }) {it}
}
fun bar(p1: (Int) -> Int, p2: (Int) -> Int) { }
@@ -3,7 +3,7 @@ fun bar(a: Int, b: Int, f: () -> Unit) {}
fun foo(a: Int, b: Int) = 2 fun foo(a: Int, b: Int) = 2
fun test() { fun test() {
bar(1, 2) { bar(1, 2 /* , , */) {
val a = foo(1, 2) val a = foo(1, 2)
} }
} }
@@ -4951,6 +4951,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("inapplicableAlreadyHasFunctionLiteral.kt")
public void testInapplicableAlreadyHasFunctionLiteral() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveLambdaOutsideParentheses/inapplicableAlreadyHasFunctionLiteral.kt");
doTest(fileName);
}
@TestMetadata("inapplicableOptionalParametersAfter.kt") @TestMetadata("inapplicableOptionalParametersAfter.kt")
public void testInapplicableOptionalParametersAfter() throws Exception { public void testInapplicableOptionalParametersAfter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveLambdaOutsideParentheses/inapplicableOptionalParametersAfter.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveLambdaOutsideParentheses/inapplicableOptionalParametersAfter.kt");