From df3bee30384787d8951ea548a4257c2cb52a16a3 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Wed, 4 Dec 2019 19:53:25 +0300 Subject: [PATCH] Annotations: support injections in string arrays (KT-35222) --- .../idea/injection/KotlinLanguageInjector.kt | 34 ++++++--- .../kotlin/psi/KotlinInjectionTest.kt | 74 ++++++++++++++++++- 2 files changed, 95 insertions(+), 13 deletions(-) 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 9fa4155f4d0..6e223869d22 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 @@ -257,11 +257,11 @@ class KotlinLanguageInjector( }.firstOrNull() } - private fun injectWithCall(host: KtElement): InjectionInfo? { - val ktHost: KtElement = host - val argument = ktHost.parent as? KtValueArgument ?: return null + private tailrec fun injectWithCall(host: KtElement): InjectionInfo? { + val argument = getArgument(host) ?: return null + val callExpression = PsiTreeUtil.getParentOfType(argument, KtCallElement::class.java) ?: return null - val callExpression = PsiTreeUtil.getParentOfType(ktHost, KtCallElement::class.java) ?: return null + if (getCallableShortName(callExpression) == "arrayOf") return injectWithCall(callExpression) val callee = getNameReference(callExpression.calleeExpression) ?: return null if (isAnalyzeOff()) return null @@ -292,10 +292,21 @@ class KotlinLanguageInjector( return callee as? KtNameReferenceExpression } - private fun injectInAnnotationCall(host: KtElement): InjectionInfo? { - val argument = host.parent as? KtValueArgument ?: return null + private fun getArgument(host: KtElement): KtValueArgument? = when (val parent = host.parent) { + is KtValueArgument -> parent + is KtCollectionLiteralExpression, is KtCallElement -> parent.parent as? KtValueArgument + else -> null + } + + private tailrec fun injectInAnnotationCall(host: KtElement): InjectionInfo? { + val argument = getArgument(host) ?: return null val annotationEntry = argument.parent.parent as? KtCallElement ?: return null - if (!fastCheckInjectionsExists(annotationEntry)) return null + + val callableShortName = getCallableShortName(annotationEntry) ?: return null + if (callableShortName == "arrayOf") return injectInAnnotationCall(annotationEntry) + + if (!fastCheckInjectionsExists(callableShortName)) return null + val calleeExpression = annotationEntry.calleeExpression ?: return null val callee = getNameReference(calleeExpression)?.mainReference?.let { reference -> allowResolveInDispatchThread { reference.resolve() } @@ -400,10 +411,11 @@ class KotlinLanguageInjector( private val injectableTargetClassShortNames = CachedValuesManager.getManager(project).createCachedValue(::createCachedValue, false) - private fun fastCheckInjectionsExists(annotationEntry: KtCallElement): Boolean { - val referencedName = getNameReference(annotationEntry.calleeExpression)?.getReferencedName() ?: return false - val annotationShortName = annotationEntry.containingKtFile.aliasImportMap()[referencedName].singleOrNull() ?: referencedName - return annotationShortName in injectableTargetClassShortNames.value + private fun fastCheckInjectionsExists(annotationShortName: String) = annotationShortName in injectableTargetClassShortNames.value + + private fun getCallableShortName(annotationEntry: KtCallElement): String? { + val referencedName = getNameReference(annotationEntry.calleeExpression)?.getReferencedName() ?: return null + return annotationEntry.containingKtFile.aliasImportMap()[referencedName].singleOrNull() ?: referencedName } private fun retrieveJavaPlaceTargetClassesFQNs(place: InjectionPlace): Collection { diff --git a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt index 96a8988cc10..aee5a2628fb 100644 --- a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt @@ -508,14 +508,44 @@ class KotlinInjectionTest : AbstractInjectionTest() { ) } + fun testKotlinAnnotationsPatternNamedArrayLiteral() { + doAnnotationInjectionTest( + patternLanguage = "kotlin", + injectedLanguage = RegExpLanguage.INSTANCE.id, + pattern = """kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""", + kotlinCode = """ + annotation class Matches(val pattern: Array) + + @Matches(pattern = ["[A-Z][a-z]+"]) + val name = "John" + """ + ) + } + + fun testKotlinAnnotationsPatternNamedArray() { + doAnnotationInjectionTest( + patternLanguage = "kotlin", + injectedLanguage = RegExpLanguage.INSTANCE.id, + pattern = """kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""", + kotlinCode = """ + annotation class Matches(val pattern: Array) + + @Matches(pattern = arrayOf("[A-Z][a-z]+")) + val name = "John" + """ + ) + } + fun testInjectionInJavaAnnotation() { - myFixture.addClass(""" + myFixture.addClass( + """ @interface InHtml { String value(); } - """) + """ + ) doAnnotationInjectionTest( injectedLanguage = HTMLLanguage.INSTANCE.id, @@ -550,6 +580,46 @@ class KotlinInjectionTest : AbstractInjectionTest() { """) } + fun testInjectionInJavaAnnotationWithNamedParamArray() { + myFixture.addClass(""" + package myinjection; + + @interface InHtml { + String[] htmls(); + } + """) + doAnnotationInjectionTest( + injectedLanguage = HTMLLanguage.INSTANCE.id, + pattern = """psiMethod().withName("htmls").withParameters().definedInClass("myinjection.InHtml")""", + kotlinCode = """ + import myinjection.InHtml + + @InHtml(htmls = arrayOf("
", "l>")) + fun foo() { + } + """) + } + + fun testInjectionInJavaAnnotationWithNamedParamLiterals() { + myFixture.addClass(""" + package myinjection; + + @interface InHtml { + String[] htmls(); + } + """) + doAnnotationInjectionTest( + injectedLanguage = HTMLLanguage.INSTANCE.id, + pattern = """psiMethod().withName("htmls").withParameters().definedInClass("myinjection.InHtml")""", + kotlinCode = """ + import myinjection.InHtml + + @InHtml(htmls = ["
", "l>"]) + fun foo() { + } + """) + } + fun testInjectionInJavaNestedAnnotation() { myFixture.addClass( """