Make add / remove labeled return working for hierarchical blocks

Related to KT-20439
This commit is contained in:
Mikhail Glukhikh
2018-02-27 13:59:41 +03:00
parent 59f6dc07cf
commit 86e6f3c5e3
8 changed files with 78 additions and 28 deletions
@@ -13,35 +13,36 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
class AddLabeledReturnInLambdaIntention : SelfTargetingRangeIntention<KtLambdaExpression>( class AddLabeledReturnInLambdaIntention : SelfTargetingRangeIntention<KtBlockExpression>(
KtLambdaExpression::class.java, KtBlockExpression::class.java,
"Add labeled return to last expression in a lambda" "Add labeled return to last expression in a lambda"
), LowPriorityAction { ), LowPriorityAction {
override fun applicabilityRange(element: KtLambdaExpression): TextRange? { override fun applicabilityRange(element: KtBlockExpression): TextRange? {
if (!isApplicableTo(element)) return null if (!isApplicableTo(element)) return null
val labelName = createLabelName(element) ?: return null val labelName = createLabelName(element) ?: return null
text = "Add return@$labelName" text = "Add return@$labelName"
return element.bodyExpression?.statements?.last()?.textRange return element.statements.lastOrNull()?.textRange
} }
override fun applyTo(element: KtLambdaExpression, editor: Editor?) { override fun applyTo(element: KtBlockExpression, editor: Editor?) {
if (!isApplicableTo(element)) return if (!isApplicableTo(element)) return
val labelName = createLabelName(element) ?: return val labelName = createLabelName(element) ?: return
val lastStatement = element.bodyExpression?.statements?.last() ?: return val lastStatement = element.statements.lastOrNull() ?: return
val newExpression = KtPsiFactory(element.project).createExpressionByPattern("return@$labelName $0", lastStatement) val newExpression = KtPsiFactory(element.project).createExpressionByPattern("return@$labelName $0", lastStatement)
lastStatement.replace(newExpression) lastStatement.replace(newExpression)
} }
private fun isApplicableTo(element: KtLambdaExpression): Boolean { private fun isApplicableTo(block: KtBlockExpression): Boolean {
val block = element.bodyExpression ?: return false val lastStatement = block.statements.lastOrNull()
val lastStatement = block.statements.last() return lastStatement !is KtReturnExpression && lastStatement?.isUsedAsExpression(lastStatement.analyze()) == true
return lastStatement !is KtReturnExpression && lastStatement.isUsedAsExpression(lastStatement.analyze())
} }
private fun createLabelName(element: KtLambdaExpression): String? { private fun createLabelName(block: KtBlockExpression): String? {
val block = element.bodyExpression ?: return null val lambdaExpression = block.getStrictParentOfType<KtLambdaExpression>() ?: return null
val callExpression = element.getStrictParentOfType<KtCallExpression>() ?: return null val callExpression = lambdaExpression.getStrictParentOfType<KtCallExpression>() ?: return null
val valueArgument = callExpression.valueArguments.findArgumentWithGivenBlock(block) ?: return null val valueArgument = callExpression.valueArguments.find {
it.getArgumentExpression()?.unpackFunctionLiteral(allowParentheses = false) === lambdaExpression
} ?: return null
val lambdaLabelName = (valueArgument.getArgumentExpression() as? KtLabeledExpression)?.getLabelName() val lambdaLabelName = (valueArgument.getArgumentExpression() as? KtLabeledExpression)?.getLabelName()
return lambdaLabelName ?: callExpression.getCallNameExpression()?.text return lambdaLabelName ?: callExpression.getCallNameExpression()?.text
} }
@@ -19,8 +19,11 @@ class RemoveLabeledReturnInLambdaIntention : SelfTargetingIntention<KtReturnExpr
val labelName = element.getLabelName() ?: return false val labelName = element.getLabelName() ?: return false
val block = element.getStrictParentOfType<KtBlockExpression>() ?: return false val block = element.getStrictParentOfType<KtBlockExpression>() ?: return false
if (block.statements.lastOrNull() != element) return false if (block.statements.lastOrNull() != element) return false
val callExpression = block.getStrictParentOfType<KtCallExpression>() ?: return false val lambdaExpression = block.getStrictParentOfType<KtLambdaExpression>() ?: return false
val lambdaArgument = callExpression.lambdaArguments.findArgumentWithGivenBlock(block) ?: return false val callExpression = lambdaExpression.getStrictParentOfType<KtCallExpression>() ?: return false
val lambdaArgument = callExpression.lambdaArguments.find {
it.getArgumentExpression().unpackFunctionLiteral(allowParentheses = false) === lambdaExpression
} ?: return false
val callName = (lambdaArgument.getArgumentExpression() as? KtLabeledExpression)?.getLabelName() val callName = (lambdaArgument.getArgumentExpression() as? KtLabeledExpression)?.getLabelName()
?: callExpression.getCallNameExpression()?.text ?: return false ?: callExpression.getCallNameExpression()?.text ?: return false
if (labelName != callName) return false if (labelName != callName) return false
@@ -294,15 +294,3 @@ fun KtCallExpression.isArrayOfMethod(): Boolean {
return (descriptor.containingDeclaration as? PackageFragmentDescriptor)?.fqName == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME && return (descriptor.containingDeclaration as? PackageFragmentDescriptor)?.fqName == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME &&
ARRAY_OF_METHODS.contains(descriptor.name) ARRAY_OF_METHODS.contains(descriptor.name)
} }
fun <T : KtValueArgument> List<T>.findArgumentWithGivenBlock(
block: KtBlockExpression
): T? = firstOrNull {
val argumentExpression = it.getArgumentExpression()
val lambda = when (argumentExpression) {
is KtLambdaExpression -> argumentExpression
is KtLabeledExpression -> argumentExpression.baseExpression as? KtLambdaExpression
else -> null
}
lambda?.bodyExpression === block
}
@@ -0,0 +1,12 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Add return@find"
fun foo() {
listOf(1,2,3).find {
if (it > 0) {
<caret>true
} else {
false
}
}
}
@@ -0,0 +1,12 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Add return@find"
fun foo() {
listOf(1,2,3).find {
if (it > 0) {
return@find true
} else {
false
}
}
}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun foo() {
listOf(1,2,3).find {
if (it > 0) {
return@find <caret>true
} else {
false
}
}
}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun foo() {
listOf(1,2,3).find {
if (it > 0) {
true
} else {
false
}
}
}
@@ -390,6 +390,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("multipleBlocks.kt")
public void testMultipleBlocks() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/multipleBlocks.kt");
doTest(fileName);
}
@TestMetadata("multipleHighOrderFun.kt") @TestMetadata("multipleHighOrderFun.kt")
public void testMultipleHighOrderFun() throws Exception { public void testMultipleHighOrderFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt");
@@ -13305,6 +13311,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("multipleBlocks.kt")
public void testMultipleBlocks() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeLabeledReturnInLambda/multipleBlocks.kt");
doTest(fileName);
}
@TestMetadata("normal.kt") @TestMetadata("normal.kt")
public void testNormal() throws Exception { public void testNormal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeLabeledReturnInLambda/normal.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeLabeledReturnInLambda/normal.kt");