diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/InterpolatedStringInjectorProcessor.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/InterpolatedStringInjectorProcessor.kt index 1b431f39c5f..5a6bd5ff92f 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/InterpolatedStringInjectorProcessor.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/InterpolatedStringInjectorProcessor.kt @@ -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>) @@ -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() + } + private val NO_VALUE_NAME = "missingValue" diff --git a/idea/tests/org/jetbrains/kotlin/psi/injection/StringInterpolationInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/injection/StringInterpolationInjectionTest.kt new file mode 100644 index 00000000000..9cb1cfb9e90 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/psi/injection/StringInterpolationInjectionTest.kt @@ -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!" + "${"$"}body" + } + """, + "Hello!" + ) + + fun testInterpolationBlock() = doTest( + """ + const val a = "Hello " + + fun foo(){ + val body = "World!" + "${"$"}{a + body}" + } + """, + "Hello World!" + ) + + 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 + +} \ No newline at end of file