FixAllContext.DiagnosticProvider.cs 10.3 KB
Newer Older
1 2
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

3
using System;
C
CyrusNajmabadi 已提交
4 5
using System.Collections.Concurrent;
using System.Collections.Generic;
6
using System.Collections.Immutable;
C
CyrusNajmabadi 已提交
7 8 9
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
10
using Microsoft.CodeAnalysis.GeneratedCodeRecognition;
C
CyrusNajmabadi 已提交
11
using Microsoft.CodeAnalysis.Internal.Log;
12
using Roslyn.Utilities;
13 14 15 16 17 18 19 20 21 22 23 24 25

namespace Microsoft.CodeAnalysis.CodeFixes
{
    /// <summary>
    /// Context for "Fix all occurrences" code fixes provided by an <see cref="FixAllProvider"/>.
    /// </summary>
    public partial class FixAllContext
    {
        /// <summary>
        /// Diagnostic provider to fetch document/project diagnostics to fix in a <see cref="FixAllContext"/>.
        /// </summary>
        public abstract class DiagnosticProvider
        {
26 27
            internal virtual bool IsFixMultiple => false;

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
            /// <summary>
            /// Gets all the diagnostics to fix in the given document in a <see cref="FixAllContext"/>.
            /// </summary>
            public abstract Task<IEnumerable<Diagnostic>> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken);

            /// <summary>
            /// Gets all the project-level diagnostics to fix, i.e. diagnostics with no source location, in the given project in a <see cref="FixAllContext"/>.
            /// </summary>
            public abstract Task<IEnumerable<Diagnostic>> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken);

            /// <summary>
            /// Gets all the diagnostics to fix in the given project in a <see cref="FixAllContext"/>.
            /// 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. 
            /// </summary>
            public abstract Task<IEnumerable<Diagnostic>> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken);
43

44 45
            internal virtual async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(
                FixAllContext fixAllContext)
46
            {
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
                using (Logger.LogBlock(FunctionId.CodeFixes_FixAllOccurrencesComputation_Diagnostics, fixAllContext.CancellationToken))
                {
                    var allDiagnostics = ImmutableArray<Diagnostic>.Empty;
                    var projectsToFix = ImmutableArray<Project>.Empty;

                    var document = fixAllContext.Document;
                    var project = fixAllContext.Project;
                    var generatedCodeServices = project.Solution.Workspace.Services.GetService<IGeneratedCodeRecognitionService>();

                    switch (fixAllContext.Scope)
                    {
                        case FixAllScope.Document:
                            if (document != null && !generatedCodeServices.IsGeneratedCode(document))
                            {
                                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 diagnostics = new ConcurrentBag<Diagnostic>();
                            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);
                                    }
                                }, fixAllContext.CancellationToken);
                            }

                            await Task.WhenAll(tasks).ConfigureAwait(false);
                            allDiagnostics = allDiagnostics.AddRange(diagnostics);
                            break;
                    }

                    if (allDiagnostics.IsEmpty)
                    {
                        return ImmutableDictionary<Document, ImmutableArray<Diagnostic>>.Empty;
                    }

                    return await GetDocumentDiagnosticsToFixAsync(allDiagnostics, projectsToFix, generatedCodeServices.IsGeneratedCode, fixAllContext.CancellationToken).ConfigureAwait(false);
                }
107 108
            }

109 110 111 112
            private async static Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(
                ImmutableArray<Diagnostic> diagnostics,
                ImmutableArray<Project> projects,
                Func<Document, bool> isGeneratedCode, CancellationToken cancellationToken)
113
            {
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
                var treeToDocumentMap = await GetTreeToDocumentMapAsync(projects, cancellationToken).ConfigureAwait(false);

                var builder = ImmutableDictionary.CreateBuilder<Document, ImmutableArray<Diagnostic>>();
                foreach (var documentAndDiagnostics in diagnostics.GroupBy(d => GetReportedDocument(d, treeToDocumentMap)))
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    var document = documentAndDiagnostics.Key;
                    if (!isGeneratedCode(document))
                    {
                        var diagnosticsForDocument = documentAndDiagnostics.ToImmutableArray();
                        builder.Add(document, diagnosticsForDocument);
                    }
                }

                return builder.ToImmutable();
129
            }
130

131 132 133 134 135 136 137 138 139 140 141 142 143
            private static async Task<ImmutableDictionary<SyntaxTree, Document>> GetTreeToDocumentMapAsync(ImmutableArray<Project> projects, CancellationToken cancellationToken)
            {
                var builder = ImmutableDictionary.CreateBuilder<SyntaxTree, Document>();
                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);
                    }
                }
144

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
                return builder.ToImmutable();
            }

            private static Document GetReportedDocument(Diagnostic diagnostic, ImmutableDictionary<SyntaxTree, Document> treeToDocumentsMap)
            {
                var tree = diagnostic.Location.SourceTree;
                if (tree != null)
                {
                    Document document;
                    if (treeToDocumentsMap.TryGetValue(tree, out document))
                    {
                        return document;
                    }
                }

                return null;
            }

163 164
            internal virtual async Task<ImmutableDictionary<Project, ImmutableArray<Diagnostic>>> GetProjectDiagnosticsToFixAsync(
                FixAllContext fixAllContext)
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
            {
                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<Project, ImmutableArray<Diagnostic>>();

                                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)
                                {
                                    if (task.Result.Diagnostics.Any())
                                    {
                                        projectsAndDiagnostics[task.Result.Project] = task.Result.Diagnostics;
                                    }
                                }

                                return projectsAndDiagnostics.ToImmutable();
                        }
                    }

                    return ImmutableDictionary<Project, ImmutableArray<Diagnostic>>.Empty;
                }
            }
204
        }
205
    }
206
}