Skip to content

Commit 893dbec

Browse files
authored
Null-guard workspaceFolders handling in OnInitialize
1 parent ee96cf3 commit 893dbec

2 files changed

Lines changed: 66 additions & 6 deletions

File tree

src/PowerShellEditorServices/Server/PsesLanguageServer.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.IO;
67
using System.Linq;
78
using System.Threading.Tasks;
@@ -16,6 +17,7 @@
1617
using Newtonsoft.Json.Linq;
1718
using OmniSharp.Extensions.JsonRpc;
1819
using OmniSharp.Extensions.LanguageServer.Protocol.General;
20+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
1921
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
2022
using OmniSharp.Extensions.LanguageServer.Server;
2123

@@ -138,12 +140,13 @@ public async Task StartAsync()
138140
languageServer.Services.GetService<ILogger<HostLoggerAdapter>>()
139141
));
140142

141-
// Set the workspace path from the parameters.
143+
// Set the workspace path from the parameters. The collection may be
144+
// absent (the field is optional in LSP), uninitialized, or contain
145+
// entries without a URI, so we treat any of those as "no folders yet"
146+
// rather than dereferencing a null and throwing.
142147
WorkspaceService workspaceService = languageServer.Services.GetService<WorkspaceService>();
143-
if (initializeParams.WorkspaceFolders is not null)
144-
{
145-
workspaceService.WorkspaceFolders.AddRange(initializeParams.WorkspaceFolders);
146-
}
148+
workspaceService.WorkspaceFolders.AddRange(
149+
GetValidWorkspaceFolders(initializeParams.WorkspaceFolders));
147150

148151
// Parse initialization options.
149152
JObject initializationOptions = initializeParams.InitializationOptions as JObject;
@@ -161,7 +164,7 @@ public async Task StartAsync()
161164
// First check the setting, then use the first workspace folder,
162165
// finally fall back to CWD.
163166
InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value<string>()
164-
?? workspaceService.WorkspaceFolders.FirstOrDefault()?.Uri.GetFileSystemPath()
167+
?? workspaceService.WorkspaceFolders.FirstOrDefault()?.Uri?.GetFileSystemPath()
165168
?? Directory.GetCurrentDirectory(),
166169
// If a shell integration script path is provided, that implies the feature is enabled.
167170
ShellIntegrationScript = initializationOptions?.GetValue("shellIntegrationScript")?.Value<string>()
@@ -180,6 +183,21 @@ public async Task StartAsync()
180183
_serverStart.SetResult(true);
181184
}
182185

186+
/// <summary>
187+
/// Filters the workspace folders provided on <c>initialize</c> down to the usable ones.
188+
/// </summary>
189+
/// <remarks>
190+
/// The <c>workspaceFolders</c> field is optional in LSP, so the collection may be absent or
191+
/// uninitialized, and individual folders may be sent without a URI. Any of those are treated
192+
/// as "no folder" so that downstream code (which dereferences <see cref="WorkspaceFolder.Uri" />)
193+
/// does not throw a <see cref="NullReferenceException" />.
194+
/// </remarks>
195+
/// <param name="workspaceFolders">The workspace folders from the initialize parameters.</param>
196+
/// <returns>The folders that have a non-null URI, or an empty sequence.</returns>
197+
internal static IEnumerable<WorkspaceFolder> GetValidWorkspaceFolders(IEnumerable<WorkspaceFolder> workspaceFolders)
198+
=> workspaceFolders?.Where(static folder => folder?.Uri is not null)
199+
?? Enumerable.Empty<WorkspaceFolder>();
200+
183201
/// <summary>
184202
/// Get a task that completes when the server is shut down.
185203
/// </summary>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Linq;
5+
using Microsoft.PowerShell.EditorServices.Server;
6+
using OmniSharp.Extensions.LanguageServer.Protocol;
7+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
8+
using Xunit;
9+
10+
namespace PowerShellEditorServices.Test.Server
11+
{
12+
[Trait("Category", "Server")]
13+
public class PsesLanguageServerTests
14+
{
15+
[Fact]
16+
public void GetValidWorkspaceFoldersReturnsEmptyWhenNull()
17+
=> Assert.Empty(PsesLanguageServer.GetValidWorkspaceFolders(null));
18+
19+
[Fact]
20+
public void GetValidWorkspaceFoldersReturnsEmptyWhenEmpty()
21+
=> Assert.Empty(PsesLanguageServer.GetValidWorkspaceFolders(new Container<WorkspaceFolder>()));
22+
23+
[Fact]
24+
public void GetValidWorkspaceFoldersSkipsNullFoldersAndNullUris()
25+
{
26+
WorkspaceFolder valid = new()
27+
{
28+
Uri = DocumentUri.FromFileSystemPath("/home/runner/work/example"),
29+
Name = "workspace"
30+
};
31+
32+
Container<WorkspaceFolder> folders = new(
33+
null,
34+
new WorkspaceFolder { Name = "missing-uri" },
35+
valid);
36+
37+
WorkspaceFolder[] result = PsesLanguageServer.GetValidWorkspaceFolders(folders).ToArray();
38+
39+
Assert.Equal(valid, Assert.Single(result));
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)