File: src\roslyn\src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Formatting\Engine\TokenData.cs
Web Access
Project: src\roslyn\src\RoslynAnalyzers\Microsoft.CodeAnalysis.Analyzers\Core\Microsoft.CodeAnalysis.Analyzers.csproj (Microsoft.CodeAnalysis.Analyzers)
// 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 Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.Formatting;

/// <summary>
/// it represents a token that is inside of token stream not also outside of token stream
/// 
/// it uses an index to navigate previous and after tokens in the stream to make navigation faster. and regular
/// Previous/NextToken for tokens outside of the stream.
/// 
/// this object is supposed to be live very short but created a lot of time. that is why it is struct. 
/// (same reason why SyntaxToken is struct - to reduce heap allocation)
/// </summary>
internal readonly record struct TokenData : IComparable<TokenData>
{
    public TokenStream TokenStream { get; }
    public int IndexInStream { get; }
    public SyntaxToken Token { get; }

    public TokenData(TokenStream tokenStream, int indexInStream, SyntaxToken token)
    {
        Contract.ThrowIfNull(tokenStream);
        Contract.ThrowIfFalse((indexInStream == -1) || (0 <= indexInStream && indexInStream < tokenStream.TokenCount));

        this.TokenStream = tokenStream;
        this.IndexInStream = indexInStream;
        this.Token = token;
    }

    public TokenData GetPreviousTokenData()
        => this.TokenStream.GetPreviousTokenData(this);

    public TokenData GetNextTokenData()
        => this.TokenStream.GetNextTokenData(this);

    public override int GetHashCode()
        => this.Token.GetHashCode();

    public bool Equals(TokenData other)
    {
        if (this.TokenStream != other.TokenStream)
        {
            return false;
        }

        if (this.IndexInStream >= 0 && this.IndexInStream == other.IndexInStream)
        {
            return true;
        }

        return this.Token.Equals(other.Token);
    }

    public int CompareTo(TokenData other)
    {
        Contract.ThrowIfFalse(this.TokenStream == other.TokenStream);

        if (this.IndexInStream >= 0 && other.IndexInStream >= 0)
            return this.IndexInStream - other.IndexInStream;

        if (this.Token == other.Token)
            return 0;

        var start = this.Token.SpanStart - other.Token.SpanStart;
        if (start != 0)
            return start;

        var end = this.Token.Span.End - other.Token.Span.End;
        if (end != 0)
            return end;

        // We have two different tokens, which are at the same location.  This can happen with things like empty/missing
        // tokens.  In order to give a strict ordering, we need to walk up the tree to find the first common ancestor
        // and see which token we hit first in that ancestor.
        var commonRoot = this.Token.GetCommonRoot(other.Token);
        Contract.ThrowIfNull(commonRoot);

        // Now, figure out the ancestor of each token parented by the common root.
        var thisTokenAncestor = GetAncestorUnderRoot(this.Token, commonRoot);
        var otherTokenAncestor = GetAncestorUnderRoot(other.Token, commonRoot);

        foreach (var child in commonRoot.ChildNodesAndTokens())
        {
            if (child == thisTokenAncestor)
                return -1;
            else if (child == otherTokenAncestor)
                return 1;
        }

        throw ExceptionUtilities.Unreachable();

        static SyntaxNodeOrToken GetAncestorUnderRoot(SyntaxNodeOrToken start, SyntaxNode root)
        {
            var current = start;
            while (current.Parent != root)
                current = current.Parent;

            return current;
        }
    }

    public static bool operator <(TokenData left, TokenData right)
        => left.CompareTo(right) < 0;

    public static bool operator >(TokenData left, TokenData right)
        => left.CompareTo(right) > 0;
}