diff --git a/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/after.kt.template b/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/after.kt.template new file mode 100644 index 00000000000..f0f1216fc33 --- /dev/null +++ b/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/after.kt.template @@ -0,0 +1,5 @@ +fun foo() { + listOf(1,2,3).find { + return@find true + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/before.kt.template b/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/before.kt.template new file mode 100644 index 00000000000..51551a0d780 --- /dev/null +++ b/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/before.kt.template @@ -0,0 +1,5 @@ +fun foo() { + listOf(1,2,3).find { + true + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/description.html b/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/description.html new file mode 100644 index 00000000000..3f65d4034a9 --- /dev/null +++ b/idea/resources/intentionDescriptions/AddLabeledReturnInLambdaIntention/description.html @@ -0,0 +1,5 @@ + + +This intention adds labeled return to last expression in a lambda. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 0b0e7b16f81..702761dfc81 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1562,6 +1562,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.AddLabeledReturnInLambdaIntention + Kotlin + + ( + KtLambdaExpression::class.java, + "Add labeled return to last expression in a lambda" +), LowPriorityAction { + override fun applicabilityRange(element: KtLambdaExpression): TextRange? { + if (!isApplicableTo(element)) return null + val labelName = createLabelName(element) ?: return null + text = "Add return@$labelName" + return element.bodyExpression?.statements?.last()?.textRange + } + + override fun applyTo(element: KtLambdaExpression, editor: Editor?) { + if (!isApplicableTo(element)) return + val labelName = createLabelName(element) ?: return + val lastStatement = element.bodyExpression?.statements?.last() ?: return + val newExpression = KtPsiFactory(element.project).createExpressionByPattern("return@$labelName $0", lastStatement) + lastStatement.replace(newExpression) + } + + private fun isApplicableTo(element: KtLambdaExpression): Boolean { + val block = element.bodyExpression ?: return false + val lastStatement = block.statements.last() + return lastStatement !is KtReturnExpression && lastStatement.isUsedAsExpression(lastStatement.analyze()) + } + + private fun createLabelName(element: KtLambdaExpression): String? { + val block = element.bodyExpression ?: return null + val callExpression = element.getStrictParentOfType() ?: return null + val valueArgument = callExpression.valueArguments.findArgumentWithGivenBlock(block) ?: return null + val lambdaLabelName = (valueArgument.getArgumentExpression() as? KtLabeledExpression)?.getLabelName() + return lambdaLabelName ?: callExpression.getCallNameExpression()?.text + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveLabeledReturnInLambdaIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveLabeledReturnInLambdaIntention.kt index 4bd667889b9..ef411747be1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveLabeledReturnInLambdaIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveLabeledReturnInLambdaIntention.kt @@ -9,7 +9,6 @@ import com.intellij.codeInsight.intention.LowPriorityAction import com.intellij.openapi.editor.Editor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression -import org.jetbrains.kotlin.psi.psiUtil.getChildOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType class RemoveLabeledReturnInLambdaIntention : SelfTargetingIntention( @@ -21,15 +20,7 @@ class RemoveLabeledReturnInLambdaIntention : SelfTargetingIntention() ?: return false if (block.statements.lastOrNull() != element) return false val callExpression = block.getStrictParentOfType() ?: return false - val lambdaArgument = callExpression.lambdaArguments.firstOrNull { - val argumentExpression = it.getArgumentExpression() - val lambda = when (argumentExpression) { - is KtLambdaExpression -> argumentExpression - is KtLabeledExpression -> argumentExpression.baseExpression as? KtLambdaExpression - else -> null - } - lambda?.bodyExpression === block - } ?: return false + val lambdaArgument = callExpression.lambdaArguments.findArgumentWithGivenBlock(block) ?: return false val callName = (lambdaArgument.getArgumentExpression() as? KtLabeledExpression)?.getLabelName() ?: callExpression.getCallNameExpression()?.text ?: return false if (labelName != callName) return false diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt index edadcb93865..c471b7f7795 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt @@ -32,7 +32,6 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.CollectionLiteralResolver -import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode @@ -295,3 +294,15 @@ fun KtCallExpression.isArrayOfMethod(): Boolean { return (descriptor.containingDeclaration as? PackageFragmentDescriptor)?.fqName == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME && ARRAY_OF_METHODS.contains(descriptor.name) } + +fun List.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 +} diff --git a/idea/testData/intentions/addLabeledReturnInLambda/.intention b/idea/testData/intentions/addLabeledReturnInLambda/.intention new file mode 100644 index 00000000000..84e97abb1bf --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.AddLabeledReturnInLambdaIntention diff --git a/idea/testData/intentions/addLabeledReturnInLambda/insideParenthesis.kt b/idea/testData/intentions/addLabeledReturnInLambda/insideParenthesis.kt new file mode 100644 index 00000000000..9b1914ee2ae --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/insideParenthesis.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@find" + +fun foo() { + listOf(1,2,3).find({ + true + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/insideParenthesis.kt.after b/idea/testData/intentions/addLabeledReturnInLambda/insideParenthesis.kt.after new file mode 100644 index 00000000000..cbf66452814 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/insideParenthesis.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@find" + +fun foo() { + listOf(1,2,3).find({ + return@find true + }) +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/labeledLambda.kt b/idea/testData/intentions/addLabeledReturnInLambda/labeledLambda.kt new file mode 100644 index 00000000000..b77258ce121 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/labeledLambda.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@label" + +fun foo() { + listOf(1,2,3).find label@{ + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/labeledLambda.kt.after b/idea/testData/intentions/addLabeledReturnInLambda/labeledLambda.kt.after new file mode 100644 index 00000000000..c6c53d61fa4 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/labeledLambda.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@label" + +fun foo() { + listOf(1,2,3).find label@{ + return@label true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/loop.kt b/idea/testData/intentions/addLabeledReturnInLambda/loop.kt new file mode 100644 index 00000000000..a9bb586e6b7 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/loop.kt @@ -0,0 +1,8 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo() { + for (i in 10..100) { + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt b/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt new file mode 100644 index 00000000000..1165d528d25 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@bar" + +private fun List.bar(a: (T) -> Boolean, b: (T) -> Boolean) {} + +fun foo() { + listOf(1,2,3).bar({ + true + }) { + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt.after b/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt.after new file mode 100644 index 00000000000..3abd39315c6 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@bar" + +private fun List.bar(a: (T) -> Boolean, b: (T) -> Boolean) {} + +fun foo() { + listOf(1,2,3).bar({ + true + }) { + return@bar true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun2.kt b/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun2.kt new file mode 100644 index 00000000000..40834302baa --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun2.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@bar" + +private fun List.bar(a: (T) -> Boolean, b: (T) -> Boolean) {} + +fun foo() { + listOf(1,2,3).bar({ + true + }) { + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun2.kt.after b/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun2.kt.after new file mode 100644 index 00000000000..7753729a450 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun2.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@bar" + +private fun List.bar(a: (T) -> Boolean, b: (T) -> Boolean) {} + +fun foo() { + listOf(1,2,3).bar({ + return@bar true + }) { + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/normal.kt b/idea/testData/intentions/addLabeledReturnInLambda/normal.kt new file mode 100644 index 00000000000..c1fc6c75604 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/normal.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@find" + +fun foo() { + listOf(1,2,3).find { + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/normal.kt.after b/idea/testData/intentions/addLabeledReturnInLambda/normal.kt.after new file mode 100644 index 00000000000..80a1e1cce60 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/normal.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@find" + +fun foo() { + listOf(1,2,3).find { + return@find true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/notLast.kt b/idea/testData/intentions/addLabeledReturnInLambda/notLast.kt new file mode 100644 index 00000000000..653e253331a --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/notLast.kt @@ -0,0 +1,9 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo() { + listOf(1,2,3).find { + 1 + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/returnStatement.kt b/idea/testData/intentions/addLabeledReturnInLambda/returnStatement.kt new file mode 100644 index 00000000000..970964b6257 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/returnStatement.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@find" + +fun foo(): Int? { + return listOf(1,2,3).find { + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/returnStatement.kt.after b/idea/testData/intentions/addLabeledReturnInLambda/returnStatement.kt.after new file mode 100644 index 00000000000..7aa2c059eff --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/returnStatement.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Add return@find" + +fun foo(): Int? { + return listOf(1,2,3).find { + return@find true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/unit.kt b/idea/testData/intentions/addLabeledReturnInLambda/unit.kt new file mode 100644 index 00000000000..8fc1c24f3f7 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/unit.kt @@ -0,0 +1,8 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo() { + listOf(1,2,3).forEach { + true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/withLabeledReturn.kt b/idea/testData/intentions/addLabeledReturnInLambda/withLabeledReturn.kt new file mode 100644 index 00000000000..9b88db1abe2 --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/withLabeledReturn.kt @@ -0,0 +1,8 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo() { + listOf(1,2,3).find { + return@find true + } +} \ No newline at end of file diff --git a/idea/testData/intentions/addLabeledReturnInLambda/withReturn.kt b/idea/testData/intentions/addLabeledReturnInLambda/withReturn.kt new file mode 100644 index 00000000000..e99ddfe8d2d --- /dev/null +++ b/idea/testData/intentions/addLabeledReturnInLambda/withReturn.kt @@ -0,0 +1,9 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +fun foo(): Boolean { + listOf(1,2,3).find { + return true + } + return false +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 286e30e6b0d..2113f8b55c2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -364,6 +364,81 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/addLabeledReturnInLambda") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AddLabeledReturnInLambda extends AbstractIntentionTest { + public void testAllFilesPresentInAddLabeledReturnInLambda() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/addLabeledReturnInLambda"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("insideParenthesis.kt") + public void testInsideParenthesis() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/insideParenthesis.kt"); + doTest(fileName); + } + + @TestMetadata("labeledLambda.kt") + public void testLabeledLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/labeledLambda.kt"); + doTest(fileName); + } + + @TestMetadata("loop.kt") + public void testLoop() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/loop.kt"); + doTest(fileName); + } + + @TestMetadata("multipleHighOrderFun.kt") + public void testMultipleHighOrderFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun.kt"); + doTest(fileName); + } + + @TestMetadata("multipleHighOrderFun2.kt") + public void testMultipleHighOrderFun2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/multipleHighOrderFun2.kt"); + doTest(fileName); + } + + @TestMetadata("normal.kt") + public void testNormal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/normal.kt"); + doTest(fileName); + } + + @TestMetadata("notLast.kt") + public void testNotLast() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/notLast.kt"); + doTest(fileName); + } + + @TestMetadata("returnStatement.kt") + public void testReturnStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/returnStatement.kt"); + doTest(fileName); + } + + @TestMetadata("unit.kt") + public void testUnit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/unit.kt"); + doTest(fileName); + } + + @TestMetadata("withLabeledReturn.kt") + public void testWithLabeledReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/withLabeledReturn.kt"); + doTest(fileName); + } + + @TestMetadata("withReturn.kt") + public void testWithReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/addLabeledReturnInLambda/withReturn.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/addMissingDestructuring") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)