File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\EmbeddedLanguages\Common\EmbeddedSyntaxNode.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.Common;
 
/// <summary>
/// Root of the embedded language syntax hierarchy.  EmbeddedSyntaxNodes are very similar to 
/// Roslyn Red-Nodes in concept, though there are differences for ease of implementation.
/// 
/// Similarities:
/// 1. Fully representative of the original source.  All source VirtualChars are contained
///    in the Regex nodes.
/// 2. Specific types for Nodes, Tokens and Trivia.
/// 3. Uniform ways of deconstructing Nodes (i.e. ChildCount + ChildAt).
/// 
/// Differences:
/// Note: these differences are not required, and can be changed if felt to be valuable.
/// 1. No parent pointers.  These have not been needed yet.
/// 2. No Update methods.  These have not been needed yet.
/// 3. No direct ways to get Positions/Spans of node/token/trivia.  Instead, that information can
///    be acquired from the VirtualChars contained within those constructs.  This does mean that
///    an empty node (for example, an empty RegexSequenceNode) effect has no way to simply ascertain
///    its location.  So far that hasn't been a problem.
/// 4. No null nodes.  Haven't been needed so far, and it keeps things extremely simple.  For 
///    example where Roslyn might have chosen an optional null child, the Regex hierarchy just
///    has multiple nodes.  For example there are distinct nodes to represent the very similar
///    {a}   {a,}    {a,b}    constructs.
/// </summary>
internal abstract class EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode>
    where TSyntaxKind : struct
    where TSyntaxNode : EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode>
{
    public readonly TSyntaxKind Kind;
    private TextSpan? _fullSpan;
 
    protected EmbeddedSyntaxNode(TSyntaxKind kind)
    {
        Debug.Assert((int)(object)kind != 0);
        Kind = kind;
    }
 
    internal abstract int ChildCount { get; }
    internal abstract EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> ChildAt(int index);
 
    public EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> this[int index] => ChildAt(index);
    public EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> this[Index index] => this[index.GetOffset(this.ChildCount)];
 
    public TextSpan GetSpan()
    {
        var start = int.MaxValue;
        var end = 0;
 
        this.GetSpan(ref start, ref end);
 
        return TextSpan.FromBounds(start, end);
    }
 
    public TextSpan? GetFullSpan()
        => _fullSpan ??= ComputeFullSpan();
 
    private TextSpan? ComputeFullSpan()
    {
        var start = ComputeStart();
        var end = ComputeEnd();
        if (start == null || end == null)
            return null;
 
        return TextSpan.FromBounds(start.Value, end.Value);
 
        int? ComputeStart()
        {
            for (int i = 0, n = ChildCount; i < n; i++)
            {
                var child = ChildAt(i);
                var span = child.GetFullSpan();
                if (span != null)
                    return span.Value.Start;
            }
 
            return null;
        }
 
        int? ComputeEnd()
        {
            for (var i = ChildCount - 1; i >= 0; i--)
            {
                var child = ChildAt(i);
                var span = child.GetFullSpan();
                if (span != null)
                    return span.Value.End;
            }
 
            return null;
        }
    }
 
    private void GetSpan(ref int start, ref int end)
    {
        foreach (var child in this)
        {
            if (child.IsNode)
            {
                child.Node.GetSpan(ref start, ref end);
            }
            else
            {
                var token = child.Token;
                if (!token.IsMissing)
                {
                    start = Math.Min(token.VirtualChars[0].Span.Start, start);
                    end = Math.Max(token.VirtualChars.Last().Span.End, end);
                }
            }
        }
    }
 
    public bool Contains(VirtualChar virtualChar)
    {
        foreach (var child in this)
        {
            if (child.IsNode)
            {
                if (child.Node.Contains(virtualChar))
                {
                    return true;
                }
            }
            else
            {
                if (child.Token.VirtualChars.Contains(virtualChar))
                {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    /// <summary>
    /// Returns the string representation of this node, not including its leading and trailing trivia.
    /// </summary>
    /// <returns>The string representation of this node, not including its leading and trailing trivia.</returns>
    /// <remarks>The length of the returned string is always the same as Span.Length</remarks>
    public override string ToString()
    {
        using var _ = PooledStringBuilder.GetInstance(out var sb);
        WriteTo(sb, leading: false, trailing: false);
        return sb.ToString();
    }
 
    /// <summary>
    /// Returns full string representation of this node including its leading and trailing trivia.
    /// </summary>
    /// <returns>The full string representation of this node including its leading and trailing trivia.</returns>
    /// <remarks>The length of the returned string is always the same as FullSpan.Length</remarks>
    public string ToFullString()
    {
        using var _ = PooledStringBuilder.GetInstance(out var sb);
        WriteTo(sb, leading: true, trailing: true);
        return sb.ToString();
    }
 
    /// <summary>
    /// Writes the node to a stringbuilder.
    /// </summary>
    /// <param name="leading">If false, leading trivia will not be added</param>
    /// <param name="trailing">If false, trailing trivia will not be added</param>
    public void WriteTo(StringBuilder sb, bool leading, bool trailing)
    {
        for (var i = 0; i < this.ChildCount; i++)
        {
            var child = this[i];
            var currentLeading = leading || i > 0;
            var curentTrailing = trailing || i < (this.ChildCount - 1);
 
            if (child.IsNode)
            {
                child.Node.WriteTo(sb, currentLeading, curentTrailing);
            }
            else
            {
                child.Token.WriteTo(sb, currentLeading, curentTrailing);
            }
        }
    }
 
    public Enumerator GetEnumerator()
        => new(this);
 
    public struct Enumerator
    {
        private readonly EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode> _node;
        private readonly int _childCount;
        private int _currentIndex;
 
        public Enumerator(EmbeddedSyntaxNode<TSyntaxKind, TSyntaxNode> node)
        {
            _node = node;
            _childCount = _node.ChildCount;
            _currentIndex = -1;
            Current = default;
        }
 
        public EmbeddedSyntaxNodeOrToken<TSyntaxKind, TSyntaxNode> Current { get; private set; }
 
        public bool MoveNext()
        {
            _currentIndex++;
            if (_currentIndex >= _childCount)
            {
                Current = default;
                return false;
            }
 
            Current = _node.ChildAt(_currentIndex);
            return true;
        }
    }
}