File: EditAndContinue\EmitSolutionUpdateResults.cs
Web Access
Project: ..\..\..\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.EditAndContinue.Contracts;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.EditAndContinue
{
    internal readonly struct EmitSolutionUpdateResults
    {
        [DataContract]
        internal readonly struct Data
        {
            [DataMember(Order = 0)]
            public readonly ModuleUpdates ModuleUpdates;
 
            [DataMember(Order = 1)]
            public readonly ImmutableArray<DiagnosticData> Diagnostics;
 
            [DataMember(Order = 2)]
            public readonly ImmutableArray<(DocumentId DocumentId, ImmutableArray<RudeEditDiagnostic> Diagnostics)> RudeEdits;
 
            [DataMember(Order = 3)]
            public readonly DiagnosticData? SyntaxError;
 
            public Data(
                ModuleUpdates moduleUpdates,
                ImmutableArray<DiagnosticData> diagnostics,
                ImmutableArray<(DocumentId DocumentId, ImmutableArray<RudeEditDiagnostic> Diagnostics)> rudeEdits,
                DiagnosticData? syntaxError)
            {
                ModuleUpdates = moduleUpdates;
                Diagnostics = diagnostics;
                RudeEdits = rudeEdits;
                SyntaxError = syntaxError;
            }
        }
 
        public static readonly EmitSolutionUpdateResults Empty =
            new(moduleUpdates: new ModuleUpdates(ModuleUpdateStatus.None, ImmutableArray<ModuleUpdate>.Empty),
                diagnostics: ImmutableArray<(ProjectId, ImmutableArray<Diagnostic>)>.Empty,
                documentsWithRudeEdits: ImmutableArray<(DocumentId, ImmutableArray<RudeEditDiagnostic>)>.Empty,
                syntaxError: null);
 
        public readonly ModuleUpdates ModuleUpdates;
        public readonly ImmutableArray<(ProjectId ProjectId, ImmutableArray<Diagnostic> Diagnostics)> Diagnostics;
        public readonly ImmutableArray<(DocumentId DocumentId, ImmutableArray<RudeEditDiagnostic> Diagnostics)> RudeEdits;
        public readonly Diagnostic? SyntaxError;
 
        public EmitSolutionUpdateResults(
            ModuleUpdates moduleUpdates,
            ImmutableArray<(ProjectId ProjectId, ImmutableArray<Diagnostic> Diagnostic)> diagnostics,
            ImmutableArray<(DocumentId DocumentId, ImmutableArray<RudeEditDiagnostic> Diagnostics)> documentsWithRudeEdits,
            Diagnostic? syntaxError)
        {
            ModuleUpdates = moduleUpdates;
            Diagnostics = diagnostics;
            RudeEdits = documentsWithRudeEdits;
            SyntaxError = syntaxError;
        }
 
        public Data Dehydrate(Solution solution)
            => new(ModuleUpdates, GetDiagnosticData(solution), RudeEdits, GetSyntaxErrorData(solution));
 
        public ImmutableArray<DiagnosticData> GetDiagnosticData(Solution solution)
        {
            using var _ = ArrayBuilder<DiagnosticData>.GetInstance(out var result);
 
            foreach (var (projectId, diagnostics) in Diagnostics)
            {
                var project = solution.GetRequiredProject(projectId);
 
                foreach (var diagnostic in diagnostics)
                {
                    var document = solution.GetDocument(diagnostic.Location.SourceTree);
                    var data = (document != null) ? DiagnosticData.Create(diagnostic, document) : DiagnosticData.Create(solution, diagnostic, project);
                    result.Add(data);
                }
            }
 
            return result.ToImmutable();
        }
 
        public DiagnosticData? GetSyntaxErrorData(Solution solution)
        {
            if (SyntaxError == null)
            {
                return null;
            }
 
            Debug.Assert(SyntaxError.Location.SourceTree != null);
            return DiagnosticData.Create(SyntaxError, solution.GetRequiredDocument(SyntaxError.Location.SourceTree));
        }
 
        public async Task<ImmutableArray<Diagnostic>> GetAllDiagnosticsAsync(Solution solution, CancellationToken cancellationToken)
        {
            using var _ = ArrayBuilder<Diagnostic>.GetInstance(out var diagnostics);
 
            // add rude edits:
            foreach (var (documentId, documentRudeEdits) in RudeEdits)
            {
                var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
                Contract.ThrowIfNull(document);
 
                var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                foreach (var documentRudeEdit in documentRudeEdits)
                {
                    diagnostics.Add(documentRudeEdit.ToDiagnostic(tree));
                }
            }
 
            // add emit diagnostics:
            foreach (var (_, projectEmitDiagnostics) in Diagnostics)
            {
                diagnostics.AddRange(projectEmitDiagnostics);
            }
 
            return diagnostics.ToImmutable();
        }
 
        internal static async ValueTask<ImmutableArray<ManagedHotReloadDiagnostic>> GetHotReloadDiagnosticsAsync(
            Solution solution,
            ImmutableArray<DiagnosticData> diagnosticData,
            ImmutableArray<(DocumentId DocumentId, ImmutableArray<RudeEditDiagnostic> Diagnostics)> rudeEdits,
            DiagnosticData? syntaxError,
            ModuleUpdateStatus updateStatus,
            CancellationToken cancellationToken)
        {
            using var _ = ArrayBuilder<ManagedHotReloadDiagnostic>.GetInstance(out var builder);
 
            // Add the first compiler emit error. Do not report warnings - they do not block applying the edit.
            // It's unnecessary to report more then one error since all the diagnostics are already reported in the Error List
            // and this is just messaging to the agent.
 
            foreach (var data in diagnosticData)
            {
                if (data.Severity != DiagnosticSeverity.Error)
                {
                    continue;
                }
 
                var fileSpan = data.DataLocation.MappedFileSpan;
 
                builder.Add(new ManagedHotReloadDiagnostic(
                    data.Id,
                    data.Message ?? FeaturesResources.Unknown_error_occurred,
                    updateStatus == ModuleUpdateStatus.RestartRequired
                        ? ManagedHotReloadDiagnosticSeverity.RestartRequired
                        : ManagedHotReloadDiagnosticSeverity.Error,
                    fileSpan.Path ?? "",
                    fileSpan.Span.ToSourceSpan()));
 
                // only report first error
                break;
            }
 
            if (syntaxError != null)
            {
                Debug.Assert(syntaxError.DataLocation != null);
                Debug.Assert(syntaxError.Message != null);
 
                var fileSpan = syntaxError.DataLocation.MappedFileSpan;
 
                builder.Add(new ManagedHotReloadDiagnostic(
                    syntaxError.Id,
                    syntaxError.Message,
                    ManagedHotReloadDiagnosticSeverity.Error,
                    fileSpan.Path,
                    fileSpan.Span.ToSourceSpan()));
            }
 
            // Report all rude edits.
 
            foreach (var (documentId, diagnostics) in rudeEdits)
            {
                var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
                var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
 
                foreach (var diagnostic in diagnostics)
                {
                    var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(diagnostic.Kind);
 
                    var severity = descriptor.DefaultSeverity switch
                    {
                        DiagnosticSeverity.Error => ManagedHotReloadDiagnosticSeverity.RestartRequired,
                        DiagnosticSeverity.Warning => ManagedHotReloadDiagnosticSeverity.Warning,
                        _ => throw ExceptionUtilities.UnexpectedValue(descriptor.DefaultSeverity)
                    };
 
                    var fileSpan = tree.GetMappedLineSpan(diagnostic.Span, cancellationToken);
 
                    builder.Add(new ManagedHotReloadDiagnostic(
                        descriptor.Id,
                        string.Format(descriptor.MessageFormat.ToString(CultureInfo.CurrentUICulture), diagnostic.Arguments),
                        severity,
                        fileSpan.Path ?? "",
                        fileSpan.Span.ToSourceSpan()));
                }
            }
 
            return builder.ToImmutable();
        }
    }
}