Replace with safe call: do not add redundant elvis operator

#KT-34432 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-10-18 20:36:59 +09:00
committed by Dmitry Gridin
parent fa0398ffd3
commit e8effe6727
10 changed files with 80 additions and 7 deletions
@@ -46,7 +46,7 @@ abstract class ReplaceCallFix(
override fun invoke(project: Project, editor: Editor?, file: KtFile) { override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return val element = element ?: return
val elvis = elvisOrEmpty(notNullNeeded) val elvis = element.elvisOrEmpty(notNullNeeded)
val betweenReceiverAndOperation = element.elementsBetweenReceiverAndOperation().joinToString(separator = "") { it.text } val betweenReceiverAndOperation = element.elementsBetweenReceiverAndOperation().joinToString(separator = "") { it.text }
val newExpression = KtPsiFactory(element).createExpressionByPattern( val newExpression = KtPsiFactory(element).createExpressionByPattern(
"$0$betweenReceiverAndOperation$operation$1$elvis", "$0$betweenReceiverAndOperation$operation$1$elvis",
@@ -54,7 +54,7 @@ abstract class ReplaceCallFix(
element.selectorExpression!! element.selectorExpression!!
) )
val replacement = element.replace(newExpression) val replacement = element.replace(newExpression)
if (notNullNeeded) { if (elvis.isNotEmpty()) {
replacement.moveCaretToEnd(editor, project) replacement.moveCaretToEnd(editor, project)
} }
} }
@@ -78,10 +78,10 @@ class ReplaceImplicitReceiverCallFix(
override fun invoke(project: Project, editor: Editor?, file: KtFile) { override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return val element = element ?: return
val elvis = elvisOrEmpty(notNullNeeded) val elvis = element.elvisOrEmpty(notNullNeeded)
val newExpression = KtPsiFactory(element).createExpressionByPattern("this?.$0$elvis", element) val newExpression = KtPsiFactory(element).createExpressionByPattern("this?.$0$elvis", element)
val replacement = element.replace(newExpression) val replacement = element.replace(newExpression)
if (notNullNeeded) { if (elvis.isNotEmpty()) {
replacement.moveCaretToEnd(editor, project) replacement.moveCaretToEnd(editor, project)
} }
} }
@@ -21,14 +21,20 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getType import org.jetbrains.kotlin.resolve.calls.callUtil.getType
fun elvisOrEmpty(notNullNeeded: Boolean): String = if (notNullNeeded) "?:" else "" fun KtExpression.elvisOrEmpty(notNullNeeded: Boolean): String {
if (!notNullNeeded) return ""
val binaryExpression = getStrictParentOfType<KtBinaryExpression>()
return if (binaryExpression?.left == this && binaryExpression.operationToken == KtTokens.ELVIS) "" else "?:"
}
fun KtExpression.shouldHaveNotNullType(): Boolean { fun KtExpression.shouldHaveNotNullType(): Boolean {
val parent = parent val parent = parent
@@ -43,7 +43,7 @@ class ReplaceInfixOrOperatorCallFix(
override fun invoke(project: Project, editor: Editor?, file: KtFile) { override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return val element = element ?: return
val psiFactory = KtPsiFactory(file) val psiFactory = KtPsiFactory(file)
val elvis = elvisOrEmpty(notNullNeeded) val elvis = element.elvisOrEmpty(notNullNeeded)
var replacement: PsiElement? = null var replacement: PsiElement? = null
when (element) { when (element) {
is KtArrayAccessExpression -> { is KtArrayAccessExpression -> {
@@ -83,7 +83,7 @@ class ReplaceInfixOrOperatorCallFix(
} }
} }
} }
if (notNullNeeded) { if (elvis.isNotEmpty()) {
replacement?.moveCaretToEnd(editor, project) replacement?.moveCaretToEnd(editor, project)
} }
} }
@@ -0,0 +1,7 @@
// "Replace with safe (?.) call" "true"
// WITH_RUNTIME
fun foo(list: List<String>?) {
var s = ""
s = list[0]<caret> ?: ""
}
@@ -0,0 +1,7 @@
// "Replace with safe (?.) call" "true"
// WITH_RUNTIME
fun foo(list: List<String>?) {
var s = ""
s = list?.get(0) ?: ""
}
+10
View File
@@ -0,0 +1,10 @@
// "Replace with safe (?.) call" "true"
// WITH_RUNTIME
fun main() {
var a = foo()<caret>.length ?: 0
}
fun foo(): String? {
return ""
}
@@ -0,0 +1,10 @@
// "Replace with safe (?.) call" "true"
// WITH_RUNTIME
fun main() {
var a = foo()?.length ?: 0
}
fun foo(): String? {
return ""
}
@@ -0,0 +1,9 @@
// "Replace with safe (this?.) call" "true"
// WITH_RUNTIME
var i = 0
fun foo(a: String?) {
a.run {
i = <caret>length ?: 0
}
}
@@ -0,0 +1,9 @@
// "Replace with safe (this?.) call" "true"
// WITH_RUNTIME
var i = 0
fun foo(a: String?) {
a.run {
i = this?.length ?: 0
}
}
@@ -10901,6 +10901,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt"); runTest("idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt");
} }
@TestMetadata("hasElvis.kt")
public void testHasElvis() throws Exception {
runTest("idea/testData/quickfix/replaceInfixOrOperatorCall/hasElvis.kt");
}
@TestMetadata("list.kt") @TestMetadata("list.kt")
public void testList() throws Exception { public void testList() throws Exception {
runTest("idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt"); runTest("idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt");
@@ -11098,6 +11103,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/replaceWithSafeCall/functionCall.kt"); runTest("idea/testData/quickfix/replaceWithSafeCall/functionCall.kt");
} }
@TestMetadata("hasElvis.kt")
public void testHasElvis() throws Exception {
runTest("idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt");
}
@TestMetadata("hasElvis2.kt")
public void testHasElvis2() throws Exception {
runTest("idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt");
}
@TestMetadata("invokeOperator.kt") @TestMetadata("invokeOperator.kt")
public void testInvokeOperator() throws Exception { public void testInvokeOperator() throws Exception {
runTest("idea/testData/quickfix/replaceWithSafeCall/invokeOperator.kt"); runTest("idea/testData/quickfix/replaceWithSafeCall/invokeOperator.kt");