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)