ExpressionReplacementPerformer: handle string templates accurately

This commit is contained in:
Mikhail Glukhikh
2017-04-13 13:36:27 +03:00
parent 815a367978
commit 91f7f19902
4 changed files with 61 additions and 17 deletions
@@ -17,13 +17,12 @@
package org.jetbrains.kotlin.idea.codeInliner
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.intentions.ConvertToBlockBodyIntention
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import java.util.*
@@ -66,6 +65,36 @@ internal class ExpressionReplacementPerformer(
expressionToBeReplaced: KtExpression
) : ReplacementPerformer<KtExpression>(codeToInline, expressionToBeReplaced) {
fun KtExpression.replacedWithStringTemplate(templateExpression: KtStringTemplateExpression): KtExpression? {
val parent = this.parent
return if (parent is KtStringTemplateEntryWithExpression
// Do not mix raw and non-raw templates
&& parent.parent.firstChild.text == templateExpression.firstChild.text) {
val entriesToAdd = templateExpression.entries
val grandParentTemplateExpression = parent.parent as KtStringTemplateExpression
val result = if (entriesToAdd.isNotEmpty()) {
grandParentTemplateExpression.addRangeBefore(entriesToAdd.first(), entriesToAdd.last(), parent)
val lastNewEntry = parent.prevSibling
val nextElement = parent.nextSibling
if (lastNewEntry is KtSimpleNameStringTemplateEntry &&
lastNewEntry.expression != null &&
!canPlaceAfterSimpleNameEntry(nextElement)) {
lastNewEntry.replace(KtPsiFactory(this).createBlockStringTemplateEntry(lastNewEntry.expression!!))
}
grandParentTemplateExpression
}
else null
parent.delete()
result
}
else {
replaced(templateExpression)
}
}
override fun doIt(postProcessing: (PsiChildRange) -> PsiChildRange): KtExpression? {
val insertedStatements = ArrayList<KtExpression>()
for (statement in codeToInline.statementsBefore) {
@@ -79,20 +108,22 @@ internal class ExpressionReplacementPerformer(
insertedStatements.add(inserted)
}
val replaced = if (codeToInline.mainExpression != null) {
elementToBeReplaced.replaced(codeToInline.mainExpression!!)
}
else {
// NB: Unit is never used as expression
val stub = elementToBeReplaced.replaced(psiFactory.createExpression("0"))
val bindingContext = stub.analyze()
val canDropElementToBeReplaced = !stub.isUsedAsExpression(bindingContext)
if (canDropElementToBeReplaced) {
stub.delete()
null
}
else {
stub.replace(psiFactory.createExpression("Unit"))
val mainExpression = codeToInline.mainExpression
val replaced: PsiElement? = when (mainExpression) {
is KtStringTemplateExpression -> elementToBeReplaced.replacedWithStringTemplate(mainExpression)
is KtExpression -> elementToBeReplaced.replaced(mainExpression)
else -> {
// NB: Unit is never used as expression
val stub = elementToBeReplaced.replaced(psiFactory.createExpression("0"))
val bindingContext = stub.analyze()
val canDropElementToBeReplaced = !stub.isUsedAsExpression(bindingContext)
if (canDropElementToBeReplaced) {
stub.delete()
null
}
else {
stub.replace(psiFactory.createExpression("Unit"))
}
}
}
@@ -0,0 +1,4 @@
fun foo() {
fun bar() = ""
val y = "!!x=${<caret>bar()}!!"
}
@@ -0,0 +1,3 @@
fun foo() {
val y = "!!x=!!"
}
@@ -226,6 +226,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
doTest(fileName);
}
@TestMetadata("StringTemplate.kt")
public void testStringTemplate() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/StringTemplate.kt");
doTest(fileName);
}
@TestMetadata("WithReference.kt")
public void testWithReference() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/WithReference.kt");