// 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;
}
}
}
}
}