KtStringTemplateExpressionManipulator: properly works with interpolations (KT-24958)
This commit is contained in:
+32
-4
@@ -16,12 +16,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.psi.psiUtil
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.AbstractElementManipulator
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
|
||||
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
|
||||
|
||||
private val LOG = Logger.getInstance(KtStringTemplateExpressionManipulator::class.java)
|
||||
|
||||
class KtStringTemplateExpressionManipulator : AbstractElementManipulator<KtStringTemplateExpression>() {
|
||||
override fun handleContentChange(
|
||||
element: KtStringTemplateExpression,
|
||||
@@ -29,11 +34,34 @@ class KtStringTemplateExpressionManipulator : AbstractElementManipulator<KtStrin
|
||||
newContent: String
|
||||
): KtStringTemplateExpression? {
|
||||
val node = element.node
|
||||
val content = if (element.isSingleQuoted()) StringUtil.escapeStringCharacters(newContent) else newContent
|
||||
val oldText = node.text
|
||||
val newText = oldText.substring(0, range.startOffset) + content + oldText.substring(range.endOffset)
|
||||
val expression = KtPsiFactory(element.project).createExpression(newText)
|
||||
node.replaceAllChildrenToChildrenOf(expression.node)
|
||||
|
||||
fun wrapAsInOld(content: String) = oldText.substring(0, range.startOffset) + content + oldText.substring(range.endOffset)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
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.KotlinLightProjectDescriptor
|
||||
import com.intellij.psi.ElementManipulators
|
||||
import org.junit.Assert.*
|
||||
import com.intellij.openapi.util.TextRange
|
||||
|
||||
class StringTemplateExpressionManipulatorTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
fun testSingleQuoted() {
|
||||
@@ -58,6 +59,27 @@ class StringTemplateExpressionManipulatorTest : KotlinLightCodeInsightFixtureTes
|
||||
fun testReplaceRange() {
|
||||
doTestContentChange("\"abc\"", "x", range = TextRange(2,3), 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) {
|
||||
@@ -69,3 +91,15 @@ class StringTemplateExpressionManipulatorTest : KotlinLightCodeInsightFixtureTes
|
||||
|
||||
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