<commit_before>﻿// 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.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CodeFixes
{
    /// <summary>
    /// Helper class for "Fix all occurrences" code fix providers.
    /// </summary>
    internal sealed class BatchFixAllProvider : AbstractDefaultFixAllProvider
    {
        public static readonly FixAllProvider Instance = new BatchFixAllProvider();

        private BatchFixAllProvider()
        {
        }

        protected sealed override string GetFixAllTitle(FixAllContext fixAllContext)
            => FixAllContextHelper.GetDefaultFixAllTitle(fixAllContext);

        protected sealed override async Task<Solution?> FixAllContextsAsync(
            FixAllContext originalFixAllContext,
            ImmutableArray<FixAllContext> fixAllContexts)
        {
            var cancellationToken = originalFixAllContext.CancellationToken;
            var progressTracker = originalFixAllContext.GetProgressTracker();
            progressTracker.Description = FixAllContextHelper.GetDefaultFixAllTitle(originalFixAllContext);

            // We have 2*P + 1 pieces of work.  Computing diagnostics and fixes/changes per context, and then one pass
            // applying fixes.
            progressTracker.AddItems(fixAllContexts.Length * 2 + 1);

            // Mapping from document to the cumulative text changes created for that document.
            var docIdToTextMerger = new Dictionary<DocumentId, TextChangeMerger>();

            // Process each context one at a time, allowing us to dump most of the information we computed for each once
            // done with it.  The only information we need to preserve is the data we store in docIdToTextMerger
            foreach (var fixAllContext in fixAllContexts)
            {
                Contract.ThrowIfFalse(fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project);
                await FixSingleContextAsync(docIdToTextMerger, fixAllContext).ConfigureAwait(false);
            }

            // Finally, merge in all text changes into the solution.  We can't do this per-project as we have to have
            // process *all* diagnostics in the solution to find the changes made to all documents.
            progressTracker.Description = WorkspaceExtensionsResources.Applying_fix_all;
            using (progressTracker.ItemCompletedScope())
            {
                if (docIdToTextMerger.Count == 0)
                    return null;

                var currentSolution = originalFixAllContext.Solution;
                foreach (var group in docIdToTextMerger.GroupBy(kvp => kvp.Key.ProjectId))
                    currentSolution = await ApplyChangesAsync(currentSolution, group.SelectAsArray(kvp => (kvp.Key, kvp.Value)), cancellationToken).ConfigureAwait(false);

                return currentSolution;
            }
        }

        private static async Task FixSingleContextAsync(Dictionary<DocumentId, TextChangeMerger> docIdToTextMerger, FixAllContext fixAllContext)
        {
            // First, determine the diagnostics to fix for that context.
            var documentToDiagnostics = await DetermineDiagnosticsAsync(fixAllContext).ConfigureAwait(false);

            // Second, process all those diagnostics, merging the cumulative set of text changes per document into docIdToTextMerger.
            await AddDocumentChangesAsync(fixAllContext, docIdToTextMerger, documentToDiagnostics).ConfigureAwait(false);
        }

        private static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> DetermineDiagnosticsAsync(FixAllContext fixAllContext)
        {
            var progressTracker = fixAllContext.GetProgressTracker();
            using var _ = progressTracker.ItemCompletedScope();

            var project = fixAllContext.Project;
            progressTracker.Description = string.Format(WorkspaceExtensionsResources._0_Computing_diagnostics, project.Name);

            var documentToDiagnostics = await fixAllContext.GetDocumentDiagnosticsToFixAsync().ConfigureAwait(false);

            var filtered = documentToDiagnostics.Where(kvp =>
            {
                if (kvp.Key.Project != project)
                    return false;

                if (fixAllContext.Document != null && fixAllContext.Document != kvp.Key)
                    return false;

                return true;
            });

            return filtered.ToImmutableDictionary();
        }

        private static async Task AddDocumentChangesAsync(
            FixAllContext fixAllContext,
            Dictionary<DocumentId, TextChangeMerger> docIdToTextMerger,
            ImmutableDictionary<Document, ImmutableArray<Diagnostic>> documentToDiagnostics)
        {
            // First, order the diagnostics so we process them in a consistent manner and get the same results given the
            // same input solution.
            var orderedDiagnostics = documentToDiagnostics.SelectMany(kvp => kvp.Value)
                                                          .Where(d => d.Location.IsInSource)
                                                          .OrderBy(d => d.Location.SourceTree!.FilePath)
                                                          .ThenBy(d => d.Location.SourceSpan.Start)
                                                          .ToImmutableArray();

            // Now determine all the document changes caused from these diagnostics.
            var allChangedDocumentsInDiagnosticsOrder =
                await GetAllChangedDocumentsInDiagnosticsOrderAsync(fixAllContext, orderedDiagnostics).ConfigureAwait(false);

            // Finally, take all the changes made to each document and merge them together into docIdToTextMerger to
            // keep track of the total set of changes to any particular document.
            await MergeTextChangesAsync(fixAllContext, allChangedDocumentsInDiagnosticsOrder, docIdToTextMerger).ConfigureAwait(false);
        }

        /// <summary>
        /// Returns all the changed documents produced by fixing the list of provided <paramref
        /// name="orderedDiagnostics"/>.  The documents will be returned such that fixed documents for a later
        /// diagnostic will appear later than those for an earlier diagnostic.
        /// </summary>
        private static async Task<ImmutableArray<Document>> GetAllChangedDocumentsInDiagnosticsOrderAsync(
            FixAllContext fixAllContext, ImmutableArray<Diagnostic> orderedDiagnostics)
        {<commit_msg>Item scope<commit_after>﻿// 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.CodeActions;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CodeFixes
{
    /// <summary>
    /// Helper class for "Fix all occurrences" code fix providers.
    /// </summary>
    internal sealed class BatchFixAllProvider : AbstractDefaultFixAllProvider
    {
        public static readonly FixAllProvider Instance = new BatchFixAllProvider();

        private BatchFixAllProvider()
        {
        }

        protected sealed override string GetFixAllTitle(FixAllContext fixAllContext)
            => FixAllContextHelper.GetDefaultFixAllTitle(fixAllContext);

        private static string GetName(FixAllContext fixAllContext)
            => fixAllContext.Document?.Name ?? fixAllContext.Project.Name;

        protected sealed override async Task<Solution?> FixAllContextsAsync(
            FixAllContext originalFixAllContext,
            ImmutableArray<FixAllContext> fixAllContexts)
        {
            var cancellationToken = originalFixAllContext.CancellationToken;
            var progressTracker = originalFixAllContext.GetProgressTracker();
            progressTracker.Description = FixAllContextHelper.GetDefaultFixAllTitle(originalFixAllContext);

            // We have 2*P + 1 pieces of work.  Computing diagnostics and fixes/changes per context, and then one pass
            // applying fixes.
            progressTracker.AddItems(fixAllContexts.Length * 2 + 1);

            // Mapping from document to the cumulative text changes created for that document.
            var docIdToTextMerger = new Dictionary<DocumentId, TextChangeMerger>();

            // Process each context one at a time, allowing us to dump most of the information we computed for each once
            // done with it.  The only information we need to preserve is the data we store in docIdToTextMerger
            foreach (var fixAllContext in fixAllContexts)
            {
                Contract.ThrowIfFalse(fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project);
                await FixSingleContextAsync(fixAllContext, progressTracker, docIdToTextMerger).ConfigureAwait(false);
            }

            // Finally, merge in all text changes into the solution.  We can't do this per-project as we have to have
            // process *all* diagnostics in the solution to find the changes made to all documents.
            using (progressTracker.ItemCompletedScope(WorkspaceExtensionsResources.Applying_fix_all))
            {
                if (docIdToTextMerger.Count == 0)
                    return null;

                var currentSolution = originalFixAllContext.Solution;
                foreach (var group in docIdToTextMerger.GroupBy(kvp => kvp.Key.ProjectId))
                    currentSolution = await ApplyChangesAsync(currentSolution, group.SelectAsArray(kvp => (kvp.Key, kvp.Value)), cancellationToken).ConfigureAwait(false);

                return currentSolution;
            }
        }

        private static async Task FixSingleContextAsync(
            FixAllContext fixAllContext, IProgressTracker progressTracker, Dictionary<DocumentId, TextChangeMerger> docIdToTextMerger)
        {
            // First, determine the diagnostics to fix for that context.
            var documentToDiagnostics = await DetermineDiagnosticsAsync(fixAllContext, progressTracker).ConfigureAwait(false);

            // Second, process all those diagnostics, merging the cumulative set of text changes per document into docIdToTextMerger.
            await AddDocumentChangesAsync(fixAllContext, progressTracker, docIdToTextMerger, documentToDiagnostics).ConfigureAwait(false);
        }

        private static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> DetermineDiagnosticsAsync(FixAllContext fixAllContext, IProgressTracker progressTracker)
        {
            using var _ = progressTracker.ItemCompletedScope(string.Format(WorkspaceExtensionsResources._0_Computing_diagnostics, GetName(fixAllContext)));

            var documentToDiagnostics = await fixAllContext.GetDocumentDiagnosticsToFixAsync().ConfigureAwait(false);

            var filtered = documentToDiagnostics.Where(kvp =>
            {
                if (kvp.Key.Project != fixAllContext.Project)
                    return false;

                if (fixAllContext.Document != null && fixAllContext.Document != kvp.Key)
                    return false;

                return true;
            });

            return filtered.ToImmutableDictionary();
        }

        private static async Task AddDocumentChangesAsync(
            FixAllContext fixAllContext,
            IProgressTracker progressTracker,
            Dictionary<DocumentId, TextChangeMerger> docIdToTextMerger,
            ImmutableDictionary<Document, ImmutableArray<Diagnostic>> documentToDiagnostics)
        {
            // First, order the diagnostics so we process them in a consistent manner and get the same results given the
            // same input solution.
            var orderedDiagnostics = documentToDiagnostics.SelectMany(kvp => kvp.Value)
                                                          .Where(d => d.Location.IsInSource)
                                                          .OrderBy(d => d.Location.SourceTree!.FilePath)
                                                          .ThenBy(d => d.Location.SourceSpan.Start)
                                                          .ToImmutableArray();

            using var _ = progressTracker.ItemCompletedScope(string.Format(WorkspaceExtensionsResources._0_Computing_fixes_for_1_diagnostics, GetName(fixAllContext), orderedDiagnostics.Length));

            // Now determine all the document changes caused from these diagnostics.
            var allChangedDocumentsInDiagnosticsOrder =
                await GetAllChangedDocumentsInDiagnosticsOrderAsync(fixAllContext, orderedDiagnostics).ConfigureAwait(false);

            // Finally, take all the changes made to each document and merge them together into docIdToTextMerger to
            // keep track of the total set of changes to any particular document.
            await MergeTextChangesAsync(fixAllContext, allChangedDocumentsInDiagnosticsOrder, docIdToTextMerger).ConfigureAwait(false);
        }

        /// <summary>
        /// Returns all the changed documents produced by fixing the list of provided <paramref
        /// name="orderedDiagnostics"/>.  The documents will be returned such that fixed documents for a later
        /// diagnostic will appear later than those for an earlier diagnostic.
        /// </summary>
        private static async Task<ImmutableArray<Document>> GetAllChangedDocumentsInDiagnosticsOrderAsync(
            FixAllContext fixAllContext, ImmutableArray<Diagnostic> orderedDiagnostics)
        {
{'fraction_non_alphanumeric': 0.06312223451327434, 'fraction_numerical': 0.0009679203539823009, 'mean_word_length': 2.5902208984859767, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 56, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7663399', 'n_tokens_mistral': 3299, 'n_tokens_neox': 3225, 'n_words': 981}