"Remove redundant '.let' call" intention: handle it inside function call
So #KT-20583 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
d4798b699f
commit
096f9fefd1
@@ -17,9 +17,16 @@
|
|||||||
package org.jetbrains.kotlin.idea.intentions
|
package org.jetbrains.kotlin.idea.intentions
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Editor
|
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.idea.inspections.IntentionBasedInspection
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
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<KtCallExpression>(ReplaceSingleLineLetIntention::class) {
|
class ReplaceSingleLineLetInspection : IntentionBasedInspection<KtCallExpression>(ReplaceSingleLineLetIntention::class) {
|
||||||
override fun inspectionTarget(element: KtCallExpression) = element.calleeExpression
|
override fun inspectionTarget(element: KtCallExpression) = element.calleeExpression
|
||||||
@@ -30,11 +37,12 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention<Kt
|
|||||||
"Remove redundant '.let' call"
|
"Remove redundant '.let' call"
|
||||||
) {
|
) {
|
||||||
override fun applyTo(element: KtCallExpression, editor: Editor?) {
|
override fun applyTo(element: KtCallExpression, editor: Editor?) {
|
||||||
element.lambdaArguments.firstOrNull()?.getLambdaExpression()?.bodyExpression?.children?.singleOrNull()?.let {
|
val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return
|
||||||
when (it) {
|
val bodyExpression = lambdaExpression.bodyExpression?.children?.singleOrNull() ?: return
|
||||||
is KtDotQualifiedExpression -> it.applyTo(element)
|
when (bodyExpression) {
|
||||||
is KtBinaryExpression -> it.applyTo(element)
|
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<Kt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KtCallExpression.applyTo(element: KtCallExpression, functionLiteral: KtFunctionLiteral, editor: Editor?) {
|
||||||
|
val parent = element.parent as? KtQualifiedExpression
|
||||||
|
val reference = functionLiteral.valueParameterReferences(this).firstOrNull()
|
||||||
|
val replaced = if (parent != null) {
|
||||||
|
reference?.replace(parent.receiverExpression)
|
||||||
|
parent.replaced(this)
|
||||||
|
} else {
|
||||||
|
reference?.replace(KtPsiFactory(this).createThisExpression())
|
||||||
|
element.replaced(this)
|
||||||
|
}
|
||||||
|
editor?.caretModel?.moveToOffset(replaced.startOffset)
|
||||||
|
}
|
||||||
|
|
||||||
override fun isApplicableTo(element: KtCallExpression): Boolean {
|
override fun isApplicableTo(element: KtCallExpression): Boolean {
|
||||||
if (!element.isLetMethodCall()) return false
|
if (!element.isLetMethodCall()) return false
|
||||||
val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return false
|
val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return false
|
||||||
@@ -86,6 +107,8 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention<Kt
|
|||||||
return when (bodyExpression) {
|
return when (bodyExpression) {
|
||||||
is KtBinaryExpression -> element.parent !is KtSafeQualifiedExpression && bodyExpression.isApplicable(parameterName)
|
is KtBinaryExpression -> element.parent !is KtSafeQualifiedExpression && bodyExpression.isApplicable(parameterName)
|
||||||
is KtDotQualifiedExpression -> bodyExpression.isApplicable(parameterName)
|
is KtDotQualifiedExpression -> bodyExpression.isApplicable(parameterName)
|
||||||
|
is KtCallExpression -> element.parent !is KtSafeQualifiedExpression
|
||||||
|
&& lambdaExpression.functionLiteral.valueParameterReferences(bodyExpression).count() <= 1
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,4 +162,15 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention<Kt
|
|||||||
|
|
||||||
private fun KtExpression.nameUsed(name: String, except: KtNameReferenceExpression? = null): Boolean =
|
private fun KtExpression.nameUsed(name: String, except: KtNameReferenceExpression? = null): Boolean =
|
||||||
anyDescendantOfType<KtNameReferenceExpression> { it != except && it.getReferencedName() == name }
|
anyDescendantOfType<KtNameReferenceExpression> { it != except && it.getReferencedName() == name }
|
||||||
|
|
||||||
|
private fun KtFunctionLiteral.valueParameterReferences(callExpression: KtCallExpression): List<KtReferenceExpression> {
|
||||||
|
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<KtReferenceExpression>().filter {
|
||||||
|
it.text == name && it.getResolvedCall(context)?.resultingDescriptor == descriptor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
s.let<caret> { foo("", 1) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
<caret>foo("", 1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
s.let { foo(it, 1)<caret> }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
<caret>foo(s, 1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
s.let<caret> { foo("", it.length) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
foo("", s.length)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
s.let<caret> { x -> foo("", x.length) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
foo("", s.length)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
s.length.let<caret> { foo("", it) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
foo("", s.length)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
fun bar() = ""
|
||||||
|
bar().let<caret> { foo(it, 1) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
fun bar() = ""
|
||||||
|
foo(bar(), 1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IS_APPLICABLE: false
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val s = ""
|
||||||
|
s.let<caret> { foo(it, it.length) }
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun String.baz(): Int {
|
||||||
|
return let<caret> { foo(it, 1) }
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun foo(s: String, i: Int) = s.length + i
|
||||||
|
|
||||||
|
fun String.baz(): Int {
|
||||||
|
return foo(this, 1)
|
||||||
|
}
|
||||||
+9
@@ -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<caret> { foo(it, 1) }
|
||||||
|
}
|
||||||
@@ -13781,6 +13781,51 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
runTest("idea/testData/intentions/replaceSingleLineLetIntention/dotWithComparison.kt");
|
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")
|
@TestMetadata("in.kt")
|
||||||
public void testIn() throws Exception {
|
public void testIn() throws Exception {
|
||||||
runTest("idea/testData/intentions/replaceSingleLineLetIntention/in.kt");
|
runTest("idea/testData/intentions/replaceSingleLineLetIntention/in.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user