KotlinLanguageInjector#injectInAnnotationCall optimization: using PsiClassNamePatternCondition to avoid calling getResolvedCall
This commit is contained in:
committed by
Nikolay Krasko
parent
81b6a61edf
commit
d7fcc69315
@@ -31,7 +31,7 @@ fun KtUserType.aliasImportMap(): Multimap<String, String> {
|
||||
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 modificationStamp = modificationStamp
|
||||
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.project.Project
|
||||
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.impl.source.tree.injected.InjectedLanguageUtil
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
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 org.intellij.plugins.intelliLang.Configuration
|
||||
import org.intellij.plugins.intelliLang.inject.InjectorUtils
|
||||
import org.intellij.plugins.intelliLang.inject.LanguageInjectionSupport
|
||||
import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry
|
||||
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.util.AnnotationUtilEx
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
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
|
||||
@@ -45,27 +53,27 @@ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
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.source.getPsi
|
||||
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
|
||||
|
||||
class KotlinLanguageInjector(
|
||||
val configuration: Configuration,
|
||||
val project: Project,
|
||||
val temporaryPlacesRegistry: TemporaryPlacesRegistry
|
||||
private val configuration: Configuration,
|
||||
private val project: Project,
|
||||
private val temporaryPlacesRegistry: TemporaryPlacesRegistry
|
||||
) : MultiHostInjector {
|
||||
companion object {
|
||||
private val STRING_LITERALS_REGEXP = "\"([^\"]*)\"".toRegex()
|
||||
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()
|
||||
}
|
||||
|
||||
private data class KotlinCachedInjection(val modificationCount: Long, val baseInjection: BaseInjection)
|
||||
|
||||
private var KtStringTemplateExpression.cachedInjectionWithModification: KotlinCachedInjection? by UserDataProperty(
|
||||
Key.create<KotlinCachedInjection>("CACHED_INJECTION_WITH_MODIFICATION"))
|
||||
|
||||
@@ -159,7 +167,6 @@ class KotlinLanguageInjector(
|
||||
?: injectInAnnotationCall(place)
|
||||
?: injectWithReceiver(place)
|
||||
?: injectWithVariableUsage(place, originalHost)
|
||||
?: injectWithAnnotationEntry(place)
|
||||
}
|
||||
|
||||
private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? {
|
||||
@@ -178,9 +185,9 @@ class KotlinLanguageInjector(
|
||||
val callExpression = qualifiedExpression.selectorExpression as? KtCallExpression ?: 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 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
|
||||
if (!originalHost) return null
|
||||
|
||||
val ktHost: KtElement = host
|
||||
val ktProperty = host.parent as? KtProperty ?: 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)
|
||||
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 callee = callExpression.calleeExpression ?: return null
|
||||
|
||||
if (isAnalyzeOff(ktHost.project)) return null
|
||||
if (isAnalyzeOff()) return null
|
||||
|
||||
for (reference in callee.references) {
|
||||
ProgressManager.checkCanceled()
|
||||
@@ -269,25 +275,34 @@ class KotlinLanguageInjector(
|
||||
private fun injectInAnnotationCall(host: KtElement): InjectionInfo? {
|
||||
val argument = host.parent as? KtValueArgument ?: return null
|
||||
val annotationEntry = argument.parent.parent as? KtAnnotationEntry ?: return null
|
||||
val callDescriptor = annotationEntry.getResolvedCall(annotationEntry.analyze())?.candidateDescriptor
|
||||
val psiClass = (callDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? PsiClass ?: return null
|
||||
val argumentName = argument.getArgumentName()?.asName?.identifier ?: "value"
|
||||
val method = psiClass.findMethodsByName(argumentName, false).singleOrNull() ?: return null
|
||||
return findInjection(method, Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID))
|
||||
if (!fastCheckInjectionsExists(annotationEntry)) return null
|
||||
val calleeReference = annotationEntry.calleeExpression?.constructorReferenceExpression?.mainReference
|
||||
val callee = calleeReference?.resolve()
|
||||
when (callee) {
|
||||
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? {
|
||||
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
|
||||
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) {
|
||||
return injectionInfo
|
||||
}
|
||||
|
||||
val annotations = AnnotationUtilEx.getAnnotationFrom(
|
||||
psiParameter,
|
||||
Configuration.getProjectInstance(psiParameter.project).advancedConfiguration.languageAnnotationPair,
|
||||
configuration.advancedConfiguration.languageAnnotationPair,
|
||||
true)
|
||||
|
||||
if (annotations.isNotEmpty()) {
|
||||
@@ -301,7 +316,7 @@ class KotlinLanguageInjector(
|
||||
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
|
||||
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) {
|
||||
return patternInjection
|
||||
}
|
||||
@@ -319,29 +334,6 @@ class KotlinLanguageInjector(
|
||||
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? {
|
||||
for (injection in injections) {
|
||||
if (injection.acceptsPsiElement(element)) {
|
||||
@@ -352,8 +344,8 @@ class KotlinLanguageInjector(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isAnalyzeOff(project: Project): Boolean {
|
||||
return Configuration.getProjectInstance(project).advancedConfiguration.dfaOption == Configuration.DfaOption.OFF
|
||||
private fun isAnalyzeOff(): Boolean {
|
||||
return configuration.advancedConfiguration.dfaOption == Configuration.DfaOption.OFF
|
||||
}
|
||||
|
||||
private class InjectionInfo(val languageId: String?, val prefix: String?, val suffix: String?) {
|
||||
@@ -382,4 +374,52 @@ class KotlinLanguageInjector(
|
||||
|
||||
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 {
|
||||
return withPatternCondition("kotlinFunctionPattern-definedInClass") { function, _ ->
|
||||
if (function.parent is KtFile) return@withPatternCondition false
|
||||
|
||||
function.containingClassOrObject?.fqName?.asString() == fqName
|
||||
class DefinedInClassCondition(val fqName: String) : PatternCondition<KtFunction>("kotlinFunctionPattern-definedInClass") {
|
||||
override fun accepts(element: KtFunction, context: ProcessingContext?): Boolean {
|
||||
if (element.parent is KtFile) return false
|
||||
return element.containingClassOrObject?.fqName?.asString() == fqName
|
||||
}
|
||||
}
|
||||
|
||||
fun definedInClass(fqName: String): KotlinFunctionPattern = with(DefinedInClassCondition(fqName))
|
||||
|
||||
fun definedInPackage(packageFqName: String): KotlinFunctionPattern {
|
||||
return withPatternCondition("kotlinFunctionPattern-definedInPackage") { function, _ ->
|
||||
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.lang.html.HTMLLanguage
|
||||
import com.intellij.openapi.fileTypes.PlainTextLanguage
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.intellij.lang.regexp.RegExpLanguage
|
||||
import org.intellij.plugins.intelliLang.Configuration
|
||||
import org.intellij.plugins.intelliLang.inject.config.BaseInjection
|
||||
@@ -266,7 +267,7 @@ class KotlinInjectionTest : AbstractInjectionTest() {
|
||||
fun other() { foo(v) }
|
||||
""",
|
||||
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false)
|
||||
|
||||
|
||||
fun testInjectionOfCustomParameterWithAnnotation() = doInjectionPresentTest(
|
||||
"""
|
||||
import org.intellij.lang.annotations.Language
|
||||
@@ -413,140 +414,126 @@ class KotlinInjectionTest : AbstractInjectionTest() {
|
||||
)
|
||||
|
||||
fun testJavaAnnotationsPattern() {
|
||||
val customInjection = BaseInjection("java")
|
||||
customInjection.injectedLanguageId = RegExpLanguage.INSTANCE.id
|
||||
val elementPattern = customInjection.compiler.createElementPattern(
|
||||
"""psiMethod().withName("value").withParameters().definedInClass("Matches")""",
|
||||
"temp rule")
|
||||
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
|
||||
myFixture.addClass("""
|
||||
@interface Matches { String value(); }
|
||||
""")
|
||||
|
||||
try {
|
||||
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
|
||||
|
||||
doInjectionPresentTest(
|
||||
javaText = """
|
||||
@interface Matches { String value(); }
|
||||
""",
|
||||
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)
|
||||
}
|
||||
doAnnotationInjectionTest(
|
||||
injectedLanguage = RegExpLanguage.INSTANCE.id,
|
||||
pattern = """psiMethod().withName("value").withParameters().definedInClass("Matches")""",
|
||||
kotlinCode = """
|
||||
@Matches("[A-Z]<caret>[a-z]+")
|
||||
val name = "John"
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
fun testKotlinAnnotationsPattern() {
|
||||
val customInjection = BaseInjection("kotlin")
|
||||
customInjection.injectedLanguageId = RegExpLanguage.INSTANCE.id
|
||||
val elementPattern = customInjection.compiler.createElementPattern(
|
||||
"""kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""".trimIndent(),
|
||||
"temp rule")
|
||||
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
|
||||
|
||||
try {
|
||||
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
|
||||
|
||||
doInjectionPresentTest(
|
||||
text = """
|
||||
doAnnotationInjectionTest(
|
||||
patternLanguage = "kotlin",
|
||||
injectedLanguage = RegExpLanguage.INSTANCE.id,
|
||||
pattern = """kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""",
|
||||
kotlinCode = """
|
||||
annotation class Matches(val pattern: String)
|
||||
|
||||
@Matches("[A-Z]<caret>[a-z]+")
|
||||
val name = "John"
|
||||
""",
|
||||
languageId = RegExpLanguage.INSTANCE.id,
|
||||
unInjectShouldBePresent = false
|
||||
)
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
doInjectionPresentTest(
|
||||
text = """
|
||||
fun testKotlinAnnotationsPatternNamed() {
|
||||
doAnnotationInjectionTest(
|
||||
patternLanguage = "kotlin",
|
||||
injectedLanguage = RegExpLanguage.INSTANCE.id,
|
||||
pattern = """kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""",
|
||||
kotlinCode = """
|
||||
annotation class Matches(val pattern: String)
|
||||
|
||||
@Matches(pattern = "[A-Z]<caret>[a-z]+")
|
||||
val name = "John"
|
||||
""",
|
||||
languageId = RegExpLanguage.INSTANCE.id,
|
||||
unInjectShouldBePresent = false
|
||||
"""
|
||||
)
|
||||
}
|
||||
finally {
|
||||
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
|
||||
myFixture.addClass("""
|
||||
@interface InHtml {
|
||||
String value();
|
||||
}
|
||||
""")
|
||||
|
||||
doInjectionPresentTest(
|
||||
"""
|
||||
@InHtml("<htm<caret>l></html>")
|
||||
fun foo() {
|
||||
}
|
||||
""", """
|
||||
@interface InHtml {
|
||||
String value();
|
||||
}
|
||||
""".trimIndent(),
|
||||
HTMLLanguage.INSTANCE.id,
|
||||
unInjectShouldBePresent = false
|
||||
)
|
||||
doAnnotationInjectionTest(
|
||||
injectedLanguage = HTMLLanguage.INSTANCE.id,
|
||||
pattern = """psiMethod().withName("value").withParameters().definedInClass("InHtml")""",
|
||||
kotlinCode = """
|
||||
@InHtml("<htm<caret>l></html>")
|
||||
fun foo() {
|
||||
}
|
||||
""",
|
||||
additionalAsserts = { assertSameElements(myFixture.complete(CompletionType.BASIC).flatMap { it.allLookupStrings }, "html") }
|
||||
)
|
||||
|
||||
assertSameElements(myFixture.complete(CompletionType.BASIC).flatMap { it.allLookupStrings },
|
||||
"html")
|
||||
}
|
||||
finally {
|
||||
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
|
||||
}
|
||||
}
|
||||
|
||||
fun testInjectionInJavaAnnotationWithNamedParam() {
|
||||
val customInjection = BaseInjection("java")
|
||||
customInjection.injectedLanguageId = HTMLLanguage.INSTANCE.id
|
||||
myFixture.addClass("""
|
||||
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(
|
||||
"""psiMethod().withName("html").withParameters().definedInClass("InHtml")""",
|
||||
"SuppressWarnings temp rule")
|
||||
pattern,
|
||||
"temp rule")
|
||||
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
|
||||
|
||||
try {
|
||||
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
|
||||
|
||||
doInjectionPresentTest(
|
||||
"""
|
||||
@InHtml(html = "<htm<caret>l></html>")
|
||||
fun foo() {
|
||||
}
|
||||
""", """
|
||||
@interface InHtml {
|
||||
String html();
|
||||
}
|
||||
""".trimIndent(),
|
||||
HTMLLanguage.INSTANCE.id,
|
||||
kotlinCode, null,
|
||||
injectedLanguage,
|
||||
unInjectShouldBePresent = false
|
||||
)
|
||||
additionalAsserts()
|
||||
}
|
||||
finally {
|
||||
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
|
||||
|
||||
Reference in New Issue
Block a user