If expression to elvis now handles call chains #KT-14084 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-10-10 16:24:03 +03:00
committed by Mikhail Glukhikh
parent 1cfb8f2aef
commit 2117b30b76
12 changed files with 146 additions and 39 deletions
@@ -58,26 +58,6 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention<Kt
return this
}
private fun KtDotQualifiedExpression.replaceFirstReceiver(
factory: KtPsiFactory,
newReceiver: KtExpression,
safeAccess: Boolean = false
): KtExpression {
val receiver = receiverExpression
when (receiver) {
is KtDotQualifiedExpression -> {
receiver.replaceFirstReceiver(factory, newReceiver, safeAccess)
}
else -> {
if (safeAccess) {
operationTokenNode.psi.replace(factory.createSafeCallNode().psi)
}
receiver.replace(newReceiver)
}
}
return this
}
override fun isApplicableTo(element: KtCallExpression): Boolean {
if (!isLetMethod(element)) return false
val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return false
@@ -98,9 +78,6 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention<Kt
return if (parameters.size == 1) parameters[0].text else "it"
}
private fun KtDotQualifiedExpression.getLeftMostReceiverExpression(): KtExpression =
(receiverExpression as? KtDotQualifiedExpression)?.getLeftMostReceiverExpression() ?: receiverExpression
private fun KtDotQualifiedExpression.receiverUsedAsArgument(receiverName: String): Boolean {
if ((selectorExpression as? KtCallExpression)?.valueArguments?.firstOrNull { it.text == receiverName } != null) return true
return (receiverExpression as? KtDotQualifiedExpression)?.receiverUsedAsArgument(receiverName) ?: false
@@ -260,3 +260,28 @@ fun KtElement?.isSizeOrLength(): Boolean {
else -> false
}
}
fun KtDotQualifiedExpression.getLeftMostReceiverExpression(): KtExpression =
(receiverExpression as? KtDotQualifiedExpression)?.getLeftMostReceiverExpression() ?: receiverExpression
fun KtDotQualifiedExpression.replaceFirstReceiver(
factory: KtPsiFactory,
newReceiver: KtExpression,
safeAccess: Boolean = false
): KtExpression {
val receiver = receiverExpression
when (receiver) {
is KtDotQualifiedExpression -> {
receiver.replaceFirstReceiver(factory, newReceiver, safeAccess)
}
else -> {
if (safeAccess) {
operationTokenNode.psi.replace(factory.createSafeCallNode().psi)
}
receiver.replace(newReceiver)
}
}
return this
}
@@ -22,6 +22,8 @@ import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
import org.jetbrains.kotlin.idea.intentions.SelfTargetingOffsetIndependentIntention
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.*
import org.jetbrains.kotlin.idea.intentions.getLeftMostReceiverExpression
import org.jetbrains.kotlin.idea.intentions.replaceFirstReceiver
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
@@ -33,31 +35,33 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
) {
private fun KtExpression.clausesReplaceableByElvis(firstClause: KtExpression, secondClause: KtExpression) =
firstClause.isNotNullExpression() && secondClause.evaluatesTo(this) &&
firstClause.isNotNullExpression() &&
(secondClause.evaluatesTo(this) || secondClause.hasFirstReceiverOf(this)) &&
!(firstClause is KtThrowExpression && firstClause.throwsNullPointerExceptionWithNoArguments())
private fun KtExpression.checkedExpression() = when (this) {
is KtBinaryExpression -> expressionComparedToNull()
is KtIsExpression -> leftHandSide
else -> null
}
override fun isApplicableTo(element: KtIfExpression): Boolean {
val condition = element.condition as? KtOperationExpression ?: return false
val thenClause = element.then ?: return false
val elseClause = element.`else` ?: return false
val expression = when (condition) {
is KtBinaryExpression -> condition.expressionComparedToNull() ?: return false
is KtIsExpression -> condition.leftHandSide
else -> return false
}
if (!expression.isStableVariable()) return false
val checkedExpression = condition.checkedExpression() ?: return false
if (!checkedExpression.isStableVariable()) return false
return when (condition) {
is KtBinaryExpression -> when (condition.operationToken) {
KtTokens.EQEQ -> expression.clausesReplaceableByElvis(thenClause, elseClause)
KtTokens.EXCLEQ -> expression.clausesReplaceableByElvis(elseClause, thenClause)
KtTokens.EQEQ -> checkedExpression.clausesReplaceableByElvis(thenClause, elseClause)
KtTokens.EXCLEQ -> checkedExpression.clausesReplaceableByElvis(elseClause, thenClause)
else -> false
}
is KtIsExpression -> when (condition.isNegated) {
true -> expression.clausesReplaceableByElvis(thenClause, elseClause)
false -> expression.clausesReplaceableByElvis(elseClause, thenClause)
true -> checkedExpression.clausesReplaceableByElvis(thenClause, elseClause)
false -> checkedExpression.clausesReplaceableByElvis(elseClause, thenClause)
}
else -> false
}
@@ -68,6 +72,20 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
return innerExpression !is KtBlockExpression && innerExpression.node.elementType != KtNodeTypes.NULL
}
private fun KtExpression.hasFirstReceiverOf(receiver: KtExpression): Boolean {
val actualReceiver = (unwrapBlockOrParenthesis() as? KtDotQualifiedExpression)?.getLeftMostReceiverExpression() ?: return false
return actualReceiver.evaluatesTo(receiver)
}
private fun KtExpression.insertSafeCalls(factory: KtPsiFactory): KtExpression {
if (this !is KtQualifiedExpression) return this
if (this is KtDotQualifiedExpression) {
operationTokenNode.psi.replace(factory.createSafeCallNode().psi)
}
receiverExpression.insertSafeCalls(factory)
return this
}
override fun applyTo(element: KtIfExpression, editor: Editor?) {
val condition = element.condition as KtOperationExpression
@@ -90,11 +108,26 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
}
val factory = KtPsiFactory(element)
val newExpr = factory.createExpressionByPattern("$0 ?: $1", left, right) as KtBinaryExpression
if (condition is KtIsExpression) {
newExpr.left!!.replace(factory.createExpressionByPattern("$0 as? $1", left, condition.typeReference!!))
val newReceiver = (condition as? KtIsExpression)?.let {
factory.createExpressionByPattern("$0 as? $1",
(left as? KtDotQualifiedExpression)?.getLeftMostReceiverExpression() ?: left,
it.typeReference!!)
}
val elvis = KtPsiUtil.deparenthesize(element.replaced(newExpr)) as KtBinaryExpression
val checkedExpression = condition.checkedExpression()!!
val replacedLeft = if (left.evaluatesTo(checkedExpression)) {
if (condition is KtIsExpression) newReceiver!! else left
}
else {
if (condition is KtIsExpression) {
(left as KtDotQualifiedExpression).replaceFirstReceiver(
factory, newReceiver!!, safeAccess = true)
}
else {
left.insertSafeCalls(factory)
}
}
val newExpr = element.replaced(factory.createExpressionByPattern("$0 ?: $1", replacedLeft, right))
val elvis = KtPsiUtil.deparenthesize(newExpr) as KtBinaryExpression
if (editor != null) {
elvis.inlineLeftSideIfApplicableWithPrompt(editor)
@@ -0,0 +1,5 @@
class My(val x: Int)
fun foo(arg: Any?): Int {
return if (<caret>arg is My) arg.x else 42
}
@@ -0,0 +1,5 @@
class My(val x: Int)
fun foo(arg: Any?): Int {
return (arg as? My)?.x ?: 42
}
@@ -0,0 +1,5 @@
class My(val x: Int)
fun foo(arg: My?): Int {
return if (<caret>arg != null) arg.x else 42
}
@@ -0,0 +1,5 @@
class My(val x: Int)
fun foo(arg: My?): Int {
return arg?.x ?: 42
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
val nullableString: String? = "abc"
val foo = if (<caret>nullableString != null) {
nullableString.toUpperCase()
} else {
""
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
val nullableString: String? = "abc"
val foo = nullableString?.toUpperCase() ?: ""
@@ -0,0 +1,9 @@
// WITH_RUNTIME
val nullableString: String? = "abc"
val foo = if (<caret>nullableString != null) {
nullableString.toUpperCase().toLowerCase()
} else {
""
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
val nullableString: String? = "abc"
val foo = nullableString?.toUpperCase()?.toLowerCase() ?: ""
@@ -1302,6 +1302,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("isCheckWithSelector.kt")
public void testIsCheckWithSelector() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/isCheckWithSelector.kt");
doTest(fileName);
}
@TestMetadata("lhsEqualsNull.kt")
public void testLhsEqualsNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/lhsEqualsNull.kt");
@@ -1374,6 +1380,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("nullCheckWithSelector.kt")
public void testNullCheckWithSelector() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/nullCheckWithSelector.kt");
doTest(fileName);
}
@TestMetadata("nullCheckWithSelectorCall.kt")
public void testNullCheckWithSelectorCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/nullCheckWithSelectorCall.kt");
doTest(fileName);
}
@TestMetadata("nullCheckWithSelectorCallChain.kt")
public void testNullCheckWithSelectorCallChain() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/nullCheckWithSelectorCallChain.kt");
doTest(fileName);
}
@TestMetadata("otherBlockHasMoreThanOneStatement.kt")
public void testOtherBlockHasMoreThanOneStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/otherBlockHasMoreThanOneStatement.kt");