/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs
543 строки
21 KB
Jared Parsons
Port more tests to run on Linux (#83052)
10 апр 2026, 00:39
Не верифицирован
10 апр 2026, 00:39
82a89d0
Код
Авторство
О чём код?
// 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.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; using Roslyn.Text.Adornments; using Xunit; using Xunit.Abstractions; using LSP = Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Completion; public sealed class CompletionResolveTests : AbstractLanguageServerProtocolTests { public CompletionResolveTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } [Theory, CombinatorialData] public async Task TestResolveCompletionItemFromListAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; var clientCapabilities = new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true, TextDocument = new TextDocumentClientCapabilities() { Completion = new VSInternalCompletionSetting() { CompletionList = new VSInternalCompletionListSetting() { Data = true, } } } }; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, clientCapabilities); var clientCompletionItem = await GetCompletionItemToResolveAsync<LSP.VSInternalCompletionItem>( testLspServer, label: "A").ConfigureAwait(false); var description = new ClassifiedTextElement(CreateClassifiedTextRunForClass("A")); var expected = CreateResolvedCompletionItem(clientCompletionItem, description, null); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); AssertJsonEquals(expected, results); } [Theory, CombinatorialData] public async Task TestResolveCompletionItemAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { {|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); var clientCompletionItem = await GetCompletionItemToResolveAsync<LSP.VSInternalCompletionItem>(testLspServer, label: "A").ConfigureAwait(false); var description = new ClassifiedTextElement(CreateClassifiedTextRunForClass("A")); var expected = CreateResolvedCompletionItem(clientCompletionItem, description, null); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); AssertJsonEquals(expected, results); } [Theory, CombinatorialData] public async Task TestResolveOverridesCompletionItemAsync(bool mutatingLspWorkspace) { var markup = """ abstract class A { public abstract void M(); } class B : A { override {|caret:|} } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); var clientCompletionItem = await GetCompletionItemToResolveAsync<LSP.VSInternalCompletionItem>(testLspServer, label: "M()").ConfigureAwait(false); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); Assert.NotNull(results.TextEdit); Assert.Null(results.InsertText); Assert.Equal(""" public override void M() { throw new System.NotImplementedException(); } """, results.TextEdit.Value.First.NewText); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/51125")] public async Task TestResolveOverridesCompletionItem_SnippetsEnabledAsync(bool mutatingLspWorkspace) { var markup = """ abstract class A { public abstract void M(); } class B : A { override {|caret:|} } """; // Explicitly enable snippets. This allows us to set the cursor with $0. Currently only applies to C# in Razor docs. var clientCapabilities = new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true, TextDocument = new LSP.TextDocumentClientCapabilities { Completion = new CompletionSetting { CompletionItem = new CompletionItemSetting { SnippetSupport = true } } } }; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, clientCapabilities); var clientCompletionItem = await GetCompletionItemToResolveAsync<LSP.VSInternalCompletionItem>( testLspServer, label: "M()").ConfigureAwait(false); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); Assert.NotNull(results.TextEdit); Assert.Null(results.InsertText); Assert.Equal(""" public override void M() { throw new System.NotImplementedException();$0 } """, results.TextEdit.Value.First.NewText); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/51125")] public async Task TestResolveOverridesCompletionItem_SnippetsEnabled_CaretOutOfSnippetScopeAsync(bool mutatingLspWorkspace) { var markup = """ abstract class A { public abstract void M(); } class B : A { override {|caret:|} } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var selectedItem = CodeAnalysis.Completion.CompletionItem.Create(displayText: "M", isComplexTextEdit: true); var (textEdit, _, _) = await CompletionResultFactory.GenerateComplexTextEditAsync( document, new TestCaretOutOfScopeCompletionService(testLspServer.TestWorkspace.Services.SolutionServices), selectedItem, snippetsSupported: true, insertNewPositionPlaceholder: true, CancellationToken.None).ConfigureAwait(false); Assert.Equal(""" public override void M() { throw new System.NotImplementedException(); } """, textEdit.NewText); } [Theory, CombinatorialData] public async Task TestResolveCompletionItemWithMarkupContentAsync(bool mutatingLspWorkspace) { var markup = """ class A { /// <summary> /// A cref <see cref="AMethod"/> /// <br/> /// <strong>strong text</strong> /// <br/> /// <em>italic text</em> /// <br/> /// <u>underline text</u> /// <para> /// <list type='bullet'> /// <item> /// <description>Item 1.</description> /// </item> /// <item> /// <description>Item 2.</description> /// </item> /// </list> /// <a href = "https://google.com" > link text</a> /// </para> /// </summary> void AMethod(int i) { } void M() { AMet{|caret:|} } } """; var clientCapabilities = new ClientCapabilities { TextDocument = new TextDocumentClientCapabilities { Completion = new CompletionSetting { CompletionItem = new CompletionItemSetting { DocumentationFormat = [MarkupKind.Markdown] } } } }; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, clientCapabilities); var clientCompletionItem = await GetCompletionItemToResolveAsync<LSP.CompletionItem>( testLspServer, label: "AMethod").ConfigureAwait(false); var results = await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); Assert.Equal(""" ```csharp void A.AMethod(int i) ``` A cref A\.AMethod\(int\) **strong text** _italic text_ <u>underline text</u> • Item 1\. • Item 2\. [link text](https://google.com) """, results.Documentation.Value.Second.Value); } [Theory, CombinatorialData] public async Task TestResolveCompletionItemWithPlainTextAsync(bool mutatingLspWorkspace) { var markup = """ class A { /// <summary> /// A cref <see cref="AMethod"/> /// <br/> /// <strong>strong text</strong> /// <br/> /// <em>italic text</em> /// <br/> /// <u>underline text</u> /// <para> /// <list type='bullet'> /// <item> /// <description>Item 1.</description> /// </item> /// <item> /// <description>Item 2.</description> /// </item> /// </list> /// <a href = "https://google.com" > link text</a> /// </para> /// </summary> void AMethod(int i) { } void M() { AMet{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace); var clientCompletionItem = await GetCompletionItemToResolveAsync<LSP.CompletionItem>( testLspServer, label: "AMethod").ConfigureAwait(false); var results = await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); Assert.Equal(""" void A.AMethod(int i) A cref A.AMethod(int) strong text italic text underline text • Item 1. • Item 2. link text """.NormalizeLineEndings(), results.Documentation.Value.Second.Value.NormalizeLineEndings()); } [Theory, CombinatorialData] public async Task TestResolveCompletionItemWithPrefixSuffixAsync(bool mutatingLspWorkspace) { var markup = """ class A { void M() { var a = 10; a.{|caret:|} } } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); var clientCompletionItem = await GetCompletionItemToResolveAsync<LSP.VSInternalCompletionItem>(testLspServer, label: "(byte)").ConfigureAwait(false); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); Assert.Equal("(byte)", results.Label); Assert.NotNull(results.Description); } [Theory, CombinatorialData] public async Task TestSemanticSnippetChangeAsync(bool mutatingLspWorkspace) { var markup = """ using System; public class Program { {|editRange:svm|}{|caret:|} } """; await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }); testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.CSharp, SnippetsRule.AlwaysInclude); testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp, true); var clientCompletionItem = await GetCompletionItemToResolveAsync<LSP.VSInternalCompletionItem>(testLspServer, label: "svm").ConfigureAwait(false); Assert.True(clientCompletionItem.VsResolveTextEditOnCommit); var results = (LSP.VSInternalCompletionItem)await RunResolveCompletionItemAsync( testLspServer, clientCompletionItem).ConfigureAwait(false); Assert.NotNull(results.TextEdit); Assert.Null(results.InsertText); Assert.Equal("static void Main(string[] args)\r\n {\r\n \r\n }", results.TextEdit.Value.First.NewText.NormalizeLineEndings()); var editRange = testLspServer.GetLocations("editRange").Single().Range; Assert.Equal(editRange, results.TextEdit.Value.First.Range); } private static async Task<LSP.CompletionItem> RunResolveCompletionItemAsync(TestLspServer testLspServer, LSP.CompletionItem completionItem) { return await testLspServer.ExecuteRequestAsync<LSP.CompletionItem, LSP.CompletionItem>(LSP.Methods.TextDocumentCompletionResolveName, completionItem, CancellationToken.None); } private static VSInternalCompletionItem Clone(VSInternalCompletionItem completionItem) { return new VSInternalCompletionItem() { Label = completionItem.Label, LabelDetails = completionItem.LabelDetails, Kind = completionItem.Kind, Detail = completionItem.Detail, Documentation = completionItem.Documentation, Preselect = completionItem.Preselect, SortText = completionItem.SortText, FilterText = completionItem.FilterText, InsertText = completionItem.InsertText, InsertTextFormat = completionItem.InsertTextFormat, TextEdit = completionItem.TextEdit, TextEditText = completionItem.TextEditText, AdditionalTextEdits = completionItem.AdditionalTextEdits, CommitCharacters = completionItem.CommitCharacters, Command = completionItem.Command, Data = completionItem.Data, Icon = completionItem.Icon, Description = completionItem.Description, VsCommitCharacters = completionItem.VsCommitCharacters, VsResolveTextEditOnCommit = completionItem.VsResolveTextEditOnCommit, }; } private static VSInternalCompletionItem CreateResolvedCompletionItem( VSInternalCompletionItem completionItem, ClassifiedTextElement description, string documentation) { var expectedCompletionItem = Clone(completionItem); if (documentation != null) { expectedCompletionItem.Documentation = new MarkupContent() { Kind = LSP.MarkupKind.PlainText, Value = documentation }; } expectedCompletionItem.Description = description; return expectedCompletionItem; } private static ClassifiedTextRun[] CreateClassifiedTextRunForClass(string className) => [ new ClassifiedTextRun("whitespace", string.Empty), new ClassifiedTextRun("keyword", "class"), new ClassifiedTextRun("whitespace", " "), new ClassifiedTextRun("class name", className), new ClassifiedTextRun("whitespace", string.Empty), ]; private static async Task<T> GetCompletionItemToResolveAsync<T>( TestLspServer testLspServer, string label) where T : LSP.CompletionItem { var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), LSP.VSInternalCompletionInvokeKind.Explicit, "\0", LSP.CompletionTriggerKind.Invoked); var completionList = await RunGetCompletionsAsync(testLspServer, completionParams); if (testLspServer.ClientCapabilities.HasCompletionListDataCapability()) { var vsCompletionList = Assert.IsAssignableFrom<VSInternalCompletionList>(completionList); Assert.NotNull(vsCompletionList.Data); } var clientCompletionItem = (T)completionList.Items.FirstOrDefault(item => item.Label == label); return clientCompletionItem; } private static async Task<LSP.CompletionList> RunGetCompletionsAsync( TestLspServer testLspServer, LSP.CompletionParams completionParams) { var completionList = await testLspServer.ExecuteRequestAsync<LSP.CompletionParams, LSP.CompletionList>(LSP.Methods.TextDocumentCompletionName, completionParams, CancellationToken.None); // Emulate client behavior of promoting "Data" completion list properties onto completion items. if (testLspServer.ClientCapabilities.HasCompletionListDataCapability() && completionList is VSInternalCompletionList vsCompletionList && vsCompletionList.Data != null) { foreach (var completionItem in completionList.Items) { Assert.Null(completionItem.Data); completionItem.Data = vsCompletionList.Data; } } return completionList; } private sealed class TestCaretOutOfScopeCompletionService : CompletionService { public TestCaretOutOfScopeCompletionService(SolutionServices services) : base(services, AsynchronousOperationListenerProvider.NullProvider) { } public override string Language => LanguageNames.CSharp; internal override async Task<CodeAnalysis.Completion.CompletionList> GetCompletionsAsync(Document document, int caretPosition, CodeAnalysis.Completion.CompletionOptions options, OptionSet passThroughOptions, CompletionTrigger trigger = default, ImmutableHashSet<string> roles = null, CancellationToken cancellationToken = default) => CodeAnalysis.Completion.CompletionList.Empty; public override async Task<CompletionChange> GetChangeAsync( Document document, CodeAnalysis.Completion.CompletionItem item, char? commitCharacter = null, CancellationToken cancellationToken = default) { var text = await document.GetTextAsync(cancellationToken); var start = text.ToString().IndexOf("override "); var textChange = new TextChange(span: new TextSpan(start: start, length: 9), newText: """ public override void M() { throw new System.NotImplementedException(); } """); return CompletionChange.Create(textChange, newPosition: 0); } internal override bool ShouldTriggerCompletion(Project project, LanguageServices languageServices, SourceText text, int caretPosition, CompletionTrigger trigger, CodeAnalysis.Completion.CompletionOptions options, OptionSet passthroughOptions, ImmutableHashSet<string> roles = null) => false; internal override CompletionRules GetRules(CodeAnalysis.Completion.CompletionOptions options) => CompletionRules.Default; internal override async Task<CompletionDescription> GetDescriptionAsync(Document document, CodeAnalysis.Completion.CompletionItem item, CodeAnalysis.Completion.CompletionOptions options, SymbolDescriptionOptions displayOptions, CancellationToken cancellationToken = default) => CompletionDescription.Empty; } }