Implement "Remove redundant '.let' call" binary operator support #KT-14396 Fixed

This commit is contained in:
shiraji
2016-10-28 14:45:56 +09:00
committed by Mikhail Glukhikh
parent ce72337ebd
commit a662d777e8
16 changed files with 208 additions and 12 deletions
@@ -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")
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun baz(foo: String) {
foo.let<caret> { it.indexOfLast { c -> c == it[0] } + 1 }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun baz(foo: String) {
foo.let<caret> { it.length + "".indexOfLast { c -> c == it[0] } }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun foo() {
"".let<caret> { it.length + "".indexOf(it) }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun foo() {
"".let<caret> { it.length + it.length }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun foo() {
"".let<caret> { it.substring(0, 1) + it }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: true
fun foo() {
"".let<caret> { it.length + 1 }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: true
fun foo() {
"".length + 1
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: true
fun foo() {
"".let<caret> { it + 1 }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: true
fun foo() {
"" + 1
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: true
fun Int.foo() {
let<caret> { it.dec() + 1 }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: true
fun Int.foo() {
dec() + 1
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: true
fun Int.foo() {
let<caret> { it + 1 }
}
@@ -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");