Evaluating string constants in injections in String Interpolations (KT-25906)

This commit is contained in:
Nicolay Mitropolsky
2018-11-19 20:20:09 +03:00
parent 0489efc0ae
commit 0cfce8bd99
2 changed files with 100 additions and 10 deletions
@@ -23,7 +23,11 @@ import com.intellij.psi.PsiLanguageInjectionHost
import org.intellij.plugins.intelliLang.inject.InjectedLanguage
import org.intellij.plugins.intelliLang.inject.InjectorUtils
import org.intellij.plugins.intelliLang.inject.config.BaseInjection
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.util.*
data class InjectionSplitResult(val isUnparsable: Boolean, val ranges: List<Trinity<PsiLanguageInjectionHost, InjectedLanguage, TextRange>>)
@@ -54,22 +58,25 @@ fun splitLiteralToInjectionParts(injection: BaseInjection, literal: KtStringTemp
val child = children[i]
val partOffsetInParent = child.startOffsetInParent
@Suppress("IMPLICIT_CAST_TO_ANY")
val part = when (child) {
is KtLiteralStringTemplateEntry, is KtEscapeStringTemplateEntry -> {
val partSize = children.subList(i, len).asSequence()
.takeWhile { it is KtLiteralStringTemplateEntry || it is KtEscapeStringTemplateEntry }
.count()
.takeWhile { it is KtLiteralStringTemplateEntry || it is KtEscapeStringTemplateEntry }
.count()
i += partSize - 1
children[i]
}
is KtSimpleNameStringTemplateEntry -> {
unparsable = true
child.expression?.text ?: NO_VALUE_NAME
}
is KtBlockStringTemplateEntry -> {
unparsable = true
NO_VALUE_NAME
}
is KtSimpleNameStringTemplateEntry ->
tryEvaluateConstant(child.expression) ?: run {
unparsable = true
child.expression?.text ?: NO_VALUE_NAME
}
is KtBlockStringTemplateEntry ->
tryEvaluateConstant(child.expression) ?: run {
unparsable = true
NO_VALUE_NAME
}
else -> {
unparsable = true
child
@@ -97,4 +104,12 @@ fun splitLiteralToInjectionParts(injection: BaseInjection, literal: KtStringTemp
return InjectionSplitResult(unparsable, result)
}
private fun tryEvaluateConstant(ktExpression: KtExpression?) =
ktExpression?.let { expression ->
ConstantExpressionEvaluator.getConstant(expression, expression.analyze())
?.takeUnless { it.isError }
?.getValue(TypeUtils.NO_EXPECTED_TYPE)
?.safeAs<String>()
}
private val NO_VALUE_NAME = "missingValue"
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.psi.injection
import com.intellij.lang.html.HTMLLanguage
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.injection.Injectable
import com.intellij.testFramework.LightProjectDescriptor
import junit.framework.TestCase
import org.intellij.plugins.intelliLang.inject.InjectLanguageAction
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
class StringInterpolationInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor(): LightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
fun testInterpolationSimpleName() = doTest(
"""
fun foo(){
val body = "Hello!"
"<html>${"$"}body</html><caret>"
}
""",
"<html>Hello!</html>"
)
fun testInterpolationBlock() = doTest(
"""
const val a = "Hello "
fun foo(){
val body = "World!"
"<html>${"$"}{a + body}</html><caret>"
}
""",
"<html>Hello World!</html>"
)
private fun doTest(text: String, expectedText: String) {
myFixture.configureByText("Injected.kt", text)
InjectLanguageAction.invokeImpl(
project,
myFixture.editor,
myFixture.file,
Injectable.fromLanguage(HTMLLanguage.INSTANCE)
)
val injectedElement = injectedElement ?: kotlin.test.fail("no injection")
TestCase.assertEquals(HTMLLanguage.INSTANCE, injectedElement.language)
val containingFile = injectedElement.containingFile
TestCase.assertEquals(expectedText, containingFile.text)
TestCase.assertFalse(
"Shouldn't be FRANKENSTEIN",
java.lang.Boolean.TRUE == containingFile.getUserData(InjectedLanguageManager.FRANKENSTEIN_INJECTION)
)
}
private val injectedLanguageManager: InjectedLanguageManager
get() = InjectedLanguageManager.getInstance(project)
private val injectedElement: PsiElement?
get() = injectedLanguageManager.findInjectedElementAt(topLevelFile, topLevelCaretPosition)
private val topLevelFile: PsiFile get() = file.let { injectedLanguageManager.getTopLevelFile(it) }
private val topLevelCaretPosition get() = editor.caretModel.offset
}