diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/resolve/reference/PsiReferenceRegistrarImpl.java b/platform/core-impl/src/com/intellij/psi/impl/source/resolve/reference/PsiReferenceRegistrarImpl.java index ac8516f8745d..7aede554fbe7 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/resolve/reference/PsiReferenceRegistrarImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/resolve/reference/PsiReferenceRegistrarImpl.java @@ -14,6 +14,7 @@ import com.intellij.util.ArrayUtilRt; import com.intellij.util.ProcessingContext; import com.intellij.util.SmartList; import com.intellij.util.containers.ConcurrentFactoryMap; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -32,20 +33,20 @@ public class PsiReferenceRegistrarImpl extends PsiReferenceRegistrar { private final List myCleanupDisposables = new ArrayList<>(); PsiReferenceRegistrarImpl() { - myBindingCache = ConcurrentFactoryMap.createMap(key-> { - List result = new SmartList<>(); - for (Class bindingClass : myBindingsMap.keySet()) { - if (bindingClass.isAssignableFrom(key)) { - result.add(myBindingsMap.get(bindingClass)); - } - } - for (Class bindingClass : myNamedBindingsMap.keySet()) { - if (bindingClass.isAssignableFrom(key)) { - result.add(myNamedBindingsMap.get(bindingClass)); - } - } - return result.toArray(new ProviderBinding[0]); - } + myBindingCache = ConcurrentFactoryMap.createMap(key -> { + List result = new SmartList<>(); + for (Class bindingClass : myBindingsMap.keySet()) { + if (bindingClass.isAssignableFrom(key)) { + result.add(myBindingsMap.get(bindingClass)); + } + } + for (Class bindingClass : myNamedBindingsMap.keySet()) { + if (bindingClass.isAssignableFrom(key)) { + result.add(myNamedBindingsMap.get(bindingClass)); + } + } + return result.toArray(new ProviderBinding[0]); + } ); } @@ -86,7 +87,8 @@ public class PsiReferenceRegistrarImpl extends PsiReferenceRegistrar { for (PatternCondition condition1 : conditions1) { if (condition1 instanceof ValuePatternCondition) { final Collection strings = ((ValuePatternCondition)condition1).getValues(); - registerNamedReferenceProvider(ArrayUtilRt.toStringArray(strings), nameCondition, scope, true, provider, priority, pattern, parentDisposable); + registerNamedReferenceProvider(ArrayUtilRt.toStringArray(strings), nameCondition, scope, true, provider, priority, pattern, + parentDisposable); return; } if (condition1 instanceof CaseInsensitiveValuePatternCondition) { @@ -174,9 +176,9 @@ public class PsiReferenceRegistrarImpl extends PsiReferenceRegistrar { } } - @NotNull - List> getPairsByElement(@NotNull PsiElement element, - @NotNull PsiReferenceService.Hints hints) { + @ApiStatus.Internal + public @NotNull List> getPairsByElement(@NotNull PsiElement element, + @NotNull PsiReferenceService.Hints hints) { final ProviderBinding[] bindings = myBindingCache.get(element.getClass()); if (bindings.length == 0) return Collections.emptyList(); diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 3172b3d784df..321da5eb08d0 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -978,7 +978,7 @@ uast.evaluator.loop.iteration.limit=20 uast.evaluator.loop.iteration.limit.description=Maximal allowed iteration count for loop evaluation in the Uast TreeBasedEvaluator visitor uast.references.by.usage=true -uast.references.by.usage.description=Enables Uast reference contributors that searches for usages of variables in the opened file and its module +uast.references.by.usage.description=Enables Uast reference contributors that searches for usages of variables in the opened file idea.report.nullity.missing.in.generated.overriders=true idea.report.nullity.missing.in.generated.overriders.description=Whether "@NotNull/@Nullable problems" inspection should complain about overriding methods or parameters missing @NotNull, which occur in generated code diff --git a/uast/uast-common/src/com/intellij/psi/UastReferenceByUsageAdapter.kt b/uast/uast-common/src/com/intellij/psi/UastReferenceByUsageAdapter.kt index 0fa5df0edf69..e7a8d6b4cd0b 100644 --- a/uast/uast-common/src/com/intellij/psi/UastReferenceByUsageAdapter.kt +++ b/uast/uast-common/src/com/intellij/psi/UastReferenceByUsageAdapter.kt @@ -2,48 +2,46 @@ package com.intellij.psi import com.intellij.codeInsight.completion.CompletionUtilCoreImpl -import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.Language import com.intellij.model.search.SearchService -import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.progress.impl.CancellationCheck import com.intellij.openapi.project.DumbService import com.intellij.openapi.util.Key import com.intellij.patterns.ElementPattern import com.intellij.patterns.uast.UExpressionPattern import com.intellij.patterns.uast.injectionHostUExpression -import com.intellij.psi.impl.cache.CacheManager -import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.UastPatternAdapter.Companion.getOrCreateCachedElement +import com.intellij.psi.impl.source.resolve.reference.PsiReferenceRegistrarImpl +import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry import com.intellij.psi.search.LocalSearchScope -import com.intellij.psi.search.PsiSearchHelper -import com.intellij.psi.search.PsiSearchHelper.SearchCostResult -import com.intellij.psi.search.UsageSearchContext import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValueProvider.Result import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiModificationTracker import com.intellij.util.ProcessingContext -import com.intellij.util.containers.ContainerUtil import org.jetbrains.annotations.ApiStatus import org.jetbrains.uast.* +import org.jetbrains.uast.expressions.UInjectionHost -internal class UastReferenceByUsageAdapter(private val usagePattern: ElementPattern, - private val provider: UastReferenceProvider) : UastReferenceProvider(UExpression::class.java) { +class UastReferenceByUsageAdapter( + val expressionPattern: ElementPattern, + val usagePattern: ElementPattern, + val provider: UastReferenceProvider +) : PsiReferenceProvider() { + + val supportedUElementTypes: List> = listOf(UExpression::class.java) override fun acceptsTarget(target: PsiElement): Boolean { return provider.acceptsTarget(target) } - override fun getReferencesByElement(element: UElement, context: ProcessingContext): Array { - val parentVariable = when (val uastParent = getOriginalUastParent(element)) { - is UVariable -> uastParent - is UPolyadicExpression -> uastParent.uastParent as? UVariable // support .withUastParentOrSelf() patterns - else -> null - } + override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { + val uElement = getOrCreateCachedElement(element, context, supportedUElementTypes) ?: return PsiReference.EMPTY_ARRAY + return CancellationCheck.runWithCancellationCheck { getReferencesByElement(uElement, context) } + } - if (parentVariable == null - || parentVariable.name.isNullOrEmpty() - || !parentVariable.type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { - return PsiReference.EMPTY_ARRAY - } + internal fun getReferencesByElement(element: UElement, context: ProcessingContext): Array { + val parentVariable = getUsageVariableTargetForInitializer(element) ?: return PsiReference.EMPTY_ARRAY val usage = getDirectVariableUsages(parentVariable).find { usage -> val refExpression = getUsageReferenceExpressionWithCache(usage, context) @@ -58,11 +56,81 @@ internal class UastReferenceByUsageAdapter(private val usagePattern: ElementPatt override fun toString(): String = "uastReferenceByUsageAdapter($provider)" } +private fun getUsageVariableTargetForInitializer(element: UElement): UVariable? { + val parentVariable = when (val uastParent = getOriginalUastParent(element)) { + is UVariable -> uastParent + is UPolyadicExpression -> uastParent.uastParent as? UVariable // support .withUastParentOrSelf() patterns + else -> null + } + + if (parentVariable == null + || parentVariable.name.isNullOrEmpty() + || !parentVariable.type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return null + } + + return parentVariable +} + +/** + * Find usages of variable or constant in the scope of module. + * + * @param element initializer of variable, String literal + * @param targetHint target hint, usually POM target element + * @return references that are supposed to be created in usage places + */ +@ApiStatus.Internal +fun getReferencesForDirectUsagesOfVariable(element: PsiElement, targetHint: PsiElement?): Array { + val uElement = element.toUElementOfType() ?: return PsiReference.EMPTY_ARRAY + val originalElement = CompletionUtilCoreImpl.getOriginalElement(element) ?: element + val uParentVariable = getUsageVariableTargetForInitializer(uElement) ?: return PsiReference.EMPTY_ARRAY + + val registrar = ReferenceProvidersRegistry.getInstance().getRegistrar(Language.findLanguageByID("UAST")!!) + val providerInfos = (registrar as PsiReferenceRegistrarImpl).getPairsByElement(originalElement, PsiReferenceService.Hints(targetHint, null)) + + // by-usage providers must implement acceptsTarget correctly, we rely on fact that they accept targetHint + val suitableProviders = providerInfos.asSequence() + .map { it.provider } + .filterIsInstance() + .toList() + + val usages = getDirectVariableUsagesWithNonLocal(uParentVariable) + for (usage in usages) { + val refExpression = usage.toUElementOfType() + + if (refExpression != null) { + val context = ProcessingContext() + context.put(USAGE_PSI_ELEMENT, usage) + context.put(REQUESTED_PSI_ELEMENT, originalElement) + + for (provider in suitableProviders) { + if (provider.usagePattern.accepts(refExpression, context)) { + val references = provider.provider.getReferencesByElement(refExpression, context) + + if (references.isNotEmpty()) { + return references + } + } + } + } + } + + return PsiReference.EMPTY_ARRAY +} + +internal val STRICT_CONSTANT_NAME_PATTERN: Regex = Regex("[\\p{Upper}_0-9]+") + @ApiStatus.Experimental fun uInjectionHostInVariable(): UExpressionPattern<*, *> = injectionHostUExpression().filter { it.uastParent is UVariable } +@ApiStatus.Experimental +fun uInjectionHostInStrictConstant(): UExpressionPattern<*, *> = injectionHostUExpression().filter { + val uastParent = it.uastParent + uastParent is UVariable && uastParent.name?.matches(STRICT_CONSTANT_NAME_PATTERN) == true +} + @ApiStatus.Experimental fun uExpressionInVariable(): UExpressionPattern<*, *> = injectionHostUExpression().filter { val parent = it.uastParent @@ -96,61 +164,22 @@ private fun getDirectVariableUsages(uVar: UVariable): Sequence { if (DumbService.isDumb(project)) return emptySequence() // do not try to search in dumb mode val cachedValue = CachedValuesManager.getManager(project).getCachedValue(variablePsi, CachedValueProvider { - val anchors = findDirectVariableUsages(variablePsi).map(PsiAnchor::create) + val anchors = findLocalDirectVariableUsages(variablePsi).map(PsiAnchor::create) Result.createSingleDependency(anchors, PsiModificationTracker.MODIFICATION_COUNT) }) return cachedValue.asSequence().mapNotNull(PsiAnchor::retrieve) } -private const val MAX_FILES_TO_FIND_USAGES: Int = 5 -private val STRICT_CONSTANT_NAME_PATTERN: Regex = Regex("[\\p{Upper}_0-9]+") - -private fun findDirectVariableUsages(variablePsi: PsiElement): Iterable { +private fun findLocalDirectVariableUsages(variablePsi: PsiElement): Iterable { val uVariable = variablePsi.toUElementOfType() val variableName = uVariable?.name if (variableName.isNullOrEmpty()) return emptyList() val currentFile = variablePsi.containingFile ?: return emptyList() - val localUsages = findVariableUsages(variablePsi, variableName, arrayOf(currentFile)) - - // non-local searches are limited for real-life use cases, we do not try to find all possible usages - if (uVariable is ULocalVariable - || (variablePsi is PsiModifierListOwner && variablePsi.hasModifier(JvmModifier.PRIVATE)) - || !STRICT_CONSTANT_NAME_PATTERN.matches(variableName)) { - return localUsages - } - - val module = ModuleUtilCore.findModuleForPsiElement(variablePsi) ?: return localUsages - val uastScope = getUastScope(module.moduleScope) - - val searchHelper = PsiSearchHelper.getInstance(module.project) - if (searchHelper.isCheapEnoughToSearch(variableName, uastScope, currentFile, null) != SearchCostResult.FEW_OCCURRENCES) { - return localUsages - } - - val cacheManager = CacheManager.getInstance(variablePsi.project) - val containingFiles = cacheManager.getVirtualFilesWithWord( - variableName, - UsageSearchContext.IN_CODE, - uastScope, - true) - val useScope = variablePsi.useScope - - val psiManager = PsiManager.getInstance(module.project) - val filesToSearch = containingFiles.asSequence() - .filter { useScope.contains(it) && it != currentFile.virtualFile } - .mapNotNull { psiManager.findFile(it) } - .sortedBy { it.virtualFile.canonicalPath } - .take(MAX_FILES_TO_FIND_USAGES) - .toList() - .toTypedArray() - - val nonLocalUsages = findVariableUsages(variablePsi, variableName, filesToSearch) - - return ContainerUtil.concat(localUsages, nonLocalUsages) + return findReferencedVariableUsages(variablePsi, variableName, arrayOf(currentFile)) } -private fun findVariableUsages(variablePsi: PsiElement, variableName: String, files: Array): List { +internal fun findReferencedVariableUsages(variablePsi: PsiElement, variableName: String, files: Array): List { if (files.isEmpty()) return emptyList() return SearchService.getInstance() @@ -170,9 +199,4 @@ private fun findVariableUsages(variablePsi: PsiElement, variableName: String, fi } .findAll() .sortedWith(compareBy({ it.containingFile?.virtualFile?.canonicalPath ?: "" }, { it.textOffset })) -} - -private fun getUastScope(originalScope: GlobalSearchScope): GlobalSearchScope { - val fileTypes = UastLanguagePlugin.getInstances().map { it.language.associatedFileType }.toTypedArray() - return GlobalSearchScope.getScopeRestrictedByFileTypes(originalScope, *fileTypes) } \ No newline at end of file diff --git a/uast/uast-common/src/com/intellij/psi/UastReferenceByUsageResolver.kt b/uast/uast-common/src/com/intellij/psi/UastReferenceByUsageResolver.kt new file mode 100644 index 000000000000..0013379293a9 --- /dev/null +++ b/uast/uast-common/src/com/intellij/psi/UastReferenceByUsageResolver.kt @@ -0,0 +1,91 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:JvmName("UastReferenceByUsageResolver") + +package com.intellij.psi + +import com.intellij.lang.jvm.JvmModifier +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.project.DumbService +import com.intellij.psi.impl.cache.CacheManager +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.PsiSearchHelper +import com.intellij.psi.search.UsageSearchContext +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import com.intellij.psi.util.PsiModificationTracker +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.uast.ULocalVariable +import org.jetbrains.uast.UVariable +import org.jetbrains.uast.UastLanguagePlugin +import org.jetbrains.uast.toUElementOfType + +private const val MAX_FILES_TO_FIND_USAGES: Int = 5 + +/** + * Searches for direct variable usages not only in the same file but also in the module. + * Must be used only from [PsiReference.resolve], do not call from [PsiReferenceProvider.getReferencesByElement]. + */ +@ApiStatus.Experimental +internal fun getDirectVariableUsagesWithNonLocal(uVar: UVariable): Sequence { + val variablePsi = uVar.sourcePsi ?: return emptySequence() + val project = variablePsi.project + + if (DumbService.isDumb(project)) return emptySequence() // do not try to search in dumb mode + + val cachedValue = CachedValuesManager.getManager(project).getCachedValue(variablePsi, CachedValueProvider { + val anchors = findAllDirectVariableUsages(variablePsi).map(PsiAnchor::create) + CachedValueProvider.Result.createSingleDependency(anchors, PsiModificationTracker.MODIFICATION_COUNT) + }) + return cachedValue.asSequence().mapNotNull(PsiAnchor::retrieve) +} + +private fun findAllDirectVariableUsages(variablePsi: PsiElement): Iterable { + val uVariable = variablePsi.toUElementOfType() + val variableName = uVariable?.name + if (variableName.isNullOrEmpty()) return emptyList() + val currentFile = variablePsi.containingFile ?: return emptyList() + + val localUsages = findReferencedVariableUsages(variablePsi, variableName, arrayOf(currentFile)) + + // non-local searches are limited for real-life use cases, we do not try to find all possible usages + if (uVariable is ULocalVariable + || (variablePsi is PsiModifierListOwner && variablePsi.hasModifier(JvmModifier.PRIVATE)) + || !STRICT_CONSTANT_NAME_PATTERN.matches(variableName)) { + return localUsages + } + + val module = ModuleUtilCore.findModuleForPsiElement(variablePsi) ?: return localUsages + val uastScope = getUastScope(module.moduleScope) + + val searchHelper = PsiSearchHelper.getInstance(module.project) + if (searchHelper.isCheapEnoughToSearch(variableName, uastScope, currentFile, null) != PsiSearchHelper.SearchCostResult.FEW_OCCURRENCES) { + return localUsages + } + + val cacheManager = CacheManager.getInstance(variablePsi.project) + val containingFiles = cacheManager.getVirtualFilesWithWord( + variableName, + UsageSearchContext.IN_CODE, + uastScope, + true) + val useScope = variablePsi.useScope + + val psiManager = PsiManager.getInstance(module.project) + val filesToSearch = containingFiles.asSequence() + .filter { useScope.contains(it) && it != currentFile.virtualFile } + .mapNotNull { psiManager.findFile(it) } + .sortedBy { it.virtualFile.canonicalPath } + .take(MAX_FILES_TO_FIND_USAGES) + .toList() + .toTypedArray() + + val nonLocalUsages = findReferencedVariableUsages(variablePsi, variableName, filesToSearch) + + return ContainerUtil.concat(localUsages, nonLocalUsages) +} + +private fun getUastScope(originalScope: GlobalSearchScope): GlobalSearchScope { + val fileTypes = UastLanguagePlugin.getInstances().map { it.language.associatedFileType }.toTypedArray() + return GlobalSearchScope.getScopeRestrictedByFileTypes(originalScope, *fileTypes) +} diff --git a/uast/uast-common/src/com/intellij/psi/UastReferenceRegistrar.kt b/uast/uast-common/src/com/intellij/psi/UastReferenceRegistrar.kt index 71ca9a23142d..bac5053c98ef 100644 --- a/uast/uast-common/src/com/intellij/psi/UastReferenceRegistrar.kt +++ b/uast/uast-common/src/com/intellij/psi/UastReferenceRegistrar.kt @@ -122,6 +122,7 @@ fun uastReferenceProviderByUsage(targetClass: Class?, * Consider using [uastReferenceProviderByUsage] if you need to obtain additional context from a usage place. */ @ApiStatus.Experimental +@JvmOverloads fun PsiReferenceRegistrar.registerReferenceProviderByUsage(expressionPattern: ElementPattern, usagePattern: ElementPattern, provider: UastReferenceProvider, @@ -129,7 +130,8 @@ fun PsiReferenceRegistrar.registerReferenceProviderByUsage(expressionPattern: El this.registerUastReferenceProvider(usagePattern, provider, priority) if (Registry.`is`("uast.references.by.usage", true)) { - this.registerUastReferenceProvider(expressionPattern, UastReferenceByUsageAdapter(usagePattern, provider), priority) + val adapter = UastReferenceByUsageAdapter(expressionPattern, usagePattern, provider) + this.registerReferenceProvider(adaptPattern(expressionPattern::accepts, adapter.supportedUElementTypes), adapter, priority) } } @@ -139,4 +141,3 @@ fun PsiReferenceRegistrar.registerReferenceProviderByUsage(usagePattern: Element priority: Double = PsiReferenceRegistrar.DEFAULT_PRIORITY) { registerReferenceProviderByUsage(uExpressionInVariable(), usagePattern, provider, priority) } -