Remove redundant let in "Safe access to if-then" #KT-13515 Fixed

This commit is contained in:
Toshiaki Kameyama
2018-07-25 12:27:38 +03:00
committed by Mikhail Glukhikh
parent 443b1834bc
commit 7e8521d9dc
12 changed files with 122 additions and 7 deletions
@@ -21,16 +21,24 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.intentions.ReplaceSingleLineLetIntention
import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfNotNullExpression
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.introduceValueForCondition
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStableSimpleExpression
import org.jetbrains.kotlin.idea.intentions.callExpression
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class SafeAccessToIfThenIntention : SelfTargetingRangeIntention<KtSafeQualifiedExpression>(KtSafeQualifiedExpression::class.java, "Replace safe access expression with 'if' expression"), LowPriorityAction {
class SafeAccessToIfThenIntention : SelfTargetingRangeIntention<KtSafeQualifiedExpression>(
KtSafeQualifiedExpression::class.java,
"Replace safe access expression with 'if' expression"
), LowPriorityAction {
override fun applicabilityRange(element: KtSafeQualifiedExpression): TextRange? {
if (element.selectorExpression == null) return null
return element.operationTokenNode.textRange
@@ -57,14 +65,33 @@ class SafeAccessToIfThenIntention : SelfTargetingRangeIntention<KtSafeQualifiedE
isAssignment = true
}
if (!receiverIsStable) {
val valueToExtract = if (isAssignment)
((ifExpression.then as? KtBinaryExpression)?.left as? KtDotQualifiedExpression)?.receiverExpression
else
(ifExpression.then as? KtDotQualifiedExpression)?.receiverExpression
val isRedundantLetCallRemoved = ifExpression.removeRedundantLetCallIfPossible(editor)
if (!receiverIsStable) {
val valueToExtract = when {
isAssignment ->
((ifExpression.then as? KtBinaryExpression)?.left as? KtDotQualifiedExpression)?.receiverExpression
isRedundantLetCallRemoved -> {
val context = ifExpression.analyze(BodyResolveMode.PARTIAL)
val descriptor = (ifExpression.condition as? KtBinaryExpression)?.left?.getResolvedCall(context)?.resultingDescriptor
ifExpression.then?.findDescendantOfType<KtNameReferenceExpression> {
it.getReferencedNameAsName() == descriptor?.name && it.getResolvedCall(context)?.resultingDescriptor == descriptor
}
}
else ->
(ifExpression.then as? KtDotQualifiedExpression)?.receiverExpression
}
if (valueToExtract != null) ifExpression.introduceValueForCondition(valueToExtract, editor)
}
}
private fun KtIfExpression.removeRedundantLetCallIfPossible(editor: Editor?): Boolean {
val callExpression = (then as? KtQualifiedExpression)?.callExpression ?: return false
if (callExpression.calleeExpression?.text != "let") return false
val replaceSingleLineLetIntention = ReplaceSingleLineLetIntention()
if (!replaceSingleLineLetIntention.isApplicableTo(callExpression)) return false
replaceSingleLineLetIntention.applyTo(callExpression, editor)
return true
}
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun foo(value: Int?): Int? {
return value<caret>?.let { it + 1 }
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun foo(value: Int?): Int? {
return if (value != null) value + 1 else null
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun foo(value: Int?): Int? {
return value<caret>?.let { baz(it) }
}
fun baz(i: Int) = i + i
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun foo(value: Int?): Int? {
return if (value != null) baz(value) else null
}
fun baz(i: Int) = i + i
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun foo(a: Int, b: Int?): Int? {
return b<caret>?.let { a + b }
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun foo(a: Int, b: Int?): Int? {
return if (b != null) b.let { a + b } else null
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
fun foo(value: Int?): Int? {
return value<caret>?.let {
println()
it + 1
}
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
fun foo(value: Int?): Int? {
return if (value != null) value.let {
println()
it + 1
} else null
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
var a: String? = "A"
fun main(args: Array<String>) {
a<caret>?.let { it.length + 1 }
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
var a: String? = "A"
fun main(args: Array<String>) {
val a1 = a
if (a1 != null) a1.length + 1
}
@@ -2796,6 +2796,31 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
runTest("idea/testData/intentions/branched/safeAccessToIfThen/equality.kt");
}
@TestMetadata("let.kt")
public void testLet() throws Exception {
runTest("idea/testData/intentions/branched/safeAccessToIfThen/let.kt");
}
@TestMetadata("let2.kt")
public void testLet2() throws Exception {
runTest("idea/testData/intentions/branched/safeAccessToIfThen/let2.kt");
}
@TestMetadata("let3.kt")
public void testLet3() throws Exception {
runTest("idea/testData/intentions/branched/safeAccessToIfThen/let3.kt");
}
@TestMetadata("letNotRedundant.kt")
public void testLetNotRedundant() throws Exception {
runTest("idea/testData/intentions/branched/safeAccessToIfThen/letNotRedundant.kt");
}
@TestMetadata("letTopLevelVar.kt")
public void testLetTopLevelVar() throws Exception {
runTest("idea/testData/intentions/branched/safeAccessToIfThen/letTopLevelVar.kt");
}
@TestMetadata("localValAsReceiver.kt")
public void testLocalValAsReceiver() throws Exception {
runTest("idea/testData/intentions/branched/safeAccessToIfThen/localValAsReceiver.kt");