FixAllContext.DiagnosticProvider.cs 10.5 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;
C
CyrusNajmabadi 已提交
12
using Microsoft.CodeAnalysis.Shared.Extensions;
13
using Roslyn.Utilities;
14 15 16 17 18 19 20 21 22 23 24 25 26

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
        {
27 28
            internal virtual bool IsFixMultiple => false;

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
            /// <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);
44

45 46
            internal virtual async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(
                FixAllContext fixAllContext)
47
            {
48 49 50 51 52 53 54 55 56 57 58
                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;

                    switch (fixAllContext.Scope)
                    {
                        case FixAllScope.Document:
C
CyrusNajmabadi 已提交
59
                            if (document != null && !document.IsGeneratedCode())
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
                            {
                                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();

78 79 80
                            var progressTracker = fixAllContext.ProgressTracker;
                            progressTracker.AddItems(projectsToFix.Length);

81 82 83 84 85 86 87 88 89 90 91 92 93 94
                            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);
                                    }
95 96

                                    progressTracker.ItemCompleted();
97 98 99 100 101 102 103 104 105 106 107 108 109
                                }, fixAllContext.CancellationToken);
                            }

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

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

C
CyrusNajmabadi 已提交
110
                    return await GetDocumentDiagnosticsToFixAsync(allDiagnostics, projectsToFix, fixAllContext.CancellationToken).ConfigureAwait(false);
111
                }
112 113
            }

114 115 116
            private async static Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(
                ImmutableArray<Diagnostic> diagnostics,
                ImmutableArray<Project> projects,
C
CyrusNajmabadi 已提交
117
                CancellationToken cancellationToken)
118
            {
119 120 121 122 123 124 125
                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;
C
CyrusNajmabadi 已提交
126
                    if (!document.IsGeneratedCode())
127 128 129 130 131 132 133
                    {
                        var diagnosticsForDocument = documentAndDiagnostics.ToImmutableArray();
                        builder.Add(document, diagnosticsForDocument);
                    }
                }

                return builder.ToImmutable();
134
            }
135

136 137 138 139 140 141 142 143 144 145 146 147 148
            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);
                    }
                }
149

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
                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;
            }

168 169
            internal virtual async Task<ImmutableDictionary<Project, ImmutableArray<Diagnostic>>> GetProjectDiagnosticsToFixAsync(
                FixAllContext fixAllContext)
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
            {
                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)
                                {
196 197
                                    var projectAndDiagnostics = await task.ConfigureAwait(false);
                                    if (projectAndDiagnostics.Diagnostics.Any())
198
                                    {
199
                                        projectsAndDiagnostics[projectAndDiagnostics.Project] = projectAndDiagnostics.Diagnostics;
200 201 202 203 204 205 206 207 208 209
                                    }
                                }

                                return projectsAndDiagnostics.ToImmutable();
                        }
                    }

                    return ImmutableDictionary<Project, ImmutableArray<Diagnostic>>.Empty;
                }
            }
210
        }
211
    }
212
}