KotlinLanguageInjector#injectInAnnotationCall optimization: using PsiClassNamePatternCondition to avoid calling getResolvedCall

This commit is contained in:
Nicolay Mitropolsky
2017-08-29 15:25:48 +03:00
committed by Nikolay Krasko
parent 81b6a61edf
commit d7fcc69315
4 changed files with 186 additions and 158 deletions
@@ -31,7 +31,7 @@ fun KtUserType.aliasImportMap(): Multimap<String, String> {
return (file as KtFile).aliasImportMap() return (file as KtFile).aliasImportMap()
} }
private fun KtFile.aliasImportMap(): Multimap<String, String> { fun KtFile.aliasImportMap(): Multimap<String, String> {
val cached = getUserData(ALIAS_IMPORT_DATA_KEY) val cached = getUserData(ALIAS_IMPORT_DATA_KEY)
val modificationStamp = modificationStamp val modificationStamp = modificationStamp
if (cached != null && modificationStamp == cached.fileModificationStamp) { if (cached != null && modificationStamp == cached.fileModificationStamp) {
@@ -23,21 +23,29 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.text.StringUtilRt
import com.intellij.patterns.PatternCondition
import com.intellij.patterns.PatternConditionPlus
import com.intellij.patterns.PsiClassNamePatternCondition
import com.intellij.patterns.ValuePatternCondition
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil
import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import org.intellij.plugins.intelliLang.Configuration import org.intellij.plugins.intelliLang.Configuration
import org.intellij.plugins.intelliLang.inject.InjectorUtils import org.intellij.plugins.intelliLang.inject.InjectorUtils
import org.intellij.plugins.intelliLang.inject.LanguageInjectionSupport import org.intellij.plugins.intelliLang.inject.LanguageInjectionSupport
import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry
import org.intellij.plugins.intelliLang.inject.config.BaseInjection import org.intellij.plugins.intelliLang.inject.config.BaseInjection
import org.intellij.plugins.intelliLang.inject.config.InjectionPlace
import org.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport import org.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport
import org.intellij.plugins.intelliLang.util.AnnotationUtilEx import org.intellij.plugins.intelliLang.util.AnnotationUtilEx
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze 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.KtReference
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriority import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriority
@@ -45,27 +53,27 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.annotations.argumentValue import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.util.aliasImportMap
import java.util.* import org.jetbrains.kotlin.utils.SmartList
import kotlin.collections.ArrayList import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class KotlinLanguageInjector( class KotlinLanguageInjector(
val configuration: Configuration, private val configuration: Configuration,
val project: Project, private val project: Project,
val temporaryPlacesRegistry: TemporaryPlacesRegistry private val temporaryPlacesRegistry: TemporaryPlacesRegistry
) : MultiHostInjector { ) : MultiHostInjector {
companion object { companion object {
private val STRING_LITERALS_REGEXP = "\"([^\"]*)\"".toRegex() private val STRING_LITERALS_REGEXP = "\"([^\"]*)\"".toRegex()
private val ABSENT_KOTLIN_INJECTION = BaseInjection("ABSENT_KOTLIN_BASE_INJECTION") private val ABSENT_KOTLIN_INJECTION = BaseInjection("ABSENT_KOTLIN_BASE_INJECTION")
} }
val kotlinSupport: KotlinLanguageInjectionSupport? by lazy { private val kotlinSupport: KotlinLanguageInjectionSupport? by lazy {
ArrayList(InjectorUtils.getActiveInjectionSupports()).filterIsInstance(KotlinLanguageInjectionSupport::class.java).firstOrNull() ArrayList(InjectorUtils.getActiveInjectionSupports()).filterIsInstance(KotlinLanguageInjectionSupport::class.java).firstOrNull()
} }
private data class KotlinCachedInjection(val modificationCount: Long, val baseInjection: BaseInjection) private data class KotlinCachedInjection(val modificationCount: Long, val baseInjection: BaseInjection)
private var KtStringTemplateExpression.cachedInjectionWithModification: KotlinCachedInjection? by UserDataProperty( private var KtStringTemplateExpression.cachedInjectionWithModification: KotlinCachedInjection? by UserDataProperty(
Key.create<KotlinCachedInjection>("CACHED_INJECTION_WITH_MODIFICATION")) Key.create<KotlinCachedInjection>("CACHED_INJECTION_WITH_MODIFICATION"))
@@ -159,7 +167,6 @@ class KotlinLanguageInjector(
?: injectInAnnotationCall(place) ?: injectInAnnotationCall(place)
?: injectWithReceiver(place) ?: injectWithReceiver(place)
?: injectWithVariableUsage(place, originalHost) ?: injectWithVariableUsage(place, originalHost)
?: injectWithAnnotationEntry(place)
} }
private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? { private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? {
@@ -178,9 +185,9 @@ class KotlinLanguageInjector(
val callExpression = qualifiedExpression.selectorExpression as? KtCallExpression ?: return null val callExpression = qualifiedExpression.selectorExpression as? KtCallExpression ?: return null
val callee = callExpression.calleeExpression ?: return null val callee = callExpression.calleeExpression ?: return null
if (isAnalyzeOff(qualifiedExpression.project)) return null if (isAnalyzeOff()) return null
val kotlinInjections = Configuration.getInstance().getInjections(KOTLIN_SUPPORT_ID) val kotlinInjections = configuration.getInjections(KOTLIN_SUPPORT_ID)
val calleeName = callee.text val calleeName = callee.text
val possibleNames = collectPossibleNames(kotlinInjections) val possibleNames = collectPossibleNames(kotlinInjections)
@@ -223,11 +230,10 @@ class KotlinLanguageInjector(
// Given place is not original host of the injection so we stop to prevent stepping through indirect references // Given place is not original host of the injection so we stop to prevent stepping through indirect references
if (!originalHost) return null if (!originalHost) return null
val ktHost: KtElement = host
val ktProperty = host.parent as? KtProperty ?: return null val ktProperty = host.parent as? KtProperty ?: return null
if (ktProperty.initializer != host) return null if (ktProperty.initializer != host) return null
if (isAnalyzeOff(ktHost.project)) return null if (isAnalyzeOff()) return null
val searchScope = LocalSearchScope(arrayOf(ktProperty.containingFile), "", true) val searchScope = LocalSearchScope(arrayOf(ktProperty.containingFile), "", true)
return ReferencesSearch.search(ktProperty, searchScope).asSequence().mapNotNull { psiReference -> return ReferencesSearch.search(ktProperty, searchScope).asSequence().mapNotNull { psiReference ->
@@ -243,7 +249,7 @@ class KotlinLanguageInjector(
val callExpression = PsiTreeUtil.getParentOfType(ktHost, KtCallExpression::class.java) ?: return null val callExpression = PsiTreeUtil.getParentOfType(ktHost, KtCallExpression::class.java) ?: return null
val callee = callExpression.calleeExpression ?: return null val callee = callExpression.calleeExpression ?: return null
if (isAnalyzeOff(ktHost.project)) return null if (isAnalyzeOff()) return null
for (reference in callee.references) { for (reference in callee.references) {
ProgressManager.checkCanceled() ProgressManager.checkCanceled()
@@ -269,25 +275,34 @@ class KotlinLanguageInjector(
private fun injectInAnnotationCall(host: KtElement): InjectionInfo? { private fun injectInAnnotationCall(host: KtElement): InjectionInfo? {
val argument = host.parent as? KtValueArgument ?: return null val argument = host.parent as? KtValueArgument ?: return null
val annotationEntry = argument.parent.parent as? KtAnnotationEntry ?: return null val annotationEntry = argument.parent.parent as? KtAnnotationEntry ?: return null
val callDescriptor = annotationEntry.getResolvedCall(annotationEntry.analyze())?.candidateDescriptor if (!fastCheckInjectionsExists(annotationEntry)) return null
val psiClass = (callDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? PsiClass ?: return null val calleeReference = annotationEntry.calleeExpression?.constructorReferenceExpression?.mainReference
val argumentName = argument.getArgumentName()?.asName?.identifier ?: "value" val callee = calleeReference?.resolve()
val method = psiClass.findMethodsByName(argumentName, false).singleOrNull() ?: return null when (callee) {
return findInjection(method, Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)) is KtFunction -> return injectionForKotlinCall(argument, callee, calleeReference)
is PsiClass -> {
val psiClass = callee as? PsiClass ?: return null
val argumentName = argument.getArgumentName()?.asName?.identifier ?: "value"
val method = psiClass.findMethodsByName(argumentName, false).singleOrNull() ?: return null
return findInjection(method, configuration.getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID))
}
else -> return null
}
} }
private fun injectionForJavaMethod(argument: KtValueArgument, javaMethod: PsiMethod): InjectionInfo? { private fun injectionForJavaMethod(argument: KtValueArgument, javaMethod: PsiMethod): InjectionInfo? {
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument) val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
val psiParameter = javaMethod.parameterList.parameters.getOrNull(argumentIndex) ?: return null val psiParameter = javaMethod.parameterList.parameters.getOrNull(argumentIndex) ?: return null
val injectionInfo = findInjection(psiParameter, Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)) val injectionInfo = findInjection(psiParameter, configuration.getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID))
if (injectionInfo != null) { if (injectionInfo != null) {
return injectionInfo return injectionInfo
} }
val annotations = AnnotationUtilEx.getAnnotationFrom( val annotations = AnnotationUtilEx.getAnnotationFrom(
psiParameter, psiParameter,
Configuration.getProjectInstance(psiParameter.project).advancedConfiguration.languageAnnotationPair, configuration.advancedConfiguration.languageAnnotationPair,
true) true)
if (annotations.isNotEmpty()) { if (annotations.isNotEmpty()) {
@@ -301,7 +316,7 @@ class KotlinLanguageInjector(
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument) val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
val ktParameter = ktFunction.valueParameters.getOrNull(argumentIndex) ?: return null val ktParameter = ktFunction.valueParameters.getOrNull(argumentIndex) ?: return null
val patternInjection = findInjection(ktParameter, Configuration.getInstance().getInjections(KOTLIN_SUPPORT_ID)) val patternInjection = findInjection(ktParameter, configuration.getInjections(KOTLIN_SUPPORT_ID))
if (patternInjection != null) { if (patternInjection != null) {
return patternInjection return patternInjection
} }
@@ -319,29 +334,6 @@ class KotlinLanguageInjector(
return InjectionInfo(languageId, null, null) return InjectionInfo(languageId, null, null)
} }
private fun injectWithAnnotationEntry(host: KtElement): InjectionInfo? {
val argument = host.parent as? KtValueArgument ?: return null
val annotationEntry = PsiTreeUtil.getParentOfType(host, KtAnnotationEntry::class.java) ?: return null
if (isAnalyzeOff(host.project)) return null
val calleeReference = annotationEntry.calleeExpression?.constructorReferenceExpression?.mainReference
val callee = calleeReference?.resolve()
return when (callee) {
is KtFunction -> injectionForKotlinCall(argument, callee, calleeReference)
is PsiClass -> {
// Look for java injections for the PsiAnnotationMethod.
(argument.reference ?: argument.getArgumentName()?.referenceExpression?.mainReference)
?.let { it.resolve() as? PsiMethod }
?.let { findInjection(it, Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)) }
?.takeIf { injectionInfo ->
// Temporary forbid injection for SpEL because of inspections that gives warnings when host language is not Java.
injectionInfo.languageId != "SpEL"
}
}
else -> null
}
}
private fun findInjection(element: PsiElement?, injections: List<BaseInjection>): InjectionInfo? { private fun findInjection(element: PsiElement?, injections: List<BaseInjection>): InjectionInfo? {
for (injection in injections) { for (injection in injections) {
if (injection.acceptsPsiElement(element)) { if (injection.acceptsPsiElement(element)) {
@@ -352,8 +344,8 @@ class KotlinLanguageInjector(
return null return null
} }
private fun isAnalyzeOff(project: Project): Boolean { private fun isAnalyzeOff(): Boolean {
return Configuration.getProjectInstance(project).advancedConfiguration.dfaOption == Configuration.DfaOption.OFF return configuration.advancedConfiguration.dfaOption == Configuration.DfaOption.OFF
} }
private class InjectionInfo(val languageId: String?, val prefix: String?, val suffix: String?) { private class InjectionInfo(val languageId: String?, val prefix: String?, val suffix: String?) {
@@ -382,4 +374,52 @@ class KotlinLanguageInjector(
return InjectionInfo(id, prefix, suffix) return InjectionInfo(id, prefix, suffix)
} }
}
private val injectableTargetClassShortNames = CachedValuesManager.getManager(project)
.createCachedValue({
CachedValueProvider.Result.create(HashSet<String>().apply {
for (injection in configuration.getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)) {
for (injectionPlace in injection.injectionPlaces) {
for (targetClassFQN in retrieveJavaPlaceTargetClassesFQNs(injectionPlace)) {
add(StringUtilRt.getShortName(targetClassFQN))
}
}
}
for (injection in configuration.getInjections(org.jetbrains.kotlin.idea.injection.KOTLIN_SUPPORT_ID)) {
for (injectionPlace in injection.injectionPlaces) {
for (targetClassFQN in retrieveKotlinPlaceTargetClassesFQNs(injectionPlace)) {
add(StringUtilRt.getShortName(targetClassFQN))
}
}
}
}, configuration)
}, false)
private fun fastCheckInjectionsExists(annotationEntry: KtAnnotationEntry): Boolean {
val referencedName = (annotationEntry.typeReference?.typeElement as? KtUserType)?.referencedName ?: return false
val annotationShortName = annotationEntry.containingKtFile.aliasImportMap()[referencedName].singleOrNull() ?: referencedName
return annotationShortName in injectableTargetClassShortNames.value
}
private fun retrieveJavaPlaceTargetClassesFQNs(place: InjectionPlace): Collection<String> {
val classCondition = place.elementPattern.condition.conditions.firstOrNull { it.debugMethodName == "definedInClass" }
as? PatternConditionPlus<*, *> ?: return emptyList()
val psiClassNamePatternCondition = classCondition.valuePattern.condition.conditions.
firstIsInstanceOrNull<PsiClassNamePatternCondition>() ?: return emptyList()
val valuePatternCondition = psiClassNamePatternCondition.namePattern.condition.conditions.firstIsInstanceOrNull<ValuePatternCondition<String>>() ?: return emptyList()
return valuePatternCondition.values
}
private fun retrieveKotlinPlaceTargetClassesFQNs(place: InjectionPlace): Collection<String> {
val classNames = SmartList<String>()
fun collect(condition: PatternCondition<*>) {
when (condition) {
is PatternConditionPlus<*, *> -> condition.valuePattern.condition.conditions.forEach { collect(it) }
is KotlinFunctionPattern.DefinedInClassCondition -> classNames.add(condition.fqName)
}
}
place.elementPattern.condition.conditions.forEach { collect(it) }
return classNames
}
}
@@ -74,14 +74,15 @@ open class KotlinFunctionPattern : PsiElementPattern<KtFunction, KotlinFunctionP
} }
} }
fun definedInClass(fqName: String): KotlinFunctionPattern { class DefinedInClassCondition(val fqName: String) : PatternCondition<KtFunction>("kotlinFunctionPattern-definedInClass") {
return withPatternCondition("kotlinFunctionPattern-definedInClass") { function, _ -> override fun accepts(element: KtFunction, context: ProcessingContext?): Boolean {
if (function.parent is KtFile) return@withPatternCondition false if (element.parent is KtFile) return false
return element.containingClassOrObject?.fqName?.asString() == fqName
function.containingClassOrObject?.fqName?.asString() == fqName
} }
} }
fun definedInClass(fqName: String): KotlinFunctionPattern = with(DefinedInClassCondition(fqName))
fun definedInPackage(packageFqName: String): KotlinFunctionPattern { fun definedInPackage(packageFqName: String): KotlinFunctionPattern {
return withPatternCondition("kotlinFunctionPattern-definedInPackage") { function, _ -> return withPatternCondition("kotlinFunctionPattern-definedInPackage") { function, _ ->
if (function.parent !is KtFile) return@withPatternCondition false if (function.parent !is KtFile) return@withPatternCondition false
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.psi
import com.intellij.codeInsight.completion.CompletionType import com.intellij.codeInsight.completion.CompletionType
import com.intellij.lang.html.HTMLLanguage import com.intellij.lang.html.HTMLLanguage
import com.intellij.openapi.fileTypes.PlainTextLanguage import com.intellij.openapi.fileTypes.PlainTextLanguage
import org.intellij.lang.annotations.Language
import org.intellij.lang.regexp.RegExpLanguage import org.intellij.lang.regexp.RegExpLanguage
import org.intellij.plugins.intelliLang.Configuration import org.intellij.plugins.intelliLang.Configuration
import org.intellij.plugins.intelliLang.inject.config.BaseInjection import org.intellij.plugins.intelliLang.inject.config.BaseInjection
@@ -266,7 +267,7 @@ class KotlinInjectionTest : AbstractInjectionTest() {
fun other() { foo(v) } fun other() { foo(v) }
""", """,
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false) languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false)
fun testInjectionOfCustomParameterWithAnnotation() = doInjectionPresentTest( fun testInjectionOfCustomParameterWithAnnotation() = doInjectionPresentTest(
""" """
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
@@ -413,140 +414,126 @@ class KotlinInjectionTest : AbstractInjectionTest() {
) )
fun testJavaAnnotationsPattern() { fun testJavaAnnotationsPattern() {
val customInjection = BaseInjection("java") myFixture.addClass("""
customInjection.injectedLanguageId = RegExpLanguage.INSTANCE.id @interface Matches { String value(); }
val elementPattern = customInjection.compiler.createElementPattern( """)
"""psiMethod().withName("value").withParameters().definedInClass("Matches")""",
"temp rule")
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
try { doAnnotationInjectionTest(
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true) injectedLanguage = RegExpLanguage.INSTANCE.id,
pattern = """psiMethod().withName("value").withParameters().definedInClass("Matches")""",
doInjectionPresentTest( kotlinCode = """
javaText = """ @Matches("[A-Z]<caret>[a-z]+")
@interface Matches { String value(); } val name = "John"
""", """
text = """ )
@Matches("[A-Z]<caret>[a-z]+")
val name = "John"
""",
languageId = RegExpLanguage.INSTANCE.id,
unInjectShouldBePresent = false
)
doInjectionPresentTest(
javaText = """
@interface Matches { String value(); }
""",
text = """
@Matches(value = "[A-Z]<caret>[a-z]+")
val name = "John"
""",
languageId = RegExpLanguage.INSTANCE.id,
unInjectShouldBePresent = false
)
}
finally {
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
}
} }
fun testKotlinAnnotationsPattern() { fun testKotlinAnnotationsPattern() {
val customInjection = BaseInjection("kotlin") doAnnotationInjectionTest(
customInjection.injectedLanguageId = RegExpLanguage.INSTANCE.id patternLanguage = "kotlin",
val elementPattern = customInjection.compiler.createElementPattern( injectedLanguage = RegExpLanguage.INSTANCE.id,
"""kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""".trimIndent(), pattern = """kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""",
"temp rule") kotlinCode = """
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
try {
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
doInjectionPresentTest(
text = """
annotation class Matches(val pattern: String) annotation class Matches(val pattern: String)
@Matches("[A-Z]<caret>[a-z]+") @Matches("[A-Z]<caret>[a-z]+")
val name = "John" val name = "John"
""", """
languageId = RegExpLanguage.INSTANCE.id, )
unInjectShouldBePresent = false }
)
doInjectionPresentTest( fun testKotlinAnnotationsPatternNamed() {
text = """ doAnnotationInjectionTest(
patternLanguage = "kotlin",
injectedLanguage = RegExpLanguage.INSTANCE.id,
pattern = """kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""",
kotlinCode = """
annotation class Matches(val pattern: String) annotation class Matches(val pattern: String)
@Matches(pattern = "[A-Z]<caret>[a-z]+") @Matches(pattern = "[A-Z]<caret>[a-z]+")
val name = "John" val name = "John"
""", """
languageId = RegExpLanguage.INSTANCE.id,
unInjectShouldBePresent = false
) )
} }
finally {
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
}
}
fun testInjectionInJavaAnnotation() { fun testInjectionInJavaAnnotation() {
val customInjection = BaseInjection("java")
customInjection.injectedLanguageId = HTMLLanguage.INSTANCE.id
val elementPattern = customInjection.compiler.createElementPattern(
"""psiMethod().withName("value").withParameters().definedInClass("InHtml")""",
"SuppressWarnings temp rule")
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
try { myFixture.addClass("""
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true) @interface InHtml {
String value();
}
""")
doInjectionPresentTest( doAnnotationInjectionTest(
""" injectedLanguage = HTMLLanguage.INSTANCE.id,
@InHtml("<htm<caret>l></html>") pattern = """psiMethod().withName("value").withParameters().definedInClass("InHtml")""",
fun foo() { kotlinCode = """
} @InHtml("<htm<caret>l></html>")
""", """ fun foo() {
@interface InHtml { }
String value(); """,
} additionalAsserts = { assertSameElements(myFixture.complete(CompletionType.BASIC).flatMap { it.allLookupStrings }, "html") }
""".trimIndent(), )
HTMLLanguage.INSTANCE.id,
unInjectShouldBePresent = false
)
assertSameElements(myFixture.complete(CompletionType.BASIC).flatMap { it.allLookupStrings },
"html")
}
finally {
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
}
} }
fun testInjectionInJavaAnnotationWithNamedParam() { fun testInjectionInJavaAnnotationWithNamedParam() {
val customInjection = BaseInjection("java") myFixture.addClass("""
customInjection.injectedLanguageId = HTMLLanguage.INSTANCE.id package myinjection;
@interface InHtml {
String html();
}
""")
doAnnotationInjectionTest(
injectedLanguage = HTMLLanguage.INSTANCE.id,
pattern = """psiMethod().withName("html").withParameters().definedInClass("myinjection.InHtml")""",
kotlinCode = """
import myinjection.InHtml
@InHtml(html = "<htm<caret>l></html>")
fun foo() {
}
""")
}
fun testInjectionInAliasedJavaAnnotation() {
myFixture.addClass("""
@interface InHtml {
String html();
}
""")
doAnnotationInjectionTest(
injectedLanguage = HTMLLanguage.INSTANCE.id,
pattern = """psiMethod().withName("html").withParameters().definedInClass("InHtml")""",
kotlinCode = """
import InHtml as InHtmlAliased
@InHtmlAliased(html = "<htm<caret>l></html>")
fun foo() {
}
"""
)
}
private fun doAnnotationInjectionTest(patternLanguage: String = "java", injectedLanguage: String, pattern: String, @Language("kotlin") kotlinCode: String, additionalAsserts: () -> Unit = {}) {
val customInjection = BaseInjection(patternLanguage)
customInjection.injectedLanguageId = injectedLanguage
val elementPattern = customInjection.compiler.createElementPattern( val elementPattern = customInjection.compiler.createElementPattern(
"""psiMethod().withName("html").withParameters().definedInClass("InHtml")""", pattern,
"SuppressWarnings temp rule") "temp rule")
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true)) customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
try { try {
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true) Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
doInjectionPresentTest( doInjectionPresentTest(
""" kotlinCode, null,
@InHtml(html = "<htm<caret>l></html>") injectedLanguage,
fun foo() {
}
""", """
@interface InHtml {
String html();
}
""".trimIndent(),
HTMLLanguage.INSTANCE.id,
unInjectShouldBePresent = false unInjectShouldBePresent = false
) )
additionalAsserts()
} }
finally { finally {
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true) Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)