File: Language\Syntax\SyntaxNodeOrToken.cs
Web Access
Project: src\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.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Internal;
 
namespace Microsoft.AspNetCore.Razor.Language.Syntax;
 
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(), nq}}")]
internal readonly struct SyntaxNodeOrToken : IEquatable<SyntaxNodeOrToken>
{
    // In a case if we are wrapping a SyntaxNode this is the SyntaxNode itself.
    // In a case where we are wrapping a token, this is the token's parent.
    private readonly SyntaxNode? _nodeOrParent;
 
    // Green node for the token. 
    private readonly GreenNode? _token;
 
    // Used in both node and token cases.
    // When we have a node, _position == _nodeOrParent.Position.
    private readonly int _position;
 
    // Index of the token among parent's children. 
    // This field only makes sense if this is a token.
    // For regular nodes it is set to -1 to distinguish from default(SyntaxToken)
    private readonly int _tokenIndex;
 
    internal SyntaxNodeOrToken(SyntaxNode node)
        : this()
    {
        Debug.Assert(!node.Green.IsList, "node cannot be a list");
        _position = node.Position;
        _nodeOrParent = node;
        _tokenIndex = -1;
    }
 
    internal SyntaxNodeOrToken(SyntaxNode? parent, GreenNode? token, int position, int index)
    {
        Debug.Assert(parent == null || !parent.Green.IsList, "parent cannot be a list");
        Debug.Assert(token != null || (parent == null && position == 0 && index == 0), "parts must form a token");
        Debug.Assert(token == null || token.IsToken, "token must be a token");
        Debug.Assert(index >= 0, "index must not be negative");
        Debug.Assert(parent == null || token != null, "null token cannot have parent");
 
        _position = position;
        _tokenIndex = index;
        _nodeOrParent = parent;
        _token = token;
    }
 
    internal string GetDebuggerDisplay()
        => $"{GetType().Name} {Kind} {ToString()}";
 
    public SyntaxKind Kind => _token?.Kind ?? _nodeOrParent?.Kind ?? 0;
 
    /// <summary>
    /// Determines whether the underlying node or token represents a language construct that was actually parsed
    /// from source code. Missing nodes and tokens are typically generated by the parser in error scenarios to
    /// represent constructs that should have been present in the source code for the source code to compile
    /// successfully but were actually missing.
    /// </summary>
    public bool IsMissing => _token?.IsMissing ?? _nodeOrParent?.IsMissing ?? false;
 
    /// <summary>
    /// The node that contains the underlying node or token in its Children collection.
    /// </summary>
    public SyntaxNode? Parent => _token != null ? _nodeOrParent : _nodeOrParent?.Parent;
 
    internal GreenNode? UnderlyingNode => _token ?? _nodeOrParent?.Green;
 
    internal GreenNode RequiredUnderlyingNode
    {
        get
        {
            Debug.Assert(UnderlyingNode != null);
            return UnderlyingNode;
        }
    }
 
    internal int Position => _position;
 
    internal int Width => _token?.Width ?? _nodeOrParent?.Width ?? 0;
 
    internal int EndPosition => _position + Width;
 
    /// <summary>
    /// Determines whether this <see cref="SyntaxNodeOrToken"/> is wrapping a token.
    /// </summary>
    public bool IsToken => !IsNode;
 
    /// <summary>
    /// Determines whether this <see cref="SyntaxNodeOrToken"/> is wrapping a node.
    /// </summary>
    public bool IsNode => _tokenIndex < 0;
 
    /// <summary>
    /// Returns the underlying token if this <see cref="SyntaxNodeOrToken"/> is wrapping a
    /// token.
    /// </summary>
    /// <returns>
    /// The underlying token if this <see cref="SyntaxNodeOrToken"/> is wrapping a token.
    /// </returns>
    public SyntaxToken AsToken()
    {
        return _token != null ? new SyntaxToken(_nodeOrParent, _token, Position, _tokenIndex) : default;
    }
 
    internal bool AsToken(out SyntaxToken token)
    {
        if (IsToken)
        {
            token = AsToken()!;
            return true;
        }
 
        token = default;
        return false;
    }
 
    /// <summary>
    /// Returns the underlying node if this <see cref="SyntaxNodeOrToken"/> is wrapping a
    /// node.
    /// </summary>
    /// <returns>
    /// The underlying node if this <see cref="SyntaxNodeOrToken"/> is wrapping a node.
    /// </returns>
    public SyntaxNode? AsNode()
    {
        return _token != null ? null : _nodeOrParent;
    }
 
    internal bool AsNode([NotNullWhen(true)] out SyntaxNode? node)
    {
        if (IsNode)
        {
            node = _nodeOrParent;
            return node != null;
        }
 
        node = null;
        return false;
    }
 
    /// <summary>
    /// The list of child nodes and tokens of the underlying node or token.
    /// </summary>
    public ChildSyntaxList ChildNodesAndTokens()
    {
        if (AsNode(out var node))
        {
            return node.ChildNodesAndTokens();
        }
 
        return default;
    }
 
    /// <summary>
    /// The absolute span of the underlying node or token in characters, not including its leading and trailing
    /// trivia.
    /// </summary>
    public TextSpan Span
    {
        get
        {
            if (_token != null)
            {
                return AsToken().Span;
            }
 
            if (_nodeOrParent != null)
            {
                return _nodeOrParent.Span;
            }
 
            return default;
        }
    }
 
    /// <summary>
    /// Same as accessing <see cref="TextSpan.Start"/> on <see cref="Span"/>.
    /// </summary>
    /// <remarks>
    /// Slight performance improvement.
    /// </remarks>
    public int SpanStart
    {
        get
        {
            if (_token != null)
            {
                return _position;
            }
 
            if (_nodeOrParent != null)
            {
                return _nodeOrParent.SpanStart;
            }
 
            return 0;
        }
    }
 
    /// <summary>
    /// Returns the string representation of this node or token, not including its leading and trailing
    /// trivia.
    /// </summary>
    /// <returns>
    /// The string representation of this node or token, 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()
    {
        if (_token != null)
        {
            return _token.ToString();
        }
 
        if (_nodeOrParent != null)
        {
            return _nodeOrParent.ToString();
        }
 
        return string.Empty;
    }
 
    /// <summary>
    /// Determines whether the underlying node or token or any of its descendant nodes, tokens or trivia have any
    /// diagnostics on them. 
    /// </summary>
    public bool ContainsDiagnostics
    {
        get
        {
            if (_token != null)
            {
                return _token.ContainsDiagnostics;
            }
 
            if (_nodeOrParent != null)
            {
                return _nodeOrParent.ContainsDiagnostics;
            }
 
            return false;
        }
    }
 
    /// <summary>
    /// Gets a list of all the diagnostics in either the sub tree that has this node as its root or
    /// associated with this token and its related trivia. 
    /// This method does not filter diagnostics based on #pragmas and compiler options
    /// like nowarn, warnaserror etc.
    /// </summary>
    public IEnumerable<RazorDiagnostic> GetDiagnostics()
    {
        if (_token != null)
        {
            return AsToken().GetDiagnostics();
        }
 
        if (_nodeOrParent != null)
        {
            return _nodeOrParent.GetDiagnostics();
        }
 
        return SpecializedCollections.EmptyEnumerable<RazorDiagnostic>();
    }
 
    /// <summary>
    /// Determines whether the supplied <see cref="SyntaxNodeOrToken"/> is equal to this
    /// <see cref="SyntaxNodeOrToken"/>.
    /// </summary>
    public bool Equals(SyntaxNodeOrToken other)
    {
        // index replaces position to ensure equality.  Assert if offset affects equality.
        Debug.Assert(
            (_nodeOrParent == other._nodeOrParent && _token == other._token && _position == other._position && _tokenIndex == other._tokenIndex) ==
            (_nodeOrParent == other._nodeOrParent && _token == other._token && _tokenIndex == other._tokenIndex));
 
        return _nodeOrParent == other._nodeOrParent &&
               _token == other._token &&
               _tokenIndex == other._tokenIndex;
    }
 
    /// <summary>
    /// Determines whether two <see cref="SyntaxNodeOrToken"/>s are equal.
    /// </summary>
    public static bool operator ==(SyntaxNodeOrToken left, SyntaxNodeOrToken right)
    {
        return left.Equals(right);
    }
 
    /// <summary>
    /// Determines whether two <see cref="SyntaxNodeOrToken"/>s are unequal.
    /// </summary>
    public static bool operator !=(SyntaxNodeOrToken left, SyntaxNodeOrToken right)
    {
        return !left.Equals(right);
    }
 
    /// <summary>
    /// Determines whether the supplied <see cref="SyntaxNodeOrToken"/> is equal to this
    /// <see cref="SyntaxNodeOrToken"/>.
    /// </summary>
    public override bool Equals(object? obj)
    {
        return obj is SyntaxNodeOrToken token && Equals(token);
    }
 
    /// <summary>
    /// Serves as hash function for <see cref="SyntaxNodeOrToken"/>.
    /// </summary>
    public override int GetHashCode()
    {
        var hash = HashCodeCombiner.Start();
        hash.Add(_nodeOrParent);
        hash.Add(_token);
        hash.Add(_tokenIndex);
 
        return hash.CombinedHash;
    }
 
    /// <summary>
    /// Returns a new <see cref="SyntaxNodeOrToken"/> that wraps the supplied token.
    /// </summary>
    /// <param name="token">The input token.</param>
    /// <returns>
    /// A <see cref="SyntaxNodeOrToken"/> that wraps the supplied token.
    /// </returns>
    public static implicit operator SyntaxNodeOrToken(SyntaxToken token)
    {
        return new SyntaxNodeOrToken(token.Parent, token.Node, token.Position, token.Index);
    }
 
    /// <summary>
    /// Returns the underlying token wrapped by the supplied <see cref="SyntaxNodeOrToken"/>.
    /// </summary>
    /// <param name="nodeOrToken">
    /// The input <see cref="SyntaxNodeOrToken"/>.
    /// </param>
    /// <returns>
    /// The underlying token wrapped by the supplied <see cref="SyntaxNodeOrToken"/>.
    /// </returns>
    public static explicit operator SyntaxToken(SyntaxNodeOrToken nodeOrToken)
    {
        return nodeOrToken.AsToken();
    }
 
    /// <summary>
    /// Returns a new <see cref="SyntaxNodeOrToken"/> that wraps the supplied node.
    /// </summary>
    /// <param name="node">The input node.</param>
    /// <returns>
    /// A <see cref="SyntaxNodeOrToken"/> that wraps the supplied node.
    /// </returns>
    public static implicit operator SyntaxNodeOrToken(SyntaxNode? node)
    {
        return node is not null
            ? new SyntaxNodeOrToken(node)
            : default;
    }
 
    /// <summary>
    /// Returns the underlying node wrapped by the supplied <see cref="SyntaxNodeOrToken"/>.
    /// </summary>
    /// <param name="nodeOrToken">
    /// The input <see cref="SyntaxNodeOrToken"/>.
    /// </param>
    /// <returns>
    /// The underlying node wrapped by the supplied <see cref="SyntaxNodeOrToken"/>.
    /// </returns>
    public static explicit operator SyntaxNode?(SyntaxNodeOrToken nodeOrToken)
    {
        return nodeOrToken.AsNode();
    }
}