File: KeywordHighlighting\AbstractKeywordHighlighterTests.cs
Web Access
Project: src\src\EditorFeatures\TestUtilities\Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities.csproj (Microsoft.CodeAnalysis.EditorFeatures.Test.Utilities)
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Highlighting;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.UnitTests.KeywordHighlighting;
 
[UseExportProvider]
public abstract class AbstractKeywordHighlighterTests
{
    private static readonly TestComposition s_baseComposition = EditorTestCompositions.EditorFeatures.AddExcludedPartTypes(typeof(IHighlighter));
    private TestComposition _lazyComposition;
 
    protected TestComposition Composition
        => _lazyComposition ??= s_baseComposition.AddParts(GetHighlighterType());
 
    internal abstract Type GetHighlighterType();
 
    protected abstract IEnumerable<ParseOptions> GetOptions();
    protected abstract EditorTestWorkspace CreateWorkspaceFromFile(string code, ParseOptions options);
 
    protected async Task TestAsync(string code)
    {
        foreach (var option in GetOptions())
        {
            await TestAsync(code, option);
        }
    }
 
    private async Task TestAsync(string markup, ParseOptions options)
    {
        using var workspace = CreateWorkspaceFromFile(markup, options);
        var testDocument = workspace.Documents.Single();
        var expectedHighlightSpans = testDocument.SelectedSpans.ToList();
        expectedHighlightSpans.Sort();
 
        var cursorSpan = testDocument.AnnotatedSpans["Cursor"].Single();
        var textSnapshot = testDocument.GetTextBuffer().CurrentSnapshot;
        var document = workspace.CurrentSolution.GetDocument(testDocument.Id);
 
        var service = Assert.IsType<HighlightingService>(workspace.ExportProvider.GetExportedValue<IHighlightingService>());
 
        var root = await document.GetSyntaxRootAsync();
 
        // Check that every point within the span (inclusive) produces the expected set of
        // results.
        for (var i = 0; i <= cursorSpan.Length; i++)
        {
            var position = cursorSpan.Start + i;
            var highlightSpans = new List<TextSpan>();
            service.AddHighlights(root, position, highlightSpans, CancellationToken.None);
 
            CheckSpans(root.SyntaxTree, expectedHighlightSpans, highlightSpans);
        }
    }
 
    private static void CheckSpans(SyntaxTree tree, IList<TextSpan> expectedHighlightSpans, List<TextSpan> highlightSpans)
    {
        for (var j = 0; j < Math.Max(highlightSpans.Count, expectedHighlightSpans.Count); j++)
        {
            if (j >= expectedHighlightSpans.Count)
            {
                var actualLineSpan = tree.GetLineSpan(highlightSpans[j]).Span;
                var actualText = tree.GetText().ToString(highlightSpans[j]);
                Assert.False(true, $"Unexpected highlight at {actualLineSpan}: '{actualText}'");
            }
            else if (j >= highlightSpans.Count)
            {
                var expectedLineSpan = tree.GetLineSpan(expectedHighlightSpans[j]).Span;
                var expectedText = tree.GetText().ToString(expectedHighlightSpans[j]);
                Assert.False(true, $"Missing highlight at {expectedLineSpan}: '{expectedText}'");
            }
 
            var expected = expectedHighlightSpans[j];
            var actual = highlightSpans[j];
 
            if (actual != expected)
                Assert.Equal(tree.GetLineSpan(expected).Span, tree.GetLineSpan(actual).Span);
        }
    }
}