Recognize injections in annotations (KT-13636)

Recognize patterns from both Java (psiMethod) and Kotlin
(kotlinParameter) that match the annotation argument.
This commit is contained in:
Michal Bendowski
2017-11-21 12:52:06 +00:00
committed by Nikolay Krasko
parent b7ba45c001
commit 6f350c032d
2 changed files with 101 additions and 2 deletions
@@ -38,6 +38,7 @@ import org.intellij.plugins.intelliLang.util.AnnotationUtilEx
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
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.util.ProjectRootsUtil
import org.jetbrains.kotlin.name.FqName
@@ -154,6 +155,7 @@ class KotlinLanguageInjector(
?: injectWithCall(place)
?: injectWithReceiver(place)
?: injectWithVariableUsage(place, originalHost)
?: injectWithAnnotationEntry(place)
}
private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? {
@@ -303,6 +305,25 @@ 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)) }
}
else -> null
}
}
private fun findInjection(element: PsiElement?, injections: List<BaseInjection>): InjectionInfo? {
for (injection in injections) {
if (injection.acceptsPsiElement(element)) {
@@ -343,4 +364,4 @@ class KotlinLanguageInjector(
return InjectionInfo(id, prefix, suffix)
}
}
}
@@ -410,4 +410,82 @@ class KotlinInjectionTest : AbstractInjectionTest() {
ShredInfo(range(7, 15), hostRange=range(12, 22), prefix="s")
)
)
}
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))
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)
}
}
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 = """
annotation class Matches(val pattern: String)
@Matches("[A-Z]<caret>[a-z]+")
val name = "John"
""",
languageId = RegExpLanguage.INSTANCE.id,
unInjectShouldBePresent = false
)
doInjectionPresentTest(
text = """
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)
}
}
}