From 5399edf59661683626f024737bc95ad9a0022c95 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Sat, 11 Jan 2025 00:05:02 -0800 Subject: [PATCH] Add tests --- ...deAnalysis.LanguageServer.UnitTests.csproj | 16 +- .../Services/ExtractRefactoringTests.cs | 96 ++++++++++ ...ctLanguageServerHostTests.TestLspServer.cs | 156 ++++++++++++++++ .../AbstractLanguageServerHostTests.cs | 173 +++++++++++------- .../Utilities/TestLspServerExtensions.cs | 45 +++++ .../HostWorkspace/OpenProjectsHandler.cs | 6 +- .../HostWorkspace/OpenSolutionHandler.cs | 4 +- .../HostWorkspace/ProjectDependencyHelper.cs | 14 +- .../ProjectInitializationHandler.cs | 2 +- 9 files changed, 434 insertions(+), 78 deletions(-) create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Services/ExtractRefactoringTests.cs create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.TestLspServer.cs create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestLspServerExtensions.cs diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj index d76aa319a94d6..d252020fa7104 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Microsoft.CodeAnalysis.LanguageServer.UnitTests.csproj @@ -17,20 +17,22 @@ - + + + + + - + + - - + + diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Services/ExtractRefactoringTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Services/ExtractRefactoringTests.cs new file mode 100644 index 0000000000000..e59e8ac92a767 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Services/ExtractRefactoringTests.cs @@ -0,0 +1,96 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Roslyn.Test.Utilities; +using Xunit.Abstractions; +using LSP = Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Services; + +public class ExtractRefactoringTests(ITestOutputHelper testOutputHelper) : AbstractLanguageServerHostTests(testOutputHelper) +{ + [Theory] + [CombinatorialData] + public async Task TestExtractBaseClass(bool includeDevKitComponents) + { + var markup = + """ + class {|caret:A|} + { + public void M() + { + } + } + """; + var expected = + """ + internal class NewBaseType + { + public void M() + { + } + } + + class A : NewBaseType + { + } + """; + + await using var testLspServer = await CreateCSharpLanguageServerAsync(markup, includeDevKitComponents); + var caretLocation = testLspServer.GetLocations("caret").Single(); + + await TestCodeActionAsync(testLspServer, caretLocation, "Extract base class...", expected); + } + + [Theory] + [CombinatorialData] + public async Task TestExtractInterface(bool includeDevKitComponents) + { + var markup = + """ + class {|caret:A|} + { + public void M() + { + } + } + """; + var expected = + """ + interface IA + { + void M(); + } + + class A : IA + { + public void M() + { + } + } + """; + + await using var testLspServer = await CreateCSharpLanguageServerAsync(markup, includeDevKitComponents); + var caretLocation = testLspServer.GetLocations("caret").Single(); + + await TestCodeActionAsync(testLspServer, caretLocation, "Extract interface...", expected); + } + + private static async Task TestCodeActionAsync(TestLspServer testLspServer, LSP.Location caretLocation, string codeActionTitle, [StringSyntax("c#-test")] string expected) + { + var codeActionResults = await testLspServer.RunGetCodeActionsAsync(CreateCodeActionParams(caretLocation)); + + var unresolvedCodeAction = Assert.Single(codeActionResults, codeAction => codeAction.Title == codeActionTitle); + + var resolvedCodeAction = await testLspServer.RunGetCodeActionResolveAsync(unresolvedCodeAction); + + Assert.True(testLspServer.TryApplyWorkspaceEdit(resolvedCodeAction.Edit)); + + var updatedCode = testLspServer.GetDocumentText(caretLocation.Uri); + + AssertEx.Equal(expected, updatedCode); + } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.TestLspServer.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.TestLspServer.cs new file mode 100644 index 0000000000000..49f5f6c43e542 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.TestLspServer.cs @@ -0,0 +1,156 @@ +// 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.Immutable; +using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; +using Microsoft.CodeAnalysis.LanguageServer.LanguageServer; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Composition; +using Nerdbank.Streams; +using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; +using StreamJsonRpc; +using LSP = Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; + +public partial class AbstractLanguageServerHostTests +{ + internal sealed class TestLspServer : IAsyncDisposable + { + private readonly Dictionary _files; + private readonly Dictionary> _locations; + private readonly Task _languageServerHostCompletionTask; + private readonly JsonRpc _clientRpc; + + private ServerCapabilities? _serverCapabilities; + + internal static async Task CreateAsync( + ClientCapabilities clientCapabilities, + TestOutputLogger logger, + string cacheDirectory, + bool includeDevKitComponents = true, + string[]? extensionPaths = null, + Dictionary? files = null, + Dictionary>? locations = null) + { + var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync( + logger.Factory, includeDevKitComponents, cacheDirectory, extensionPaths, out var serverConfiguration, out var assemblyLoader); + exportProvider.GetExportedValue().InitializeConfiguration(serverConfiguration); + + var testLspServer = new TestLspServer(exportProvider, logger, assemblyLoader, files ?? [], locations ?? []); + var initializeResponse = await testLspServer.Initialize(clientCapabilities); + Assert.NotNull(initializeResponse?.Capabilities); + testLspServer._serverCapabilities = initializeResponse!.Capabilities; + + await testLspServer.Initialized(); + + return testLspServer; + } + + internal LanguageServerHost LanguageServerHost { get; } + public ExportProvider ExportProvider { get; } + + internal ServerCapabilities ServerCapabilities => _serverCapabilities ?? throw new InvalidOperationException("Initialize has not been called"); + + private TestLspServer(ExportProvider exportProvider, TestOutputLogger logger, IAssemblyLoader assemblyLoader, Dictionary files, Dictionary> locations) + { + _files = files; + _locations = locations; + + var typeRefResolver = new ExtensionTypeRefResolver(assemblyLoader, logger.Factory); + + var (clientStream, serverStream) = FullDuplexStream.CreatePair(); + LanguageServerHost = new LanguageServerHost(serverStream, serverStream, exportProvider, logger, typeRefResolver); + + var messageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); + _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, messageFormatter)) + { + AllowModificationWhileListening = true, + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; + + _clientRpc.StartListening(); + + // This task completes when the server shuts down. We store it so that we can wait for completion + // when we dispose of the test server. + LanguageServerHost.Start(); + + _languageServerHostCompletionTask = LanguageServerHost.WaitForExitAsync(); + ExportProvider = exportProvider; + } + + public async Task ExecuteRequestAsync(string methodName, TRequestType request, CancellationToken cancellationToken) where TRequestType : class + { + var result = await _clientRpc.InvokeWithParameterObjectAsync(methodName, request, cancellationToken: cancellationToken); + return result; + } + + public Task ExecuteNotificationAsync(string methodName, RequestType request) where RequestType : class + { + return _clientRpc.NotifyWithParameterObjectAsync(methodName, request); + } + + public Task ExecuteNotification0Async(string methodName) + { + return _clientRpc.NotifyWithParameterObjectAsync(methodName); + } + + public void AddClientLocalRpcTarget(object target) + { + _clientRpc.AddLocalRpcTarget(target); + } + + public void AddClientLocalRpcTarget(string methodName, Delegate handler) + { + _clientRpc.AddLocalRpcMethod(methodName, handler); + } + + public bool TryApplyWorkspaceEdit(WorkspaceEdit? workspaceEdit) + { + Assert.NotNull(workspaceEdit); + + // We do not support applying the following edits + Assert.Null(workspaceEdit.Changes); + Assert.Null(workspaceEdit.ChangeAnnotations); + + // Currently we only support applying TextDocumentEdits + var textDocumentEdits = (TextDocumentEdit[]?)workspaceEdit.DocumentChanges?.Value; + Assert.NotNull(textDocumentEdits); + + foreach (var documentEdit in textDocumentEdits) + { + var uri = documentEdit.TextDocument.Uri; + var document = _files[uri]; + + var changes = documentEdit.Edits + .Select(edit => edit.Value) + .Cast() + .SelectAsArray(edit => ProtocolConversions.TextEditToTextChange(edit, document)); + + var updatedDocument = document.WithChanges(changes); + _files[uri] = updatedDocument; + } + + return true; + } + + public string GetDocumentText(Uri uri) => _files[uri].ToString(); + + public IList GetLocations(string locationName) => _locations[locationName]; + + public async ValueTask DisposeAsync() + { + await _clientRpc.InvokeAsync(Methods.ShutdownName); + await _clientRpc.NotifyAsync(Methods.ExitName); + + // The language server host task should complete once shutdown and exit are called. +#pragma warning disable VSTHRD003 // Avoid awaiting foreign Tasks + await _languageServerHostCompletionTask; +#pragma warning restore VSTHRD003 // Avoid awaiting foreign Tasks + + _clientRpc.Dispose(); + } + } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs index 209704c7f0593..196dae4699f7a 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/AbstractLanguageServerHostTests.cs @@ -2,17 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.CodeAnalysis.LanguageServer.LanguageServer; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.Composition; -using Nerdbank.Streams; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; -using StreamJsonRpc; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit.Abstractions; +using LSP = Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; -public abstract class AbstractLanguageServerHostTests : IDisposable +public abstract partial class AbstractLanguageServerHostTests : IDisposable { protected TestOutputLogger TestOutputLogger { get; } protected TempRoot TempRoot { get; } @@ -25,88 +29,127 @@ protected AbstractLanguageServerHostTests(ITestOutputHelper testOutputHelper) MefCacheDirectory = TempRoot.CreateDirectory(); } - protected Task CreateLanguageServerAsync(bool includeDevKitComponents = true) - { - return TestLspServer.CreateAsync(new ClientCapabilities(), TestOutputLogger, MefCacheDirectory.Path, includeDevKitComponents); - } - public void Dispose() { TempRoot.Dispose(); } - protected sealed class TestLspServer : IAsyncDisposable + private protected Task CreateLanguageServerAsync(bool includeDevKitComponents = true) { - private readonly Task _languageServerHostCompletionTask; - private readonly JsonRpc _clientRpc; + return TestLspServer.CreateAsync(new ClientCapabilities(), TestOutputLogger, MefCacheDirectory.Path, includeDevKitComponents); + } - private ServerCapabilities? _serverCapabilities; + private protected async Task CreateCSharpLanguageServerAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markupCode, + bool includeDevKitComponents = true) + { + string code; + int? cursorPosition; + ImmutableDictionary> spans; + TestFileMarkupParser.GetPositionAndSpans(markupCode, out code, out cursorPosition, out spans); + + // Write project file + var projectDirectory = TempRoot.CreateDirectory(); + var projectPath = Path.Combine(projectDirectory.Path, "Project.csproj"); + await File.WriteAllTextAsync(projectPath, $""" + + + Library + net{Environment.Version.Major}.0 + + + """); + + // Write code file + var codePath = Path.Combine(projectDirectory.Path, "Code.cs"); + await File.WriteAllTextAsync(codePath, code); + +#pragma warning disable RS0030 // Do not use banned APIs + Uri codeUri = new(codePath); +#pragma warning restore RS0030 // Do not use banned APIs + var text = SourceText.From(code); + Dictionary files = new() { [codeUri] = text }; + var annotatedLocations = GetAnnotatedLocations(codeUri, text, spans); + + // Create server and open the project + var server = await TestLspServer.CreateAsync( + new ClientCapabilities(), + TestOutputLogger, + MefCacheDirectory.Path, + includeDevKitComponents, + files: files, + locations: annotatedLocations); + + // Perform restore and mock up project restore client handler + ProcessUtilities.Run("dotnet", $"restore --project {projectPath}"); + server.AddClientLocalRpcTarget(ProjectDependencyHelper.ProjectNeedsRestoreName, new Action((projectFilePaths) => { })); + + // Listen for project initialization + var projectInitialized = new TaskCompletionSource(); + server.AddClientLocalRpcTarget(ProjectInitializationHandler.ProjectInitializationCompleteName, () => projectInitialized.SetResult()); + +#pragma warning disable RS0030 // Do not use banned APIs + await server.OpenProjectsAsync([new(projectPath)]); +#pragma warning restore RS0030 // Do not use banned APIs + + // Wait for initialization + await projectInitialized.Task; + + return server; + } - internal static async Task CreateAsync(ClientCapabilities clientCapabilities, TestOutputLogger logger, string cacheDirectory, bool includeDevKitComponents = true, string[]? extensionPaths = null) + private protected static Dictionary> GetAnnotatedLocations(Uri codeUri, SourceText text, ImmutableDictionary> spanMap) + { + var locations = new Dictionary>(); + foreach (var (name, spans) in spanMap) { - var exportProvider = await LanguageServerTestComposition.CreateExportProviderAsync( - logger.Factory, includeDevKitComponents, cacheDirectory, extensionPaths, out var _, out var assemblyLoader); - var testLspServer = new TestLspServer(exportProvider, logger, assemblyLoader); - var initializeResponse = await testLspServer.ExecuteRequestAsync(Methods.InitializeName, new InitializeParams { Capabilities = clientCapabilities }, CancellationToken.None); - Assert.NotNull(initializeResponse?.Capabilities); - testLspServer._serverCapabilities = initializeResponse!.Capabilities; + var locationsForName = locations.GetValueOrDefault(name, []); + locationsForName.AddRange(spans.Select(span => ConvertTextSpanWithTextToLocation(span, text, codeUri))); - await testLspServer.ExecuteRequestAsync(Methods.InitializedName, new InitializedParams(), CancellationToken.None); - - return testLspServer; + // Linked files will return duplicate annotated Locations for each document that links to the same file. + // Since the test output only cares about the actual file, make sure we de-dupe before returning. + locations[name] = [.. locationsForName.Distinct()]; } - internal LanguageServerHost LanguageServerHost { get; } - public ExportProvider ExportProvider { get; } - - internal ServerCapabilities ServerCapabilities => _serverCapabilities ?? throw new InvalidOperationException("Initialize has not been called"); + return locations; - private TestLspServer(ExportProvider exportProvider, TestOutputLogger logger, IAssemblyLoader assemblyLoader) + static LSP.Location ConvertTextSpanWithTextToLocation(TextSpan span, SourceText text, Uri documentUri) { - var typeRefResolver = new ExtensionTypeRefResolver(assemblyLoader, logger.Factory); - - var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - LanguageServerHost = new LanguageServerHost(serverStream, serverStream, exportProvider, logger, typeRefResolver); - - var messageFormatter = RoslynLanguageServer.CreateJsonMessageFormatter(); - _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, messageFormatter)) + var location = new LSP.Location { - AllowModificationWhileListening = true, - ExceptionStrategy = ExceptionProcessing.ISerializable, + Uri = documentUri, + Range = ProtocolConversions.TextSpanToRange(span, text), }; - _clientRpc.StartListening(); - - // This task completes when the server shuts down. We store it so that we can wait for completion - // when we dispose of the test server. - LanguageServerHost.Start(); - - _languageServerHostCompletionTask = LanguageServerHost.WaitForExitAsync(); - ExportProvider = exportProvider; + return location; } + } - public async Task ExecuteRequestAsync(string methodName, TRequestType request, CancellationToken cancellationToken) where TRequestType : class - { - var result = await _clientRpc.InvokeWithParameterObjectAsync(methodName, request, cancellationToken: cancellationToken); - return result; - } + private protected static TextDocumentIdentifier CreateTextDocumentIdentifier(Uri uri, ProjectId? projectContext = null) + { + var documentIdentifier = new VSTextDocumentIdentifier { Uri = uri }; - public void AddClientLocalRpcTarget(object target) + if (projectContext != null) { - _clientRpc.AddLocalRpcTarget(target); + documentIdentifier.ProjectContext = new VSProjectContext + { + Id = ProtocolConversions.ProjectIdToProjectContextId(projectContext), + Label = projectContext.DebugName!, + Kind = LSP.VSProjectKind.CSharp + }; } - public async ValueTask DisposeAsync() - { - await _clientRpc.InvokeAsync(Methods.ShutdownName); - await _clientRpc.NotifyAsync(Methods.ExitName); - - // The language server host task should complete once shutdown and exit are called. -#pragma warning disable VSTHRD003 // Avoid awaiting foreign Tasks - await _languageServerHostCompletionTask; -#pragma warning restore VSTHRD003 // Avoid awaiting foreign Tasks - - _clientRpc.Dispose(); - } + return documentIdentifier; } + + private protected static CodeActionParams CreateCodeActionParams(LSP.Location location) + => new() + { + TextDocument = CreateTextDocumentIdentifier(location.Uri), + Range = location.Range, + Context = new CodeActionContext + { + // TODO - Code actions should respect context. + } + }; } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestLspServerExtensions.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestLspServerExtensions.cs new file mode 100644 index 0000000000000..ac304ae173487 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/TestLspServerExtensions.cs @@ -0,0 +1,45 @@ +// 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 Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; +using Roslyn.LanguageServer.Protocol; +using static Microsoft.CodeAnalysis.LanguageServer.UnitTests.AbstractLanguageServerHostTests; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests; + +internal static class TestLspServerExtensions +{ + public static async Task Initialized(this TestLspServer testLspServer) + { + await testLspServer.ExecuteRequestAsync(Methods.InitializedName, new InitializedParams(), CancellationToken.None); + } + + public static async Task Initialize(this TestLspServer testLspServer, ClientCapabilities clientCapabilities) + { + return await testLspServer.ExecuteRequestAsync(Methods.InitializeName, new InitializeParams { Capabilities = clientCapabilities }, CancellationToken.None); + } + + public static async Task OpenProjectsAsync(this TestLspServer testLspServer, Uri[] projects) + { + await testLspServer.ExecuteNotificationAsync(OpenProjectHandler.OpenProjectName, new() { Projects = projects }); + } + + public static async Task RunGetCodeActionsAsync( + this TestLspServer testLspServer, + CodeActionParams codeActionParams) + { + var result = await testLspServer.ExecuteRequestAsync(Methods.TextDocumentCodeActionName, codeActionParams, CancellationToken.None); + Assert.NotNull(result); + return [.. result.Cast()]; + } + + public static async Task RunGetCodeActionResolveAsync( + this TestLspServer testLspServer, + VSInternalCodeAction unresolvedCodeAction) + { + var result = (VSInternalCodeAction?)await testLspServer.ExecuteRequestAsync(Methods.CodeActionResolveName, unresolvedCodeAction, CancellationToken.None); + Assert.NotNull(result); + return result; + } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/OpenProjectsHandler.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/OpenProjectsHandler.cs index 615c0863e5239..82dfdc68f6ea6 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/OpenProjectsHandler.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/OpenProjectsHandler.cs @@ -12,9 +12,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; [ExportCSharpVisualBasicStatelessLspService(typeof(OpenProjectHandler)), Shared] -[Method("project/open")] +[Method(OpenProjectName)] internal class OpenProjectHandler : ILspServiceNotificationHandler { + internal const string OpenProjectName = "project/open"; + private readonly LanguageServerProjectSystem _projectSystem; [ImportingConstructor] @@ -32,7 +34,7 @@ Task INotificationHandler.HandleNotification return _projectSystem.OpenProjectsAsync(request.Projects.SelectAsArray(p => p.LocalPath)); } - private class NotificationParams + internal class NotificationParams { [JsonPropertyName("projects")] public required Uri[] Projects { get; set; } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/OpenSolutionHandler.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/OpenSolutionHandler.cs index 198c329b2ad96..4719287a0d00e 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/OpenSolutionHandler.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/OpenSolutionHandler.cs @@ -11,9 +11,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; [ExportCSharpVisualBasicStatelessLspService(typeof(OpenSolutionHandler)), Shared] -[Method("solution/open")] +[Method(OpenSolutionName)] internal class OpenSolutionHandler : ILspServiceNotificationHandler { + internal const string OpenSolutionName = "solution/open"; + private readonly LanguageServerProjectSystem _projectSystem; [ImportingConstructor] diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectDependencyHelper.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectDependencyHelper.cs index ac0659d8c0df4..46c785388f106 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectDependencyHelper.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectDependencyHelper.cs @@ -11,11 +11,12 @@ using NuGet.ProjectModel; using NuGet.Versioning; using Roslyn.Utilities; +using StreamJsonRpc; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; internal static class ProjectDependencyHelper { - private const string ProjectNeedsRestoreName = "workspace/_roslyn_projectNeedsRestore"; + internal const string ProjectNeedsRestoreName = "workspace/_roslyn_projectNeedsRestore"; internal static bool NeedsRestore(ProjectFileInfo newProjectFileInfo, ProjectFileInfo? previousProjectFileInfo, ILogger logger) { @@ -134,6 +135,15 @@ internal static async Task RestoreProjectsAsync(ImmutableArray projectPa await languageServerManager.SendRequestAsync(ProjectNeedsRestoreName, unresolvedParams, cancellationToken); } - private record UnresolvedDependenciesParams( + internal record UnresolvedDependenciesParams( [property: JsonPropertyName("projectFilePaths")] string[] ProjectFilePaths); + + internal sealed class TestProjectDependencyHandler + { + [JsonRpcMethod(ProjectNeedsRestoreName, UseSingleObjectParameterDeserialization = true)] + public Task HandleAsync(UnresolvedDependenciesParams dependencyParams, CancellationToken _) + { + return Task.CompletedTask; + } + } } diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectInitializationHandler.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectInitializationHandler.cs index 79194cbe099ad..2917f1fbc307c 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectInitializationHandler.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectInitializationHandler.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; [Export, Shared] internal sealed class ProjectInitializationHandler : IDisposable { - private const string ProjectInitializationCompleteName = "workspace/projectInitializationComplete"; + internal const string ProjectInitializationCompleteName = "workspace/projectInitializationComplete"; private readonly IServiceBroker _serviceBroker; private readonly ServiceBrokerClient _serviceBrokerClient;