// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.GeneratedCodeRecognition; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeFixes { /// /// Context for "Fix all occurrences" code fixes provided by an . /// public partial class FixAllContext { /// /// Diagnostic provider to fetch document/project diagnostics to fix in a . /// public abstract class DiagnosticProvider { internal virtual bool IsFixMultiple => false; /// /// Gets all the diagnostics to fix in the given document in a . /// public abstract Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken); /// /// Gets all the project-level diagnostics to fix, i.e. diagnostics with no source location, in the given project in a . /// public abstract Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken); /// /// Gets all the diagnostics to fix in the given project in a . /// This includes both document-level diagnostics for all documents in the given project and project-level diagnostics, i.e. diagnostics with no source location, in the given project. /// public abstract Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken); internal virtual async Task>> GetDocumentDiagnosticsToFixAsync( FixAllContext fixAllContext) { using (Logger.LogBlock(FunctionId.CodeFixes_FixAllOccurrencesComputation_Diagnostics, fixAllContext.CancellationToken)) { var allDiagnostics = ImmutableArray.Empty; var projectsToFix = ImmutableArray.Empty; var document = fixAllContext.Document; var project = fixAllContext.Project; switch (fixAllContext.Scope) { case FixAllScope.Document: if (document != null && !document.IsGeneratedCode()) { var documentDiagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false); var kvp = SpecializedCollections.SingletonEnumerable(KeyValuePair.Create(document, documentDiagnostics)); return ImmutableDictionary.CreateRange(kvp); } break; case FixAllScope.Project: projectsToFix = ImmutableArray.Create(project); allDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false); break; case FixAllScope.Solution: projectsToFix = project.Solution.Projects .Where(p => p.Language == project.Language) .ToImmutableArray(); var progressTracker = fixAllContext.ProgressTracker; progressTracker.AddItems(projectsToFix.Length); var diagnostics = new ConcurrentBag(); var tasks = new Task[projectsToFix.Length]; for (int i = 0; i < projectsToFix.Length; i++) { fixAllContext.CancellationToken.ThrowIfCancellationRequested(); var projectToFix = projectsToFix[i]; tasks[i] = Task.Run(async () => { var projectDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(projectToFix).ConfigureAwait(false); foreach (var diagnostic in projectDiagnostics) { fixAllContext.CancellationToken.ThrowIfCancellationRequested(); diagnostics.Add(diagnostic); } progressTracker.ItemCompleted(); }, fixAllContext.CancellationToken); } await Task.WhenAll(tasks).ConfigureAwait(false); allDiagnostics = allDiagnostics.AddRange(diagnostics); break; } if (allDiagnostics.IsEmpty) { return ImmutableDictionary>.Empty; } return await GetDocumentDiagnosticsToFixAsync(allDiagnostics, projectsToFix, fixAllContext.CancellationToken).ConfigureAwait(false); } } private async static Task>> GetDocumentDiagnosticsToFixAsync( ImmutableArray diagnostics, ImmutableArray projects, CancellationToken cancellationToken) { var treeToDocumentMap = await GetTreeToDocumentMapAsync(projects, cancellationToken).ConfigureAwait(false); var builder = ImmutableDictionary.CreateBuilder>(); foreach (var documentAndDiagnostics in diagnostics.GroupBy(d => GetReportedDocument(d, treeToDocumentMap))) { cancellationToken.ThrowIfCancellationRequested(); var document = documentAndDiagnostics.Key; if (!document.IsGeneratedCode()) { var diagnosticsForDocument = documentAndDiagnostics.ToImmutableArray(); builder.Add(document, diagnosticsForDocument); } } return builder.ToImmutable(); } private static async Task> GetTreeToDocumentMapAsync(ImmutableArray projects, CancellationToken cancellationToken) { var builder = ImmutableDictionary.CreateBuilder(); foreach (var project in projects) { cancellationToken.ThrowIfCancellationRequested(); foreach (var document in project.Documents) { cancellationToken.ThrowIfCancellationRequested(); var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); builder.Add(tree, document); } } return builder.ToImmutable(); } private static Document GetReportedDocument(Diagnostic diagnostic, ImmutableDictionary treeToDocumentsMap) { var tree = diagnostic.Location.SourceTree; if (tree != null) { Document document; if (treeToDocumentsMap.TryGetValue(tree, out document)) { return document; } } return null; } internal virtual async Task>> GetProjectDiagnosticsToFixAsync( FixAllContext fixAllContext) { using (Logger.LogBlock(FunctionId.CodeFixes_FixAllOccurrencesComputation_Diagnostics, fixAllContext.CancellationToken)) { var project = fixAllContext.Project; if (project != null) { switch (fixAllContext.Scope) { case FixAllScope.Project: var diagnostics = await fixAllContext.GetProjectDiagnosticsAsync(project).ConfigureAwait(false); var kvp = SpecializedCollections.SingletonEnumerable(KeyValuePair.Create(project, diagnostics)); return ImmutableDictionary.CreateRange(kvp); case FixAllScope.Solution: var projectsAndDiagnostics = ImmutableDictionary.CreateBuilder>(); var tasks = project.Solution.Projects.Select(async p => new { Project = p, Diagnostics = await fixAllContext.GetProjectDiagnosticsAsync(p).ConfigureAwait(false) }).ToArray(); await Task.WhenAll(tasks).ConfigureAwait(false); foreach (var task in tasks) { var projectAndDiagnostics = await task.ConfigureAwait(false); if (projectAndDiagnostics.Diagnostics.Any()) { projectsAndDiagnostics[projectAndDiagnostics.Project] = projectAndDiagnostics.Diagnostics; } } return projectsAndDiagnostics.ToImmutable(); } } return ImmutableDictionary>.Empty; } } } } }