File: FindSymbols\FindReferences\Finders\NamedTypeSymbolReferenceFinder.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.FindSymbols.Finders;
 
internal sealed class NamedTypeSymbolReferenceFinder : AbstractReferenceFinder<INamedTypeSymbol>
{
    protected override bool CanFind(INamedTypeSymbol symbol)
        => symbol.TypeKind != TypeKind.Error;
 
    protected override Task<ImmutableArray<string>> DetermineGlobalAliasesAsync(INamedTypeSymbol symbol, Project project, CancellationToken cancellationToken)
    {
        return GetAllMatchingGlobalAliasNamesAsync(project, symbol.Name, symbol.Arity, cancellationToken);
    }
 
    protected override ValueTask<ImmutableArray<ISymbol>> DetermineCascadedSymbolsAsync(
        INamedTypeSymbol symbol,
        Solution solution,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<ISymbol>.GetInstance(out var result);
 
        if (symbol.AssociatedSymbol != null)
            Add(result, ImmutableArray.Create(symbol.AssociatedSymbol));
 
        // cascade to constructors
        Add(result, symbol.Constructors);
 
        // cascade to destructor
        Add(result, symbol.GetMembers(WellKnownMemberNames.DestructorName));
 
        return new(result.ToImmutable());
    }
 
    private static void Add<TSymbol>(ArrayBuilder<ISymbol> result, ImmutableArray<TSymbol> enumerable) where TSymbol : ISymbol
    {
        result.AddRange(enumerable.Cast<ISymbol>());
    }
 
    protected override async Task DetermineDocumentsToSearchAsync<TData>(
        INamedTypeSymbol symbol,
        HashSet<string>? globalAliases,
        Project project,
        IImmutableSet<Document>? documents,
        Action<Document, TData> processResult,
        TData processResultData,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        await AddDocumentsToSearchAsync(symbol.Name, project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false);
        if (globalAliases != null)
        {
            foreach (var alias in globalAliases)
                await AddDocumentsToSearchAsync(alias, project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false);
        }
 
        await FindDocumentsAsync(
            project, documents, symbol.SpecialType.ToPredefinedType(), processResult, processResultData, cancellationToken).ConfigureAwait(false);
 
        await FindDocumentsWithGlobalSuppressMessageAttributeAsync(
            project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false);
    }
 
    /// <summary>
    /// Looks for documents likely containing <paramref name="throughName"/> in them.  That name will either be the actual
    /// name of the named type we're looking for, or it might be a global alias to it.
    /// </summary>
    private static async Task AddDocumentsToSearchAsync<TData>(
        string throughName,
        Project project,
        IImmutableSet<Document>? documents,
        Action<Document, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        var syntaxFacts = project.Services.GetRequiredService<ISyntaxFactsService>();
 
        await FindDocumentsAsync(
            project, documents, processResult, processResultData, cancellationToken, throughName).ConfigureAwait(false);
 
        if (TryGetNameWithoutAttributeSuffix(throughName, syntaxFacts, out var simpleName))
            await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, simpleName).ConfigureAwait(false);
    }
 
    private static bool IsPotentialReference(
        PredefinedType predefinedType,
        ISyntaxFactsService syntaxFacts,
        SyntaxToken token)
    {
        return
            syntaxFacts.TryGetPredefinedType(token, out var actualType) &&
            predefinedType == actualType;
    }
 
    protected override void FindReferencesInDocument<TData>(
        INamedTypeSymbol namedType,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<FinderLocation>.GetInstance(out var tempReferences);
 
        // First find all references to this type, using it's actual .Net name.
        AddReferencesToTypeOrGlobalAliasToIt(
            namedType, state, StandardCallbacks<FinderLocation>.AddToArrayBuilder, tempReferences, cancellationToken);
 
        // Next, if this named type is a predefined type (like int/long), also search for it with the C# name,
        // not the .Net one.
        AddPredefinedTypeReferences(namedType, state, tempReferences, cancellationToken);
 
        // The items in tempReferences need to be both reported and used later to calculate additional results.
        foreach (var location in tempReferences)
            processResult(location, processResultData);
 
        // This named type may end up being locally aliased as well.  If so, now find all the references to the local
        // alias. Note: the local alias may be like `using X = System.Int32` or it could be `using X = int`.  Because
        // we searched for both forms above, we'll find all references to the local aliases to either form here.
 
        FindLocalAliasReferences(
            tempReferences, state, processResult, processResultData, cancellationToken);
 
        FindReferencesInDocumentInsideGlobalSuppressions(
            namedType, state, processResult, processResultData, cancellationToken);
    }
 
    internal static void AddReferencesToTypeOrGlobalAliasToIt<TData>(
        INamedTypeSymbol namedType,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        AddNonAliasReferences(
            namedType, namedType.Name, state, processResult, processResultData, cancellationToken);
 
        foreach (var globalAlias in state.GlobalAliases)
            FindReferenceToAlias(namedType, state, processResult, processResultData, globalAlias, cancellationToken);
 
        foreach (var localAlias in state.Cache.SyntaxTreeIndex.GetAliases(namedType.Name, namedType.Arity))
            FindReferenceToAlias(namedType, state, processResult, processResultData, localAlias, cancellationToken);
    }
 
    private static void FindReferenceToAlias<TData>(
        INamedTypeSymbol namedType, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData, string alias, CancellationToken cancellationToken)
    {
        // ignore the cases where the global alias might match the type name (i.e.
        // global alias Console = System.Console).  We'll already find those references
        // above.
        if (state.SyntaxFacts.StringComparer.Equals(namedType.Name, alias))
            return;
 
        AddNonAliasReferences(
            namedType, alias, state, processResult, processResultData, cancellationToken);
    }
 
    /// <summary>
    /// Finds references to <paramref name="symbol"/> in this <paramref name="state"/>, but
    /// only if it referenced though <paramref name="name"/> (which might be the actual name
    /// of the type, or a global alias to it).
    /// </summary>
    private static void AddNonAliasReferences<TData>(
        INamedTypeSymbol symbol,
        string name,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        FindOrdinaryReferences(
            symbol, name, state, processResult, processResultData, cancellationToken);
 
        FindAttributeReferences(
            symbol, name, state, processResult, processResultData, cancellationToken);
    }
 
    private static void FindOrdinaryReferences<TData>(
        INamedTypeSymbol namedType,
        string name,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        // Get the parent node that best matches what this token represents.  For example, if we have `new a.b()`
        // then the parent node of `b` won't be `a.b`, but rather `new a.b()`.  This will actually cause us to bind
        // to the constructor not the type.  That's a good thing as we don't want these object-creations to
        // associate with the type, but rather with the constructor itself.
 
        FindReferencesInDocumentUsingIdentifier(
            namedType, name, state, processResult, processResultData, cancellationToken);
    }
 
    private static void AddPredefinedTypeReferences(
        INamedTypeSymbol symbol,
        FindReferencesDocumentState state,
        ArrayBuilder<FinderLocation> tempReferences,
        CancellationToken cancellationToken)
    {
        var predefinedType = symbol.SpecialType.ToPredefinedType();
        if (predefinedType == PredefinedType.None)
            return;
 
        var tokens = state.Root
            .DescendantTokens(descendIntoTrivia: true)
            .WhereAsArray(
                static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token),
                (state, predefinedType));
 
        FindReferencesInTokens(
            symbol, state, tokens, StandardCallbacks<FinderLocation>.AddToArrayBuilder, tempReferences, cancellationToken);
    }
 
    private static void FindAttributeReferences<TData>(
        INamedTypeSymbol namedType,
        string name,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix))
            FindReferencesInDocumentUsingIdentifier(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken);
    }
}