File: EmbeddedLanguages\Json\CSharpJsonParserTests.cs
Web Access
Project: src\src\EditorFeatures\CSharpTest2\Microsoft.CodeAnalysis.CSharp.EditorFeatures2.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.EditorFeatures2.UnitTests)
// 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 System.Collections.Immutable;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.EmbeddedLanguages.Common;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.Features.EmbeddedLanguages.Json;
using Newtonsoft.Json.Linq;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.EmbeddedLanguages.Json;
 
using JsonSeparatedList = EmbeddedSeparatedSyntaxNodeList<JsonKind, JsonNode, JsonValueNode>;
using JsonToken = EmbeddedSyntaxToken<JsonKind>;
using JsonTrivia = EmbeddedSyntaxTrivia<JsonKind>;
 
public partial class CSharpJsonParserTests
{
    private const string SupportedLanguage = "en-US";
 
    private readonly IVirtualCharService _service = CSharpVirtualCharService.Instance;
    private const string StatementPrefix = "var v = ";
 
    private static SyntaxToken GetStringToken(string text)
    {
        var statement = StatementPrefix + text;
        var parsedStatement = SyntaxFactory.ParseStatement(statement);
        var token = parsedStatement.DescendantTokens().ToArray()[3];
        Assert.True(token.Kind() == SyntaxKind.StringLiteralToken);
 
        return token;
    }
 
    protected void Test(
        string stringText,
        string? expected,
        string looseDiagnostics,
        string strictDiagnostics,
        bool runLooseSubTreeCheck = true)
    {
        Test(stringText, JsonOptions.Loose, expected, looseDiagnostics, runLooseSubTreeCheck);
        Test(stringText, JsonOptions.Strict, expected, strictDiagnostics, runSubTreeChecks: true);
    }
 
    private void Test(
        string stringText, JsonOptions options,
        string? expectedTree, string expectedDiagnostics,
        bool runSubTreeChecks)
    {
        var tree = TryParseTree(stringText, options, conversionFailureOk: false);
        if (tree == null)
        {
            Assert.Null(expectedTree);
            return;
        }
 
        Assert.NotNull(expectedTree);
 
        // Tests are allowed to not run the subtree tests.  This is because some
        // subtrees can cause the native regex parser to exhibit very bad behavior
        // (like not ever actually finishing compiling).
        if (runSubTreeChecks)
            TryParseSubTrees(stringText, options);
 
        var actualTree = TreeToText(tree).Replace("\"", "\"\"");
        AssertEx.Equal(expectedTree!.Replace("\"", "\"\""), actualTree);
 
        ValidateDiagnostics(expectedDiagnostics, tree);
    }
 
    private protected static void ValidateDiagnostics(string expectedDiagnostics, JsonTree tree)
    {
        var actualDiagnostics = DiagnosticsToText(tree.Diagnostics).Replace("\"", "\"\"");
        AssertEx.Equal(RemoveMessagesInNonSupportedLanguage(expectedDiagnostics).Replace("\"", "\"\""), actualDiagnostics);
    }
 
    private static string RemoveMessagesInNonSupportedLanguage(string value)
    {
        if (value == "")
            return value;
 
        if (Thread.CurrentThread.CurrentCulture.Name == SupportedLanguage)
            return value;
 
        var diagnosticsElement = XElement.Parse(value);
        foreach (var diagnosticElement in diagnosticsElement.Elements("Diagnostic"))
            diagnosticElement.Attribute("Message").Remove();
 
        return diagnosticsElement.ToString();
    }
 
    private void TryParseSubTrees(string stringText, JsonOptions options)
    {
        // Trim the input from the right and make sure tree invariants hold
        var current = stringText;
        while (current != "@\"\"" && current != "\"\"")
        {
            current = current[0..^2] + "\"";
            TryParseTree(current, options, conversionFailureOk: true);
        }
 
        // Trim the input from the left and make sure tree invariants hold
        current = stringText;
        while (current != "@\"\"" && current != "\"\"")
        {
            current = current[0] == '@'
                ? "@\"" + current[3..]
                : "\"" + current[2..];
 
            TryParseTree(current, options, conversionFailureOk: true);
        }
 
        for (var start = stringText[0] == '@' ? 2 : 1; start < stringText.Length - 1; start++)
        {
            TryParseTree(
                stringText[..start] + stringText[(start + 1)..],
                options, conversionFailureOk: true);
        }
    }
 
    private protected (SyntaxToken, JsonTree?, VirtualCharSequence) JustParseTree(
        string stringText, JsonOptions options, bool conversionFailureOk)
    {
        var token = GetStringToken(stringText);
        if (token.ValueText == "")
            return default;
 
        var allChars = _service.TryConvertToVirtualChars(token);
        if (allChars.IsDefault)
        {
            Assert.True(conversionFailureOk, "Failed to convert text to token.");
            return (token, null, allChars);
        }
 
        var tree = JsonParser.TryParse(allChars, options);
        return (token, tree, allChars);
    }
 
    private JsonTree? TryParseTree(
        string stringText, JsonOptions options, bool conversionFailureOk)
    {
        var (token, tree, allChars) = JustParseTree(stringText, options, conversionFailureOk);
        if (tree == null)
        {
            Assert.True(allChars.IsDefault);
            return null;
        }
 
        CheckInvariants(tree, allChars);
 
        if (options == JsonOptions.Loose)
        {
            try
            {
                JToken.Parse(token.ValueText);
            }
            catch (Exception)
            {
                Assert.NotEmpty(tree.Diagnostics);
                return tree;
            }
        }
        else
        {
            try
            {
                JsonDocument.Parse(token.ValueText, new JsonDocumentOptions { AllowTrailingCommas = false, CommentHandling = JsonCommentHandling.Disallow });
            }
            catch (Exception)
            {
                Assert.NotEmpty(tree.Diagnostics);
                return tree;
            }
        }
 
        Assert.Empty(tree.Diagnostics);
        return tree;
    }
 
    private protected static string TreeToText(JsonTree tree)
        => new XElement("Tree",
            NodeToElement(tree.Root)).ToString();
 
    private protected static string DiagnosticsToText(ImmutableArray<EmbeddedDiagnostic> diagnostics)
    {
        if (diagnostics.IsEmpty)
            return "";
 
        return new XElement("Diagnostics",
            diagnostics.Select(d =>
            {
                var element = new XElement("Diagnostic");
                // Ensure the diagnostic we emit is the same as the .NET one. Note: we can only
                // do this in en-US as that's the only culture where we control the text exactly
                // and can ensure it exactly matches Regex.  We depend on localization to do a 
                // good enough job here for other languages.
                if (Thread.CurrentThread.CurrentCulture.Name == SupportedLanguage)
                    element.Add(new XAttribute("Message", d.Message));
 
                element.Add(new XAttribute("Start", d.Span.Start));
                element.Add(new XAttribute("Length", d.Span.Length));
 
                return element;
            })).ToString();
    }
 
    private static XElement NodeToElement(JsonNode node)
        => node switch
        {
            JsonArrayNode arrayNode => ArrayNodeToElement(arrayNode),
            JsonCompilationUnit compilationUnit => CompilationUnitToElement(compilationUnit),
            JsonObjectNode objectNode => ObjectNodeToElement(objectNode),
            JsonConstructorNode constructorNode => ConstructorNodeToElement(constructorNode),
            _ => NodeToElementWorker(node),
        };
 
    private static XElement NodeToElementWorker(JsonNode node)
    {
        var element = new XElement(node.Kind.ToString());
        foreach (var child in node)
            element.Add(NodeOrTokenToElement(child));
 
        return element;
    }
 
    private static XElement NodeOrTokenToElement(EmbeddedSyntaxNodeOrToken<JsonKind, JsonNode> child)
        => child.IsNode ? NodeToElement(child.Node) : TokenToElement(child.Token);
 
    private static XElement ConstructorNodeToElement(JsonConstructorNode node)
        => new(
            node.Kind.ToString(),
            TokenToElement(node.NewKeyword),
            TokenToElement(node.NameToken),
            TokenToElement(node.OpenParenToken),
            CreateSequenceNode(node.Sequence),
            TokenToElement(node.CloseParenToken));
 
    private static XElement ObjectNodeToElement(JsonObjectNode node)
        => new(
            node.Kind.ToString(),
            TokenToElement(node.OpenBraceToken),
            CreateSequenceNode(node.Sequence),
            TokenToElement(node.CloseBraceToken));
 
    private static XElement CompilationUnitToElement(JsonCompilationUnit node)
        => new(
            node.Kind.ToString(),
            CreateSequenceNode(node.Sequence),
            TokenToElement(node.EndOfFileToken));
 
    private static XElement ArrayNodeToElement(JsonArrayNode node)
        => new(
            node.Kind.ToString(),
            TokenToElement(node.OpenBracketToken),
            CreateSequenceNode(node.Sequence),
            TokenToElement(node.CloseBracketToken));
 
    private static XElement CreateSequenceNode(ImmutableArray<JsonValueNode> sequence)
        => new("Sequence", sequence.Select(NodeToElement));
 
    private static XElement CreateSequenceNode(JsonSeparatedList sequence)
        => new("Sequence", sequence.NodesAndTokens.Select(NodeOrTokenToElement));
 
    private static XElement TokenToElement(JsonToken token)
    {
        var element = new XElement(token.Kind.ToString());
 
        if (token.Value != null)
            element.Add(new XAttribute("value", token.Value));
 
        if (token.LeadingTrivia.Length > 0)
            element.Add(new XElement("Trivia", token.LeadingTrivia.Select(t => TriviaToElement(t))));
 
        if (token.VirtualChars.Length > 0)
            element.Add(token.VirtualChars.CreateString());
 
        if (token.TrailingTrivia.Length > 0)
            element.Add(new XElement("Trivia", token.TrailingTrivia.Select(t => TriviaToElement(t))));
 
        return element;
    }
 
    private static XElement TriviaToElement(JsonTrivia trivia)
        => new(
            trivia.Kind.ToString(),
            trivia.VirtualChars.CreateString().Replace("\f", "\\f"));
 
    private protected static void CheckInvariants(JsonTree tree, VirtualCharSequence allChars)
    {
        var root = tree.Root;
        var position = 0;
        CheckInvariants(root, ref position, allChars);
        Assert.Equal(allChars.Length, position);
    }
 
    private static void CheckInvariants(JsonNode node, ref int position, VirtualCharSequence allChars)
    {
        foreach (var child in node)
        {
            if (child.IsNode)
            {
                CheckInvariants(child.Node, ref position, allChars);
            }
            else
            {
                CheckInvariants(child.Token, ref position, allChars);
            }
        }
    }
 
    private static void CheckInvariants(JsonToken token, ref int position, VirtualCharSequence allChars)
    {
        CheckInvariants(token.LeadingTrivia, ref position, allChars);
        CheckCharacters(token.VirtualChars, ref position, allChars);
        CheckInvariants(token.TrailingTrivia, ref position, allChars);
    }
 
    private static void CheckInvariants(ImmutableArray<JsonTrivia> leadingTrivia, ref int position, VirtualCharSequence allChars)
    {
        foreach (var trivia in leadingTrivia)
            CheckInvariants(trivia, ref position, allChars);
    }
 
    private static void CheckInvariants(JsonTrivia trivia, ref int position, VirtualCharSequence allChars)
    {
        switch (trivia.Kind)
        {
            case JsonKind.SingleLineCommentTrivia:
            case JsonKind.MultiLineCommentTrivia:
            case JsonKind.WhitespaceTrivia:
            case JsonKind.EndOfLineTrivia:
                break;
            default:
                Assert.False(true, "Incorrect trivia kind");
                return;
        }
 
        CheckCharacters(trivia.VirtualChars, ref position, allChars);
    }
 
    private static void CheckCharacters(VirtualCharSequence virtualChars, ref int position, VirtualCharSequence allChars)
    {
        for (var i = 0; i < virtualChars.Length; i++)
            Assert.Equal(allChars[position + i], virtualChars[i]);
 
        position += virtualChars.Length;
    }
 
    private object RemoveSequenceNode(XNode node)
    {
        if (node is not XElement element)
            return node;
 
        var children = element.Nodes().Select(RemoveSequenceNode);
 
        if (element.Name == "Sequence")
            return children;
        return new XElement(element.Name, children);
    }
 
    [Fact]
    public void TestDeepRecursion1()
    {
        var (token, tree, chars) =
            JustParseTree(
                """
                @"[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
                [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["
                """,
                JsonOptions.Loose, conversionFailureOk: false);
        Assert.False(token.IsMissing);
        Assert.False(chars.IsDefaultOrEmpty);
        Assert.Null(tree);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_queries/edit/1691963")]
    public void TestDeepRecursion2()
    {
        var (token, tree, chars) =
            JustParseTree(
                """
                @"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"
                """,
                JsonOptions.Loose, conversionFailureOk: false);
        Assert.False(token.IsMissing);
        Assert.False(chars.IsDefaultOrEmpty);
        Assert.Null(tree);
    }
}