KotlinLanguageInjector: injecting into returned literals (#KT-21673)

This commit is contained in:
Nicolay Mitropolsky
2018-06-08 18:38:51 +03:00
parent 675d02093d
commit 79487a6a21
5 changed files with 209 additions and 11 deletions
@@ -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<StringValue>()?.value ?: return null
return InjectionInfo(languageId, null, null)
@@ -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<StringValue>()?.value ?: return null
return InjectionInfo(languageId, null, null)
@@ -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<StringValue>()?.value ?: return null
return InjectionInfo(languageId, null, null)
@@ -612,4 +612,61 @@ class KotlinInjectionTest : AbstractInjectionTest() {
}
}
fun testInjectionOnReturnResultWithAnnotation() = doInjectionPresentTest(
"""
import org.intellij.lang.annotations.Language
@Language("HTML")
fun htmlProvider(): String {
return "<ht<caret>ml></html>"
}
""",
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
)
fun testInjectionOnReturnResultWithElvisWithAnnotation() = doInjectionPresentTest(
"""
import org.intellij.lang.annotations.Language
@Language("HTML")
fun htmlProvider(arg: String?): String {
return arg ?: "<ht<caret>ml></html>"
}
""",
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) "<div>" else "<ht<caret>ml></html>"
}
""",
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
)
fun testInjectionExpressionBodyWithAnnotation() = doInjectionPresentTest(
"""
import org.intellij.lang.annotations.Language
@Language("HTML")
fun htmlProvider() = "<ht<caret>ml></html>"
""",
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
)
fun testInjectionExpressionBodyElvisWithAnnotation() = doInjectionPresentTest(
"""
import org.intellij.lang.annotations.Language
@Language("HTML")
fun htmlProvider(arg: String?) = arg ?: "<ht<caret>ml></html>"
""",
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
)
}
@@ -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 "<ht<caret>ml></html>"
}
""",
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
)
fun testInjectionOnReturnResultWithElvisWithAnnotation() = doInjectionPresentTest(
"""
import org.intellij.lang.annotations.Language
@Language("HTML")
fun htmlProvider(arg: String?): String {
return arg ?: "<ht<caret>ml></html>"
}
""",
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) "<div>" else "<ht<caret>ml></html>"
}
""",
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
)
fun testInjectionExpressionBodyWithAnnotation() = doInjectionPresentTest(
"""
import org.intellij.lang.annotations.Language
@Language("HTML")
fun htmlProvider() = "<ht<caret>ml></html>"
""",
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
)
fun testInjectionExpressionBodyElvisWithAnnotation() = doInjectionPresentTest(
"""
import org.intellij.lang.annotations.Language
@Language("HTML")
fun htmlProvider(arg: String?) = arg ?: "<ht<caret>ml></html>"
""",
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
)
}