diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtExpressionImpl.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtExpressionImpl.kt index adba23417cb..8399c7ee0dd 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtExpressionImpl.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtExpressionImpl.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.psi import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import org.jetbrains.kotlin.KtNodeType +import org.jetbrains.kotlin.psi.psiUtil.canPlaceAfterSimpleNameEntry public abstract class KtExpressionImpl(node: ASTNode) : KtElementImpl(node), KtExpression { @@ -38,14 +39,33 @@ public abstract class KtExpressionImpl(node: ASTNode) : KtElementImpl(node), KtE val parent = expression.getParent() if (newElement is KtExpression) { - when (parent) { - is KtExpression -> { + when { + parent is KtStringTemplateEntryWithExpression && + newElement is KtStringTemplateExpression && + // Do not mix raw and non-raw templates + parent.parent.firstChild.text == newElement.firstChild.text -> { + val entriesToAdd = newElement.entries + val templateExpression = parent.parent as KtStringTemplateExpression + if (entriesToAdd.size > 0) { + templateExpression.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(expression).createBlockStringTemplateEntry(lastNewEntry.expression!!)) + } + } + parent.delete() + } + + parent is KtExpression -> { if (KtPsiUtil.areParenthesesNecessary(newElement, expression, parent)) { return rawReplaceHandler(KtPsiFactory(expression).createExpressionByPattern("($0)", newElement)) } } - is KtSimpleNameStringTemplateEntry -> { + parent is KtSimpleNameStringTemplateEntry -> { if (newElement !is KtSimpleNameExpression) { val newEntry = parent.replace(KtPsiFactory(expression).createBlockStringTemplateEntry(newElement)) as KtBlockStringTemplateEntry return newEntry.getExpression()!! diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index ee25c02e715..e6b1fc9e8fe 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -36,6 +36,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.types.expressions.OperatorConventions import java.util.* import kotlin.test.assertTrue +import kotlin.text.Regex // NOTE: in this file we collect only Kotlin-specific methods working with PSI and not modifying it @@ -423,4 +424,11 @@ public fun KtExpression.getOutermostParenthesizerOrThis(): KtExpression { }?.first as KtExpression? ?: this } -public fun PsiElement.isFunctionalExpression(): Boolean = this is KtNamedFunction && nameIdentifier == null \ No newline at end of file +public fun PsiElement.isFunctionalExpression(): Boolean = this is KtNamedFunction && nameIdentifier == null + +private val BAD_NEIGHBOUR_FOR_SIMPLE_TEMPLATE_ENTRY_PATTERN = Regex("[a-zA-Z0-9_].*") + +fun canPlaceAfterSimpleNameEntry(element: PsiElement?): Boolean { + val entryText = element?.text ?: return true + return !BAD_NEIGHBOUR_FOR_SIMPLE_TEMPLATE_ENTRY_PATTERN.matches(entryText) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveCurlyBracesFromTemplateIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveCurlyBracesFromTemplateIntention.kt index 0e1178101d3..37aa3bc165d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveCurlyBracesFromTemplateIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveCurlyBracesFromTemplateIntention.kt @@ -23,15 +23,14 @@ import org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry import org.jetbrains.kotlin.psi.KtNameReferenceExpression import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression -import java.util.regex.Pattern +import org.jetbrains.kotlin.psi.psiUtil.canPlaceAfterSimpleNameEntry public class RemoveCurlyBracesFromTemplateInspection : IntentionBasedInspection(RemoveCurlyBracesFromTemplateIntention()) public class RemoveCurlyBracesFromTemplateIntention : SelfTargetingOffsetIndependentIntention(javaClass(), "Remove curly braces") { override fun isApplicableTo(element: KtBlockStringTemplateEntry): Boolean { if (element.getExpression() !is KtNameReferenceExpression) return false - val nextSiblingText = element.getNextSibling()?.getText() - return nextSiblingText == null || !pattern.matcher(nextSiblingText).matches() + return canPlaceAfterSimpleNameEntry(element.nextSibling) } override fun applyTo(element: KtBlockStringTemplateEntry, editor: Editor) { @@ -43,8 +42,4 @@ public class RemoveCurlyBracesFromTemplateIntention : SelfTargetingOffsetIndepen val newEntry = KtPsiFactory(element).createSimpleNameStringTemplateEntry(name) return element.replaced(newEntry) } - - companion object { - private val pattern = Pattern.compile("[a-zA-Z0-9_].*") - } } diff --git a/idea/testData/refactoring/inline/stringTemplates/addBraces.kt b/idea/testData/refactoring/inline/stringTemplates/addBraces.kt new file mode 100644 index 00000000000..03ab1052836 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/addBraces.kt @@ -0,0 +1,5 @@ +fun foo() { + val a = 1 + val x = " $a" + val y = "x=${x}_" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/addBraces.kt.after b/idea/testData/refactoring/inline/stringTemplates/addBraces.kt.after new file mode 100644 index 00000000000..a375856d39b --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/addBraces.kt.after @@ -0,0 +1,4 @@ +fun foo() { + val a = 1 + val y = "x= ${a}_" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/blockEntry.kt b/idea/testData/refactoring/inline/stringTemplates/blockEntry.kt new file mode 100644 index 00000000000..0b2d1b6a9cb --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/blockEntry.kt @@ -0,0 +1,5 @@ +fun foo() { + val a = 1 + val x = "_$a:${a + 1}_" + val y = "__x=${x}__" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/blockEntry.kt.after b/idea/testData/refactoring/inline/stringTemplates/blockEntry.kt.after new file mode 100644 index 00000000000..7088676f3a3 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/blockEntry.kt.after @@ -0,0 +1,4 @@ +fun foo() { + val a = 1 + val y = "__x=_$a:${a + 1}___" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/empty.kt b/idea/testData/refactoring/inline/stringTemplates/empty.kt new file mode 100644 index 00000000000..6e615eac5cb --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/empty.kt @@ -0,0 +1,4 @@ +fun foo() { + val x = "" + val y = "!!x=$x!!" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/empty.kt.after b/idea/testData/refactoring/inline/stringTemplates/empty.kt.after new file mode 100644 index 00000000000..4ac49b5bce4 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/empty.kt.after @@ -0,0 +1,3 @@ +fun foo() { + val y = "!!x=!!" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/nonEmpty.kt b/idea/testData/refactoring/inline/stringTemplates/nonEmpty.kt new file mode 100644 index 00000000000..859aed88f81 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/nonEmpty.kt @@ -0,0 +1,5 @@ +fun foo() { + val a = 1 + val x = "_$a:${a + 1}_" + val y = "!!x=$x!!" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/nonEmpty.kt.after b/idea/testData/refactoring/inline/stringTemplates/nonEmpty.kt.after new file mode 100644 index 00000000000..32749a3eda2 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/nonEmpty.kt.after @@ -0,0 +1,4 @@ +fun foo() { + val a = 1 + val y = "!!x=_$a:${a + 1}_!!" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/nonRawToRaw.kt b/idea/testData/refactoring/inline/stringTemplates/nonRawToRaw.kt new file mode 100644 index 00000000000..247e22985c8 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/nonRawToRaw.kt @@ -0,0 +1,4 @@ +fun foo() { + val x = "ab\nc" + val y = """x=$x""" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/nonRawToRaw.kt.after b/idea/testData/refactoring/inline/stringTemplates/nonRawToRaw.kt.after new file mode 100644 index 00000000000..0bf7751753e --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/nonRawToRaw.kt.after @@ -0,0 +1,3 @@ +fun foo() { + val y = """x=${"ab\nc"}""" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/rawString.kt b/idea/testData/refactoring/inline/stringTemplates/rawString.kt new file mode 100644 index 00000000000..09d0ca5121b --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/rawString.kt @@ -0,0 +1,7 @@ +fun foo() { + val a = 1 + val x = """_ + $a:${a + 1} + _""" + val y = """!!x=$x!!""" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/rawString.kt.after b/idea/testData/refactoring/inline/stringTemplates/rawString.kt.after new file mode 100644 index 00000000000..c4e7abbb634 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/rawString.kt.after @@ -0,0 +1,6 @@ +fun foo() { + val a = 1 + val y = """!!x=_ + $a:${a + 1} + _!!""" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/rawToNonRaw.kt b/idea/testData/refactoring/inline/stringTemplates/rawToNonRaw.kt new file mode 100644 index 00000000000..8ca2c283229 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/rawToNonRaw.kt @@ -0,0 +1,5 @@ +fun foo() { + val x = """ab + c""" + val y = "x=$x" +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/stringTemplates/rawToNonRaw.kt.after b/idea/testData/refactoring/inline/stringTemplates/rawToNonRaw.kt.after new file mode 100644 index 00000000000..e20ba0da300 --- /dev/null +++ b/idea/testData/refactoring/inline/stringTemplates/rawToNonRaw.kt.after @@ -0,0 +1,4 @@ +fun foo() { + val y = "x=${"""ab + c"""}" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java index 9cb193e3d05..91da7c40b8f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java @@ -484,4 +484,55 @@ public class InlineTestGenerated extends AbstractInlineTest { doTest(fileName); } } + + @TestMetadata("idea/testData/refactoring/inline/stringTemplates") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class StringTemplates extends AbstractInlineTest { + @TestMetadata("addBraces.kt") + public void testAddBraces() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/stringTemplates/addBraces.kt"); + doTest(fileName); + } + + public void testAllFilesPresentInStringTemplates() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/inline/stringTemplates"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("blockEntry.kt") + public void testBlockEntry() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/stringTemplates/blockEntry.kt"); + doTest(fileName); + } + + @TestMetadata("empty.kt") + public void testEmpty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/stringTemplates/empty.kt"); + doTest(fileName); + } + + @TestMetadata("nonEmpty.kt") + public void testNonEmpty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/stringTemplates/nonEmpty.kt"); + doTest(fileName); + } + + @TestMetadata("nonRawToRaw.kt") + public void testNonRawToRaw() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/stringTemplates/nonRawToRaw.kt"); + doTest(fileName); + } + + @TestMetadata("rawString.kt") + public void testRawString() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/stringTemplates/rawString.kt"); + doTest(fileName); + } + + @TestMetadata("rawToNonRaw.kt") + public void testRawToNonRaw() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/stringTemplates/rawToNonRaw.kt"); + doTest(fileName); + } + } }