KtStringTemplateExpressionManipulator: properly works with interpolations (KT-24958)

This commit is contained in:
xiexed
2018-06-22 21:54:28 +03:00
committed by GitHub
parent 01db78d776
commit 414fabb2c6
3 changed files with 69 additions and 78 deletions
@@ -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
}