File: Snippets\RoslynLSPSnippetConvertTests.cs
Web Access
Project: src\src\EditorFeatures\Test\Microsoft.CodeAnalysis.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.EditorFeatures.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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.UnitTests.Snippets;
 
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)]
public class RoslynLSPSnippetConvertTests
{
    #region Edgecase extend TextChange tests
 
    [Fact]
    public Task TestExtendSnippetTextChangeForwardsForCaret()
        => TestAsync("""
            [|if ({|placeholder:true|})
            {
            }|] $$
            """, """
            if (${1:true})
            {
            } $0
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeBackwardsForCaret()
        => TestAsync("""
            $$ [|if ({|placeholder:true|})
            {
            }|]
            """, """
            $0 if (${1:true})
            {
            }
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeForwardsForPlaceholder()
        => TestAsync("""
            [|if (true)
            {$$
            }|] {|placeholder:test|}
            """, """
            if (true)
            {$0
            } ${1:test}
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeBackwardsForPlaceholder()
        => TestAsync("""
            {|placeholder:test|} [|if (true)
            {$$
            }|]
            """, """
            ${1:test} if (true)
            {$0
            }
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeForwardsForPlaceholderThenCaret()
        => TestAsync("""
            [|if (true)
            {
            }|] {|placeholder:test|} $$
            """, """
            if (true)
            {
            } ${1:test} $0
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeForwardsForCaretThenPlaceholder()
        => TestAsync("""
            [|if (true)
            {
            }|] $$ {|placeholder:test|}
            """, """
            if (true)
            {
            } $0 ${1:test}
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeBackwardsForPlaceholderThenCaret()
        => TestAsync("""
            {|placeholder:test|} $$ [|if (true)
            {
            }|]
            """, """
            ${1:test} $0 if (true)
            {
            }
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeBackwardsForCaretThenPlaceholder()
        => TestAsync("""
            $$ {|placeholder:test|} [|if (true)
            {
            }|]
            """, """
            $0 ${1:test} if (true)
            {
            }
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeBackwardsForCaretForwardsForPlaceholder()
        => TestAsync("""
            $$ [|if (true)
            {
            }|] {|placeholder:test|}
            """, """
            $0 if (true)
            {
            } ${1:test}
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeBackwardsForPlaceholderForwardsForCaret()
        => TestAsync("""
            {|placeholder:test|} [|if (true)
            {
            }|] $$
            """, """
            ${1:test} if (true)
            {
            } $0
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodForwardsForCaret()
        => TestAsync("""
            public void Method()
            {
                [|if ({|placeholder:true|})
                {
                }|] $$
            }
            """, """
            if (${1:true})
                {
                } $0
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodBackwardsForCaret()
        => TestAsync("""
            public void Method()
            {
                $$ [|if ({|placeholder:true|})
                {
                }|]
            }
            """, """
            $0 if (${1:true})
                {
                }
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodForwardsForPlaceholder()
        => TestAsync("""
            public void Method()
            {
                [|if (true)
                 {$$
                 }|] {|placeholder:test|}
            }
            """, """
            if (true)
                 {$0
                 } ${1:test}
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholder()
        => TestAsync("""
            public void Method()
            {
                {|placeholder:test|} [|if (true)
                {$$
                }|]
            """, """
            ${1:test} if (true)
                {$0
                }
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodForwardsForPlaceholderThenCaret()
        => TestAsync("""
            public void Method()
            {
                [|if (true)
                {
                }|] {|placeholder:test|} $$
            }
            """, """
            if (true)
                {
                } ${1:test} $0
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodForwardsForCaretThenPlaceholder()
        => TestAsync("""
            public void Method()
            {
                [|if (true)
                {
                }|] $$ {|placeholder:test|}
            }
            """, """
            if (true)
                {
                } $0 ${1:test}
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderThenCaret()
        => TestAsync("""
            public void Method()
            {
                {|placeholder:test|} $$ [|if (true)
                {
                }|]
            }
            """, """
            ${1:test} $0 if (true)
                {
                }
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodBackwardsForCaretThenPlaceholder()
        => TestAsync("""
            public void Method()
            {
                $$ {|placeholder:test|} [|if (true)
                {
                }|]
            }
            """, """
            $0 ${1:test} if (true)
                {
                }
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodBackwardsForCaretForwardsForPlaceholder()
        => TestAsync("""
            public void Method()
            {
                $$ [|if (true)
                {
                }|] {|placeholder:test|}
            }
            """, """
            $0 if (true)
                {
                } ${1:test}
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderForwardsForCaret()
        => TestAsync("""
            public void Method()
            {
                {|placeholder:test|} [|if (true)
                {
                }|] $$
            }
            """, """
            ${1:test} if (true)
                {
                } $0
            """);
 
    [Fact]
    public Task TestExtendSnippetTextChangeInMethodWithCodeBeforeAndAfterBackwardsForPlaceholderForwardsForCaret()
        => TestAsync("""
            public void Method()
            {
                var x = 5;
                {|placeholder:test|} [|if (true)
                {
                }|] $$
 
                x = 3;
            }
            """, """
            ${1:test} if (true)
                {
                } $0
            """);
 
    [Fact]
    public void TestExtendTextChangeInsertion()
    {
        var testString = "foo bar quux baz";
        using var workspace = CreateWorkspaceFromCode(testString);
        var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id);
        var lspSnippetString = RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, caretPosition: 12,
            [], new TextChange(new TextSpan(8, 0), "quux"), triggerLocation: 12, CancellationToken.None).Result;
        AssertEx.EqualOrDiff("quux$0", lspSnippetString);
    }
 
    [Fact]
    public void TestExtendTextChangeReplacement()
    {
        var testString = "foo bar quux baz";
        using var workspace = CreateWorkspaceFromCode(testString);
        var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id);
        var lspSnippetString = RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, caretPosition: 12,
            [], new TextChange(new TextSpan(4, 4), "bar quux"), triggerLocation: 12, CancellationToken.None).Result;
        AssertEx.EqualOrDiff("bar quux$0", lspSnippetString);
    }
 
    #endregion
 
    #region LSP Snippet generation tests
 
    [Fact]
    public Task TestForLoopSnippet()
        => TestAsync("""
            [|for (var {|placeholder1:i|} = 0; {|placeholder1:i|} < {|placeholder2:length|}; {|placeholder1:i|}++)
            {$$
            }|]
            """, """
            for (var ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++)
            {$0
            }
            """);
 
    [Fact]
    public Task TestIfSnippetSamePlaceholderCursorLocation()
        => TestAsync("""
            public void Method()
            {
                var x = 5;
                [|if ({|placeholder:true|}$$)
                {
                }|]
 
                x = 3;
            }
            """, """
            if (${1:true}$0)
                {
                }
            """);
 
    [Fact]
    public Task TestIfSnippetSameCursorPlaceholderLocation()
        => TestAsync("""
            public void Method()
            {
                var x = 5;
                [|if ($${|placeholder:true|})
                {
                }|]
 
                x = 3;
            }
            """, """
            if ($0${1:true})
                {
                }
            """);
 
    #endregion
 
    protected static TestWorkspace CreateWorkspaceFromCode(string code)
     => TestWorkspace.CreateCSharp(code);
 
    private static async Task TestAsync(string markup, string output)
    {
        MarkupTestFile.GetPositionAndSpans(markup, out var text, out var cursorPosition, out IDictionary<string, ImmutableArray<TextSpan>> placeholderDictionary);
        var stringSpan = placeholderDictionary[""].First();
        var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), text.Substring(stringSpan.Start, stringSpan.Length));
        var placeholders = GetSnippetPlaceholders(text, placeholderDictionary);
        using var workspace = CreateWorkspaceFromCode(markup);
        var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id);
 
        var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, cursorPosition!.Value, placeholders, textChange, stringSpan.Start, CancellationToken.None).ConfigureAwait(false);
        AssertEx.EqualOrDiff(output, lspSnippetString);
    }
 
    private static ImmutableArray<SnippetPlaceholder> GetSnippetPlaceholders(string text, IDictionary<string, ImmutableArray<TextSpan>> placeholderDictionary)
    {
        using var _ = ArrayBuilder<SnippetPlaceholder>.GetInstance(out var arrayBuilder);
        foreach (var kvp in placeholderDictionary)
        {
            if (kvp.Key.Length > 0)
            {
                var spans = kvp.Value;
                var placeholderText = text.Substring(spans[0].Start, spans[0].Length);
                var placeholders = spans.Select(span => span.Start).ToImmutableArray();
                arrayBuilder.Add(new SnippetPlaceholder(placeholderText, placeholders));
            }
        }
 
        return arrayBuilder.ToImmutableAndClear();
    }
}