/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/Test/Completion/CompletionServiceTests.cs
149 строк
7 KB
Cyrus Najmabadi
Reapply "Update methods to be `async`." (#81808)
31 дек 2025, 01:59
31 дек 2025, 01:59
3d73546
Код
Авторство
О чём код?
// 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; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Xunit; namespace Microsoft.CodeAnalysis.Editor.UnitTests.Completion; [UseExportProvider] public sealed class CompletionServiceTests { [Fact] public async Task TestNuGetCompletionProvider() { var code = """ using System.Diagnostics; class Test { void Method() { Debug.Assert(true, "$$"); } } """; using var workspace = TestWorkspace.CreateCSharp(code, openDocuments: true); var nugetCompletionProvider = new DebugAssertTestCompletionProvider(); var reference = new MockAnalyzerReference(nugetCompletionProvider); var project = workspace.CurrentSolution.Projects.Single().AddAnalyzerReference(reference); var completionService = project.Services.GetRequiredService<CompletionService>(); var document = project.Documents.Single(); var caretPosition = workspace.DocumentWithCursor.CursorPosition ?? throw new InvalidOperationException(); var completions = await completionService.GetCompletionsAsync(document, caretPosition, CompletionOptions.Default, OptionSet.Empty); // NuGet providers are not included until it's loaded and cached, this is to avoid potential delays, especially on UI thread. Assert.Empty(completions.ItemsList); // NuGet analyzers for the project will be loaded when this returns var waiter = workspace.ExportProvider.GetExportedValue<AsynchronousOperationListenerProvider>().GetWaiter(FeatureAttribute.CompletionSet); await waiter.ExpeditedWaitAsync(); completions = await completionService.GetCompletionsAsync(document, caretPosition, CompletionOptions.Default, OptionSet.Empty); Assert.NotEmpty(completions.ItemsList); var item = Assert.Single(completions.ItemsList, item => item.ProviderName == typeof(DebugAssertTestCompletionProvider).FullName); Assert.Equal(nameof(DebugAssertTestCompletionProvider), item.DisplayText); var expectedDescriptionText = nameof(DebugAssertTestCompletionProvider); var actualDescriptionText = (await completionService.GetDescriptionAsync(document, item, CompletionOptions.Default, SymbolDescriptionOptions.Default).ConfigureAwait(false))!.Text; Assert.Equal(expectedDescriptionText, actualDescriptionText); var expectedChange = new TextChange(item.Span, nameof(DebugAssertTestCompletionProvider)); var actualChange = (await completionService.GetChangeAsync(document, item).ConfigureAwait(false)).TextChange; Assert.Equal(expectedChange, actualChange); } private sealed class MockAnalyzerReference : AnalyzerReference, ICompletionProviderFactory { private readonly CompletionProvider _completionProvider; public MockAnalyzerReference(CompletionProvider completionProvider) { _completionProvider = completionProvider; } public override string FullPath => ""; public override object Id => nameof(MockAnalyzerReference); public override ImmutableArray<DiagnosticAnalyzer> GetAnalyzers(string language) => []; public override ImmutableArray<DiagnosticAnalyzer> GetAnalyzersForAllLanguages() => []; public ImmutableArray<CompletionProvider> GetCompletionProviders() => [_completionProvider]; } private sealed class DebugAssertTestCompletionProvider : CompletionProvider { public DebugAssertTestCompletionProvider() { } public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options) { return trigger.Kind switch { CompletionTriggerKind.Invoke => true, CompletionTriggerKind.InvokeAndCommitIfUnique => true, CompletionTriggerKind.Insertion => trigger.Character == '"', _ => false, }; } public override async Task ProvideCompletionsAsync(CompletionContext context) { var completionItem = CompletionItem.Create(displayText: nameof(DebugAssertTestCompletionProvider), displayTextSuffix: "", rules: CompletionItemRules.Default); context.AddItem(completionItem); context.CompletionListSpan = await GetTextChangeSpanAsync(context.Document, context.CompletionListSpan, context.CancellationToken).ConfigureAwait(false); } public override async Task<CompletionDescription> GetDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken) { return CompletionDescription.FromText(nameof(DebugAssertTestCompletionProvider)); } public override async Task<CompletionChange> GetChangeAsync(Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken) { return CompletionChange.Create(new TextChange(item.Span, nameof(DebugAssertTestCompletionProvider))); } private static async Task<TextSpan> GetTextChangeSpanAsync(Document document, TextSpan startSpan, CancellationToken cancellationToken) { var result = startSpan; var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>(); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var token = root.FindToken(result.Start); if (syntaxFacts.IsStringLiteral(token) || syntaxFacts.IsVerbatimStringLiteral(token)) { var text = root.GetText(); // Expand selection in both directions until a double quote or any line break character is reached static bool IsWordCharacter(char ch) => !(ch == '"' || TextUtilities.IsAnyLineBreakCharacter(ch)); result = CommonCompletionUtilities.GetWordSpan( text, startSpan.Start, IsWordCharacter, IsWordCharacter, alwaysExtendEndSpan: true); } return result; } } }