File: Language\Intermediate\IntermediateNode.cs
Web Access
Project: src\roslyn\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Razor.PooledObjects;

namespace Microsoft.AspNetCore.Razor.Language.Intermediate;

[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
public abstract class IntermediateNode
{
    private ImmutableArray<RazorDiagnostic>.Builder? _diagnosticsBuilder;
    private ImmutableArray<RazorDiagnostic>? _diagnostics;

    public ImmutableArray<RazorDiagnostic> Diagnostics
        => _diagnostics ??= _diagnosticsBuilder?.ToImmutable() ?? [];

    public bool HasDiagnostics => _diagnosticsBuilder is { Count: > 0 };

    public SourceSpan? Source { get; set; }

    public bool IsImported { get; set; }

    /// <summary>
    /// True for nodes that Razor codegen synthesizes as compiler plumbing rather than
    /// content derived from user-authored source. Consumers that distinguish "user API
    /// surface" from "generator plumbing" -- such as the decl/impl partial-file split
    /// for components -- use this flag to decide where a node belongs.
    /// </summary>
    internal bool IsSynthesizedHelper { get; init; }

    public abstract IntermediateNodeCollection Children { get; }

    public abstract void Accept(IntermediateNodeVisitor visitor);

    public void AddDiagnostic(RazorDiagnostic diagnostic)
    {
        _diagnosticsBuilder ??= ImmutableArray.CreateBuilder<RazorDiagnostic>();
        _diagnosticsBuilder.Add(diagnostic);
        _diagnostics = null;
    }

    public void AddDiagnosticsFromNode(IntermediateNode node)
    {
        if (node.HasDiagnostics)
        {
            _diagnosticsBuilder ??= ImmutableArray.CreateBuilder<RazorDiagnostic>();
            _diagnosticsBuilder.AddRange(node.Diagnostics);
            _diagnostics = null;
        }
    }

    [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
    [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members")]
    private string Tree
    {
        get
        {
            using var _ = StringBuilderPool.GetPooledObject(out var builder);

            var formatter = new IntermediateNodeFormatter(builder);
            formatter.FormatTree(this);

            return builder.ToString();
        }
    }

    internal string GetDebuggerDisplay()
    {
        using var _ = StringBuilderPool.GetPooledObject(out var builder);

        var formatter = new IntermediateNodeFormatter(builder);
        formatter.FormatNode(this);

        return builder.ToString();
    }

    public virtual void FormatNode(IntermediateNodeFormatter formatter)
    {
    }

    /// <summary>
    ///  Returns a deep copy of this node and its descendants. The node-specific state is produced by
    ///  <see cref="CloneNode"/>; this method copies the common state (source span, imported flag,
    ///  diagnostics) and deep-clones the children onto it. A node with a property that aliases one of
    ///  its <see cref="Children"/> overrides this to re-point that property at the cloned child.
    /// </summary>
    internal virtual IntermediateNode Clone()
    {
        var clone = CloneNode();

        clone.Source = Source;
        clone.IsImported = IsImported;
        clone.AddDiagnosticsFromNode(this);

        foreach (var child in Children)
        {
            clone.Children.Add(child.Clone());
        }

        return clone;
    }

    /// <summary>
    ///  Creates a copy of this node carrying only its own state -- the node-specific fields (including the
    ///  init-only <see cref="IsSynthesizedHelper"/>) and any child nodes held outside <see cref="Children"/>
    ///  (deep-cloned). The common state and the <see cref="Children"/> are copied by <see cref="Clone"/>.
    ///  Overridden by every node kind that can appear in an unresolved tree; the base throws so an
    ///  unexpected kind fails loudly rather than silently producing an incomplete copy.
    /// </summary>
    protected virtual IntermediateNode CloneNode()
        => throw new NotSupportedException($"{GetType().Name} does not support cloning.");
}