File: CodeFixes\FixAllOccurrences\FixAllContextHelper.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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixesAndRefactorings;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CodeFixes;
 
internal static partial class FixAllContextHelper
{
    public static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(
        FixAllContext fixAllContext)
    {
        var cancellationToken = fixAllContext.CancellationToken;
 
        var allDiagnostics = ImmutableArray<Diagnostic>.Empty;
 
        var document = fixAllContext.Document;
        var project = fixAllContext.Project;
 
        var progressTracker = fixAllContext.Progress;
 
        switch (fixAllContext.Scope)
        {
            case FixAllScope.Document:
                // Note: We avoid fixing diagnostics in generated code.
                if (document != null && !await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false))
                {
                    var documentDiagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
                    return ImmutableDictionary<Document, ImmutableArray<Diagnostic>>.Empty.SetItem(document, documentDiagnostics);
                }
 
                break;
 
            case FixAllScope.ContainingMember or FixAllScope.ContainingType:
                // Note: We avoid fixing diagnostics in generated code.
                if (document != null && !await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false))
                {
                    var diagnosticSpan = fixAllContext.State.DiagnosticSpan;
                    if (diagnosticSpan.HasValue &&
                        document.GetLanguageService<IFixAllSpanMappingService>() is { } spanMappingService)
                    {
                        var documentsAndSpans = await spanMappingService.GetFixAllSpansAsync(document,
                            diagnosticSpan.Value, fixAllContext.Scope, fixAllContext.CancellationToken).ConfigureAwait(false);
                        return await GetSpanDiagnosticsAsync(fixAllContext, documentsAndSpans).ConfigureAwait(false);
                    }
                }
 
                break;
 
            case FixAllScope.Project:
                allDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false);
                break;
 
            case FixAllScope.Solution:
                {
                    var projectsToFix = project.Solution.Projects
                        .Where(p => p.Language == project.Language)
                        .ToImmutableArray();
 
                    // Update the progress dialog with the count of projects to actually fix. We'll update the progress
                    // bar as we get all the documents in AddDocumentDiagnosticsAsync.
 
                    progressTracker.AddItems(projectsToFix.Length);
 
                    allDiagnostics = await ProducerConsumer<ImmutableArray<Diagnostic>>.RunParallelAsync(
                        source: projectsToFix,
                        produceItems: static async (projectToFix, callback, args, cancellationToken) =>
                        {
                            var (fixAllContext, progressTracker) = args;
                            using var _ = progressTracker.ItemCompletedScope();
                            callback(await fixAllContext.GetAllDiagnosticsAsync(projectToFix).ConfigureAwait(false));
                        },
                        consumeItems: static async (results, _1, cancellationToken) =>
                        {
                            using var _2 = ArrayBuilder<Diagnostic>.GetInstance(out var builder);
 
                            await foreach (var diagnostics in results)
                                builder.AddRange(diagnostics);
 
                            return builder.ToImmutableAndClear();
                        },
                        args: (fixAllContext, progressTracker),
                        cancellationToken).ConfigureAwait(false);
                }
 
                break;
        }
 
        if (allDiagnostics.IsEmpty)
        {
            return ImmutableDictionary<Document, ImmutableArray<Diagnostic>>.Empty;
        }
 
        return await GetDocumentDiagnosticsToFixAsync(
            fixAllContext.Solution, allDiagnostics, fixAllContext.CancellationToken).ConfigureAwait(false);
 
        static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetSpanDiagnosticsAsync(
            FixAllContext fixAllContext,
            IEnumerable<KeyValuePair<Document, ImmutableArray<TextSpan>>> documentsAndSpans)
        {
            var builder = PooledDictionary<Document, ArrayBuilder<Diagnostic>>.GetInstance();
            foreach (var (document, spans) in documentsAndSpans)
            {
                foreach (var span in spans)
                {
                    var documentDiagnostics = await fixAllContext.GetDocumentSpanDiagnosticsAsync(document, span).ConfigureAwait(false);
                    builder.MultiAddRange(document, documentDiagnostics);
                }
            }
 
            return builder.ToImmutableMultiDictionaryAndFree();
        }
    }
 
    private static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(
        Solution solution,
        ImmutableArray<Diagnostic> diagnostics,
        CancellationToken cancellationToken)
    {
        var builder = ImmutableDictionary.CreateBuilder<Document, ImmutableArray<Diagnostic>>();
 
        // NOTE: We use 'GetTextDocumentForLocation' extension to ensure we also handle external location diagnostics in non-C#/VB languages.
        foreach (var (textDocument, diagnosticsForDocument) in diagnostics.GroupBy(d => solution.GetTextDocumentForLocation(d.Location)))
        {
            if (textDocument is not Document document)
                continue;
 
            cancellationToken.ThrowIfCancellationRequested();
            if (!await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false))
            {
                builder.Add(document, diagnosticsForDocument.ToImmutableArray());
            }
        }
 
        return builder.ToImmutable();
    }
}