KT-14032 related: if expression to elvis now handles also is and !is checks

This commit is contained in:
Mikhail Glukhikh
2016-10-07 21:38:33 +03:00
committed by Mikhail Glukhikh
parent 8188bb1e54
commit 121f0ec810
7 changed files with 92 additions and 22 deletions
@@ -27,26 +27,38 @@ import org.jetbrains.kotlin.psi.*
class IfThenToElvisInspection : IntentionBasedInspection<KtIfExpression>(IfThenToElvisIntention::class) class IfThenToElvisInspection : IntentionBasedInspection<KtIfExpression>(IfThenToElvisIntention::class)
class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpression>(KtIfExpression::class.java, "Replace 'if' expression with elvis expression") { class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpression>(
KtIfExpression::class.java,
"Replace 'if' expression with elvis expression"
) {
private fun KtExpression.clausesReplaceableByElvis(firstClause: KtExpression, secondClause: KtExpression) =
firstClause.isNotNullExpression() && secondClause.evaluatesTo(this) &&
!(firstClause is KtThrowExpression && firstClause.throwsNullPointerExceptionWithNoArguments())
override fun isApplicableTo(element: KtIfExpression): Boolean { override fun isApplicableTo(element: KtIfExpression): Boolean {
val condition = element.condition as? KtBinaryExpression ?: 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 = condition.expressionComparedToNull() ?: return false val expression = when (condition) {
is KtBinaryExpression -> condition.expressionComparedToNull() ?: return false
is KtIsExpression -> condition.leftHandSide
else -> return false
}
if (!expression.isStableVariable()) return false if (!expression.isStableVariable()) return false
return when (condition.operationToken) { return when (condition) {
KtTokens.EQEQ -> is KtBinaryExpression -> when (condition.operationToken) {
thenClause.isNotNullExpression() && elseClause.evaluatesTo(expression) && KtTokens.EQEQ -> expression.clausesReplaceableByElvis(thenClause, elseClause)
!(thenClause is KtThrowExpression && thenClause.throwsNullPointerExceptionWithNoArguments()) KtTokens.EXCLEQ -> expression.clausesReplaceableByElvis(elseClause, thenClause)
else -> false
}
KtTokens.EXCLEQ -> is KtIsExpression -> when (condition.isNegated) {
elseClause.isNotNullExpression() && thenClause.evaluatesTo(expression) && true -> expression.clausesReplaceableByElvis(thenClause, elseClause)
!(elseClause is KtThrowExpression && elseClause.throwsNullPointerExceptionWithNoArguments()) false -> expression.clausesReplaceableByElvis(elseClause, thenClause)
}
else -> false else -> false
} }
} }
@@ -57,22 +69,32 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
} }
override fun applyTo(element: KtIfExpression, editor: Editor?) { override fun applyTo(element: KtIfExpression, editor: Editor?) {
val condition = element.condition as KtBinaryExpression val condition = element.condition as KtOperationExpression
val thenClause = element.then!! val thenClause = element.then!!
val elseClause = element.`else`!! val elseClause = element.`else`!!
val thenExpression = thenClause.unwrapBlockOrParenthesis() val thenExpression = thenClause.unwrapBlockOrParenthesis()
val elseExpression = elseClause.unwrapBlockOrParenthesis() val elseExpression = elseClause.unwrapBlockOrParenthesis()
val (left, right) = val (left, right) = when (condition) {
when(condition.operationToken) { is KtBinaryExpression -> when (condition.operationToken) {
KtTokens.EQEQ -> Pair(elseExpression, thenExpression) KtTokens.EQEQ -> Pair(elseExpression, thenExpression)
KtTokens.EXCLEQ -> Pair(thenExpression, elseExpression) KtTokens.EXCLEQ -> Pair(thenExpression, elseExpression)
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
is KtIsExpression -> when (condition.isNegated) {
true -> Pair(elseExpression, thenExpression)
false -> Pair(thenExpression, elseExpression)
}
else -> throw IllegalArgumentException()
}
val newExpr = element.replaced(KtPsiFactory(element).createExpressionByPattern("$0 ?: $1", left, right)) val factory = KtPsiFactory(element)
val elvis = KtPsiUtil.deparenthesize(newExpr) as KtBinaryExpression 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 elvis = KtPsiUtil.deparenthesize(element.replaced(newExpr)) as KtBinaryExpression
if (editor != null) { if (editor != null) {
elvis.inlineLeftSideIfApplicableWithPrompt(editor) elvis.inlineLeftSideIfApplicableWithPrompt(editor)
@@ -103,4 +103,20 @@
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">If-Then foldable to '?:'</problem_class> <problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">If-Then foldable to '?:'</problem_class>
<description>Replace 'if' expression with elvis expression</description> <description>Replace 'if' expression with elvis expression</description>
</problem> </problem>
<problem>
<file>notIsCheck.kt</file>
<line>4</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/notIsCheck.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">If-Then foldable to '?:'</problem_class>
<description>Replace 'if' expression with elvis expression</description>
</problem>
<problem>
<file>isCheck.kt</file>
<line>4</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="temp:///src/isCheck.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">If-Then foldable to '?:'</problem_class>
<description>Replace 'if' expression with elvis expression</description>
</problem>
</problems> </problems>
@@ -0,0 +1,5 @@
class My(val x: Int)
fun foo(arg: Any?): My {
return if (<caret>arg is My) arg else My(42)
}
@@ -0,0 +1,5 @@
class My(val x: Int)
fun foo(arg: Any?): My {
return arg as? My ?: My(42)
}
@@ -0,0 +1,5 @@
class My(val x: Int)
fun foo(arg: Any?): My {
return if (<caret>arg !is My) My(42) else arg
}
@@ -0,0 +1,5 @@
class My(val x: Int)
fun foo(arg: Any?): My {
return arg as? My ?: My(42)
}
@@ -1296,6 +1296,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("isCheck.kt")
public void testIsCheck() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/isCheck.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");
@@ -1356,6 +1362,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("notIsCheck.kt")
public void testNotIsCheck() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/notIsCheck.kt");
doTest(fileName);
}
@TestMetadata("nullBranchAlsoNull.kt") @TestMetadata("nullBranchAlsoNull.kt")
public void testNullBranchAlsoNull() throws Exception { public void testNullBranchAlsoNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/nullBranchAlsoNull.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/nullBranchAlsoNull.kt");