File: MetaAnalyzers\Fixers\AnalyzerReleaseTrackingFix.FixAllProvider.cs
Web Access
Project: src\src\RoslynAnalyzers\Microsoft.CodeAnalysis.Analyzers\Core\Microsoft.CodeAnalysis.Analyzers.csproj (Microsoft.CodeAnalysis.Analyzers)
// 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;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.ReleaseTracking;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers.Fixers
{
    public sealed partial class AnalyzerReleaseTrackingFix
    {
        private sealed class ReleaseTrackingFixAllProvider : FixAllProvider
        {
            public static readonly FixAllProvider Instance = new ReleaseTrackingFixAllProvider();
            public ReleaseTrackingFixAllProvider() { }
            public override async Task<CodeAction?> GetFixAsync(FixAllContext fixAllContext)
            {
                var diagnosticsToFix = new List<KeyValuePair<Project, ImmutableArray<Diagnostic>>>();
                switch (fixAllContext.Scope)
                {
                    case FixAllScope.Document:
                        {
                            ImmutableArray<Diagnostic> diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(fixAllContext.Document!).ConfigureAwait(false);
                            diagnosticsToFix.Add(new KeyValuePair<Project, ImmutableArray<Diagnostic>>(fixAllContext.Project, diagnostics));
                            break;
                        }
 
                    case FixAllScope.Project:
                        {
                            Project project = fixAllContext.Project;
                            ImmutableArray<Diagnostic> diagnostics = await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false);
                            diagnosticsToFix.Add(new KeyValuePair<Project, ImmutableArray<Diagnostic>>(fixAllContext.Project, diagnostics));
                            break;
                        }
 
                    case FixAllScope.Solution:
                        {
                            foreach (Project project in fixAllContext.Solution.Projects)
                            {
                                ImmutableArray<Diagnostic> diagnostics = await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false);
                                diagnosticsToFix.Add(new KeyValuePair<Project, ImmutableArray<Diagnostic>>(project, diagnostics));
                            }
 
                            break;
                        }
 
                    case FixAllScope.Custom:
                        return null;
 
                    default:
                        Debug.Fail($"Unknown FixAllScope '{fixAllContext.Scope}'");
                        return null;
                }
 
                if (fixAllContext.CodeActionEquivalenceKey == CodeAnalysisDiagnosticsResources.EnableAnalyzerReleaseTrackingRuleTitle)
                {
                    var projectIds = diagnosticsToFix.Select(d => d.Key.Id).ToImmutableArray();
                    return new FixAllAddAdditionalDocumentsAction(projectIds, fixAllContext.Solution);
                }
 
                return new FixAllAdditionalDocumentChangeAction(fixAllContext.Scope, fixAllContext.Solution, diagnosticsToFix, fixAllContext.CodeActionEquivalenceKey);
            }
 
            private sealed class FixAllAdditionalDocumentChangeAction : CodeAction
            {
                private readonly List<KeyValuePair<Project, ImmutableArray<Diagnostic>>> _diagnosticsToFix;
                private readonly Solution _solution;
 
                public FixAllAdditionalDocumentChangeAction(FixAllScope fixAllScope, Solution solution, List<KeyValuePair<Project, ImmutableArray<Diagnostic>>> diagnosticsToFix, string? equivalenceKey)
                {
                    this.Title = fixAllScope.ToString();
                    _solution = solution;
                    _diagnosticsToFix = diagnosticsToFix;
                    this.EquivalenceKey = equivalenceKey;
                }
 
                public override string Title { get; }
                public override string? EquivalenceKey { get; }
 
                protected override async Task<Solution?> GetChangedSolutionAsync(CancellationToken cancellationToken)
                {
                    var updatedUnshippedText = new List<(DocumentId, SourceText)>();
 
                    foreach (KeyValuePair<Project, ImmutableArray<Diagnostic>> pair in _diagnosticsToFix)
                    {
                        Project project = pair.Key;
                        ImmutableArray<Diagnostic> diagnostics = pair.Value;
 
                        TextDocument? unshippedDocument = project.AdditionalDocuments.FirstOrDefault(a => a.Name == ReleaseTrackingHelper.UnshippedFileName);
                        if (unshippedDocument == null || diagnostics.IsEmpty)
                        {
                            continue;
                        }
 
                        if (EquivalenceKey == CodeAnalysisDiagnosticsResources.AddEntryForDiagnosticIdInAnalyzerReleaseCodeFixTitle)
                        {
                            var newText = await AddEntriesToUnshippedFileForDiagnosticsAsync(unshippedDocument, diagnostics, cancellationToken).ConfigureAwait(false);
                            updatedUnshippedText.Add((unshippedDocument.Id, newText));
                        }
                        else if (EquivalenceKey == CodeAnalysisDiagnosticsResources.UpdateEntryForDiagnosticIdInAnalyzerReleaseCodeFixTitle)
                        {
                            var newText = await UpdateEntriesInUnshippedFileForDiagnosticsAsync(unshippedDocument, diagnostics, cancellationToken).ConfigureAwait(false);
                            updatedUnshippedText.Add((unshippedDocument.Id, newText));
                        }
                        else
                        {
                            throw new NotImplementedException();
                        }
                    }
 
                    Solution newSolution = _solution;
                    foreach (var (unshippedDocumentId, newText) in updatedUnshippedText)
                    {
                        newSolution = newSolution.WithAdditionalDocumentText(unshippedDocumentId, newText);
                    }
 
                    return newSolution;
                }
 
                private static async Task<SourceText> AddEntriesToUnshippedFileForDiagnosticsAsync(TextDocument unshippedDataDocument, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
                {
                    var entriesToAdd = new SortedSet<string>();
                    foreach (var diagnostic in diagnostics)
                    {
                        if (IsAddEntryToUnshippedFileDiagnostic(diagnostic, out var entryToAdd))
                        {
                            entriesToAdd.Add(entryToAdd);
                        }
                    }
 
                    return await AddEntriesToUnshippedFileAsync(unshippedDataDocument, entriesToAdd, cancellationToken).ConfigureAwait(false);
                }
 
                private static async Task<SourceText> UpdateEntriesInUnshippedFileForDiagnosticsAsync(TextDocument unshippedDataDocument, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
                {
                    var entriesToUpdate = new Dictionary<string, string>();
                    foreach (var diagnostic in diagnostics)
                    {
                        if (IsUpdateEntryToUnshippedFileDiagnostic(diagnostic, out var ruleId, out var entryToUpdate) &&
                            !entriesToUpdate.ContainsKey(ruleId))
                        {
                            entriesToUpdate.Add(ruleId, entryToUpdate);
                        }
                    }
 
                    return await UpdateEntriesInUnshippedFileAsync(unshippedDataDocument, entriesToUpdate, cancellationToken).ConfigureAwait(false);
                }
            }
 
            private sealed class FixAllAddAdditionalDocumentsAction : CodeAction
            {
                private readonly ImmutableArray<ProjectId> _projectIds;
                private readonly Solution _solution;
 
                public FixAllAddAdditionalDocumentsAction(ImmutableArray<ProjectId> projectIds, Solution solution)
                {
                    _projectIds = projectIds;
                    _solution = solution;
                }
 
                public override string Title => CodeAnalysisDiagnosticsResources.EnableAnalyzerReleaseTrackingRuleTitle;
                public override string EquivalenceKey => CodeAnalysisDiagnosticsResources.EnableAnalyzerReleaseTrackingRuleTitle;
 
                protected override async Task<Solution?> GetChangedSolutionAsync(CancellationToken cancellationToken)
                {
                    var newSolution = _solution;
                    foreach (var projectId in _projectIds)
                    {
                        newSolution = await AddAnalyzerReleaseTrackingFilesAsync(newSolution.GetProject(projectId)!).ConfigureAwait(false);
                    }
 
                    return newSolution;
                }
            }
        }
    }
}