File: FindSymbols\StreamingProgressCollector.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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FindSymbols;
 
/// <summary>
/// Collects all the <see cref="ISymbol"/> definitions and <see cref="ReferenceLocation"/> 
/// references that are reported independently and packages them up into the final list
/// of <see cref="ReferencedSymbol" />.  This is used by the old non-streaming Find-References
/// APIs to return all the results at the end of the operation, as opposed to broadcasting
/// the results as they are found.
/// </summary>
internal sealed class StreamingProgressCollector(
    IStreamingFindReferencesProgress underlyingProgress) : IStreamingFindReferencesProgress
{
    private readonly object _gate = new();
    private readonly Dictionary<ISymbol, List<ReferenceLocation>> _symbolToLocations = [];
 
    public IStreamingProgressTracker ProgressTracker => underlyingProgress.ProgressTracker;
 
    public StreamingProgressCollector()
        : this(NoOpStreamingFindReferencesProgress.Instance)
    {
    }
 
    public ImmutableArray<ReferencedSymbol> GetReferencedSymbols()
    {
        lock (_gate)
        {
            var result = new FixedSizeArrayBuilder<ReferencedSymbol>(_symbolToLocations.Count);
            foreach (var (symbol, locations) in _symbolToLocations)
                result.Add(new ReferencedSymbol(symbol, [.. locations]));
 
            return result.MoveToImmutable();
        }
    }
 
    public ValueTask OnStartedAsync(CancellationToken cancellationToken) => underlyingProgress.OnStartedAsync(cancellationToken);
    public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => underlyingProgress.OnCompletedAsync(cancellationToken);
 
    public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken)
    {
        try
        {
            lock (_gate)
            {
                foreach (var definition in group.Symbols)
                    _symbolToLocations[definition] = [];
            }
 
            return underlyingProgress.OnDefinitionFoundAsync(group, cancellationToken);
        }
        catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, cancellationToken))
        {
            throw ExceptionUtilities.Unreachable();
        }
    }
 
    public async ValueTask OnReferencesFoundAsync(
        ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references, CancellationToken cancellationToken)
    {
        lock (_gate)
        {
            foreach (var tuple in references)
                _symbolToLocations[tuple.symbol].Add(tuple.location);
        }
 
        await underlyingProgress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false);
    }
}