File: Snippets\AbstractSnippetProviderTests.cs
Web Access
Project: src\src\Features\TestUtilities\Microsoft.CodeAnalysis.Features.Test.Utilities.csproj (Microsoft.CodeAnalysis.Features.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.
 
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Test.Utilities.Snippets;
 
[UseExportProvider]
public abstract class AbstractSnippetProviderTests
{
    protected abstract string SnippetIdentifier { get; }
 
    protected abstract string LanguageName { get; }
 
    protected async Task VerifySnippetAsync(
        [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markupBeforeCommit,
        [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markupAfterCommit,
        string? editorconfig = null,
        ReferenceAssemblies? referenceAssemblies = null)
    {
        using var workspace = new TestWorkspace(FeaturesTestCompositions.Features);
 
        referenceAssemblies ??= ReferenceAssemblies.Default;
        var metadataReferences = await referenceAssemblies.ResolveAsync(LanguageName, CancellationToken.None);
        var project = workspace.CurrentSolution.
            AddProject("TestProject", "TestAssembly", LanguageName)
            .WithMetadataReferences(metadataReferences);
 
        TestFileMarkupParser.GetPosition(markupBeforeCommit, out markupBeforeCommit, out var snippetRequestPosition);
        var document = project.AddDocument("TestDocument", markupBeforeCommit, filePath: "/TestDocument");
 
        if (editorconfig is not null)
        {
            var editorConfigDoc = document.Project.AddAnalyzerConfigDocument(".editorconfig", SourceText.From(editorconfig), filePath: "/.editorconfig");
            document = editorConfigDoc.Project.GetDocument(document.Id)!;
        }
 
        var snippetServiceInterface = document.GetRequiredLanguageService<ISnippetService>();
        var snippetService = Assert.IsAssignableFrom<AbstractSnippetService>(snippetServiceInterface);
 
        snippetService.EnsureSnippetsLoaded(LanguageName);
        var snippetProvider = snippetService.GetSnippetProvider(SnippetIdentifier);
 
        var syntaxContextService = document.GetRequiredLanguageService<ISyntaxContextService>();
        var semanticModel = await document.GetRequiredSemanticModelAsync(CancellationToken.None);
        var syntaxContext = syntaxContextService.CreateContext(document, semanticModel, snippetRequestPosition, CancellationToken.None);
 
        var snippetContext = new SnippetContext(syntaxContext);
        var isValidSnippetLocation = snippetProvider.IsValidSnippetLocation(snippetContext, CancellationToken.None);
        Assert.True(isValidSnippetLocation, "Snippet is unexpectedly invalid on a given position");
 
        var snippetChange = await snippetProvider.GetSnippetChangeAsync(document, snippetRequestPosition, CancellationToken.None);
        var documentSourceText = await document.GetTextAsync();
        var documentTextAfterSnippet = documentSourceText.WithChanges(snippetChange.TextChanges);
 
        TestFileMarkupParser.GetPositionAndSpans(markupAfterCommit, out markupAfterCommit, out int finalCaretPosition, out ImmutableDictionary<string, ImmutableArray<TextSpan>> placeholderLocations);
        Assert.Equal(markupAfterCommit, documentTextAfterSnippet.ToString());
 
        var placeholderLocationsArray = new ImmutableArray<TextSpan>[placeholderLocations.Count];
        var snippetPlaceholders = snippetChange.Placeholders;
        Assert.Equal(placeholderLocationsArray.Length, snippetPlaceholders.Length);
 
        foreach (var placeholderLocationPair in placeholderLocations)
        {
            if (!int.TryParse(placeholderLocationPair.Key, out var locationIndex))
            {
                Assert.True(false, "Expected placeholder locations contains span with invalid annotation");
                return;
            }
 
            placeholderLocationsArray[locationIndex] = placeholderLocationPair.Value;
        }
 
        for (var i = 0; i < placeholderLocationsArray.Length; i++)
        {
            if (placeholderLocationsArray[i].IsDefault)
            {
                Assert.True(false, $"Placeholder location for index {i} was not specified");
            }
        }
 
        for (var i = 0; i < snippetPlaceholders.Length; i++)
        {
            var expectedSpans = placeholderLocationsArray[i];
            var (placeholderText, placeholderPositions) = snippetPlaceholders[i];
 
            Assert.Equal(expectedSpans.Length, placeholderPositions.Length);
 
            for (var j = 0; j < placeholderPositions.Length; j++)
            {
                var expectedSpan = expectedSpans[j];
                Assert.Contains(expectedSpan.Start, placeholderPositions);
                Assert.Equal(documentTextAfterSnippet.ToString(expectedSpan), placeholderText);
            }
        }
 
        Assert.Equal(finalCaretPosition, snippetChange.FinalCaretPosition);
    }
 
    protected async Task VerifySnippetIsAbsentAsync(
        [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markup,
        ReferenceAssemblies? referenceAssemblies = null)
    {
        using var workspace = new TestWorkspace(FeaturesTestCompositions.Features);
 
        referenceAssemblies ??= ReferenceAssemblies.Default;
        var metadataReferences = await referenceAssemblies.ResolveAsync(LanguageName, CancellationToken.None);
        var project = workspace.CurrentSolution.
            AddProject("TestProject", "TestAssembly", LanguageName)
            .WithMetadataReferences(metadataReferences);
 
        TestFileMarkupParser.GetPosition(markup, out markup, out var snippetRequestPosition);
        var document = project.AddDocument("TestDocument", markup);
 
        var snippetServiceInterface = document.GetRequiredLanguageService<ISnippetService>();
        var snippetService = Assert.IsAssignableFrom<AbstractSnippetService>(snippetServiceInterface);
 
        snippetService.EnsureSnippetsLoaded(LanguageName);
        var snippetProvider = snippetService.GetSnippetProvider(SnippetIdentifier);
 
        var syntaxContextService = document.GetRequiredLanguageService<ISyntaxContextService>();
        var semanticModel = await document.GetRequiredSemanticModelAsync(CancellationToken.None);
        var syntaxContext = syntaxContextService.CreateContext(document, semanticModel, snippetRequestPosition, CancellationToken.None);
 
        var snippetContext = new SnippetContext(syntaxContext);
        var isValidSnippetLocation = snippetProvider.IsValidSnippetLocation(snippetContext, CancellationToken.None);
        Assert.False(isValidSnippetLocation, "Snippet is unexpectedly valid on a given position");
    }
}