Inline Variable: Inline string template entries
#KT-7654 Fixed
This commit is contained in:
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.psi
|
|||||||
import com.intellij.lang.ASTNode
|
import com.intellij.lang.ASTNode
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.KtNodeType
|
import org.jetbrains.kotlin.KtNodeType
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.canPlaceAfterSimpleNameEntry
|
||||||
|
|
||||||
public abstract class KtExpressionImpl(node: ASTNode) : KtElementImpl(node), KtExpression {
|
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()
|
val parent = expression.getParent()
|
||||||
|
|
||||||
if (newElement is KtExpression) {
|
if (newElement is KtExpression) {
|
||||||
when (parent) {
|
when {
|
||||||
is KtExpression -> {
|
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)) {
|
if (KtPsiUtil.areParenthesesNecessary(newElement, expression, parent)) {
|
||||||
return rawReplaceHandler(KtPsiFactory(expression).createExpressionByPattern("($0)", newElement))
|
return rawReplaceHandler(KtPsiFactory(expression).createExpressionByPattern("($0)", newElement))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is KtSimpleNameStringTemplateEntry -> {
|
parent is KtSimpleNameStringTemplateEntry -> {
|
||||||
if (newElement !is KtSimpleNameExpression) {
|
if (newElement !is KtSimpleNameExpression) {
|
||||||
val newEntry = parent.replace(KtPsiFactory(expression).createBlockStringTemplateEntry(newElement)) as KtBlockStringTemplateEntry
|
val newEntry = parent.replace(KtPsiFactory(expression).createBlockStringTemplateEntry(newElement)) as KtBlockStringTemplateEntry
|
||||||
return newEntry.getExpression()!!
|
return newEntry.getExpression()!!
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
|
|||||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertTrue
|
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
|
// 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
|
}?.first as KtExpression? ?: this
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun PsiElement.isFunctionalExpression(): Boolean = this is KtNamedFunction && nameIdentifier == null
|
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)
|
||||||
|
}
|
||||||
+2
-7
@@ -23,15 +23,14 @@ import org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry
|
|||||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||||
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
|
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
|
||||||
import java.util.regex.Pattern
|
import org.jetbrains.kotlin.psi.psiUtil.canPlaceAfterSimpleNameEntry
|
||||||
|
|
||||||
public class RemoveCurlyBracesFromTemplateInspection : IntentionBasedInspection<KtBlockStringTemplateEntry>(RemoveCurlyBracesFromTemplateIntention())
|
public class RemoveCurlyBracesFromTemplateInspection : IntentionBasedInspection<KtBlockStringTemplateEntry>(RemoveCurlyBracesFromTemplateIntention())
|
||||||
|
|
||||||
public class RemoveCurlyBracesFromTemplateIntention : SelfTargetingOffsetIndependentIntention<KtBlockStringTemplateEntry>(javaClass(), "Remove curly braces") {
|
public class RemoveCurlyBracesFromTemplateIntention : SelfTargetingOffsetIndependentIntention<KtBlockStringTemplateEntry>(javaClass(), "Remove curly braces") {
|
||||||
override fun isApplicableTo(element: KtBlockStringTemplateEntry): Boolean {
|
override fun isApplicableTo(element: KtBlockStringTemplateEntry): Boolean {
|
||||||
if (element.getExpression() !is KtNameReferenceExpression) return false
|
if (element.getExpression() !is KtNameReferenceExpression) return false
|
||||||
val nextSiblingText = element.getNextSibling()?.getText()
|
return canPlaceAfterSimpleNameEntry(element.nextSibling)
|
||||||
return nextSiblingText == null || !pattern.matcher(nextSiblingText).matches()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyTo(element: KtBlockStringTemplateEntry, editor: Editor) {
|
override fun applyTo(element: KtBlockStringTemplateEntry, editor: Editor) {
|
||||||
@@ -43,8 +42,4 @@ public class RemoveCurlyBracesFromTemplateIntention : SelfTargetingOffsetIndepen
|
|||||||
val newEntry = KtPsiFactory(element).createSimpleNameStringTemplateEntry(name)
|
val newEntry = KtPsiFactory(element).createSimpleNameStringTemplateEntry(name)
|
||||||
return element.replaced(newEntry)
|
return element.replaced(newEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val pattern = Pattern.compile("[a-zA-Z0-9_].*")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fun foo() {
|
||||||
|
val a = 1
|
||||||
|
val <caret>x = " $a"
|
||||||
|
val y = "x=${x}_"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo() {
|
||||||
|
val a = 1
|
||||||
|
val y = "x= ${a}_"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fun foo() {
|
||||||
|
val a = 1
|
||||||
|
val <caret>x = "_$a:${a + 1}_"
|
||||||
|
val y = "__x=${x}__"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo() {
|
||||||
|
val a = 1
|
||||||
|
val y = "__x=_$a:${a + 1}___"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo() {
|
||||||
|
val <caret>x = ""
|
||||||
|
val y = "!!x=$x!!"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo() {
|
||||||
|
val y = "!!x=!!"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fun foo() {
|
||||||
|
val a = 1
|
||||||
|
val <caret>x = "_$a:${a + 1}_"
|
||||||
|
val y = "!!x=$x!!"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo() {
|
||||||
|
val a = 1
|
||||||
|
val y = "!!x=_$a:${a + 1}_!!"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo() {
|
||||||
|
val <caret>x = "ab\nc"
|
||||||
|
val y = """x=$x"""
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo() {
|
||||||
|
val y = """x=${"ab\nc"}"""
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun foo() {
|
||||||
|
val a = 1
|
||||||
|
val <caret>x = """_
|
||||||
|
$a:${a + 1}
|
||||||
|
_"""
|
||||||
|
val y = """!!x=$x!!"""
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
fun foo() {
|
||||||
|
val a = 1
|
||||||
|
val y = """!!x=_
|
||||||
|
$a:${a + 1}
|
||||||
|
_!!"""
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fun foo() {
|
||||||
|
val <caret>x = """ab
|
||||||
|
c"""
|
||||||
|
val y = "x=$x"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo() {
|
||||||
|
val y = "x=${"""ab
|
||||||
|
c"""}"
|
||||||
|
}
|
||||||
@@ -484,4 +484,55 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
|||||||
doTest(fileName);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user