diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSingleLineLetIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSingleLineLetIntention.kt index ff24c79fb60..3a3143929ae 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSingleLineLetIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceSingleLineLetIntention.kt @@ -17,9 +17,16 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode class ReplaceSingleLineLetInspection : IntentionBasedInspection(ReplaceSingleLineLetIntention::class) { override fun inspectionTarget(element: KtCallExpression) = element.calleeExpression @@ -30,11 +37,12 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention it.applyTo(element) - is KtBinaryExpression -> it.applyTo(element) - } + val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return + val bodyExpression = lambdaExpression.bodyExpression?.children?.singleOrNull() ?: return + when (bodyExpression) { + is KtDotQualifiedExpression -> bodyExpression.applyTo(element) + is KtBinaryExpression -> bodyExpression.applyTo(element) + is KtCallExpression -> bodyExpression.applyTo(element, lambdaExpression.functionLiteral, editor) } } @@ -77,6 +85,19 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention element.parent !is KtSafeQualifiedExpression && bodyExpression.isApplicable(parameterName) is KtDotQualifiedExpression -> bodyExpression.isApplicable(parameterName) + is KtCallExpression -> element.parent !is KtSafeQualifiedExpression + && lambdaExpression.functionLiteral.valueParameterReferences(bodyExpression).count() <= 1 else -> false } } @@ -139,4 +162,15 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention { it != except && it.getReferencedName() == name } + + private fun KtFunctionLiteral.valueParameterReferences(callExpression: KtCallExpression): List { + val context = analyze(BodyResolveMode.PARTIAL) + val descriptor = context[BindingContext.FUNCTION, this]?.valueParameters?.singleOrNull() ?: return emptyList() + val name = descriptor.name.asString() + return callExpression.valueArguments.flatMap { arg -> + arg.collectDescendantsOfType().filter { + it.text == name && it.getResolvedCall(context)?.resultingDescriptor == descriptor + } + } + } } \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall1.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall1.kt new file mode 100644 index 00000000000..4838ec77623 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall1.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + s.let { foo("", 1) } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall1.kt.after b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall1.kt.after new file mode 100644 index 00000000000..3847d2f396d --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall1.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + foo("", 1) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall2.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall2.kt new file mode 100644 index 00000000000..4accb193e75 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall2.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + s.let { foo(it, 1) } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall2.kt.after b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall2.kt.after new file mode 100644 index 00000000000..4415eed32e5 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall2.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + foo(s, 1) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall3.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall3.kt new file mode 100644 index 00000000000..383728c20c0 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall3.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + s.let { foo("", it.length) } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall3.kt.after b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall3.kt.after new file mode 100644 index 00000000000..9607a2a97e5 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall3.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + foo("", s.length) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall4.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall4.kt new file mode 100644 index 00000000000..5ff2f7f21ba --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall4.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + s.let { x -> foo("", x.length) } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall4.kt.after b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall4.kt.after new file mode 100644 index 00000000000..9607a2a97e5 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall4.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + foo("", s.length) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall5.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall5.kt new file mode 100644 index 00000000000..9311fa10a23 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall5.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + s.length.let { foo("", it) } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall5.kt.after b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall5.kt.after new file mode 100644 index 00000000000..9607a2a97e5 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall5.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + foo("", s.length) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall6.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall6.kt new file mode 100644 index 00000000000..697d99d1585 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall6.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + fun bar() = "" + bar().let { foo(it, 1) } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall6.kt.after b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall6.kt.after new file mode 100644 index 00000000000..ee0eafe0af0 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall6.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + fun bar() = "" + foo(bar(), 1) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCall7.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall7.kt new file mode 100644 index 00000000000..7918b5a8fb2 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCall7.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val s = "" + s.let { foo(it, it.length) } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCallInExtension.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCallInExtension.kt new file mode 100644 index 00000000000..dc1040a873c --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCallInExtension.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun String.baz(): Int { + return let { foo(it, 1) } +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCallInExtension.kt.after b/idea/testData/intentions/replaceSingleLineLetIntention/functionCallInExtension.kt.after new file mode 100644 index 00000000000..1816001fbaf --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCallInExtension.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(s: String, i: Int) = s.length + i + +fun String.baz(): Int { + return foo(this, 1) +} \ No newline at end of file diff --git a/idea/testData/intentions/replaceSingleLineLetIntention/functionCallOnSafeCall.kt b/idea/testData/intentions/replaceSingleLineLetIntention/functionCallOnSafeCall.kt new file mode 100644 index 00000000000..9f129500089 --- /dev/null +++ b/idea/testData/intentions/replaceSingleLineLetIntention/functionCallOnSafeCall.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false + +fun foo(s: String, i: Int) = s.length + i + +fun test() { + val nullable: String? = null + nullable?.let { foo(it, 1) } +} \ 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 2c4b189fe20..ec4f5067f49 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -13781,6 +13781,51 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/replaceSingleLineLetIntention/dotWithComparison.kt"); } + @TestMetadata("functionCall1.kt") + public void testFunctionCall1() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCall1.kt"); + } + + @TestMetadata("functionCall2.kt") + public void testFunctionCall2() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCall2.kt"); + } + + @TestMetadata("functionCall3.kt") + public void testFunctionCall3() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCall3.kt"); + } + + @TestMetadata("functionCall4.kt") + public void testFunctionCall4() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCall4.kt"); + } + + @TestMetadata("functionCall5.kt") + public void testFunctionCall5() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCall5.kt"); + } + + @TestMetadata("functionCall6.kt") + public void testFunctionCall6() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCall6.kt"); + } + + @TestMetadata("functionCall7.kt") + public void testFunctionCall7() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCall7.kt"); + } + + @TestMetadata("functionCallInExtension.kt") + public void testFunctionCallInExtension() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCallInExtension.kt"); + } + + @TestMetadata("functionCallOnSafeCall.kt") + public void testFunctionCallOnSafeCall() throws Exception { + runTest("idea/testData/intentions/replaceSingleLineLetIntention/functionCallOnSafeCall.kt"); + } + @TestMetadata("in.kt") public void testIn() throws Exception { runTest("idea/testData/intentions/replaceSingleLineLetIntention/in.kt");