KtStringTemplateExpressionManipulator: properly works with interpolations (KT-24958)
This commit is contained in:
+32
-4
@@ -16,12 +16,17 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.psi.psiUtil
|
package org.jetbrains.kotlin.psi.psiUtil
|
||||||
|
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.AbstractElementManipulator
|
import com.intellij.psi.AbstractElementManipulator
|
||||||
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||||
|
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
|
||||||
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
|
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
|
||||||
|
|
||||||
|
private val LOG = Logger.getInstance(KtStringTemplateExpressionManipulator::class.java)
|
||||||
|
|
||||||
class KtStringTemplateExpressionManipulator : AbstractElementManipulator<KtStringTemplateExpression>() {
|
class KtStringTemplateExpressionManipulator : AbstractElementManipulator<KtStringTemplateExpression>() {
|
||||||
override fun handleContentChange(
|
override fun handleContentChange(
|
||||||
element: KtStringTemplateExpression,
|
element: KtStringTemplateExpression,
|
||||||
@@ -29,11 +34,34 @@ class KtStringTemplateExpressionManipulator : AbstractElementManipulator<KtStrin
|
|||||||
newContent: String
|
newContent: String
|
||||||
): KtStringTemplateExpression? {
|
): KtStringTemplateExpression? {
|
||||||
val node = element.node
|
val node = element.node
|
||||||
val content = if (element.isSingleQuoted()) StringUtil.escapeStringCharacters(newContent) else newContent
|
|
||||||
val oldText = node.text
|
val oldText = node.text
|
||||||
val newText = oldText.substring(0, range.startOffset) + content + oldText.substring(range.endOffset)
|
|
||||||
val expression = KtPsiFactory(element.project).createExpression(newText)
|
fun wrapAsInOld(content: String) = oldText.substring(0, range.startOffset) + content + oldText.substring(range.endOffset)
|
||||||
node.replaceAllChildrenToChildrenOf(expression.node)
|
|
||||||
|
fun makeKtExpressionFromText(text: String): KtExpression {
|
||||||
|
val ktExpression = KtPsiFactory(element.project).createExpression(text)
|
||||||
|
if (ktExpression !is KtStringTemplateExpression) {
|
||||||
|
LOG.error("can't create a `KtStringTemplateExpression` from '$text'")
|
||||||
|
}
|
||||||
|
return ktExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
val newContentPreprocessed: String =
|
||||||
|
if (element.isSingleQuoted()) {
|
||||||
|
val expressionFromText = makeKtExpressionFromText("\"\"\"$newContent\"\"\"")
|
||||||
|
if (expressionFromText is KtStringTemplateExpression) {
|
||||||
|
expressionFromText.entries.joinToString("") { entry ->
|
||||||
|
when (entry) {
|
||||||
|
is KtStringTemplateEntryWithExpression -> entry.text
|
||||||
|
else -> StringUtil.escapeStringCharacters(entry.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else newContent
|
||||||
|
} else newContent
|
||||||
|
|
||||||
|
val newKtExpression = makeKtExpressionFromText(wrapAsInOld(newContentPreprocessed))
|
||||||
|
node.replaceAllChildrenToChildrenOf(newKtExpression.node)
|
||||||
|
|
||||||
return node.getPsi(KtStringTemplateExpression::class.java)
|
return node.getPsi(KtStringTemplateExpression::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.psi
|
package org.jetbrains.kotlin.psi
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.psi.ElementManipulators
|
||||||
|
import com.intellij.testFramework.LoggedErrorProcessor
|
||||||
|
import org.apache.log4j.Logger
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
||||||
import com.intellij.psi.ElementManipulators
|
|
||||||
import org.junit.Assert.*
|
|
||||||
import com.intellij.openapi.util.TextRange
|
|
||||||
|
|
||||||
class StringTemplateExpressionManipulatorTest : KotlinLightCodeInsightFixtureTestCase() {
|
class StringTemplateExpressionManipulatorTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
fun testSingleQuoted() {
|
fun testSingleQuoted() {
|
||||||
@@ -58,6 +59,27 @@ class StringTemplateExpressionManipulatorTest : KotlinLightCodeInsightFixtureTes
|
|||||||
fun testReplaceRange() {
|
fun testReplaceRange() {
|
||||||
doTestContentChange("\"abc\"", "x", range = TextRange(2,3), expected = "\"axc\"")
|
doTestContentChange("\"abc\"", "x", range = TextRange(2,3), expected = "\"axc\"")
|
||||||
doTestContentChange("\"\"\"abc\"\"\"", "x", range = TextRange(4,5), expected = "\"\"\"axc\"\"\"")
|
doTestContentChange("\"\"\"abc\"\"\"", "x", range = TextRange(4,5), expected = "\"\"\"axc\"\"\"")
|
||||||
|
doTestContentChange(
|
||||||
|
"\"<div style = \\\"default\\\">\${foo(\"\")}</div>\"",
|
||||||
|
"custom", range = TextRange(16, 23),
|
||||||
|
expected = "\"<div style = \\\"custom\\\">\${foo(\"\")}</div>\""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun testHackyReplaceRange() {
|
||||||
|
suppressFallingOnLogError {
|
||||||
|
doTestContentChange("\"a\\\"bc\"", "'", range = TextRange(0, 4), expected = "'bc\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testTemplateWithInterpolation() {
|
||||||
|
doTestContentChange("\"<div>\${foo(\"\")}</div>\"", "<p>\${foo(\"\")}</p>", "\"<p>\${foo(\"\")}</p>\"")
|
||||||
|
doTestContentChange(
|
||||||
|
"\"<div style = \\\"default\\\">\${foo(\"\")}</div>\"",
|
||||||
|
"<p style = \"custom\">\${foo(\"\")}</p>",
|
||||||
|
"\"<p style = \\\"custom\\\">\${foo(\"\")}</p>\""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doTestContentChange(original: String, newContent: String, expected: String, range: TextRange? = null) {
|
private fun doTestContentChange(original: String, newContent: String, expected: String, range: TextRange? = null) {
|
||||||
@@ -69,3 +91,15 @@ class StringTemplateExpressionManipulatorTest : KotlinLightCodeInsightFixtureTes
|
|||||||
|
|
||||||
override fun getProjectDescriptor() = KotlinLightProjectDescriptor.INSTANCE
|
override fun getProjectDescriptor() = KotlinLightProjectDescriptor.INSTANCE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T> suppressFallingOnLogError(call: () -> T) {
|
||||||
|
val loggedErrorProcessor = LoggedErrorProcessor.getInstance()
|
||||||
|
try {
|
||||||
|
LoggedErrorProcessor.setNewInstance(object : LoggedErrorProcessor() {
|
||||||
|
override fun processError(message: String?, t: Throwable?, details: Array<out String>?, logger: Logger) {}
|
||||||
|
})
|
||||||
|
call()
|
||||||
|
} finally {
|
||||||
|
LoggedErrorProcessor.setNewInstance(loggedErrorProcessor)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2015 JetBrains s.r.o.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.psi
|
|
||||||
|
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
|
||||||
import com.intellij.psi.ElementManipulators
|
|
||||||
import org.junit.Assert.*
|
|
||||||
import com.intellij.openapi.util.TextRange
|
|
||||||
|
|
||||||
class StringTemplateExpressionManipulatorTest : KotlinLightCodeInsightFixtureTestCase() {
|
|
||||||
fun testSingleQuoted() {
|
|
||||||
doTestContentChange("\"a\"", "b", "\"b\"")
|
|
||||||
doTestContentChange("\"\"", "b", "\"b\"")
|
|
||||||
doTestContentChange("\"a\"", "\t", "\"\\t\"")
|
|
||||||
doTestContentChange("\"a\"", "\n", "\"\\n\"")
|
|
||||||
doTestContentChange("\"a\"", "\\t", "\"\\\\t\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testUnclosedQuoted() {
|
|
||||||
doTestContentChange("\"a", "b", "\"b")
|
|
||||||
doTestContentChange("\"", "b", "\"b")
|
|
||||||
doTestContentChange("\"a", "\t", "\"\\t")
|
|
||||||
doTestContentChange("\"a", "\n", "\"\\n")
|
|
||||||
doTestContentChange("\"a", "\\t", "\"\\\\t")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testTripleQuoted() {
|
|
||||||
doTestContentChange("\"\"\"a\"\"\"", "b", "\"\"\"b\"\"\"")
|
|
||||||
doTestContentChange("\"\"\"\"\"\"", "b", "\"\"\"b\"\"\"")
|
|
||||||
doTestContentChange("\"\"\"a\"\"\"", "\t", "\"\"\"\t\"\"\"")
|
|
||||||
doTestContentChange("\"\"\"a\"\"\"", "\n", "\"\"\"\n\"\"\"")
|
|
||||||
doTestContentChange("\"\"\"a\"\"\"", "\\t", "\"\"\"\\t\"\"\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testUnclosedTripleQuoted() {
|
|
||||||
doTestContentChange("\"\"\"a", "b", "\"\"\"b")
|
|
||||||
doTestContentChange("\"\"\"", "b", "\"\"\"b")
|
|
||||||
doTestContentChange("\"\"\"a", "\t", "\"\"\"\t")
|
|
||||||
doTestContentChange("\"\"\"a", "\n", "\"\"\"\n")
|
|
||||||
doTestContentChange("\"\"\"a", "\\t", "\"\"\"\\t")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testReplaceRange() {
|
|
||||||
doTestContentChange("\"abc\"", "x", range = TextRange(2,3), expected = "\"axc\"")
|
|
||||||
doTestContentChange("\"\"\"abc\"\"\"", "x", range = TextRange(4,5), expected = "\"\"\"axc\"\"\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doTestContentChange(original: String, newContent: String, expected: String, range: TextRange? = null) {
|
|
||||||
val expression = KtPsiFactory(project).createExpression(original) as KtStringTemplateExpression
|
|
||||||
val manipulator = ElementManipulators.getNotNullManipulator(expression)
|
|
||||||
val newExpression = if (range == null) manipulator.handleContentChange(expression, newContent) else manipulator.handleContentChange(expression, range, newContent)
|
|
||||||
assertEquals(expected, newExpression.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getProjectDescriptor() = KotlinLightProjectDescriptor.INSTANCE
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user