File: Syntax\FindTokenTests.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.AspNetCore.Razor.Language\test\Microsoft.AspNetCore.Razor.Language.UnitTests.csproj (Microsoft.AspNetCore.Razor.Language.UnitTests)
// 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.Linq;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.AspNetCore.Razor.Language.Test;
 
public class FindTokenTests
{
    private const string PositionMarker = "$$";
 
    private static (RazorSyntaxTree Tree, int Position) ParseWithPosition(string textWithPosition)
    {
        var position = textWithPosition.IndexOf(PositionMarker, StringComparison.Ordinal);
        if (position == -1)
        {
            throw new ArgumentException("The text must contain a '$$' character to indicate the position to find the token at.", nameof(textWithPosition));
        }
 
        var text = textWithPosition.Remove(position, PositionMarker.Length);
        var tree = Parse(text);
        return (tree, position);
    }
 
    private static RazorSyntaxTree Parse(string text)
    {
        return RazorSyntaxTree.Parse(RazorSourceDocument.Create(text, System.Text.Encoding.Default, RazorSourceDocumentProperties.Default));
    }
 
    [Fact, WorkItem("https://github.com/dotnet/razor/issues/7505")]
    public void ReturnsEofOnFileEnd()
    {
        var text = "<div></div>$$";
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""EndOfFile;[];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact, WorkItem("https://github.com/dotnet/razor/issues/7505")]
    public void ReturnsEofOnFileEnd_WithTrailingTrivia()
    {
        var text = """
            <div></div>
            $$
 
            """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""EndOfFile;[];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact, WorkItem("https://github.com/dotnet/razor/issues/9040")]
    public void LeadingWhitespace_BeforeAnyNode()
    {
        var text = """
        $$ <Component></Component>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""OpenAngle;[<];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void ReturnsOpenAngle()
    {
        var text = "$$<div></div>";
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""OpenAngle;[<];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Theory]
    [InlineData("<$$div></div>")]
    [InlineData("<d$$iv></div>")]
    [InlineData("<di$$v></div>")]
    public void ReturnsStartDivTag(string text)
    {
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Text;[div];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void ReturnsCloseAngle()
    {
        var text = "<div$$></div>";
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact, WorkItem("https://github.com/dotnet/razor/issues/7630")]
    public void ReturnsEof_AfterVoidTag()
    {
        var text = "<input>$$";
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""EndOfFile;[];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact, WorkItem("https://github.com/dotnet/razor/issues/7630")]
    public void ReturnsCloseAngle_AfterVoidTagWithTrailingSpace()
    {
        var text = "<input>$$ ";
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_01()
    {
        var text = """
        $$@if (true)
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Transition;[@];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_02()
    {
        var text = """
        @$$if (true)
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Keyword;[if];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_03_IgnoreWhitespace()
    {
        var text = """
        @if$$ (true)
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Keyword;[if];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_03_IncludeWhitespace()
    {
        var text = """
        @if$$ (true)
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position, includeWhitespace: true);
 
        AssertEx.Equal("""Whitespace;[ ];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_04()
    {
        var text = """
        @if $$(true)
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""LeftParenthesis;[(];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_05()
    {
        var text = """
        @if ($$true)
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Keyword;[true];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_06()
    {
        var text = """
        @if (true$$)
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RightParenthesis;[)];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_07_IgnoreWhitespace()
    {
        var text = """
        @if (true)$$
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RightParenthesis;[)];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_07_IncludeWhitespace()
    {
        var text = """
        @if (true)$$
        {
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position, includeWhitespace: true);
 
        AssertEx.Equal("""NewLine;[LF];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_08()
    {
        var text = """
        @if (true)
        $${
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""LeftBrace;[{];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_09()
    {
        var text = """
        @if (true)
        {
            $$
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RightBrace;[}];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_10()
    {
        var text = """
        @if (true)
        {
        $$}
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RightBrace;[}];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_11()
    {
        var text = """
        <div attr=$$@value />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Transition;[@];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_12()
    {
        var text = """
        <div attr=@$$value />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Identifier;[value];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_12_IgnoreWhitespace()
    {
        var text = """
        <div attr=@value$$ />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Identifier;[value];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_12_IncludeWhitespace()
    {
        var text = """
        <div attr=@value$$ />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position, includeWhitespace: true);
 
        AssertEx.Equal("""Whitespace;[ ];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_13()
    {
        var text = """
        <div attr=$$@(value) />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Transition;[@];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_14()
    {
        var text = """
        <div attr=@$$(value) />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""LeftParenthesis;[(];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_15()
    {
        var text = """
        <div attr=@($$value) />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Identifier;[value];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_16()
    {
        var text = """
        <div attr=@(value$$) />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RightParenthesis;[)];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_17_IgnoreWhitespace()
    {
        var text = """
        <div attr=@(value)$$ />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RightParenthesis;[)];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void CSharpTransition_17_IncludeWhitespace()
    {
        var text = """
        <div attr=@(value)$$ />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position, includeWhitespace: true);
 
        AssertEx.Equal("""Whitespace;[ ];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlTransition_01()
    {
        var text = """
        @if (true)
        {
        $$    <div />
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""OpenAngle;[<];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Theory, WorkItem("https://github.com/dotnet/razor/issues/7630")]
    [InlineData("div")]
    [InlineData("div /")]
    [InlineData("input")]
    public void HtmlTransition_02(string tagContent)
    {
        var text = $$"""
        @if (true)
        {
            <{{tagContent}}>$$
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact, WorkItem("https://github.com/dotnet/razor/issues/7630")]
    public void HtmlTransition_03()
    {
        var text = """
        @if (true)
        {
            <div><em>$$</div>
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""OpenAngle;[<];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact, WorkItem("https://github.com/dotnet/razor/issues/7630")]
    public void HtmlTransition_04()
    {
        var text = """
        @if (true)
        {
            <div><em$$></div>
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Theory, WorkItem("https://github.com/dotnet/razor/issues/7505")]
    [CombinatorialData]
    public void HtmlTransition_05(bool withComment)
    {
        var commentText = withComment ?
        """
 
                <!--asdfasd-->
        """
        : "";
        var text = $$"""
        @foreach (var num in Enumerable.Range(1, 10))
        {
            <span class="skill_result btn">{{commentText}}
                $$<span style="margin-left:0px">
                    <svg>
                        <rect width="1" height="1" />
                    </svg>
                </span>
            </span>
        }
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""OpenAngle;[<];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_BeforeNewline_01()
    {
        var text = """
        <div>    $$
 
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_BeforeNewline_02()
    {
        var text = """
        <div>   asdf  $$
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Text;[asdf];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_AfterNewline()
    {
        var text = """
        <div>
        $$
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""OpenAngle;[<];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_01()
    {
        var text = """
        <div $$ Attribute = "value">
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Text;[div];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_02()
    {
        var text = """
        <div Attribute $$ = "value">
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Text;[Attribute];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_03()
    {
        var text = """
        <div Attribute = $$ "value">
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Equals;[=];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_04()
    {
        var text = """
        <div Attribute =
            $$ "value">
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""DoubleQuote;["];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_05()
    {
        var text = """
        <div Attribute = " $$ value" >
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""DoubleQuote;["];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_06()
    {
        var text = """
        <div Attribute = "value $$ " >
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Text;[value];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_07()
    {
        var text = """
        <div Attribute = "value" $$ >
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""DoubleQuote;["];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_08()
    {
        var text = """
        <div Attribute = "value" $$>
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void IgnoreWhitespace_InsideTag_09()
    {
        var text = """
        <div Attribute = "value"
        $$ >
        </div>
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_01()
    {
        var text = """
        $$<!-- Comment -->
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""OpenAngle;[<];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_02()
    {
        var text = """
        <$$!-- Comment -->
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Bang;[!];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_03()
    {
        var text = """
        <!$$-- Comment -->
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""DoubleHyphen;[--];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_04_IgnoreWhitespace()
    {
        var text = """
        <!--$$ Comment -->
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""DoubleHyphen;[--];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_04_IncludeWhitespace()
    {
        var text = """
        <!--$$ Comment -->
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position, includeWhitespace: true);
 
        AssertEx.Equal("""Whitespace;[ ];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_05()
    {
        var text = """
        <!-- $$Comment -->
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Text;[Comment];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_06()
    {
        var text = """
        <!-- Comment$$ -->
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""Text;[Comment];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_07()
    {
        var text = """
        <!-- Comment $$-->
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""DoubleHyphen;[--];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_08()
    {
        var text = """
        <!-- Comment --$$>
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void HtmlComment_09()
    {
        var text = """
        <!-- Comment -->$$
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""CloseAngle;[>];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_01()
    {
        var text = """
        $$@* Comment *@
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RazorCommentTransition;[@];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_02()
    {
        var text = """
        @$$* Comment *@
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RazorCommentStar;[*];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_03_IgnoreWhitespace()
    {
        var text = """
        @*$$ Comment *@
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RazorCommentLiteral;[ Comment ];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_03_IncludeWhitespace()
    {
        var text = """
        @*$$ Comment *@
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position, includeWhitespace: true);
 
        AssertEx.Equal("""RazorCommentLiteral;[ Comment ];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_04()
    {
        var text = """
        @* $$Comment *@
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RazorCommentLiteral;[ Comment ];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_05()
    {
        var text = """
        @* Comment$$ *@
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RazorCommentLiteral;[ Comment ];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_06()
    {
        var text = """
        @* Comment $$*@
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RazorCommentStar;[*];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_07()
    {
        var text = """
        @* Comment *$$@
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RazorCommentTransition;[@];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void RazorComment_08()
    {
        var text = """
        @* Comment *@$$
        <div />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.FindToken(position);
 
        AssertEx.Equal("""RazorCommentTransition;[@];""", TestSyntaxSerializer.Serialize(token).Trim());
    }
 
    [Fact]
    public void OutOfRange_NoRangeInDocument()
    {
        var text = """
        <div />$$
        """;
        var (tree, position) = ParseWithPosition(text);
 
        Assert.ThrowsAny<ArgumentOutOfRangeException>(() => tree.Root.FindToken(-1));
        Assert.ThrowsAny<ArgumentOutOfRangeException>(() => tree.Root.FindToken(position + 1));
    }
 
    [Fact]
    public void OutOfRange_NoRangeInWhitespaceNode_NoNewline()
    {
        var text = """
        <div $$ />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.DescendantTokens().Single(t => t.Kind == SyntaxKind.Whitespace);
        var parent = token.Parent;
        Assert.NotNull(parent);
        Assert.ThrowsAny<ArgumentOutOfRangeException>(() => parent.FindToken(position, includeWhitespace: false));
        Assert.Equal(token, parent.FindToken(position, includeWhitespace: true));
    }
 
    [Fact]
    public void OutOfRange_NoRangeInWhitespaceNode_AfterNewline()
    {
        var text = """
        <div
         $$ />
        """;
        var (tree, position) = ParseWithPosition(text);
 
        var token = tree.Root.DescendantTokens().Last(t => t.Kind == SyntaxKind.Whitespace);
        var parent = token.Parent;
        Assert.NotNull(parent);
        Assert.ThrowsAny<ArgumentOutOfRangeException>(() => parent.FindToken(position, includeWhitespace: false));
        Assert.Equal(token, parent.FindToken(position, includeWhitespace: true));
    }
}