diff --git a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs index 52f02f4950ab6..841532b9e395e 100644 --- a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs @@ -2,6 +2,7 @@ // 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; @@ -13,6 +14,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.LanguageService; @@ -85,6 +87,19 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) private async Task ProcessResultAsync( Solution originalSolution, Solution currentSolution, Diagnostic diagnostic, CancellationToken cancellationToken) + { + try + { + return await ProcessResultWorkerAsync(originalSolution, currentSolution, diagnostic, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex)) + { + return currentSolution; + } + } + + private async Task ProcessResultWorkerAsync( + Solution originalSolution, Solution currentSolution, Diagnostic diagnostic, CancellationToken cancellationToken) { var (field, property) = await MapDiagnosticToCurrentSolutionAsync( diagnostic, originalSolution, currentSolution, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/UseAutoProperty/UseAutoPropertyFixAllProvider.cs b/src/Features/Core/Portable/UseAutoProperty/UseAutoPropertyFixAllProvider.cs index 333052f575da3..122e14bff5003 100644 --- a/src/Features/Core/Portable/UseAutoProperty/UseAutoPropertyFixAllProvider.cs +++ b/src/Features/Core/Portable/UseAutoProperty/UseAutoPropertyFixAllProvider.cs @@ -2,12 +2,13 @@ // 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.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.UseAutoProperty; @@ -28,29 +29,54 @@ private sealed class UseAutoPropertyFixAllProvider(TProvider provider) : FixAllP private async Task FixAllContextsHelperAsync(FixAllContext originalContext, ImmutableArray contexts) { - var cancellationToken = originalContext.CancellationToken; - // Very slow approach, but the only way we know how to do this correctly and without colliding edits. We // effectively apply each fix one at a time, moving the solution forward each time. As we process each // diagnostic, we attempt to re-recover the field/property it was referring to in the original solution to // the current solution. - var originalSolution = originalContext.Solution; - var currentSolution = originalSolution; + // + // Note: we can process each project in parallel. That's because all changes to a field/prop only impact + // the project they are in, and nothing beyond that. + + // Add a progress item for each context we need to process. + originalContext.Progress.AddItems(contexts.Length); - foreach (var currentContext in contexts) - { - var documentToDiagnostics = await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(currentContext).ConfigureAwait(false); - foreach (var (_, diagnostics) in documentToDiagnostics) + var documentsIdsAndNewRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( + contexts, + produceItems: async static (currentContext, callback, args, cancellationToken) => { - foreach (var diagnostic in diagnostics) + // Within a single context (a project) get all diagnostics, and then handle each diagnostic, one at + // a time, to get the final state of the project. + var (originalContext, provider) = args; + + // Complete a progress item as we finish each project. + using var _ = originalContext.Progress.ItemCompletedScope(); + + var originalSolution = originalContext.Solution; + var currentSolution = originalSolution; + + var documentToDiagnostics = await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(currentContext).ConfigureAwait(false); + foreach (var (_, diagnostics) in documentToDiagnostics) + { + foreach (var diagnostic in diagnostics) + { + currentSolution = await provider.ProcessResultAsync( + originalSolution, currentSolution, diagnostic, cancellationToken).ConfigureAwait(false); + } + } + + // After we finish this context, report the changed documents to the consumeItems callback to process. + // This also lets us release all the forked solution info we created above. + foreach (var changedDocumentId in originalSolution.GetChangedDocuments(currentSolution)) { - currentSolution = await provider.ProcessResultAsync( - originalSolution, currentSolution, diagnostic, cancellationToken).ConfigureAwait(false); + var changedDocument = currentSolution.GetRequiredDocument(changedDocumentId); + var changedRoot = await changedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + callback((changedDocumentId, changedRoot)); } - } - } + }, + args: (originalContext, provider), + originalContext.CancellationToken).ConfigureAwait(false); - return currentSolution; + return originalContext.Solution.WithDocumentSyntaxRoots(documentsIdsAndNewRoots); } } }