|
// 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();
}
}
|