File: src\Razor\src\Razor\test\Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests\DocumentExcerpt\DocumentExcerptServiceTestBase.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.VisualStudio.LanguageServices.Razor.UnitTests\Microsoft.VisualStudio.LanguageServices.Razor.UnitTests.csproj (Microsoft.VisualStudio.LanguageServices.Razor.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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
using Xunit;
using Xunit.Abstractions;
using TestFileMarkupParser = Microsoft.CodeAnalysis.Testing.TestFileMarkupParser;
 
namespace Microsoft.AspNetCore.Razor.Test.Common.Workspaces;
 
public abstract class DocumentExcerptServiceTestBase(ITestOutputHelper testOutput) : CohostEndpointTestBase(testOutput)
{
    public static (SourceText sourceText, TextSpan span) CreateText(string text)
    {
        // Since we're using positions, normalize to Windows style
        text = text.Replace("\r", "").Replace("\n", "\r\n");
 
        TestFileMarkupParser.GetSpan(text, out text, out var span);
        return (SourceText.From(text), span);
    }
 
    // Adds the text to a ProjectSnapshot, generates code, and updates the workspace.
    private async Task<(IDocumentSnapshot primary, SourceGeneratedDocument secondary)> InitializeDocumentAsync(SourceText sourceText)
    {
        var document = CreateProjectAndRazorDocument(sourceText.ToString());
 
        var snapshotManager = OOPExportProvider.GetExportedValue<RemoteSnapshotManager>();
        var snapshot = snapshotManager.GetSnapshot(document);
        var generatedDocument = await document.Project.TryGetSourceGeneratedDocumentForRazorDocumentAsync(document, DisposalToken);
 
        return (snapshot, generatedDocument.AssumeNotNull());
    }
 
    // Maps a span in the primary buffer to the secondary buffer. This is only valid for C# code
    // that appears in the primary buffer.
    private static async Task<TextSpan> GetSecondarySpanAsync(IDocumentSnapshot primary, TextSpan primarySpan, Document secondary, CancellationToken cancellationToken)
    {
        var output = await primary.GetGeneratedOutputAsync(cancellationToken);
 
        foreach (var mapping in output.GetRequiredCSharpDocument().SourceMappingsSortedByOriginal)
        {
            if (mapping.OriginalSpan.AbsoluteIndex <= primarySpan.Start &&
                (mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length) >= primarySpan.End)
            {
                var offset = mapping.GeneratedSpan.AbsoluteIndex - mapping.OriginalSpan.AbsoluteIndex;
                var secondarySpan = new TextSpan(primarySpan.Start + offset, primarySpan.Length);
                Assert.Equal(
                    (await primary.GetTextAsync(cancellationToken)).ToString(primarySpan),
                    (await secondary.GetTextAsync(cancellationToken)).ToString(secondarySpan));
                return secondarySpan;
            }
        }
 
        throw new InvalidOperationException("Could not map the primary span to the generated code.");
    }
 
    public async Task<(SourceGeneratedDocument generatedDocument, SourceText razorSourceText, TextSpan primarySpan, TextSpan generatedSpan)> InitializeAsync(string razorSource, CancellationToken cancellationToken)
    {
        var (razorSourceText, primarySpan) = CreateText(razorSource);
        var (primary, generatedDocument) = await InitializeDocumentAsync(razorSourceText);
        var generatedSpan = await GetSecondarySpanAsync(primary, primarySpan, generatedDocument, cancellationToken);
        return (generatedDocument, razorSourceText, primarySpan, generatedSpan);
    }
 
    internal async Task<(IDocumentSnapshot primary, SourceGeneratedDocument generatedDocument, TextSpan generatedSpan)> InitializeWithSnapshotAsync(string razorSource, CancellationToken cancellationToken)
    {
        var (razorSourceText, primarySpan) = CreateText(razorSource);
        var (primary, generatedDocument) = await InitializeDocumentAsync(razorSourceText);
        var generatedSpan = await GetSecondarySpanAsync(primary, primarySpan, generatedDocument, cancellationToken);
        return (primary, generatedDocument, generatedSpan);
    }
}