diff --git a/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs index 19594816f1b9c..5b811b7126bae 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs @@ -28,12 +28,12 @@ public HandlerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) } protected override TestComposition Composition => base.Composition.AddParts( - typeof(DocumentHandler), - typeof(RequestHandlerWithNoParams), - typeof(NotificationHandler), - typeof(NotificationWithoutParamsHandler), - typeof(LanguageSpecificHandler), - typeof(LanguageSpecificHandlerWithDifferentParams)); + typeof(TestDocumentHandler), + typeof(TestRequestHandlerWithNoParams), + typeof(TestNotificationHandlerFactory), + typeof(TestNotificationWithoutParamsHandlerFactory), + typeof(TestLanguageSpecificHandler), + typeof(TestLanguageSpecificHandlerWithDifferentParams)); [Theory, CombinatorialData] public async Task CanExecuteRequestHandler(bool mutatingLspWorkspace) @@ -44,8 +44,8 @@ public async Task CanExecuteRequestHandler(bool mutatingLspWorkspace) { Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\test.cs") }); - var response = await server.ExecuteRequestAsync(DocumentHandler.MethodName, request, CancellationToken.None); - Assert.Equal(typeof(DocumentHandler).Name, response); + var response = await server.ExecuteRequestAsync(TestDocumentHandler.MethodName, request, CancellationToken.None); + Assert.Equal(typeof(TestDocumentHandler).Name, response); } [Theory, CombinatorialData] @@ -53,8 +53,8 @@ public async Task CanExecuteRequestHandlerWithNoParams(bool mutatingLspWorkspace { await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); - var response = await server.ExecuteRequest0Async(RequestHandlerWithNoParams.MethodName, CancellationToken.None); - Assert.Equal(typeof(RequestHandlerWithNoParams).Name, response); + var response = await server.ExecuteRequest0Async(TestRequestHandlerWithNoParams.MethodName, CancellationToken.None); + Assert.Equal(typeof(TestRequestHandlerWithNoParams).Name, response); } [Theory, CombinatorialData] @@ -66,9 +66,10 @@ public async Task CanExecuteNotificationHandler(bool mutatingLspWorkspace) { Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\test.cs") }); - await server.ExecuteNotificationAsync(NotificationHandler.MethodName, request); - var response = await NotificationHandler.ResultSource.Task; - Assert.Equal(typeof(NotificationHandler).Name, response); + + await server.ExecuteNotificationAsync(TestNotificationHandler.MethodName, request); + var response = await server.GetRequiredLspService().ResultSource.Task; + Assert.Equal(typeof(TestNotificationHandler).Name, response); } [Theory, CombinatorialData] @@ -76,9 +77,9 @@ public async Task CanExecuteNotificationHandlerWithNoParams(bool mutatingLspWork { await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); - await server.ExecuteNotification0Async(NotificationWithoutParamsHandler.MethodName); - var response = await NotificationWithoutParamsHandler.ResultSource.Task; - Assert.Equal(typeof(NotificationWithoutParamsHandler).Name, response); + await server.ExecuteNotification0Async(TestNotificationWithoutParamsHandler.MethodName); + var response = await server.GetRequiredLspService().ResultSource.Task; + Assert.Equal(typeof(TestNotificationWithoutParamsHandler).Name, response); } [Theory, CombinatorialData] @@ -90,8 +91,8 @@ public async Task CanExecuteLanguageSpecificHandler(bool mutatingLspWorkspace) { Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\test.fs") }); - var response = await server.ExecuteRequestAsync(DocumentHandler.MethodName, request, CancellationToken.None); - Assert.Equal(typeof(LanguageSpecificHandler).Name, response); + var response = await server.ExecuteRequestAsync(TestDocumentHandler.MethodName, request, CancellationToken.None); + Assert.Equal(typeof(TestLanguageSpecificHandler).Name, response); } [Theory, CombinatorialData] @@ -103,15 +104,15 @@ public async Task CanExecuteLanguageSpecificHandlerWithDifferentRequestTypes(boo { Uri = ProtocolConversions.CreateAbsoluteUri(@"C:\test.vb") }); - var response = await server.ExecuteRequestAsync(DocumentHandler.MethodName, request, CancellationToken.None); - Assert.Equal(typeof(LanguageSpecificHandlerWithDifferentParams).Name, response); + var response = await server.ExecuteRequestAsync(TestDocumentHandler.MethodName, request, CancellationToken.None); + Assert.Equal(typeof(TestLanguageSpecificHandlerWithDifferentParams).Name, response); } [Theory, CombinatorialData] public async Task ThrowsOnInvalidLanguageSpecificHandler(bool mutatingLspWorkspace) { // Arrange - await Assert.ThrowsAsync(async () => await CreateTestLspServerAsync("", mutatingLspWorkspace, extraExportedTypes: [typeof(DuplicateLanguageSpecificHandler)])); + await Assert.ThrowsAsync(async () => await CreateTestLspServerAsync("", mutatingLspWorkspace, extraExportedTypes: [typeof(TestDuplicateLanguageSpecificHandler)])); } [Theory, CombinatorialData] @@ -120,7 +121,7 @@ public async Task ThrowsIfDeserializationFails(bool mutatingLspWorkspace) await using var server = await CreateTestLspServerAsync("", mutatingLspWorkspace); var request = new TestRequestTypeThree("value"); - await Assert.ThrowsAsync(async () => await server.ExecuteRequestAsync(DocumentHandler.MethodName, request, CancellationToken.None)); + await Assert.ThrowsAsync(async () => await server.ExecuteRequestAsync(TestDocumentHandler.MethodName, request, CancellationToken.None)); } [DataContract] @@ -132,17 +133,13 @@ internal record TestRequestTypeTwo([property: DataMember(Name = "textDocument"), [DataContract] internal record TestRequestTypeThree([property: DataMember(Name = "someValue")] string SomeValue); - [ExportCSharpVisualBasicStatelessLspService(typeof(DocumentHandler)), PartNotDiscoverable, Shared] + [ExportCSharpVisualBasicStatelessLspService(typeof(TestDocumentHandler)), PartNotDiscoverable, Shared] [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] - internal class DocumentHandler : ILspServiceDocumentRequestHandler + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestDocumentHandler() : ILspServiceDocumentRequestHandler { - public const string MethodName = nameof(DocumentHandler); - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DocumentHandler() - { - } + public const string MethodName = nameof(TestDocumentHandler); public bool MutatesSolutionState => true; public bool RequiresLSPSolution => true; @@ -158,17 +155,13 @@ public Task HandleRequestAsync(TestRequestTypeOne request, RequestContex } } - [ExportCSharpVisualBasicStatelessLspService(typeof(RequestHandlerWithNoParams)), PartNotDiscoverable, Shared] + [ExportCSharpVisualBasicStatelessLspService(typeof(TestRequestHandlerWithNoParams)), PartNotDiscoverable, Shared] [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] - internal class RequestHandlerWithNoParams : ILspServiceRequestHandler + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestRequestHandlerWithNoParams() : ILspServiceRequestHandler { - public const string MethodName = nameof(RequestHandlerWithNoParams); - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RequestHandlerWithNoParams() - { - } + public const string MethodName = nameof(TestRequestHandlerWithNoParams); public bool MutatesSolutionState => true; public bool RequiresLSPSolution => true; @@ -179,18 +172,11 @@ public Task HandleRequestAsync(RequestContext context, CancellationToken } } - [ExportCSharpVisualBasicStatelessLspService(typeof(NotificationHandler)), PartNotDiscoverable, Shared] [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] - internal class NotificationHandler : ILspServiceNotificationHandler + internal sealed class TestNotificationHandler() : ILspServiceNotificationHandler { - public const string MethodName = nameof(NotificationHandler); - public static readonly TaskCompletionSource ResultSource = new(); - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public NotificationHandler() - { - } + public const string MethodName = nameof(TestNotificationHandler); + public readonly TaskCompletionSource ResultSource = new(); public bool MutatesSolutionState => true; public bool RequiresLSPSolution => true; @@ -198,22 +184,29 @@ public NotificationHandler() public Task HandleNotificationAsync(TestRequestTypeOne request, RequestContext context, CancellationToken cancellationToken) { ResultSource.SetResult(this.GetType().Name); - return ResultSource.Task; + return Task.CompletedTask; } } - [ExportCSharpVisualBasicStatelessLspService(typeof(NotificationWithoutParamsHandler)), PartNotDiscoverable, Shared] - [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] - internal class NotificationWithoutParamsHandler : ILspServiceNotificationHandler + /// + /// Exported via a factory as we need a new instance for each server (the task completion result should be unique per server). + /// + [ExportCSharpVisualBasicLspServiceFactory(typeof(TestNotificationHandler)), PartNotDiscoverable, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestNotificationHandlerFactory() : ILspServiceFactory { - public const string MethodName = nameof(NotificationWithoutParamsHandler); - public static readonly TaskCompletionSource ResultSource = new(); - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public NotificationWithoutParamsHandler() + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { + return new TestNotificationHandler(); } + } + + [LanguageServerEndpoint(MethodName, LanguageServerConstants.DefaultLanguageName)] + internal sealed class TestNotificationWithoutParamsHandler() : ILspServiceNotificationHandler + { + public const string MethodName = nameof(TestNotificationWithoutParamsHandler); + public readonly TaskCompletionSource ResultSource = new(); public bool MutatesSolutionState => true; public bool RequiresLSPSolution => true; @@ -221,23 +214,33 @@ public NotificationWithoutParamsHandler() public Task HandleNotificationAsync(RequestContext context, CancellationToken cancellationToken) { ResultSource.SetResult(this.GetType().Name); - return ResultSource.Task; + return Task.CompletedTask; } } /// - /// Defines a language specific handler with the same method as + /// Exported via a factory as we need a new instance for each server (the task completion result should be unique per server). /// - [ExportCSharpVisualBasicStatelessLspService(typeof(LanguageSpecificHandler)), PartNotDiscoverable, Shared] - [LanguageServerEndpoint(DocumentHandler.MethodName, LanguageNames.FSharp)] - internal class LanguageSpecificHandler : ILspServiceDocumentRequestHandler + [ExportCSharpVisualBasicLspServiceFactory(typeof(TestNotificationWithoutParamsHandler)), PartNotDiscoverable, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestNotificationWithoutParamsHandlerFactory() : ILspServiceFactory { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public LanguageSpecificHandler() + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { + return new TestNotificationWithoutParamsHandler(); } + } + /// + /// Defines a language specific handler with the same method as + /// + [ExportCSharpVisualBasicStatelessLspService(typeof(TestLanguageSpecificHandler)), PartNotDiscoverable, Shared] + [LanguageServerEndpoint(TestDocumentHandler.MethodName, LanguageNames.FSharp)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestLanguageSpecificHandler() : ILspServiceDocumentRequestHandler + { public bool MutatesSolutionState => true; public bool RequiresLSPSolution => true; @@ -253,19 +256,15 @@ public Task HandleRequestAsync(TestRequestTypeOne request, RequestContex } /// - /// Defines a language specific handler with the same method as + /// Defines a language specific handler with the same method as /// but using different request and response types. /// - [ExportCSharpVisualBasicStatelessLspService(typeof(LanguageSpecificHandlerWithDifferentParams)), PartNotDiscoverable, Shared] - [LanguageServerEndpoint(DocumentHandler.MethodName, LanguageNames.VisualBasic)] - internal class LanguageSpecificHandlerWithDifferentParams : ILspServiceDocumentRequestHandler + [ExportCSharpVisualBasicStatelessLspService(typeof(TestLanguageSpecificHandlerWithDifferentParams)), PartNotDiscoverable, Shared] + [LanguageServerEndpoint(TestDocumentHandler.MethodName, LanguageNames.VisualBasic)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestLanguageSpecificHandlerWithDifferentParams() : ILspServiceDocumentRequestHandler { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public LanguageSpecificHandlerWithDifferentParams() - { - } - public bool MutatesSolutionState => true; public bool RequiresLSPSolution => true; @@ -281,19 +280,15 @@ public Task HandleRequestAsync(TestRequestTypeTwo request, RequestContex } /// - /// Defines a language specific handler with the same method and language as + /// Defines a language specific handler with the same method and language as /// but with different params (an error) /// - [ExportCSharpVisualBasicStatelessLspService(typeof(DuplicateLanguageSpecificHandler)), PartNotDiscoverable, Shared] - [LanguageServerEndpoint(DocumentHandler.MethodName, LanguageNames.FSharp)] - internal class DuplicateLanguageSpecificHandler : ILspServiceRequestHandler + [ExportCSharpVisualBasicStatelessLspService(typeof(TestDuplicateLanguageSpecificHandler)), PartNotDiscoverable, Shared] + [LanguageServerEndpoint(TestDocumentHandler.MethodName, LanguageNames.FSharp)] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class TestDuplicateLanguageSpecificHandler() : ILspServiceRequestHandler { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DuplicateLanguageSpecificHandler() - { - } - public bool MutatesSolutionState => true; public bool RequiresLSPSolution => true;