File: Cohost\CohostEndpointTest.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.VisualStudio.LanguageServices.Razor.UnitTests\Microsoft.VisualStudio.LanguageServices.Razor.UnitTests.csproj (Microsoft.VisualStudio.LanguageServices.Razor.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.Mef;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServer.ContainedLanguage;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Threading;
using Newtonsoft.Json.Linq;
using Xunit;
using Xunit.Abstractions;
using VSLSP = Microsoft.VisualStudio.LanguageServer.Protocol;
 
namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
 
public class CohostEndpointTest(ITestOutputHelper testOutputHelper) : ToolingTestBase(testOutputHelper)
{
    [Fact]
    public void EndpointsHaveUniqueLSPMethods()
    {
        var methods = new Dictionary<string, Type>();
 
        var endpoints = typeof(CohostColorPresentationEndpoint).Assembly.GetTypes()
            .Where(t => t.GetCustomAttribute<CohostEndpointAttribute>() != null)
            .Select(t => (t, t.GetCustomAttribute<CohostEndpointAttribute>().Method));
 
        foreach (var (endpointType, method) in endpoints)
        {
            if (methods.TryGetValue(method, out var existing))
            {
                Assert.Fail($"Could not register {endpointType.Name} for {method} because {existing.Name} already has.");
            }
 
            methods.Add(method, endpointType);
        }
    }
 
    [Fact]
    public void RegistrationsProvideFilter()
    {
        var testComposition = TestComposition.Roslyn
            .Add(TestComposition.Editor)
            .AddAssemblies(typeof(CohostLinkedEditingRangeEndpoint).Assembly)
            .AddAssemblies(typeof(DefaultLSPRequestInvoker).Assembly)
            .AddAssemblies(typeof(LanguageServerFeatureOptions).Assembly)
            .AddParts(typeof(TestILanguageServiceBroker2))
            .AddExcludedPartTypes(typeof(IWorkspaceProvider))
            .AddParts(typeof(TestWorkspaceProvider))
            .AddExcludedPartTypes(typeof(ILspEditorFeatureDetector))
            .AddParts(typeof(TestLspEditorFeatureDetector))
            .AddExcludedPartTypes(typeof(IIncompatibleProjectService))
            .AddParts(typeof(TestIncompatibleProjectService))
            .AddParts(typeof(TestVsServiceProvider));
 
        using var exportProvider = testComposition.ExportProviderFactory.CreateExportProvider();
 
        var providers = exportProvider.GetExportedValues<IDynamicRegistrationProvider>().ToList();
 
        // First we verify that the MEF composition above is correct, otherwise this test will be invalid
        var actualProviders = typeof(CohostLinkedEditingRangeEndpoint).Assembly.GetTypes().Where(t => !t.IsInterface && typeof(IDynamicRegistrationProvider).IsAssignableFrom(t)).ToList();
        Assert.Equal([.. actualProviders.OrderBy(a => a.Name).Select(r => r.Name)], [.. providers.OrderBy(e => e.GetType().Name).Select(r => r.GetType().Name)]);
 
        var clientCapabilities = new VSInternalClientCapabilities()
        {
            SupportsVisualStudioExtensions = true,
            TextDocument = new VSInternalTextDocumentClientCapabilities()
            {
                CodeAction = new() { DynamicRegistration = true },
                CodeLens = new() { DynamicRegistration = true },
                Completion = new() { DynamicRegistration = true },
                Definition = new() { DynamicRegistration = true },
                Diagnostic = new() { DynamicRegistration = true },
                DocumentHighlight = new() { DynamicRegistration = true },
                DocumentLink = new() { DynamicRegistration = true },
                DocumentSymbol = new() { DynamicRegistration = true },
                FoldingRange = new() { DynamicRegistration = true },
                Formatting = new() { DynamicRegistration = true },
                Hover = new() { DynamicRegistration = true },
                Implementation = new() { DynamicRegistration = true },
                InlayHint = new() { DynamicRegistration = true },
                LinkedEditingRange = new() { DynamicRegistration = true },
                OnAutoInsert = new() { DynamicRegistration = true },
                OnTypeFormatting = new() { DynamicRegistration = true },
                RangeFormatting = new() { DynamicRegistration = true },
                References = new() { DynamicRegistration = true },
                Rename = new() { DynamicRegistration = true },
                SemanticTokens = new() { DynamicRegistration = true },
                SignatureHelp = new() { DynamicRegistration = true },
                Synchronization = new() { DynamicRegistration = true },
                TypeDefinition = new() { DynamicRegistration = true }
            },
        };
 
        var clientCapabilitiesService = (RazorCohostClientCapabilitiesService)exportProvider.GetExportedValue<IClientCapabilitiesService>();
        clientCapabilitiesService.SetCapabilities(clientCapabilities);
 
        foreach (var endpoint in providers)
        {
            if (endpoint is CohostSemanticTokensRegistration)
            {
                // We can't currently test this, as the GetRegistrations method calls requestContext.GetRequiredService
                // and we can't create a request context ourselves
                continue;
            }
 
            var registrations = endpoint.GetRegistrations(clientCapabilities, requestContext: new());
 
            // If we didn't get any registrations then the test is probably invalid, and we need to update client capabilities above
            if (registrations.Length == 0)
            {
                Assert.Fail($"Did not get any registrations from {endpoint.GetType().Name}. Client capabilities might be wrong?");
            }
 
            Assert.All(registrations, registration => Assert.IsAssignableFrom<ITextDocumentRegistrationOptions>(registration.RegisterOptions));
        }
    }
 
    [Export(typeof(ILanguageServiceBroker2)), PartNotDiscoverable]
    private class TestILanguageServiceBroker2 : ILanguageServiceBroker2
    {
        public IEnumerable<ILanguageClientInstance> ActiveLanguageClients => throw new NotImplementedException();
        public IEnumerable<Lazy<ILanguageClient, IContentTypeMetadata>> FactoryLanguageClients => throw new NotImplementedException();
        public IEnumerable<Lazy<ILanguageClient, IContentTypeMetadata>> LanguageClients => throw new NotImplementedException();
 
        public event EventHandler<LanguageClientLoadedEventArgs> LanguageClientLoaded { add { } remove { } }
        public event AsyncEventHandler<LanguageClientNotifyEventArgs> ClientNotifyAsync { add { } remove { } }
 
        public void AddCustomBufferContentTypes(IEnumerable<string> contentTypes) => throw new NotImplementedException();
        public void AddLanguageClients(IEnumerable<Lazy<ILanguageClient, IContentTypeMetadata>> items) => throw new NotImplementedException();
        public Task LoadAsync(IContentTypeMetadata contentType, ILanguageClient client) => throw new NotImplementedException();
        public void Notify<T>(Notification<T> notification, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task NotifyAsync(ILanguageClient languageClient, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task OnDidChangeTextDocumentAsync(ITextSnapshot before, ITextSnapshot after, IEnumerable<ITextChange> textChanges) => throw new NotImplementedException();
        public Task OnDidCloseTextDocumentAsync(ITextSnapshot snapShot) => throw new NotImplementedException();
        public Task OnDidOpenTextDocumentAsync(ITextSnapshot snapShot) => throw new NotImplementedException();
        public Task OnDidSaveTextDocumentAsync(ITextDocument document) => throw new NotImplementedException();
        public void RemoveCustomBufferContentTypes(IEnumerable<string> contentTypes) => throw new NotImplementedException();
        public void RemoveLanguageClients(IEnumerable<Lazy<ILanguageClient, IContentTypeMetadata>> items) => throw new NotImplementedException();
        public IAsyncEnumerable<(string client, TResponse? response)> RequestAllAsync<TRequest, TResponse>(Request<TRequest, TResponse> request, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<(ILanguageClient?, JToken?)> RequestAsync(string[] contentTypes, Func<JToken, bool> capabilitiesFilter, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<(ILanguageClient?, JToken?)> RequestAsync(string[] contentTypes, Func<JToken, bool> capabilitiesFilter, string clientName, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<ManualInvocationResponse?> RequestAsync(ITextBuffer textBuffer, Func<JToken, bool> capabilitiesFilter, string languageServerName, string method, Func<ITextSnapshot, JToken> parameterFactory, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<JToken?> RequestAsync(ILanguageClient languageClient, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<TResponse?> RequestAsync<TRequest, TResponse>(Request<TRequest, TResponse> request, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<(ILanguageClient?, TOut)> RequestAsync<TIn, TOut>(string[] contentTypes, Func<VSLSP.ServerCapabilities, bool> capabilitiesFilter, VSLSP.LspRequest<TIn, TOut> method, TIn parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<TOut> RequestAsync<TIn, TOut>(ILanguageClient languageClient, VSLSP.LspRequest<TIn, TOut> method, TIn parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<IEnumerable<(ILanguageClient, JToken?)>> RequestMultipleAsync(string[] contentTypes, Func<JToken, bool> capabilitiesFilter, string method, JToken parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
        public IAsyncEnumerable<ManualInvocationResponse> RequestMultipleAsync(ITextBuffer textBuffer, Func<JToken, bool> capabilitiesFilter, string method, Func<ITextSnapshot, JToken> parameterFactory, CancellationToken cancellationToken) => throw new NotImplementedException();
        public Task<IEnumerable<(ILanguageClient, TOut)>> RequestMultipleAsync<TIn, TOut>(string[] contentTypes, Func<VSLSP.ServerCapabilities, bool> capabilitiesFilter, VSLSP.LspRequest<TIn, TOut> method, TIn parameters, CancellationToken cancellationToken) => throw new NotImplementedException();
    }
 
    [Export(typeof(IWorkspaceProvider)), PartNotDiscoverable]
    private class TestWorkspaceProvider : IWorkspaceProvider
    {
        public CodeAnalysis.Workspace GetWorkspace() => throw new NotImplementedException();
    }
 
    [Export(typeof(ILspEditorFeatureDetector)), PartNotDiscoverable]
    private class TestLspEditorFeatureDetector : ILspEditorFeatureDetector
    {
        public bool IsLiveShareHost() => throw new NotImplementedException();
        public bool IsLspEditorSupported(string documentFilePath) => throw new NotImplementedException();
        public CapabilityCheckResult IsDotNetCoreProject(string documentFilePath) => throw new NotImplementedException();
        public bool IsRemoteClient() => throw new NotImplementedException();
    }
 
    [Export(typeof(IRazorSemanticTokensRefreshQueue)), PartNotDiscoverable]
    private class TestRazorSemanticTokensRefreshQueue : IRazorSemanticTokensRefreshQueue
    {
        public void Initialize(string clientCapabilitiesString) => throw new NotImplementedException();
        public Task TryEnqueueRefreshComputationAsync(Project project, CancellationToken cancellationToken) => throw new NotImplementedException();
    }
}