File: SemanticSearch\Tools\ReferencingSyntaxFinder.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.
 
#if NET
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
 
namespace Microsoft.CodeAnalysis.SemanticSearch;
 
internal sealed class ReferencingSyntaxFinder(Solution solution, CancellationToken cancellationToken)
{
    private static readonly FindReferencesSearchOptions s_options = new()
    {
        AssociatePropertyReferencesWithSpecificAccessor = false,
        Cascade = false,
        DisplayAllDefinitions = false,
        Explicit = true,
        UnidirectionalHierarchyCascade = false
    };
 
    public IEnumerable<SyntaxNode> Find(ISymbol symbol)
        => FindAsync(symbol).ToBlockingEnumerable(cancellationToken);
 
    public async IAsyncEnumerable<SyntaxNode> FindAsync(ISymbol symbol)
    {
        using var _ = PooledHashSet<SyntaxNode>.GetInstance(out var cachedRoots);
 
        // Kick off the SymbolFinder.FindReferencesAsync call on the provided symbol/solution.  As it finds
        // ReferenceLocations, it will push those into the 'callback' delegate passed into it. ProducerConsumer will
        // then convert this to a simple IAsyncEnumerable<ReferenceLocation> that we can iterate over, converting those
        // locations to SyntaxNodes in the corresponding C# or VB document.
        await foreach (var item in ProducerConsumer<ReferenceLocation>.RunAsync(
            FindReferencesAsync, args: (solution, symbol), cancellationToken))
        {
            var root = await item.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            if (root == null)
                continue;
 
            // Hold onto the root so that if we find more references in the same document, we don't have to reparse it.
            cachedRoots.Add(root);
            yield return item.Location.FindNode(findInsideTrivia: true, getInnermostNodeForTie: true, cancellationToken);
        }
    }
 
    private static Task FindReferencesAsync(Action<ReferenceLocation> callback, (Solution solution, ISymbol symbol) args, CancellationToken cancellationToken)
        => SymbolFinder.FindReferencesAsync(
            args.symbol, args.solution, new Progress(callback), documents: null, s_options, cancellationToken);
 
    private sealed class Progress(Action<ReferenceLocation> callback) : IStreamingFindReferencesProgress
    {
        public ValueTask OnStartedAsync(CancellationToken cancellationToken) => ValueTask.CompletedTask;
        public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => ValueTask.CompletedTask;
        public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) => ValueTask.CompletedTask;
 
        public ValueTask OnReferencesFoundAsync(ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken)
        {
            foreach (var (_, _, location) in references)
                callback(location);
 
            return ValueTask.CompletedTask;
        }
 
        public IStreamingProgressTracker ProgressTracker
            => NoOpStreamingFindReferencesProgress.Instance.ProgressTracker;
    }
}
#endif