Implement "Remove redundant '.let' call" binary operator support #KT-14396 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
ce72337ebd
commit
a662d777e8
@@ -78,6 +78,9 @@ class KtPsiFactory(private val project: Project) {
|
||||
return if (expression.text == text) expression else null
|
||||
}
|
||||
|
||||
fun createThisExpression() =
|
||||
(createExpression("this.x") as KtQualifiedExpression).receiverExpression as KtThisExpression
|
||||
|
||||
fun createClassLiteral(className: String): KtClassLiteralExpression =
|
||||
createExpression("$className::class") as KtClassLiteralExpression
|
||||
|
||||
|
||||
@@ -31,18 +31,49 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention<Kt
|
||||
"Remove redundant '.let' call"
|
||||
) {
|
||||
override fun applyTo(element: KtCallExpression, editor: Editor?) {
|
||||
val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return
|
||||
val bodyExpression = lambdaExpression.bodyExpression?.children?.singleOrNull() ?: return
|
||||
val dotQualifiedExpression = bodyExpression as? KtDotQualifiedExpression ?: return
|
||||
element.lambdaArguments.firstOrNull()?.getLambdaExpression()?.bodyExpression?.children?.singleOrNull()?.let {
|
||||
when (it) {
|
||||
is KtDotQualifiedExpression -> it.applyTo(element)
|
||||
is KtBinaryExpression -> it.applyTo(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtBinaryExpression.applyTo(element: KtCallExpression) {
|
||||
val left = left ?: return
|
||||
val factory = KtPsiFactory(element.project)
|
||||
val parent = element.parent
|
||||
when (parent) {
|
||||
is KtQualifiedExpression -> {
|
||||
val receiver = parent.receiverExpression
|
||||
val newLeft = when (left) {
|
||||
is KtDotQualifiedExpression -> left.replaceFirstReceiver(factory, receiver, parent is KtSafeQualifiedExpression)
|
||||
else -> receiver
|
||||
}
|
||||
val newExpression = factory.createExpressionByPattern("$0 $1 $2", newLeft, operationReference, right!!)
|
||||
parent.replace(newExpression)
|
||||
}
|
||||
else -> {
|
||||
val newLeft = when (left) {
|
||||
is KtDotQualifiedExpression -> left.deleteFirstReceiver()
|
||||
else -> factory.createThisExpression()
|
||||
}
|
||||
val newExpression = factory.createExpressionByPattern("$0 $1 $2", newLeft, operationReference, right!!)
|
||||
element.replace(newExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtDotQualifiedExpression.applyTo(element: KtCallExpression) {
|
||||
val parent = element.parent
|
||||
when (parent) {
|
||||
is KtQualifiedExpression -> {
|
||||
val factory = KtPsiFactory(element.project)
|
||||
val receiver = parent.receiverExpression
|
||||
parent.replace(dotQualifiedExpression.replaceFirstReceiver(factory, receiver, parent.operationSign == KtTokens.SAFE_ACCESS))
|
||||
parent.replace(replaceFirstReceiver(factory, receiver, parent is KtSafeQualifiedExpression))
|
||||
}
|
||||
else -> {
|
||||
element.replace(dotQualifiedExpression.deleteFirstReceiver())
|
||||
element.replace(deleteFirstReceiver())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,17 +90,47 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention<Kt
|
||||
override fun isApplicableTo(element: KtCallExpression): Boolean {
|
||||
if (!isLetMethod(element)) return false
|
||||
val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return false
|
||||
val bodyExpression = lambdaExpression.bodyExpression?.children?.singleOrNull() ?: return false
|
||||
val dotQualifiedExpression = bodyExpression as? KtDotQualifiedExpression ?: return false
|
||||
val parameterName = lambdaExpression.getParameterName() ?: return false
|
||||
val receiverExpression = dotQualifiedExpression.getLeftMostReceiverExpression()
|
||||
if (receiverExpression.text != parameterName) return false
|
||||
dotQualifiedExpression.selectorExpression?.let {
|
||||
if (it.anyDescendantOfType<KtLambdaExpression>()) return false
|
||||
val bodyExpression = lambdaExpression.bodyExpression?.children?.singleOrNull() ?: return false
|
||||
|
||||
return when (bodyExpression) {
|
||||
is KtBinaryExpression -> bodyExpression.isApplicable(parameterName)
|
||||
is KtDotQualifiedExpression -> bodyExpression.isApplicable(parameterName)
|
||||
else -> false
|
||||
}
|
||||
return !dotQualifiedExpression.receiverUsedAsArgument(parameterName)
|
||||
}
|
||||
|
||||
private fun KtBinaryExpression.isApplicable(parameterName: String): Boolean {
|
||||
val left = left ?: return false
|
||||
when (left) {
|
||||
is KtNameReferenceExpression -> if (left.text != parameterName) return false
|
||||
is KtDotQualifiedExpression -> if (!left.isApplicable(parameterName)) return false
|
||||
}
|
||||
|
||||
val right = right ?: return false
|
||||
when (right) {
|
||||
is KtNameReferenceExpression -> return right.text != parameterName
|
||||
is KtDotQualifiedExpression -> {
|
||||
if (right.isParameterLeftMostReceiver(parameterName)) return false
|
||||
if (right.hasLambdaExpression()) return false
|
||||
return !right.receiverUsedAsArgument(parameterName)
|
||||
}
|
||||
else -> return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtDotQualifiedExpression.isApplicable(parameterName: String): Boolean {
|
||||
if (!isParameterLeftMostReceiver(parameterName)) return false
|
||||
if (hasLambdaExpression()) return false
|
||||
return !receiverUsedAsArgument(parameterName)
|
||||
}
|
||||
|
||||
private fun KtDotQualifiedExpression.hasLambdaExpression()
|
||||
= selectorExpression?.anyDescendantOfType<KtLambdaExpression>() ?: false
|
||||
|
||||
private fun KtDotQualifiedExpression.isParameterLeftMostReceiver(parameterName: String)
|
||||
= getLeftMostReceiverExpression().text == parameterName
|
||||
|
||||
private fun isLetMethod(element: KtCallExpression) =
|
||||
element.calleeExpression?.text == "let" && element.isMethodCall("kotlin.let")
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
|
||||
fun baz(foo: String) {
|
||||
foo.let<caret> { it.indexOfLast { c -> c == it[0] } + 1 }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
|
||||
fun baz(foo: String) {
|
||||
foo.let<caret> { it.length + "".indexOfLast { c -> c == it[0] } }
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
|
||||
fun foo() {
|
||||
"".let<caret> { it.length + "".indexOf(it) }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
|
||||
fun foo() {
|
||||
"".let<caret> { it.length + it.length }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: false
|
||||
|
||||
fun foo() {
|
||||
"".let<caret> { it.substring(0, 1) + it }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
fun foo() {
|
||||
"".let<caret> { it.length + 1 }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
fun foo() {
|
||||
"".length + 1
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
fun foo() {
|
||||
"".let<caret> { it + 1 }
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
fun foo() {
|
||||
"" + 1
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
fun Int.foo() {
|
||||
let<caret> { it.dec() + 1 }
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
fun Int.foo() {
|
||||
dec() + 1
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
fun Int.foo() {
|
||||
let<caret> { it + 1 }
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
fun Int.foo() {
|
||||
this + 1
|
||||
}
|
||||
@@ -11799,6 +11799,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceSingleLineLetIntention"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("lambdaWithBinaryExpression.kt")
|
||||
public void testLambdaWithBinaryExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/lambdaWithBinaryExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("lambdaWithBinaryExpression2.kt")
|
||||
public void testLambdaWithBinaryExpression2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/lambdaWithBinaryExpression2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("let.kt")
|
||||
public void testLet() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/let.kt");
|
||||
@@ -11835,6 +11847,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letUseItAsParamWithBinaryExpression.kt")
|
||||
public void testLetUseItAsParamWithBinaryExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letUseItAsParamWithBinaryExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letUseItWithBinaryExpression.kt")
|
||||
public void testLetUseItWithBinaryExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letUseItWithBinaryExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letUseItWithBinaryExpression2.kt")
|
||||
public void testLetUseItWithBinaryExpression2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letUseItWithBinaryExpression2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letUseItWithMultipleMethodCall1.kt")
|
||||
public void testLetUseItWithMultipleMethodCall1() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letUseItWithMultipleMethodCall1.kt");
|
||||
@@ -11859,6 +11889,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letWithBinaryExpression.kt")
|
||||
public void testLetWithBinaryExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letWithBinaryExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letWithMethodCall.kt")
|
||||
public void testLetWithMethodCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letWithMethodCall.kt");
|
||||
@@ -11877,6 +11913,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letWithSimpleBinaryExpression.kt")
|
||||
public void testLetWithSimpleBinaryExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letWithSimpleBinaryExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letWithThisBinaryExpression.kt")
|
||||
public void testLetWithThisBinaryExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letWithThisBinaryExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("letWithThisShortBinaryExpression.kt")
|
||||
public void testLetWithThisShortBinaryExpression() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/letWithThisShortBinaryExpression.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multipleReceiver.kt")
|
||||
public void testMultipleReceiver() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceSingleLineLetIntention/multipleReceiver.kt");
|
||||
|
||||
Reference in New Issue
Block a user