File: LexicalAndXml\DocumentationCommentLexerTestBase.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Syntax\Microsoft.CodeAnalysis.CSharp.Syntax.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Syntax.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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
using InternalSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
    public abstract class DocumentationCommentLexerTestBase : CSharpTestBase
    {
        /// <summary>
        /// Lexes the given text into a sequence of tokens and asserts that the actual
        /// tokens match the expected tokens.
        /// </summary>
        /// <remarks>
        /// Prints a baseline on failure.
        /// </remarks>
        internal void AssertTokens(string text, params TokenDescription[] expected)
        {
            var actual = GetTokens(text);
 
            var actualEnumerator = actual.GetEnumerator();
            var expectedEnumerator = expected.GetEnumerator();
 
            try
            {
                while (true)
                {
                    bool actualHadNext = actualEnumerator.MoveNext();
                    bool expectedHadNext = expectedEnumerator.MoveNext();
 
                    if (!actualHadNext && !expectedHadNext)
                    {
                        return;
                    }
                    else if (!actualHadNext)
                    {
                        Assert.True(expectedHadNext);
                        AssertEx.Fail("Unmatched expected: " + expectedEnumerator.Current);
                    }
                    else if (!expectedHadNext)
                    {
                        Assert.True(actualHadNext);
                        AssertEx.Fail("Unmatched actual: " + expectedEnumerator.Current);
                    }
 
                    var actualToken = actualEnumerator.Current;
                    var expectedToken = (TokenDescription)expectedEnumerator.Current;
 
                    Assert.Equal(expectedToken.Text, actualToken.Text); //This first, since it's easiest to debug.
                    Assert.Equal(expectedToken.ValueText, actualToken.ValueText);
                    Assert.Equal(expectedToken.Kind, actualToken.Kind);
                }
            }
            catch
            {
                var baseline = actual.Select(ToExpectedTokenString);
                Console.WriteLine(string.Join("," + Environment.NewLine, baseline));
                throw;
            }
        }
 
        /// <summary>
        /// Basic cleverness for cutting down the verbosity of expected token baselines
        /// (i.e. don't output a parameter if the default is correct).
        /// </summary>
        private static string ToExpectedTokenString(InternalSyntax.SyntaxToken token)
        {
            var canonicalText = SyntaxFacts.GetText(token.Kind);
 
            var builder = new StringBuilder();
 
            builder.AppendFormat("Token(SyntaxKind.{0}", token.Kind);
 
            if (token.Text != canonicalText)
            {
                builder.AppendFormat(", \"{0}\"", token.Text);
 
                if (token.ValueText != token.Text)
                {
                    builder.AppendFormat(", \"{0}\"", token.ValueText);
 
                    if (token.ContextualKind != token.Kind)
                    {
                        builder.AppendFormat(", SyntaxKind.{0}", token.ContextualKind);
                    }
                }
            }
 
            builder.Append(')');
 
            return builder.ToString();
        }
 
        /// <summary>
        /// Convenience method for constructing TokenDescriptions.  If the text fields can be populated based
        /// on the SyntaxKind, then they will be.
        /// </summary>
        /// <param name="kind">Mandatory.</param>
        /// <param name="text">Defaults to Syntax.GetText of (contextual) kind.</param>
        /// <param name="valueText">Defaults to the computed value of text.</param>
        /// <param name="contextualKind">Defaults to None.</param>
        internal static TokenDescription Token(SyntaxKind kind, string text = null, string valueText = null, SyntaxKind contextualKind = SyntaxKind.None)
        {
            string canonicalText = contextualKind == SyntaxKind.None
                ? SyntaxFacts.GetText(kind)
                : SyntaxFacts.GetText(contextualKind);
            return new TokenDescription
            {
                Kind = kind,
                ContextualKind = contextualKind,
                Text = text ?? canonicalText,
                ValueText = valueText ?? text ?? canonicalText
            };
        }
 
        internal class TokenDescription
        {
            public SyntaxKind Kind;
            public SyntaxKind ContextualKind;
            public string Text;
            public string ValueText;
 
            public override string ToString()
            {
                return Kind + " (" + ContextualKind + ") '" + ValueText + "' (really '" + Text + "')";
            }
        }
 
        /// <summary>
        /// Wrap the text in enough context to make the lexer happy and return a sequence of tokens.
        /// </summary>
        internal abstract IEnumerable<InternalSyntax.SyntaxToken> GetTokens(string text);
    }
}