if to when intention now detects effectively else branches in subsequent code + performs more accurate comment handling #KT-10750 Fixed
Also #KT-11424 Fixed
This commit is contained in:
+91
-12
@@ -18,12 +18,18 @@ package org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.getSubjectToIntroduce
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.introduceSubject
|
||||
import org.jetbrains.kotlin.idea.util.CommentSaver
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
|
||||
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||
import java.util.*
|
||||
|
||||
class IfToWhenIntention : SelfTargetingRangeIntention<KtIfExpression>(KtIfExpression::class.java, "Replace 'if' with 'when'") {
|
||||
@@ -32,15 +38,70 @@ class IfToWhenIntention : SelfTargetingRangeIntention<KtIfExpression>(KtIfExpres
|
||||
return element.ifKeyword.textRange
|
||||
}
|
||||
|
||||
override fun applyTo(element: KtIfExpression, editor: Editor?) {
|
||||
val commentSaver = CommentSaver(element)
|
||||
private fun canPassThrough(expression: KtExpression?): Boolean =
|
||||
when (expression) {
|
||||
is KtReturnExpression, is KtThrowExpression ->
|
||||
false
|
||||
is KtBlockExpression ->
|
||||
expression.statements.all { canPassThrough(it) }
|
||||
is KtIfExpression ->
|
||||
canPassThrough(expression.then) || canPassThrough(expression.`else`)
|
||||
else ->
|
||||
true
|
||||
}
|
||||
|
||||
private fun buildNextBranch(ifExpression: KtIfExpression): KtExpression? {
|
||||
var nextSibling = ifExpression.getNextSiblingIgnoringWhitespaceAndComments() ?: return null
|
||||
return when (nextSibling) {
|
||||
is KtIfExpression ->
|
||||
if (nextSibling.then == null) null else nextSibling
|
||||
else -> {
|
||||
val builder = StringBuilder()
|
||||
while (true) {
|
||||
builder.append(nextSibling.text)
|
||||
nextSibling = nextSibling.nextSibling ?: break
|
||||
}
|
||||
KtPsiFactory(ifExpression).createBlock(builder.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtExpression?.unwrapBlockIfNeeded(): KtExpression? =
|
||||
(this as? KtBlockExpression)?.statements?.singleOrNull() ?: this
|
||||
|
||||
private fun KtIfExpression.siblingsUpTo(other: KtExpression): List<PsiElement> {
|
||||
val result = ArrayList<PsiElement>()
|
||||
var nextSibling = nextSibling
|
||||
// We delete elements up to the next if (or up to the end of the surrounding block)
|
||||
while (nextSibling != null && nextSibling != other) {
|
||||
// RBRACE closes the surrounding block, so it should not be copied / deleted
|
||||
if (nextSibling !is PsiWhiteSpace && nextSibling.node.elementType != KtTokens.RBRACE) {
|
||||
result.add(nextSibling)
|
||||
}
|
||||
nextSibling = nextSibling.nextSibling
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun BuilderByPattern<*>.appendElseBlock(block: KtExpression?) {
|
||||
appendFixedText("else->")
|
||||
appendExpression(block.unwrapBlockIfNeeded())
|
||||
appendFixedText("\n")
|
||||
}
|
||||
|
||||
override fun applyTo(element: KtIfExpression, editor: Editor?) {
|
||||
val siblings = element.siblings()
|
||||
val commentSaver = CommentSaver(PsiChildRange(element, siblings.last()), saveLineBreaks = true)
|
||||
|
||||
val toDelete = ArrayList<PsiElement>()
|
||||
var whenExpression = KtPsiFactory(element).buildExpression {
|
||||
appendFixedText("when {\n")
|
||||
|
||||
var ifExpression = element
|
||||
var currentIfExpression = element
|
||||
var baseIfExpressionForSyntheticBranch = currentIfExpression
|
||||
var canPassThrough = false
|
||||
while (true) {
|
||||
val condition = ifExpression.condition
|
||||
val condition = currentIfExpression.condition
|
||||
val orBranches = ArrayList<KtExpression>()
|
||||
if (condition != null) {
|
||||
orBranches.addOrBranches(condition)
|
||||
@@ -50,18 +111,33 @@ class IfToWhenIntention : SelfTargetingRangeIntention<KtIfExpression>(KtIfExpres
|
||||
|
||||
appendFixedText("->")
|
||||
|
||||
val thenBranch = ifExpression.then
|
||||
appendExpression(thenBranch)
|
||||
val currentThenBranch = currentIfExpression.then
|
||||
appendExpression(currentThenBranch.unwrapBlockIfNeeded())
|
||||
appendFixedText("\n")
|
||||
|
||||
val elseBranch = ifExpression.`else` ?: break
|
||||
if (elseBranch is KtIfExpression) {
|
||||
ifExpression = elseBranch
|
||||
canPassThrough = canPassThrough || canPassThrough(currentThenBranch)
|
||||
|
||||
val currentElseBranch = currentIfExpression.`else`
|
||||
if (currentElseBranch == null) {
|
||||
// Try to build synthetic if / else according to KT-10750
|
||||
val syntheticElseBranch = if (canPassThrough) break else buildNextBranch(baseIfExpressionForSyntheticBranch) ?: break
|
||||
toDelete.addAll(baseIfExpressionForSyntheticBranch.siblingsUpTo(syntheticElseBranch))
|
||||
if (syntheticElseBranch is KtIfExpression) {
|
||||
baseIfExpressionForSyntheticBranch = syntheticElseBranch
|
||||
currentIfExpression = syntheticElseBranch
|
||||
toDelete.add(syntheticElseBranch)
|
||||
}
|
||||
else {
|
||||
appendElseBlock(syntheticElseBranch)
|
||||
break
|
||||
}
|
||||
}
|
||||
else if (currentElseBranch is KtIfExpression) {
|
||||
currentIfExpression = currentElseBranch
|
||||
}
|
||||
else {
|
||||
appendFixedText("else->")
|
||||
appendExpression(elseBranch)
|
||||
appendFixedText("\n")
|
||||
toDelete.addAll(baseIfExpressionForSyntheticBranch.siblingsUpTo(currentElseBranch))
|
||||
appendElseBlock(currentElseBranch)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -76,6 +152,9 @@ class IfToWhenIntention : SelfTargetingRangeIntention<KtIfExpression>(KtIfExpres
|
||||
|
||||
val result = element.replace(whenExpression)
|
||||
commentSaver.restore(result)
|
||||
toDelete.forEach {
|
||||
it.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<KtExpression>.addOrBranches(expression: KtExpression): List<KtExpression> {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fun bar(arg: Int): String {
|
||||
<caret>if (arg == 1) return "One"
|
||||
else if (arg == 2) return "Two"
|
||||
if (arg == 0) return "Zero"
|
||||
if (arg == -1) return "Minus One"
|
||||
else if (arg == -2) return "Minus Two"
|
||||
return "Something Complex"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fun bar(arg: Int): String {
|
||||
when (arg) {
|
||||
1 -> return "One"
|
||||
2 -> return "Two"
|
||||
0 -> return "Zero"
|
||||
-1 -> return "Minus One"
|
||||
-2 -> return "Minus Two"
|
||||
else -> return "Something Complex"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(arg: Int): Int {
|
||||
// 1
|
||||
<caret>if (arg == 0) return 0
|
||||
// 2
|
||||
else return -1
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fun foo(arg: Int): Int {
|
||||
// 1
|
||||
when (arg) {
|
||||
0 -> return 0
|
||||
// 2
|
||||
else -> return -1
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo(arg: Int): Int {
|
||||
<caret>if (arg == 0) return 0 // 1
|
||||
else return -1 // 2
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
fun foo(arg: Int): Int {
|
||||
when (arg) {
|
||||
0 -> return 0 // 1
|
||||
else -> return -1 // 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fun bar(arg: Int): Int {
|
||||
<caret>if (arg < 0) {
|
||||
if (arg == -3) return 0
|
||||
}
|
||||
if (arg > 0) {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fun bar(arg: Int): Int {
|
||||
when {
|
||||
arg < 0 -> if (arg == -3) return 0
|
||||
}
|
||||
if (arg > 0) {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
fun foo(arg: Int): Int {
|
||||
<caret>if (arg < 0) {
|
||||
var x = arg + 1
|
||||
x++
|
||||
return x
|
||||
}
|
||||
if (arg > 0) {
|
||||
var y = arg - 1
|
||||
y--
|
||||
return y
|
||||
}
|
||||
var z = arg
|
||||
z *= 2
|
||||
return z
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
fun foo(arg: Int): Int {
|
||||
when {
|
||||
arg < 0 -> {
|
||||
var x = arg + 1
|
||||
x++
|
||||
return x
|
||||
}
|
||||
arg > 0 -> {
|
||||
var y = arg - 1
|
||||
y--
|
||||
return y
|
||||
}
|
||||
else -> {
|
||||
var z = arg
|
||||
z *= 2
|
||||
return z
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
fun foo(arg: Any?): Int {
|
||||
// 1
|
||||
<caret>if (arg is Int) {
|
||||
return arg // 2
|
||||
}
|
||||
// 3
|
||||
if (arg is String) {
|
||||
return 42 // 4
|
||||
}
|
||||
// 5
|
||||
if (arg == null) {
|
||||
// 6
|
||||
return 0
|
||||
}
|
||||
// 7
|
||||
return -1 // 8
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
fun foo(arg: Any?): Int {
|
||||
// 1
|
||||
when (arg) {
|
||||
is Int -> return arg // 2
|
||||
// 3
|
||||
is String -> return 42 // 4
|
||||
// 5
|
||||
null -> // 6
|
||||
return 0
|
||||
// 7
|
||||
// 8
|
||||
else -> return -1
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
fun bar(arg: Int): Int {
|
||||
<caret>if (arg == 1) return 2
|
||||
if (arg == 2) return 5
|
||||
if (arg == 3) return 9
|
||||
return 13
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fun bar(arg: Int): Int {
|
||||
when (arg) {
|
||||
1 -> return 2
|
||||
2 -> return 5
|
||||
3 -> return 9
|
||||
else -> return 13
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo(arg: Int) {
|
||||
<caret>if (arg == 0) return
|
||||
if (arg == 1) else return
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo(arg: Int) {
|
||||
when (arg) {
|
||||
0 -> return
|
||||
}
|
||||
if (arg == 1) else return
|
||||
}
|
||||
@@ -1495,12 +1495,30 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/branched/ifWhen/ifToWhen"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("combinedIf.kt")
|
||||
public void testCombinedIf() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/combinedIf.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("comment.kt")
|
||||
public void testComment() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/comment.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ifElseSwallowComments.kt")
|
||||
public void testIfElseSwallowComments() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/ifElseSwallowComments.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ifElseSwallowReturnComment.kt")
|
||||
public void testIfElseSwallowReturnComment() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/ifElseSwallowReturnComment.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ifWithEqualityTests.kt")
|
||||
public void testIfWithEqualityTests() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/ifWithEqualityTests.kt");
|
||||
@@ -1555,6 +1573,36 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multipleIfFake.kt")
|
||||
public void testMultipleIfFake() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/multipleIfFake.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multipleIfWithLongBranches.kt")
|
||||
public void testMultipleIfWithLongBranches() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/multipleIfWithLongBranches.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multipleIfWithReturns.kt")
|
||||
public void testMultipleIfWithReturns() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/multipleIfWithReturns.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("multipleIfWithSingleReturns.kt")
|
||||
public void testMultipleIfWithSingleReturns() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/multipleIfWithSingleReturns.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("secondIfNoThen.kt")
|
||||
public void testSecondIfNoThen() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/secondIfNoThen.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("whenWithMultipleConditionTypes.kt")
|
||||
public void testWhenWithMultipleConditionTypes() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifWhen/ifToWhen/whenWithMultipleConditionTypes.kt");
|
||||
|
||||
Reference in New Issue
Block a user