diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt index bd2cc4c29b1..b17e75e6250 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt @@ -45,12 +45,15 @@ import org.intellij.plugins.intelliLang.inject.config.InjectionPlace import org.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport import org.intellij.plugins.intelliLang.util.AnnotationUtilEx import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.patterns.KotlinFunctionPattern import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriority +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.annotations.argumentValue @@ -172,10 +175,32 @@ class KotlinLanguageInjector( private fun findInjectionInfo(place: KtElement, originalHost: Boolean = true): InjectionInfo? { return injectWithExplicitCodeInstruction(place) - ?: injectWithCall(place) - ?: injectInAnnotationCall(place) - ?: injectWithReceiver(place) - ?: injectWithVariableUsage(place, originalHost) + ?: injectWithCall(place) + ?: injectReturnValue(place) + ?: injectInAnnotationCall(place) + ?: injectWithReceiver(place) + ?: injectWithVariableUsage(place, originalHost) + } + + private fun injectReturnValue(place: KtElement): InjectionInfo? { + val parent = place.parent + + tailrec fun findReturnExpression(expression: PsiElement?): KtReturnExpression? = when (expression) { + is KtReturnExpression -> expression + is KtBinaryExpression -> findReturnExpression(expression.takeIf { it.operationToken == KtTokens.ELVIS }?.parent) + is KtContainerNodeForControlStructureBody, is KtIfExpression -> findReturnExpression(expression.parent) + else -> null + } + + val returnExp = findReturnExpression(parent) ?: return null + + if (returnExp.labeledExpression != null) return null + + val callableDeclaration = PsiTreeUtil.getParentOfType(returnExp, KtDeclaration::class.java) as? KtCallableDeclaration ?: return null + if (callableDeclaration.annotationEntries.isEmpty()) return null + + val descriptor = callableDeclaration.descriptor ?: return null + return injectionInfoByAnnotation(descriptor) } private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? { @@ -339,7 +364,11 @@ class KotlinLanguageInjector( val functionDescriptor = ktReference.resolveToDescriptors(bindingContext).singleOrNull() as? FunctionDescriptor ?: return null val parameterDescriptor = functionDescriptor.valueParameters.getOrNull(argumentIndex) ?: return null - val injectAnnotation = parameterDescriptor.annotations.findAnnotation(FqName(AnnotationUtil.LANGUAGE)) ?: return null + return injectionInfoByAnnotation(parameterDescriptor) + } + + private fun injectionInfoByAnnotation(annotated: Annotated): InjectionInfo? { + val injectAnnotation = annotated.annotations.findAnnotation(FqName(AnnotationUtil.LANGUAGE)) ?: return null val languageId = injectAnnotation.argumentValue("value")?.safeAs()?.value ?: return null return InjectionInfo(languageId, null, null) diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt.172 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt.172 index 44882dc742e..7fc890a226f 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt.172 +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt.172 @@ -36,22 +36,22 @@ import org.intellij.plugins.intelliLang.inject.config.BaseInjection import org.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport import org.intellij.plugins.intelliLang.util.AnnotationUtilEx import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriority +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.annotations.argumentValue import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.util.* import kotlin.collections.ArrayList -import org.jetbrains.kotlin.util.aliasImportMap -import org.jetbrains.kotlin.utils.SmartList -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -import org.jetbrains.kotlin.utils.addToStdlib.safeAs class KotlinLanguageInjector( val configuration: Configuration, @@ -158,11 +158,33 @@ class KotlinLanguageInjector( private fun findInjectionInfo(place: KtElement, originalHost: Boolean = true): InjectionInfo? { return injectWithExplicitCodeInstruction(place) ?: injectWithCall(place) + ?: injectReturnValue(place) ?: injectWithReceiver(place) ?: injectWithVariableUsage(place, originalHost) ?: injectWithAnnotationEntry(place) } + private fun injectReturnValue(place: KtElement): InjectionInfo? { + val parent = place.parent + + tailrec fun findReturnExpression(expression: PsiElement?): KtReturnExpression? = when (expression) { + is KtReturnExpression -> expression + is KtBinaryExpression -> findReturnExpression(expression.takeIf { it.operationToken == KtTokens.ELVIS }?.parent) + is KtContainerNodeForControlStructureBody, is KtIfExpression -> findReturnExpression(expression.parent) + else -> null + } + + val returnExp = findReturnExpression(parent) ?: return null + + if (returnExp.labeledExpression != null) return null + + val callableDeclaration = PsiTreeUtil.getParentOfType(returnExp, KtDeclaration::class.java) as? KtCallableDeclaration ?: return null + if (callableDeclaration.annotationEntries.isEmpty()) return null + + val descriptor = callableDeclaration.descriptor ?: return null + return injectionInfoByAnnotation(descriptor) + } + private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? { val support = kotlinSupport ?: return null return InjectionInfo.fromBaseInjection(support.findCommentInjection(host)) ?: support.findAnnotationInjectionLanguageId(host) @@ -300,7 +322,11 @@ class KotlinLanguageInjector( val functionDescriptor = ktReference.resolveToDescriptors(bindingContext).singleOrNull() as? FunctionDescriptor ?: return null val parameterDescriptor = functionDescriptor.valueParameters.getOrNull(argumentIndex) ?: return null - val injectAnnotation = parameterDescriptor.annotations.findAnnotation(FqName(AnnotationUtil.LANGUAGE)) ?: return null + return injectionInfoByAnnotation(parameterDescriptor) + } + + private fun injectionInfoByAnnotation(annotated: Annotated): InjectionInfo? { + val injectAnnotation = annotated.annotations.findAnnotation(FqName(AnnotationUtil.LANGUAGE)) ?: return null val languageId = injectAnnotation.argumentValue("value")?.safeAs()?.value ?: return null return InjectionInfo(languageId, null, null) diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt.173 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt.173 index dfa68796bf1..790b2fb3852 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt.173 +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt.173 @@ -45,12 +45,15 @@ import org.intellij.plugins.intelliLang.inject.config.InjectionPlace import org.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport import org.intellij.plugins.intelliLang.util.AnnotationUtilEx import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.patterns.KotlinFunctionPattern import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriority +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.util.ProjectRootsUtil +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.annotations.argumentValue @@ -169,11 +172,33 @@ class KotlinLanguageInjector( private fun findInjectionInfo(place: KtElement, originalHost: Boolean = true): InjectionInfo? { return injectWithExplicitCodeInstruction(place) ?: injectWithCall(place) + ?: injectReturnValue(place) ?: injectInAnnotationCall(place) ?: injectWithReceiver(place) ?: injectWithVariableUsage(place, originalHost) } + private fun injectReturnValue(place: KtElement): InjectionInfo? { + val parent = place.parent + + tailrec fun findReturnExpression(expression: PsiElement?): KtReturnExpression? = when (expression) { + is KtReturnExpression -> expression + is KtBinaryExpression -> findReturnExpression(expression.takeIf { it.operationToken == KtTokens.ELVIS }?.parent) + is KtContainerNodeForControlStructureBody, is KtIfExpression -> findReturnExpression(expression.parent) + else -> null + } + + val returnExp = findReturnExpression(parent) ?: return null + + if (returnExp.labeledExpression != null) return null + + val callableDeclaration = PsiTreeUtil.getParentOfType(returnExp, KtDeclaration::class.java) as? KtCallableDeclaration ?: return null + if (callableDeclaration.annotationEntries.isEmpty()) return null + + val descriptor = callableDeclaration.descriptor ?: return null + return injectionInfoByAnnotation(descriptor) + } + private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? { val support = kotlinSupport ?: return null return InjectionInfo.fromBaseInjection(support.findCommentInjection(host)) ?: support.findAnnotationInjectionLanguageId(host) @@ -335,7 +360,11 @@ class KotlinLanguageInjector( val functionDescriptor = ktReference.resolveToDescriptors(bindingContext).singleOrNull() as? FunctionDescriptor ?: return null val parameterDescriptor = functionDescriptor.valueParameters.getOrNull(argumentIndex) ?: return null - val injectAnnotation = parameterDescriptor.annotations.findAnnotation(FqName(AnnotationUtil.LANGUAGE)) ?: return null + return injectionInfoByAnnotation(parameterDescriptor) + } + + private fun injectionInfoByAnnotation(annotated: Annotated): InjectionInfo? { + val injectAnnotation = annotated.annotations.findAnnotation(FqName(AnnotationUtil.LANGUAGE)) ?: return null val languageId = injectAnnotation.argumentValue("value")?.safeAs()?.value ?: return null return InjectionInfo(languageId, null, null) diff --git a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt index 17e604f6332..391bc11ea4d 100644 --- a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt @@ -612,4 +612,61 @@ class KotlinInjectionTest : AbstractInjectionTest() { } } + fun testInjectionOnReturnResultWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider(): String { + return "ml>" + } + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + + fun testInjectionOnReturnResultWithElvisWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider(arg: String?): String { + return arg ?: "ml>" + } + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + + fun testInjectionOnReturnResultWithIfWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider(arg: String?): String { + return if (arg == null) "
" else "ml>" + } + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + + fun testInjectionExpressionBodyWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider() = "ml>" + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + + fun testInjectionExpressionBodyElvisWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider(arg: String?) = arg ?: "ml>" + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + } + diff --git a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt.172 b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt.172 index a4dc61c6497..579c03a95dc 100644 --- a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt.172 +++ b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt.172 @@ -506,4 +506,61 @@ class KotlinInjectionTest : AbstractInjectionTest() { Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true) } } + + fun testInjectionOnReturnResultWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider(): String { + return "ml>" + } + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + + fun testInjectionOnReturnResultWithElvisWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider(arg: String?): String { + return arg ?: "ml>" + } + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + + fun testInjectionOnReturnResultWithIfWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider(arg: String?): String { + return if (arg == null) "
" else "ml>" + } + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + + fun testInjectionExpressionBodyWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider() = "ml>" + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + + fun testInjectionExpressionBodyElvisWithAnnotation() = doInjectionPresentTest( + """ + import org.intellij.lang.annotations.Language + + @Language("HTML") + fun htmlProvider(arg: String?) = arg ?: "ml>" + """, + languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false + ) + }