File: EditAndContinue\DocumentAnalysisResults.cs
Web Access
Project: src\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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Contracts.EditAndContinue;
 
namespace Microsoft.CodeAnalysis.EditAndContinue;
 
internal sealed class DocumentAnalysisResults
{
    /// <summary>
    /// The state of the document the results are calculated for.
    /// </summary>
    public DocumentId DocumentId { get; }
 
    /// <summary>
    /// Document file path for logging.
    /// </summary>
    public string FilePath;
 
    /// <summary>
    /// Spans of active statements in the document, or null if the document has syntax errors or has not changed.
    /// Calculated even in presence of rude edits so that the active statements can be rendered in the editor.
    /// </summary>
    public ImmutableArray<ActiveStatement> ActiveStatements { get; }
 
    /// <summary>
    /// Diagnostics for rude edits in the document, or empty if the document is unchanged or has syntax errors.
    /// Includes errors, should block the update and warnings, which should not.
    /// </summary>
    public ImmutableArray<RudeEditDiagnostic> RudeEdits { get; }
 
    /// <summary>
    /// The first syntax error, or null if the document does not have syntax errors reported by the compiler.
    /// </summary>
    public Diagnostic? SyntaxError { get; }
 
    /// <summary>
    /// Edits made in the document, or null if the document is unchanged, has syntax errors or rude edits.
    /// </summary>
    public ImmutableArray<SemanticEditInfo> SemanticEdits { get; }
 
    /// <summary>
    /// Exception regions -- spans of catch and finally handlers that surround the active statements.
    /// 
    /// Null if the document has syntax errors, rude edits or has not changed.
    /// </summary>
    /// <remarks>
    /// Null if there are any rude edit diagnostics.
    /// 
    /// Otherwise, each active statement in <see cref="ActiveStatements"/> has a corresponding slot in <see cref="ExceptionRegions"/>.
    ///
    /// Exception regions for each EH block/clause are marked as |...|.
    ///   try { ... AS ... } |catch { } finally { }|
    ///   try { } |catch { ... AS ... }| finally { }
    ///   try { } catch { } |finally { ... AS ... }|
    /// 
    /// Contains a minimal set of spans that cover the handlers.
    /// For example:
    ///   try { } |finally { try { ... AS ... } catch {  } }|
    ///   try { } |finally { try { } catch { ... AS ... } }|
    ///   try { try { } |finally { ... AS ... }| } |catch { } catch { } finally { }|
    /// </remarks>
    public ImmutableArray<ImmutableArray<SourceFileSpan>> ExceptionRegions { get; }
 
    /// <summary>
    /// Line edits in the document (or mapped documents), or null if the document has syntax errors, rude edits or has not changed.
    /// </summary>
    /// <remarks>
    /// Grouped by file name and updates in each group are ordered by <see cref="SourceLineUpdate.OldLine"/>. 
    /// Each entry in the group applies the delta of <see cref="SourceLineUpdate.NewLine"/> - <see cref="SourceLineUpdate.OldLine"/>
    /// to all lines in range [<see cref="SourceLineUpdate.OldLine"/>, next entry's <see cref="SourceLineUpdate.OldLine"/>).
    /// </remarks>
    public ImmutableArray<SequencePointUpdates> LineEdits { get; }
 
    /// <summary>
    /// Capabilities that are required for the updates made in this document.
    /// <see cref="EditAndContinueCapabilities.None"/> if the document does not have valid changes.
    /// </summary>
    public EditAndContinueCapabilities RequiredCapabilities { get; }
 
    /// <summary>
    /// Time span it took to perform the analysis.
    /// </summary>
    public TimeSpan ElapsedTime { get; }
 
    /// <summary>
    /// Document contains errors that block EnC analysis.
    /// </summary>
    public bool HasSyntaxErrors { get; }
 
    /// <summary>
    /// Document contains changes.
    /// </summary>
    public bool HasChanges { get; }
 
    /// <summary>
    /// True if any of the <see cref="RudeEdits"/> are blocking.
    /// </summary>
    public bool HasBlockingRudeEdits { get; }
 
    public DocumentAnalysisResults(
        DocumentId documentId,
        string filePath,
        ImmutableArray<ActiveStatement> activeStatementsOpt,
        ImmutableArray<RudeEditDiagnostic> rudeEdits,
        Diagnostic? syntaxError,
        ImmutableArray<SemanticEditInfo> semanticEditsOpt,
        ImmutableArray<ImmutableArray<SourceFileSpan>> exceptionRegionsOpt,
        ImmutableArray<SequencePointUpdates> lineEditsOpt,
        EditAndContinueCapabilities requiredCapabilities,
        TimeSpan elapsedTime,
        bool hasChanges,
        bool hasSyntaxErrors,
        bool hasBlockingRudeEdits)
    {
        Debug.Assert(!rudeEdits.IsDefault);
        Debug.Assert(hasBlockingRudeEdits == (!rudeEdits.IsDefault && rudeEdits.HasBlockingRudeEdits()));
 
        if (hasSyntaxErrors || !hasChanges)
        {
            Debug.Assert(activeStatementsOpt.IsDefault);
            Debug.Assert(semanticEditsOpt.IsDefault);
            Debug.Assert(exceptionRegionsOpt.IsDefault);
            Debug.Assert(lineEditsOpt.IsDefault);
            Debug.Assert(syntaxError != null || hasBlockingRudeEdits || !hasChanges);
            Debug.Assert(requiredCapabilities == EditAndContinueCapabilities.None);
        }
        else
        {
            Debug.Assert(!activeStatementsOpt.IsDefault);
            Debug.Assert(syntaxError == null);
 
            if (hasBlockingRudeEdits)
            {
                Debug.Assert(semanticEditsOpt.IsDefault);
                Debug.Assert(exceptionRegionsOpt.IsDefault);
                Debug.Assert(lineEditsOpt.IsDefault);
                Debug.Assert(requiredCapabilities == EditAndContinueCapabilities.None);
            }
            else
            {
                Debug.Assert(!semanticEditsOpt.IsDefault);
                Debug.Assert(!exceptionRegionsOpt.IsDefault);
                Debug.Assert(!lineEditsOpt.IsDefault);
 
                // no duplicate files in line edits:
                Debug.Assert(lineEditsOpt.Select(edit => edit.FileName).Distinct().Count() == lineEditsOpt.Length);
 
                // line updates are sorted:
                Debug.Assert(lineEditsOpt.All(documentLineEdits => documentLineEdits.LineUpdates.IsSorted(Comparer<SourceLineUpdate>.Create(
                    (x, y) => x.OldLine.CompareTo(y.OldLine)))));
 
                Debug.Assert(exceptionRegionsOpt.Length == activeStatementsOpt.Length);
                Debug.Assert(requiredCapabilities != EditAndContinueCapabilities.None);
            }
        }
 
        DocumentId = documentId;
        FilePath = filePath;
        RudeEdits = rudeEdits;
        SyntaxError = syntaxError;
        SemanticEdits = semanticEditsOpt;
        ActiveStatements = activeStatementsOpt;
        ExceptionRegions = exceptionRegionsOpt;
        LineEdits = lineEditsOpt;
        RequiredCapabilities = requiredCapabilities;
        ElapsedTime = elapsedTime;
        HasSyntaxErrors = hasSyntaxErrors;
        HasChanges = hasChanges;
        HasBlockingRudeEdits = hasBlockingRudeEdits;
    }
 
    public bool HasChangesAndErrors
        => HasChanges && (HasSyntaxErrors || HasBlockingRudeEdits);
 
    public bool HasChangesAndSyntaxErrors
        => HasChanges && HasSyntaxErrors;
 
    public bool HasSignificantValidChanges
        => HasChanges && (!SemanticEdits.IsDefaultOrEmpty || !LineEdits.IsDefaultOrEmpty);
 
    /// <summary>
    /// Report errors blocking the document analysis.
    /// </summary>
    public static DocumentAnalysisResults SyntaxErrors(DocumentId documentId, string filePath, ImmutableArray<RudeEditDiagnostic> rudeEdits, Diagnostic? syntaxError, TimeSpan elapsedTime, bool hasChanges)
        => new(
            documentId,
            filePath,
            activeStatementsOpt: default,
            rudeEdits,
            syntaxError,
            semanticEditsOpt: default,
            exceptionRegionsOpt: default,
            lineEditsOpt: default,
            EditAndContinueCapabilities.None,
            elapsedTime,
            hasChanges,
            hasSyntaxErrors: true,
            hasBlockingRudeEdits: !rudeEdits.IsEmpty);
 
    /// <summary>
    /// Report unchanged document results.
    /// </summary>
    public static DocumentAnalysisResults Unchanged(DocumentId documentId, string filePath, TimeSpan elapsedTime)
        => new(
            documentId,
            filePath,
            activeStatementsOpt: default,
            rudeEdits: [],
            syntaxError: null,
            semanticEditsOpt: default,
            exceptionRegionsOpt: default,
            lineEditsOpt: default,
            EditAndContinueCapabilities.None,
            elapsedTime,
            hasChanges: false,
            hasSyntaxErrors: false,
            hasBlockingRudeEdits: false);
}