|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.FindSymbols.Finders;
internal abstract partial class AbstractReferenceFinder : IReferenceFinder
{
public const string ContainingTypeInfoPropertyName = "ContainingTypeInfo";
public const string ContainingMemberInfoPropertyName = "ContainingMemberInfo";
public abstract Task<ImmutableArray<string>> DetermineGlobalAliasesAsync(
ISymbol symbol, Project project, CancellationToken cancellationToken);
public abstract ValueTask<ImmutableArray<ISymbol>> DetermineCascadedSymbolsAsync(
ISymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken);
public abstract Task DetermineDocumentsToSearchAsync<TData>(
ISymbol symbol, HashSet<string>? globalAliases, Project project, IImmutableSet<Document>? documents, Action<Document, TData> processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken);
public abstract void FindReferencesInDocument<TData>(
ISymbol symbol, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken);
private static (bool matched, CandidateReason reason) SymbolsMatch(
ISymbol symbol, FindReferencesDocumentState state, SyntaxToken token, CancellationToken cancellationToken)
{
// delegates don't have exposed symbols for their constructors. so when you do `new MyDel()`, that's only a
// reference to a type (as we don't have any real constructor symbols that can actually cascade to). So
// don't do any special finding in that case.
var parent = symbol.IsDelegateType()
? token.Parent
: state.SyntaxFacts.TryGetBindableParent(token);
parent ??= token.Parent!;
return SymbolsMatch(symbol, state, parent, cancellationToken);
}
protected static (bool matched, CandidateReason reason) SymbolsMatch(
ISymbol searchSymbol, FindReferencesDocumentState state, SyntaxNode node, CancellationToken cancellationToken)
{
var symbolInfo = state.Cache.GetSymbolInfo(node, cancellationToken);
return Matches(searchSymbol, state, symbolInfo);
}
protected static (bool matched, CandidateReason reason) Matches(
ISymbol searchSymbol, FindReferencesDocumentState state, SymbolInfo symbolInfo)
{
if (SymbolFinder.OriginalSymbolsMatch(state.Solution, searchSymbol, symbolInfo.Symbol))
return (matched: true, CandidateReason.None);
foreach (var candidate in symbolInfo.CandidateSymbols)
{
if (SymbolFinder.OriginalSymbolsMatch(state.Solution, searchSymbol, candidate))
return (matched: true, symbolInfo.CandidateReason);
}
return default;
}
protected static bool TryGetNameWithoutAttributeSuffix(
string name,
ISyntaxFactsService syntaxFacts,
[NotNullWhen(returnValue: true)] out string? result)
{
return name.TryGetWithoutAttributeSuffix(syntaxFacts.IsCaseSensitive, out result);
}
protected static async Task FindDocumentsAsync<T, TData>(
Project project,
IImmutableSet<Document>? scope,
Func<Document, T, CancellationToken, ValueTask<bool>> predicateAsync,
T value,
Action<Document, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
// special case for highlight references
if (scope != null && scope.Count == 1)
{
var document = scope.First();
if (document.Project == project)
processResult(document, processResultData);
return;
}
await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken))
{
if (scope != null && !scope.Contains(document))
continue;
if (await predicateAsync(document, value, cancellationToken).ConfigureAwait(false))
processResult(document, processResultData);
}
}
/// <summary>
/// Finds all the documents in the provided project that contain the requested string
/// values
/// </summary>
protected static Task FindDocumentsAsync<TData>(
Project project,
IImmutableSet<Document>? documents,
Action<Document, TData> processResult,
TData processResultData,
CancellationToken cancellationToken,
params string[] values)
{
return FindDocumentsWithPredicateAsync(project, documents, static (index, values) =>
{
foreach (var value in values)
{
if (!index.ProbablyContainsIdentifier(value))
return false;
}
return true;
}, values, processResult, processResultData, cancellationToken);
}
/// <summary>
/// Finds all the documents in the provided project that contain a global attribute in them.
/// </summary>
protected static Task FindDocumentsWithGlobalSuppressMessageAttributeAsync<TData>(
Project project, IImmutableSet<Document>? documents, Action<Document, TData> processResult, TData processResultData, CancellationToken cancellationToken)
{
return FindDocumentsWithPredicateAsync(
project, documents, static index => index.ContainsGlobalSuppressMessageAttribute, processResult, processResultData, cancellationToken);
}
protected static Task FindDocumentsAsync<TData>(
Project project,
IImmutableSet<Document>? documents,
PredefinedType predefinedType,
Action<Document, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
if (predefinedType == PredefinedType.None)
return Task.CompletedTask;
return FindDocumentsWithPredicateAsync(
project, documents, static (index, predefinedType) => index.ContainsPredefinedType(predefinedType), predefinedType, processResult, processResultData, cancellationToken);
}
protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string name, SyntaxToken token)
=> syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, name);
protected static void FindReferencesInDocumentUsingIdentifier<TData>(
ISymbol symbol,
string identifier,
FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
var tokens = FindMatchingIdentifierTokens(state, identifier, cancellationToken);
FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken);
}
public static ImmutableArray<SyntaxToken> FindMatchingIdentifierTokens(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken)
=> state.Cache.FindMatchingIdentifierTokens(identifier, cancellationToken);
protected static void FindReferencesInTokens<TData>(
ISymbol symbol,
FindReferencesDocumentState state,
ImmutableArray<SyntaxToken> tokens,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
if (tokens.IsEmpty)
return;
foreach (var token in tokens)
{
cancellationToken.ThrowIfCancellationRequested();
var (matched, reason) = SymbolsMatch(symbol, state, token, cancellationToken);
if (matched)
{
var finderLocation = CreateFinderLocation(state, token, reason, cancellationToken);
processResult(finderLocation, processResultData);
}
}
}
protected static FinderLocation CreateFinderLocation(FindReferencesDocumentState state, SyntaxToken token, CandidateReason reason, CancellationToken cancellationToken)
=> new(token.GetRequiredParent(), CreateReferenceLocation(state, token, reason, cancellationToken));
public static ReferenceLocation CreateReferenceLocation(FindReferencesDocumentState state, SyntaxToken token, CandidateReason reason, CancellationToken cancellationToken)
=> new(
state.Document,
state.Cache.GetAliasInfo(state.SemanticFacts, token, cancellationToken),
token.GetLocation(),
isImplicit: false,
GetSymbolUsageInfo(token.GetRequiredParent(), state, cancellationToken),
GetAdditionalFindUsagesProperties(token.GetRequiredParent(), state),
reason);
private static IAliasSymbol? GetAliasSymbol(
FindReferencesDocumentState state,
SyntaxNode node,
CancellationToken cancellationToken)
{
var syntaxFacts = state.Document.GetRequiredLanguageService<ISyntaxFactsService>();
if (syntaxFacts.IsRightOfQualifiedName(node))
node = node.GetRequiredParent();
if (syntaxFacts.IsUsingDirectiveName(node))
{
var directive = node.GetRequiredParent();
// In the case of a same-named alias. i.e. `using Console = System.Console;` we don't actually want
// search for the alias. We'll already be checking any references called 'Console' and will find them
// as matches.
if (state.SemanticModel.GetDeclaredSymbol(directive, cancellationToken) is IAliasSymbol aliasSymbol &&
!syntaxFacts.StringComparer.Equals(aliasSymbol.Name, aliasSymbol.Target.Name))
{
return aliasSymbol;
}
}
return null;
}
protected static void FindLocalAliasReferences<TData>(
ArrayBuilder<FinderLocation> initialReferences,
ISymbol symbol,
FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken);
if (!aliasSymbols.IsDefaultOrEmpty)
FindReferencesThroughLocalAliasSymbols(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken);
}
protected static void FindLocalAliasReferences<TData>(
ArrayBuilder<FinderLocation> initialReferences,
FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken);
if (!aliasSymbols.IsDefaultOrEmpty)
FindReferencesThroughLocalAliasSymbols(state, aliasSymbols, processResult, processResultData, cancellationToken);
}
private static ImmutableArray<IAliasSymbol> GetLocalAliasSymbols(
FindReferencesDocumentState state,
ArrayBuilder<FinderLocation> initialReferences,
CancellationToken cancellationToken)
{
using var aliasSymbols = TemporaryArray<IAliasSymbol>.Empty;
foreach (var reference in initialReferences)
{
var symbol = GetAliasSymbol(state, reference.Node, cancellationToken);
if (symbol != null)
aliasSymbols.Add(symbol);
}
return aliasSymbols.ToImmutableAndClear();
}
private static void FindReferencesThroughLocalAliasSymbols<TData>(
ISymbol symbol,
FindReferencesDocumentState state,
ImmutableArray<IAliasSymbol> localAliasSymbols,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
foreach (var localAliasSymbol in localAliasSymbols)
{
FindReferencesInDocumentUsingIdentifier(
symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken);
// the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the
// shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {})
if (TryGetNameWithoutAttributeSuffix(localAliasSymbol.Name, state.SyntaxFacts, out var simpleName))
{
FindReferencesInDocumentUsingIdentifier(
symbol, simpleName, state, processResult, processResultData, cancellationToken);
}
}
}
private static void FindReferencesThroughLocalAliasSymbols<TData>(
FindReferencesDocumentState state,
ImmutableArray<IAliasSymbol> localAliasSymbols,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
foreach (var aliasSymbol in localAliasSymbols)
{
FindReferencesInDocumentUsingIdentifier(
aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken);
// the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the
// shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {})
if (TryGetNameWithoutAttributeSuffix(aliasSymbol.Name, state.SyntaxFacts, out var simpleName))
{
FindReferencesInDocumentUsingIdentifier(
aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken);
}
}
}
protected static Task FindDocumentsWithPredicateAsync<T, TData>(
Project project,
IImmutableSet<Document>? documents,
Func<SyntaxTreeIndex, T, bool> predicate,
T value,
Action<Document, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
return FindDocumentsAsync(project, documents, static async (d, t, c) =>
{
var info = await SyntaxTreeIndex.GetRequiredIndexAsync(d, c).ConfigureAwait(false);
return t.predicate(info, t.value);
}, (predicate, value), processResult, processResultData, cancellationToken);
}
protected static Task FindDocumentsWithPredicateAsync<TData>(
Project project,
IImmutableSet<Document>? documents,
Func<SyntaxTreeIndex, bool> predicate,
Action<Document, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
return FindDocumentsWithPredicateAsync(
project, documents,
static (info, predicate) => predicate(info),
predicate,
processResult,
processResultData,
cancellationToken);
}
protected static Task FindDocumentsWithForEachStatementsAsync<TData>(Project project, IImmutableSet<Document>? documents, Action<Document, TData> processResult, TData processResultData, CancellationToken cancellationToken)
=> FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsForEachStatement, processResult, processResultData, cancellationToken);
/// <summary>
/// If the `node` implicitly matches the `symbol`, then it will be added to `locations`.
/// </summary>
protected delegate void CollectMatchingReferences<TData>(
SyntaxNode node, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData);
protected static void FindReferencesInDocument<TData>(
FindReferencesDocumentState state,
Func<SyntaxTreeIndex, bool> isRelevantDocument,
CollectMatchingReferences<TData> collectMatchingReferences,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
var syntaxTreeInfo = state.Cache.SyntaxTreeIndex;
if (isRelevantDocument(syntaxTreeInfo))
{
foreach (var node in state.Root.DescendantNodesAndSelf())
{
cancellationToken.ThrowIfCancellationRequested();
collectMatchingReferences(node, state, processResult, processResultData);
}
}
}
protected void FindReferencesInForEachStatements<TData>(
ISymbol symbol,
FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken);
return;
static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo)
=> syntaxTreeInfo.ContainsForEachStatement;
void CollectMatchingReferences(
SyntaxNode node, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData)
{
var info = state.SemanticFacts.GetForEachSymbols(state.SemanticModel, node);
if (Matches(info.GetEnumeratorMethod, symbol) ||
Matches(info.MoveNextMethod, symbol) ||
Matches(info.CurrentProperty, symbol) ||
Matches(info.DisposeMethod, symbol))
{
var location = node.GetFirstToken().GetLocation();
var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken);
var result = new FinderLocation(node, new ReferenceLocation(
state.Document,
alias: null,
location: location,
isImplicit: true,
symbolUsageInfo,
GetAdditionalFindUsagesProperties(node, state),
candidateReason: CandidateReason.None));
processResult(result, processResultData);
}
}
}
protected void FindReferencesInCollectionInitializer<TData>(
ISymbol symbol,
FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken);
return;
static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo)
=> syntaxTreeInfo.ContainsCollectionInitializer;
void CollectMatchingReferences(
SyntaxNode node, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData)
{
if (!state.SyntaxFacts.IsObjectCollectionInitializer(node))
return;
var expressions = state.SyntaxFacts.GetExpressionsOfObjectCollectionInitializer(node);
foreach (var expression in expressions)
{
var info = state.SemanticFacts.GetCollectionInitializerSymbolInfo(state.SemanticModel, expression, cancellationToken);
if (Matches(info, symbol))
{
var location = expression.GetFirstToken().GetLocation();
var symbolUsageInfo = GetSymbolUsageInfo(expression, state, cancellationToken);
var result = new FinderLocation(expression, new ReferenceLocation(
state.Document,
alias: null,
location: location,
isImplicit: true,
symbolUsageInfo,
GetAdditionalFindUsagesProperties(expression, state),
candidateReason: CandidateReason.None));
processResult(result, processResultData);
}
}
}
}
protected void FindReferencesInDeconstruction<TData>(
ISymbol symbol,
FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken);
return;
static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo)
=> syntaxTreeInfo.ContainsDeconstruction;
void CollectMatchingReferences(
SyntaxNode node, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData)
{
var semanticModel = state.SemanticModel;
var semanticFacts = state.SemanticFacts;
var deconstructMethods = semanticFacts.GetDeconstructionAssignmentMethods(semanticModel, node);
if (deconstructMethods.IsEmpty)
{
// This was not a deconstruction assignment, it may still be a deconstruction foreach
deconstructMethods = semanticFacts.GetDeconstructionForEachMethods(semanticModel, node);
}
if (deconstructMethods.Any(static (m, symbol) => Matches(m, symbol), symbol))
{
var location = state.SyntaxFacts.GetDeconstructionReferenceLocation(node);
var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken);
var result = new FinderLocation(node, new ReferenceLocation(
state.Document, alias: null, location, isImplicit: true, symbolUsageInfo,
GetAdditionalFindUsagesProperties(node, state), CandidateReason.None));
processResult(result, processResultData);
}
}
}
protected void FindReferencesInAwaitExpression<TData>(
ISymbol symbol,
FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken);
return;
static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo)
=> syntaxTreeInfo.ContainsAwait;
void CollectMatchingReferences(
SyntaxNode node, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData)
{
var awaitExpressionMethod = state.SemanticFacts.GetGetAwaiterMethod(state.SemanticModel, node);
if (Matches(awaitExpressionMethod, symbol))
{
var location = node.GetFirstToken().GetLocation();
var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken);
var result = new FinderLocation(node, new ReferenceLocation(
state.Document, alias: null, location, isImplicit: true, symbolUsageInfo,
GetAdditionalFindUsagesProperties(node, state), CandidateReason.None));
processResult(result, processResultData);
}
}
}
protected void FindReferencesInImplicitObjectCreationExpression<TData>(
ISymbol symbol,
FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult,
TData processResultData,
CancellationToken cancellationToken)
{
FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken);
return;
static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo)
=> syntaxTreeInfo.ContainsImplicitObjectCreation;
void CollectMatchingReferences(
SyntaxNode node, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData)
{
// Avoid binding unrelated nodes
if (!state.SyntaxFacts.IsImplicitObjectCreationExpression(node))
return;
var constructor = state.SemanticModel.GetSymbolInfo(node, cancellationToken).Symbol;
if (Matches(constructor, symbol))
{
var location = node.GetFirstToken().GetLocation();
var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken);
var result = new FinderLocation(node, new ReferenceLocation(
state.Document, alias: null, location, isImplicit: true, symbolUsageInfo,
GetAdditionalFindUsagesProperties(node, state), CandidateReason.None));
processResult(result, processResultData);
}
}
}
protected static bool Matches(SymbolInfo info, ISymbol notNullOriginalUnreducedSymbol2)
{
if (Matches(info.Symbol, notNullOriginalUnreducedSymbol2))
return true;
foreach (var symbol in info.CandidateSymbols)
{
if (Matches(symbol, notNullOriginalUnreducedSymbol2))
return true;
}
return false;
}
protected static bool Matches(ISymbol? symbol1, ISymbol notNullOriginalUnreducedSymbol2)
{
Contract.ThrowIfFalse(notNullOriginalUnreducedSymbol2.GetOriginalUnreducedDefinition().Equals(notNullOriginalUnreducedSymbol2));
return symbol1 != null && SymbolEquivalenceComparer.Instance.Equals(
symbol1.GetOriginalUnreducedDefinition(),
notNullOriginalUnreducedSymbol2);
}
protected static SymbolUsageInfo GetSymbolUsageInfo(
SyntaxNode node,
FindReferencesDocumentState state,
CancellationToken cancellationToken)
{
var syntaxFacts = state.SyntaxFacts;
var semanticFacts = state.SemanticFacts;
var semanticModel = state.SemanticModel;
var topNameNode = node;
while (syntaxFacts.IsQualifiedName(topNameNode.Parent))
topNameNode = topNameNode.Parent;
var isInNamespaceNameContext = syntaxFacts.IsBaseNamespaceDeclaration(topNameNode.Parent);
return syntaxFacts.IsInNamespaceOrTypeContext(topNameNode)
? SymbolUsageInfo.Create(GetTypeOrNamespaceUsageInfo())
: GetSymbolUsageInfoCommon();
// Local functions.
TypeOrNamespaceUsageInfo GetTypeOrNamespaceUsageInfo()
{
var usageInfo = IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts) || syntaxFacts.IsLeftSideOfExplicitInterfaceSpecifier(node)
? TypeOrNamespaceUsageInfo.Qualified
: TypeOrNamespaceUsageInfo.None;
if (isInNamespaceNameContext)
{
usageInfo |= TypeOrNamespaceUsageInfo.NamespaceDeclaration;
}
else if (node.FirstAncestorOrSelf<SyntaxNode, ISyntaxFactsService>((node, syntaxFacts) => syntaxFacts.IsUsingOrExternOrImport(node), syntaxFacts) != null)
{
usageInfo |= TypeOrNamespaceUsageInfo.Import;
}
while (syntaxFacts.IsQualifiedName(node.Parent))
node = node.Parent;
if (syntaxFacts.IsTypeArgumentList(node.Parent))
{
usageInfo |= TypeOrNamespaceUsageInfo.TypeArgument;
}
else if (syntaxFacts.IsTypeConstraint(node.Parent))
{
usageInfo |= TypeOrNamespaceUsageInfo.TypeConstraint;
}
else if (syntaxFacts.IsBaseTypeList(node.Parent) ||
syntaxFacts.IsBaseTypeList(node.Parent?.Parent))
{
usageInfo |= TypeOrNamespaceUsageInfo.Base;
}
else if (syntaxFacts.IsTypeOfObjectCreationExpression(node))
{
usageInfo |= TypeOrNamespaceUsageInfo.ObjectCreation;
}
return usageInfo;
}
SymbolUsageInfo GetSymbolUsageInfoCommon()
{
if (semanticFacts.IsInOutContext(semanticModel, node, cancellationToken))
{
return SymbolUsageInfo.Create(ValueUsageInfo.WritableReference);
}
else if (semanticFacts.IsInRefContext(semanticModel, node, cancellationToken))
{
return SymbolUsageInfo.Create(ValueUsageInfo.ReadableWritableReference);
}
else if (semanticFacts.IsInInContext(semanticModel, node, cancellationToken))
{
return SymbolUsageInfo.Create(ValueUsageInfo.ReadableReference);
}
else if (semanticFacts.IsOnlyWrittenTo(semanticModel, node, cancellationToken))
{
return SymbolUsageInfo.Create(ValueUsageInfo.Write);
}
else
{
var operation = semanticModel.GetOperation(node, cancellationToken);
switch (operation?.Parent)
{
case INameOfOperation:
case ITypeOfOperation:
case ISizeOfOperation:
return SymbolUsageInfo.Create(ValueUsageInfo.Name);
}
if (node.IsPartOfStructuredTrivia())
{
return SymbolUsageInfo.Create(ValueUsageInfo.Name);
}
var symbolInfo = semanticModel.GetSymbolInfo(node, cancellationToken);
if (symbolInfo.Symbol != null)
{
switch (symbolInfo.Symbol.Kind)
{
case SymbolKind.Namespace:
var namespaceUsageInfo = TypeOrNamespaceUsageInfo.None;
if (isInNamespaceNameContext)
namespaceUsageInfo |= TypeOrNamespaceUsageInfo.NamespaceDeclaration;
if (IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts))
namespaceUsageInfo |= TypeOrNamespaceUsageInfo.Qualified;
return SymbolUsageInfo.Create(namespaceUsageInfo);
case SymbolKind.NamedType:
var typeUsageInfo = TypeOrNamespaceUsageInfo.None;
if (IsNodeOrAnyAncestorLeftSideOfDot(node, syntaxFacts))
typeUsageInfo |= TypeOrNamespaceUsageInfo.Qualified;
return SymbolUsageInfo.Create(typeUsageInfo);
case SymbolKind.Method:
case SymbolKind.Property:
case SymbolKind.Field:
case SymbolKind.Event:
case SymbolKind.Parameter:
case SymbolKind.Local:
var valueUsageInfo = ValueUsageInfo.Read;
if (semanticFacts.IsWrittenTo(semanticModel, node, cancellationToken))
valueUsageInfo |= ValueUsageInfo.Write;
return SymbolUsageInfo.Create(valueUsageInfo);
}
}
return SymbolUsageInfo.None;
}
}
}
private static bool IsNodeOrAnyAncestorLeftSideOfDot(SyntaxNode node, ISyntaxFactsService syntaxFacts)
{
if (syntaxFacts.IsLeftSideOfDot(node))
{
return true;
}
if (syntaxFacts.IsRightOfQualifiedName(node) ||
syntaxFacts.IsNameOfSimpleMemberAccessExpression(node) ||
syntaxFacts.IsNameOfMemberBindingExpression(node))
{
return syntaxFacts.IsLeftSideOfDot(node.Parent);
}
return false;
}
internal static ImmutableArray<(string key, string value)> GetAdditionalFindUsagesProperties(
SyntaxNode node, FindReferencesDocumentState state)
{
using var additionalProperties = TemporaryArray<(string key, string value)>.Empty;
var syntaxFacts = state.SyntaxFacts;
var semanticModel = state.SemanticModel;
TryAddAdditionalProperty(
syntaxFacts.GetContainingTypeDeclaration(node, node.SpanStart),
ContainingTypeInfoPropertyName);
TryAddAdditionalProperty(
syntaxFacts.GetContainingMemberDeclaration(node, node.SpanStart),
ContainingMemberInfoPropertyName);
return additionalProperties.ToImmutableAndClear();
void TryAddAdditionalProperty(SyntaxNode? node, string key)
{
if (node != null)
{
var symbol = semanticModel.GetDeclaredSymbol(node);
if (symbol != null)
additionalProperties.Add((key, symbol.Name));
}
}
}
internal static ImmutableArray<(string key, string value)> GetAdditionalFindUsagesProperties(ISymbol definition)
{
using var additionalProperties = TemporaryArray<(string key, string value)>.Empty;
var containingType = definition.ContainingType;
if (containingType != null)
additionalProperties.Add((ContainingTypeInfoPropertyName, containingType.Name));
// Containing member should only include fields, properties, methods, or events. Since ContainingSymbol can
// return other types, use the return value of GetMemberType to restrict to members only.)
var containingSymbol = definition.ContainingSymbol;
if (containingSymbol != null && containingSymbol.GetMemberType() != null)
additionalProperties.Add((ContainingMemberInfoPropertyName, containingSymbol.Name));
return additionalProperties.ToImmutableAndClear();
}
}
internal abstract partial class AbstractReferenceFinder<TSymbol> : AbstractReferenceFinder
where TSymbol : ISymbol
{
protected abstract bool CanFind(TSymbol symbol);
protected abstract Task DetermineDocumentsToSearchAsync<TData>(
TSymbol symbol, HashSet<string>? globalAliases, Project project, IImmutableSet<Document>? documents,
Action<Document, TData> processResult, TData processResultData,
FindReferencesSearchOptions options, CancellationToken cancellationToken);
protected abstract void FindReferencesInDocument<TData>(
TSymbol symbol, FindReferencesDocumentState state,
Action<FinderLocation, TData> processResult, TData processResultData,
FindReferencesSearchOptions options, CancellationToken cancellationToken);
protected virtual Task<ImmutableArray<string>> DetermineGlobalAliasesAsync(
TSymbol symbol, Project project, CancellationToken cancellationToken)
{
return SpecializedTasks.EmptyImmutableArray<string>();
}
public sealed override Task<ImmutableArray<string>> DetermineGlobalAliasesAsync(
ISymbol symbol, Project project, CancellationToken cancellationToken)
{
return symbol is TSymbol typedSymbol && CanFind(typedSymbol)
? DetermineGlobalAliasesAsync(typedSymbol, project, cancellationToken)
: SpecializedTasks.EmptyImmutableArray<string>();
}
public sealed override Task DetermineDocumentsToSearchAsync<TData>(
ISymbol symbol, HashSet<string>? globalAliases, Project project,
IImmutableSet<Document>? documents, Action<Document, TData> processResult,
TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken)
{
if (symbol is TSymbol typedSymbol && CanFind(typedSymbol))
return DetermineDocumentsToSearchAsync(typedSymbol, globalAliases, project, documents, processResult, processResultData, options, cancellationToken);
return Task.CompletedTask;
}
public sealed override void FindReferencesInDocument<TData>(
ISymbol symbol, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken)
{
if (symbol is TSymbol typedSymbol && CanFind(typedSymbol))
FindReferencesInDocument(typedSymbol, state, processResult, processResultData, options, cancellationToken);
}
public sealed override ValueTask<ImmutableArray<ISymbol>> DetermineCascadedSymbolsAsync(
ISymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken)
{
if (options.Cascade &&
symbol is TSymbol typedSymbol &&
CanFind(typedSymbol))
{
return DetermineCascadedSymbolsAsync(typedSymbol, solution, options, cancellationToken);
}
return new([]);
}
protected virtual ValueTask<ImmutableArray<ISymbol>> DetermineCascadedSymbolsAsync(
TSymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken)
{
return new([]);
}
protected static void FindReferencesInDocumentUsingSymbolName<TData>(
TSymbol symbol, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData, CancellationToken cancellationToken)
{
FindReferencesInDocumentUsingIdentifier(
symbol, symbol.Name, state, processResult, processResultData, cancellationToken);
}
protected static async Task<ImmutableArray<string>> GetAllMatchingGlobalAliasNamesAsync(
Project project, string name, int arity, CancellationToken cancellationToken)
{
using var result = TemporaryArray<string>.Empty;
await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken))
{
var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false);
foreach (var alias in index.GetGlobalAliases(name, arity))
result.Add(alias);
}
return result.ToImmutableAndClear();
}
}
|