UastLiteralReferenceProvider as optimization for UastReferenceProvider (IDEA-186086)

This commit is contained in:
Nicolay Mitropolsky
2018-03-13 18:24:57 +03:00
parent 60be7dafb1
commit 4e50a4f77b
4 changed files with 119 additions and 30 deletions
@@ -27,11 +27,12 @@ public class TestDataFilesReferencesContributor extends PsiReferenceContributor
.registerUastReferenceProvider(
registrar,
UastPatterns.stringLiteralExpression().inCall(UastPatterns.callExpression()),
new UastReferenceProvider() {
new UastLiteralReferenceProvider() {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull UElement element, @NotNull ProcessingContext context) {
ULiteralExpression expression = (ULiteralExpression)element;
public PsiReference[] getReferencesByULiteral(@NotNull ULiteralExpression expression,
@NotNull PsiLanguageInjectionHost host,
@NotNull ProcessingContext context) {
UCallExpression call = UastUtils.getUCallExpression(expression);
if (call == null) return PsiReference.EMPTY_ARRAY;
@@ -45,9 +46,6 @@ public class TestDataFilesReferencesContributor extends PsiReferenceContributor
return PsiReference.EMPTY_ARRAY;
}
PsiLanguageInjectionHost host = UastLiteralUtils.getPsiLanguageInjectionHost(expression);
if (host == null) return PsiReference.EMPTY_ARRAY;
FileReferenceSet fileReferenceSet = new FileReferenceSet(host);
fileReferenceSet.addCustomization(
FileReferenceSet.DEFAULT_PATH_EVALUATOR_OPTION,
@@ -33,7 +33,6 @@ import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.UastLiteralUtils;
@@ -54,16 +53,12 @@ public class TestDataReferenceContributor extends PsiReferenceContributor {
new TestDataReferenceProvider(), PsiReferenceRegistrar.DEFAULT_PRIORITY);
}
private static class TestDataReferenceProvider extends UastReferenceProvider {
private static class TestDataReferenceProvider extends UastLiteralReferenceProvider {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull final UElement element, @NotNull final ProcessingContext context) {
if (!(element instanceof ULiteralExpression)) return PsiReference.EMPTY_ARRAY;
ULiteralExpression literalExpression = (ULiteralExpression)element;
PsiLanguageInjectionHost host = UastLiteralUtils.getPsiLanguageInjectionHost(literalExpression);
if (host == null) return PsiReference.EMPTY_ARRAY;
public PsiReference[] getReferencesByULiteral(@NotNull final ULiteralExpression literalExpression,
@NotNull PsiLanguageInjectionHost host,
@NotNull final ProcessingContext context) {
TextRange range = ElementManipulators.getValueTextRange(host);
@@ -30,44 +30,69 @@ import org.jetbrains.uast.toUElement
fun PsiReferenceRegistrar.registerUastReferenceProvider(pattern: (UElement, ProcessingContext) -> Boolean,
provider: UastReferenceProvider,
priority: Double = PsiReferenceRegistrar.DEFAULT_PRIORITY) {
this.registerReferenceProvider(UastPatternAdapter(pattern), UastReferenceProviderAdapter(provider), priority)
this.registerReferenceProvider(UastPatternAdapter(pattern, provider.supportedUElementTypes),
UastReferenceProviderAdapter(provider),
priority)
}
fun PsiReferenceRegistrar.registerUastReferenceProvider(pattern: ElementPattern<out UElement>,
provider: UastReferenceProvider,
priority: Double = PsiReferenceRegistrar.DEFAULT_PRIORITY) {
this.registerReferenceProvider(UastPatternAdapter(pattern::accepts), UastReferenceProviderAdapter(provider), priority)
this.registerReferenceProvider(UastPatternAdapter(pattern::accepts, provider.supportedUElementTypes),
UastReferenceProviderAdapter(provider), priority)
}
abstract class UastReferenceProvider {
open val supportedUElementTypes: List<Class<out UElement>> = listOf(UElement::class.java)
abstract fun getReferencesByElement(element: UElement, context: ProcessingContext): Array<PsiReference>
}
abstract class UastLiteralReferenceProvider : UastReferenceProvider() {
override val supportedUElementTypes: List<Class<out UElement>> = listOf(ULiteralExpression::class.java)
override fun getReferencesByElement(element: UElement, context: ProcessingContext): Array<PsiReference> {
val uLiteral = element as? ULiteralExpression ?: return PsiReference.EMPTY_ARRAY
val host = uLiteral.psiLanguageInjectionHost ?: return PsiReference.EMPTY_ARRAY
return getReferencesByULiteral(uLiteral, host, context)
}
abstract fun getReferencesByULiteral(uLiteral: ULiteralExpression,
host: PsiLanguageInjectionHost,
context: ProcessingContext): Array<PsiReference>
}
fun uastLiteralReferenceProvider(provider: (ULiteralExpression, PsiLanguageInjectionHost) -> Array<PsiReference>) =
object : UastReferenceProvider() {
override fun getReferencesByElement(element: UElement, context: ProcessingContext): Array<PsiReference> {
val uLiteral = element as? ULiteralExpression ?: return PsiReference.EMPTY_ARRAY
val host = uLiteral.psiLanguageInjectionHost ?: return PsiReference.EMPTY_ARRAY
return provider(uLiteral, host)
}
object : UastLiteralReferenceProvider() {
override fun getReferencesByULiteral(uLiteral: ULiteralExpression,
host: PsiLanguageInjectionHost,
context: ProcessingContext): Array<PsiReference> = provider(uLiteral, host)
}
private val cachedUElement = Key.create<UElement>("UastReferenceRegistrar.cachedUElement")
private fun getOrCreateCachedElement(element: PsiElement, context: ProcessingContext?): UElement? =
element as? UElement ?:
context?.get(cachedUElement) ?:
element.toUElement()?.also { context?.put(cachedUElement, it) }
private fun getOrCreateCachedElement(element: PsiElement,
context: ProcessingContext?,
supportedUElementTypes: List<Class<out UElement>>): UElement? =
element as? UElement ?: context?.get(cachedUElement) ?: supportedUElementTypes.asSequence().mapNotNull {
element.toUElement(it)
}.firstOrNull()?.also { context?.put(cachedUElement, it) }
private class UastPatternAdapter(val predicate: (UElement, ProcessingContext) -> Boolean) : ElementPattern<PsiElement> {
private class UastPatternAdapter(
val predicate: (UElement, ProcessingContext) -> Boolean,
val supportedUElementTypes: List<Class<out UElement>>
) : ElementPattern<PsiElement> {
override fun accepts(o: Any?): Boolean = accepts(o, null)
override fun accepts(o: Any?, context: ProcessingContext?): Boolean = when (o) {
is PsiElement ->
getOrCreateCachedElement(o, context)
getOrCreateCachedElement(o, context, supportedUElementTypes)
?.let { predicate(it, context ?: ProcessingContext()) }
?: false
else -> false
@@ -82,7 +107,7 @@ private class UastPatternAdapter(val predicate: (UElement, ProcessingContext) ->
private class UastReferenceProviderAdapter(val provider: UastReferenceProvider) : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
val uElement = getOrCreateCachedElement(element, context) ?: return PsiReference.EMPTY_ARRAY
val uElement = getOrCreateCachedElement(element, context, provider.supportedUElementTypes) ?: return PsiReference.EMPTY_ARRAY
return provider.getReferencesByElement(uElement, context)
}
@@ -0,0 +1,71 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi
import com.intellij.patterns.ElementPattern
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import com.intellij.util.ProcessingContext
import junit.framework.TestCase
import org.junit.Test
class UastReferenceRegistrarTest : LightCodeInsightFixtureTestCase() {
@Test
fun testUastReferenceContributorCalledOnlyOnLiterals() {
/**
* This implementation is used instead of [ReferenceProvidersRegistry] to emulate behaviour that [PsiReferenceProvider] will be called on
* each [PsiElement] not only [ContributedReferenceHost]-s as it is usually done for Java
*/
val psiReferenceRegistrar = object : PsiReferenceRegistrar() {
private val providers = mutableListOf<Pair<ElementPattern<*>, PsiReferenceProvider>>()
fun getReferencesFor(psiElement: PsiElement): List<PsiReference> {
val context = ProcessingContext()
return providers.flatMap { (pattern, provider) ->
if (pattern.accepts(psiElement, context) && provider.acceptsTarget(psiElement))
provider.getReferencesByElement(psiElement, context).toList()
else emptyList()
}
}
override fun <T : PsiElement?> registerReferenceProvider(pattern: ElementPattern<T>,
provider: PsiReferenceProvider,
priority: Double) {
providers += pattern to provider
}
}
val expectedInvocationCount = 10
var invocationCount = 0
psiReferenceRegistrar.registerUastReferenceProvider(
{ _, _ -> invocationCount++; true },
uastLiteralReferenceProvider { _, _ -> PsiReference.EMPTY_ARRAY }
)
myFixture.addClass("""
class MyClass {
String foo(){
return new java.lang.StringBuilder()
${(1..10).joinToString("\n") { ".append(\"$it\")" }}
.toString();
}
}
""".trimIndent())
myFixture.configureByFile("MyClass.java")
myFixture.file.accept(object : JavaRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
psiReferenceRegistrar.getReferencesFor(element)
super.visitElement(element)
}
})
TestCase.assertEquals(expectedInvocationCount, invocationCount)
}
}