|
// 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()
{
var markup =
@"[|if ({|placeholder:true|})
{
}|] $$";
var expectedLSPSnippet =
@"if (${1:true})
{
} $0";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeBackwardsForCaret()
{
var markup =
@"$$ [|if ({|placeholder:true|})
{
}|]";
var expectedLSPSnippet =
@"$0 if (${1:true})
{
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeForwardsForPlaceholder()
{
var markup =
@"[|if (true)
{$$
}|] {|placeholder:test|}";
var expectedLSPSnippet =
@"if (true)
{$0
} ${1:test}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeBackwardsForPlaceholder()
{
var markup =
@"{|placeholder:test|} [|if (true)
{$$
}|]";
var expectedLSPSnippet =
@"${1:test} if (true)
{$0
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeForwardsForPlaceholderThenCaret()
{
var markup =
@"[|if (true)
{
}|] {|placeholder:test|} $$";
var expectedLSPSnippet =
@"if (true)
{
} ${1:test} $0";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeForwardsForCaretThenPlaceholder()
{
var markup =
@"[|if (true)
{
}|] $$ {|placeholder:test|}";
var expectedLSPSnippet =
@"if (true)
{
} $0 ${1:test}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeBackwardsForPlaceholderThenCaret()
{
var markup =
@"{|placeholder:test|} $$ [|if (true)
{
}|]";
var expectedLSPSnippet =
@"${1:test} $0 if (true)
{
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeBackwardsForCaretThenPlaceholder()
{
var markup =
@"$$ {|placeholder:test|} [|if (true)
{
}|]";
var expectedLSPSnippet =
@"$0 ${1:test} if (true)
{
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeBackwardsForCaretForwardsForPlaceholder()
{
var markup =
@"$$ [|if (true)
{
}|] {|placeholder:test|}";
var expectedLSPSnippet =
@"$0 if (true)
{
} ${1:test}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeBackwardsForPlaceholderForwardsForCaret()
{
var markup =
@"{|placeholder:test|} [|if (true)
{
}|] $$";
var expectedLSPSnippet =
@"${1:test} if (true)
{
} $0";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodForwardsForCaret()
{
var markup =
@"public void Method()
{
[|if ({|placeholder:true|})
{
}|] $$
}";
var expectedLSPSnippet =
@"if (${1:true})
{
} $0";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodBackwardsForCaret()
{
var markup =
@"public void Method()
{
$$ [|if ({|placeholder:true|})
{
}|]
}";
var expectedLSPSnippet =
@"$0 if (${1:true})
{
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodForwardsForPlaceholder()
{
var markup =
@"public void Method()
{
[|if (true)
{$$
}|] {|placeholder:test|}
}";
var expectedLSPSnippet =
@"if (true)
{$0
} ${1:test}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholder()
{
var markup =
@"public void Method()
{
{|placeholder:test|} [|if (true)
{$$
}|]";
var expectedLSPSnippet =
@"${1:test} if (true)
{$0
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodForwardsForPlaceholderThenCaret()
{
var markup =
@"public void Method()
{
[|if (true)
{
}|] {|placeholder:test|} $$
}";
var expectedLSPSnippet =
@"if (true)
{
} ${1:test} $0";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodForwardsForCaretThenPlaceholder()
{
var markup =
@"public void Method()
{
[|if (true)
{
}|] $$ {|placeholder:test|}
}";
var expectedLSPSnippet =
@"if (true)
{
} $0 ${1:test}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderThenCaret()
{
var markup =
@"public void Method()
{
{|placeholder:test|} $$ [|if (true)
{
}|]
}";
var expectedLSPSnippet =
@"${1:test} $0 if (true)
{
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodBackwardsForCaretThenPlaceholder()
{
var markup =
@"public void Method()
{
$$ {|placeholder:test|} [|if (true)
{
}|]
}";
var expectedLSPSnippet =
@"$0 ${1:test} if (true)
{
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodBackwardsForCaretForwardsForPlaceholder()
{
var markup =
@"public void Method()
{
$$ [|if (true)
{
}|] {|placeholder:test|}
}";
var expectedLSPSnippet =
@"$0 if (true)
{
} ${1:test}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderForwardsForCaret()
{
var markup =
@"public void Method()
{
{|placeholder:test|} [|if (true)
{
}|] $$
}";
var expectedLSPSnippet =
@"${1:test} if (true)
{
} $0";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestExtendSnippetTextChangeInMethodWithCodeBeforeAndAfterBackwardsForPlaceholderForwardsForCaret()
{
var markup =
@"public void Method()
{
var x = 5;
{|placeholder:test|} [|if (true)
{
}|] $$
x = 3;
}";
var expectedLSPSnippet =
@"${1:test} if (true)
{
} $0";
return TestAsync(markup, expectedLSPSnippet);
}
[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,
ImmutableArray<SnippetPlaceholder>.Empty, 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,
ImmutableArray<SnippetPlaceholder>.Empty, 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()
{
var markup =
@"[|for (var {|placeholder1:i|} = 0; {|placeholder1:i|} < {|placeholder2:length|}; {|placeholder1:i|}++)
{$$
}|]";
var expectedLSPSnippet =
@"for (var ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++)
{$0
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestIfSnippetSamePlaceholderCursorLocation()
{
var markup =
@"public void Method()
{
var x = 5;
[|if ({|placeholder:true|}$$)
{
}|]
x = 3;
}";
var expectedLSPSnippet =
@"if (${1:true}$0)
{
}";
return TestAsync(markup, expectedLSPSnippet);
}
[Fact]
public Task TestIfSnippetSameCursorPlaceholderLocation()
{
var markup =
@"public void Method()
{
var x = 5;
[|if ($${|placeholder:true|})
{
}|]
x = 3;
}";
var expectedLSPSnippet =
@"if ($0${1:true})
{
}";
return TestAsync(markup, expectedLSPSnippet);
}
#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();
}
}
|