Skip to content

Commit

Permalink
Warn instead of crashing if we get a request with unknown language
Browse files Browse the repository at this point in the history
  • Loading branch information
dibarbet committed Oct 14, 2024
1 parent 8cd45a9 commit 3793cf8
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 4 deletions.
15 changes: 12 additions & 3 deletions src/LanguageServer/Protocol/LanguageInfoProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis.Features.Workspaces;
using Microsoft.CommonLanguageServerProtocol.Framework;

namespace Microsoft.CodeAnalysis.LanguageServer
{
internal class LanguageInfoProvider : ILanguageInfoProvider
internal class LanguageInfoProvider(ILspLogger logger) : ILanguageInfoProvider
{
// Constant so that Razor can use it (exposed via EA) otherwise their endpoints won't get hit
public const string RazorLanguageName = "Razor";
Expand Down Expand Up @@ -58,7 +59,7 @@ public LanguageInformation GetLanguageInformation(Uri uri, string? lspLanguageId
}

// If the URI file path mapping failed, use the languageId from the LSP client (if any).
return lspLanguageId switch
var languageId = lspLanguageId switch
{
"csharp" => s_csharpLanguageInformation,
"fsharp" => s_fsharpLanguageInformation,
Expand All @@ -67,8 +68,16 @@ public LanguageInformation GetLanguageInformation(Uri uri, string? lspLanguageId
"xaml" => s_xamlLanguageInformation,
"typescript" => s_typeScriptLanguageInformation,
"javascript" => s_typeScriptLanguageInformation,
_ => throw new InvalidOperationException($"Unable to determine language for '{uri}' with LSP language id '{lspLanguageId}'")
_ => null
};

if (languageId == null)
{
logger.LogWarning($"Unable to determine language for '{uri}' with LSP language id '{lspLanguageId}'");
return s_csharpLanguageInformation;
}

return languageId;
}
}
}
2 changes: 1 addition & 1 deletion src/LanguageServer/Protocol/RoslynLanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private FrozenDictionary<string, ImmutableArray<BaseService>> GetBaseServices(
AddService<IMethodHandler>(new InitializeHandler());
AddService<IMethodHandler>(new InitializedHandler());
AddService<IOnInitialized>(this);
AddService<ILanguageInfoProvider>(new LanguageInfoProvider());
AddService<ILanguageInfoProvider>(new LanguageInfoProvider(logger));

// In all VS cases, we already have a misc workspace. Specifically
// Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.MiscellaneousFilesWorkspace. In
Expand Down
28 changes: 28 additions & 0 deletions src/LanguageServer/ProtocolUnitTests/UriTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,34 @@ await Assert.ThrowsAnyAsync<Exception>(async ()
new CustomResolveParams(new LSP.TextDocumentIdentifier { Uri = lowerCaseUri }), CancellationToken.None));
}

[Theory, CombinatorialData]
public async Task TestDoesNotCrashIfUnableToDetermineLanguageInfo(bool mutatingLspWorkspace)
{
// Create a server that supports LSP misc files and verify no misc files present.
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });

// Open an empty loose file that hasn't been saved with a name.
var looseFileUri = ProtocolConversions.CreateAbsoluteUri(@"untitled:untitledFile");
await testLspServer.OpenDocumentAsync(looseFileUri, "hello", languageId: "csharp").ConfigureAwait(false);

// Verify file is added to the misc file workspace.
var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = looseFileUri }, CancellationToken.None);
Assert.True(workspace is LspMiscellaneousFilesWorkspace);
AssertEx.NotNull(document);
Assert.Equal(looseFileUri, document.GetURI());
Assert.Equal(looseFileUri.OriginalString, document.FilePath);

// Close the document (deleting the saved language information)
await testLspServer.CloseDocumentAsync(looseFileUri);

// Assert that the request throws but the server does not crash.
await Assert.ThrowsAnyAsync<Exception>(async ()
=> await testLspServer.ExecuteRequestAsync<CustomResolveParams, ResolvedDocumentInfo>(CustomResolveHandler.MethodName,
new CustomResolveParams(new LSP.TextDocumentIdentifier { Uri = looseFileUri }), CancellationToken.None));
Assert.False(testLspServer.GetServerAccessor().HasShutdownStarted());
Assert.False(testLspServer.GetQueueAccessor()!.Value.IsComplete());
}

private record class ResolvedDocumentInfo(string WorkspaceKind, string ProjectLanguage);
private record class CustomResolveParams([property: JsonPropertyName("textDocument")] LSP.TextDocumentIdentifier TextDocument);

Expand Down

0 comments on commit 3793cf8

Please sign in to comment.