If expression to elvis now handles call chains #KT-14084 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
1cfb8f2aef
commit
2117b30b76
@@ -58,26 +58,6 @@ class ReplaceSingleLineLetIntention : SelfTargetingOffsetIndependentIntention<Kt
|
|||||||
return this
|
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 {
|
override fun isApplicableTo(element: KtCallExpression): Boolean {
|
||||||
if (!isLetMethod(element)) return false
|
if (!isLetMethod(element)) return false
|
||||||
val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: 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"
|
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 {
|
private fun KtDotQualifiedExpression.receiverUsedAsArgument(receiverName: String): Boolean {
|
||||||
if ((selectorExpression as? KtCallExpression)?.valueArguments?.firstOrNull { it.text == receiverName } != null) return true
|
if ((selectorExpression as? KtCallExpression)?.valueArguments?.firstOrNull { it.text == receiverName } != null) return true
|
||||||
return (receiverExpression as? KtDotQualifiedExpression)?.receiverUsedAsArgument(receiverName) ?: false
|
return (receiverExpression as? KtDotQualifiedExpression)?.receiverUsedAsArgument(receiverName) ?: false
|
||||||
|
|||||||
@@ -260,3 +260,28 @@ fun KtElement?.isSizeOrLength(): Boolean {
|
|||||||
else -> false
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+49
-16
@@ -22,6 +22,8 @@ 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.idea.intentions.SelfTargetingOffsetIndependentIntention
|
import org.jetbrains.kotlin.idea.intentions.SelfTargetingOffsetIndependentIntention
|
||||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.*
|
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.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
|
||||||
@@ -33,31 +35,33 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
private fun KtExpression.clausesReplaceableByElvis(firstClause: KtExpression, secondClause: KtExpression) =
|
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())
|
!(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 {
|
override fun isApplicableTo(element: KtIfExpression): Boolean {
|
||||||
val condition = element.condition as? KtOperationExpression ?: return false
|
val condition = element.condition as? KtOperationExpression ?: return false
|
||||||
val thenClause = element.then ?: return false
|
val thenClause = element.then ?: return false
|
||||||
val elseClause = element.`else` ?: return false
|
val elseClause = element.`else` ?: return false
|
||||||
|
|
||||||
val expression = when (condition) {
|
val checkedExpression = condition.checkedExpression() ?: return false
|
||||||
is KtBinaryExpression -> condition.expressionComparedToNull() ?: return false
|
if (!checkedExpression.isStableVariable()) return false
|
||||||
is KtIsExpression -> condition.leftHandSide
|
|
||||||
else -> return false
|
|
||||||
|
|
||||||
}
|
|
||||||
if (!expression.isStableVariable()) return false
|
|
||||||
|
|
||||||
return when (condition) {
|
return when (condition) {
|
||||||
is KtBinaryExpression -> when (condition.operationToken) {
|
is KtBinaryExpression -> when (condition.operationToken) {
|
||||||
KtTokens.EQEQ -> expression.clausesReplaceableByElvis(thenClause, elseClause)
|
KtTokens.EQEQ -> checkedExpression.clausesReplaceableByElvis(thenClause, elseClause)
|
||||||
KtTokens.EXCLEQ -> expression.clausesReplaceableByElvis(elseClause, thenClause)
|
KtTokens.EXCLEQ -> checkedExpression.clausesReplaceableByElvis(elseClause, thenClause)
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
is KtIsExpression -> when (condition.isNegated) {
|
is KtIsExpression -> when (condition.isNegated) {
|
||||||
true -> expression.clausesReplaceableByElvis(thenClause, elseClause)
|
true -> checkedExpression.clausesReplaceableByElvis(thenClause, elseClause)
|
||||||
false -> expression.clausesReplaceableByElvis(elseClause, thenClause)
|
false -> checkedExpression.clausesReplaceableByElvis(elseClause, thenClause)
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
@@ -68,6 +72,20 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
|
|||||||
return innerExpression !is KtBlockExpression && innerExpression.node.elementType != KtNodeTypes.NULL
|
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?) {
|
override fun applyTo(element: KtIfExpression, editor: Editor?) {
|
||||||
val condition = element.condition as KtOperationExpression
|
val condition = element.condition as KtOperationExpression
|
||||||
|
|
||||||
@@ -90,11 +108,26 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
|
|||||||
}
|
}
|
||||||
|
|
||||||
val factory = KtPsiFactory(element)
|
val factory = KtPsiFactory(element)
|
||||||
val newExpr = factory.createExpressionByPattern("$0 ?: $1", left, right) as KtBinaryExpression
|
val newReceiver = (condition as? KtIsExpression)?.let {
|
||||||
if (condition is KtIsExpression) {
|
factory.createExpressionByPattern("$0 as? $1",
|
||||||
newExpr.left!!.replace(factory.createExpressionByPattern("$0 as? $1", left, condition.typeReference!!))
|
(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) {
|
if (editor != null) {
|
||||||
elvis.inlineLeftSideIfApplicableWithPrompt(editor)
|
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
|
||||||
|
}
|
||||||
+5
@@ -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 {
|
||||||
|
""
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
val nullableString: String? = "abc"
|
||||||
|
|
||||||
|
val foo = nullableString?.toUpperCase() ?: ""
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
val nullableString: String? = "abc"
|
||||||
|
|
||||||
|
val foo = if (<caret>nullableString != null) {
|
||||||
|
nullableString.toUpperCase().toLowerCase()
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
+5
@@ -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);
|
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")
|
@TestMetadata("lhsEqualsNull.kt")
|
||||||
public void testLhsEqualsNull() throws Exception {
|
public void testLhsEqualsNull() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/lhsEqualsNull.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/lhsEqualsNull.kt");
|
||||||
@@ -1374,6 +1380,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("otherBlockHasMoreThanOneStatement.kt")
|
||||||
public void testOtherBlockHasMoreThanOneStatement() throws Exception {
|
public void testOtherBlockHasMoreThanOneStatement() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/otherBlockHasMoreThanOneStatement.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/otherBlockHasMoreThanOneStatement.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user