| File: Language\Syntax\SyntaxNodeOrToken.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.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(); } }