// 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.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeFixes.MatchFolderAndNamespace; /// <summary> /// Custom fix all provider for namespace sync. Does fix all on per document level. Since /// multiple documents may be updated when changing a single namespace, it happens /// on a sequential level instead of batch fixing and merging the changes. This prevents /// collisions that the batch fixer won't handle correctly but is slower. /// </summary> internal abstract partial class AbstractChangeNamespaceToMatchFolderCodeFixProvider { private sealed class CustomFixAllProvider : FixAllProvider { public static readonly CustomFixAllProvider Instance = new(); public override async Task<CodeAction?> GetFixAsync(FixAllContext fixAllContext) { var diagnostics = fixAllContext.Scope switch { FixAllScope.Document when fixAllContext.Document is not null => await fixAllContext.GetDocumentDiagnosticsAsync(fixAllContext.Document).ConfigureAwait(false), FixAllScope.Project => await fixAllContext.GetAllDiagnosticsAsync(fixAllContext.Project).ConfigureAwait(false), FixAllScope.Solution => await GetSolutionDiagnosticsAsync(fixAllContext).ConfigureAwait(false), _ => default }; if (diagnostics.IsDefaultOrEmpty) return null; var title = fixAllContext.GetDefaultFixAllTitle(); return CodeAction.Create( title, cancellationToken => FixAllByDocumentAsync( fixAllContext.Project.Solution, diagnostics, fixAllContext.Progress, cancellationToken), title); static async Task<ImmutableArray<Diagnostic>> GetSolutionDiagnosticsAsync(FixAllContext fixAllContext) { var diagnostics = ImmutableArray.CreateBuilder<Diagnostic>(); foreach (var project in fixAllContext.Solution.Projects) { var projectDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false); diagnostics.AddRange(projectDiagnostics); } return diagnostics.ToImmutableAndClear(); } } private static async Task<Solution> FixAllByDocumentAsync( Solution solution, ImmutableArray<Diagnostic> diagnostics, IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken) { // Use documentId instead of tree here because the // FixAsync call can modify more than one document per call. The // important thing is that the fix works on fixing the namespaces in a single document, // but references in other documents will be updated to be correct. Id will remain // across this mutation, but lookup via SyntaxTree directly will not work because // the tree won't be the same. var documentIdToDiagnosticsMap = diagnostics .GroupBy(diagnostic => diagnostic.Location.SourceTree) .Where(group => group.Key is not null) .SelectAsArray(group => (id: solution.GetRequiredDocument(group.Key!).Id, diagnostics: group.ToImmutableArray())); var newSolution = solution; progressTracker.AddItems(documentIdToDiagnosticsMap.Length); foreach (var (documentId, diagnosticsInTree) in documentIdToDiagnosticsMap) { var document = newSolution.GetRequiredDocument(documentId); using var _ = progressTracker.ItemCompletedScope(document.Name); newSolution = await FixAllInDocumentAsync(document, diagnosticsInTree, cancellationToken).ConfigureAwait(false); } return newSolution; } } } |