Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use correct serializer options when serializing and deserializing #11268

Merged
merged 2 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.LanguageServer.Protocol;
Expand Down Expand Up @@ -77,8 +78,7 @@ private static (JsonRpc, JsonSerializerOptions) CreateJsonRpc(Stream input, Stre
{
var messageFormatter = new SystemTextJsonFormatter();

// In its infinite wisdom, the LSP client has a public method that takes Newtonsoft.Json types, but an internal method that takes System.Text.Json types.
typeof(VSInternalExtensionUtilities).GetMethod("AddVSInternalExtensionConverters", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!.Invoke(null, [messageFormatter.JsonSerializerOptions]);
JsonHelpers.AddVSInternalExtensionConverters(messageFormatter.JsonSerializerOptions);

var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(output, input, messageFormatter));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Text.Json;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Newtonsoft.Json.Linq;

namespace Microsoft.CodeAnalysis.Razor.Protocol;

internal static class JsonHelpers
{
private const string s_convertedFlag = "__convertedFromJObject";
private static readonly Lazy<JsonSerializerOptions> s_roslynLspJsonSerializerOptions = new(CreateRoslynLspJsonSerializerOptions);
private static readonly Lazy<JsonSerializerOptions> s_vsLspJsonSerializerOptions = new(CreateVsLspJsonSerializerOptions);

/// <summary>
/// Normalizes data from JObject to JsonElement as thats what we expect to process
Expand Down Expand Up @@ -37,4 +42,59 @@ internal static class JsonHelpers

return data;
}

/// <summary>
/// Serializer options to use when serializing or deserializing a Roslyn LSP type
/// </summary>
internal static JsonSerializerOptions RoslynLspJsonSerializerOptions => s_roslynLspJsonSerializerOptions.Value;

/// <summary>
/// Serializer options to use when serializing or deserializing a VS Platform LSP type
/// </summary>
internal static JsonSerializerOptions VsLspJsonSerializerOptions => s_vsLspJsonSerializerOptions.Value;

/// <summary>
/// Converts a Roslyn LSP object to a VS Platform LSP object via serializing to text and deserializing to VS LSP type
/// </summary>
internal static TVsLspResult? ToVsLSP<TVsLspResult, TRoslynLspSource>(TRoslynLspSource source)
{
return JsonSerializer.Deserialize<TVsLspResult>(JsonSerializer.SerializeToDocument(source, RoslynLspJsonSerializerOptions), VsLspJsonSerializerOptions);
}

/// <summary>
/// Converts a VS Platform LSP object to a Roslyn LSP object via serializing to text and deserializing to Roslyn LSP type
/// </summary>
internal static TRoslynLspResult? ToRoslynLSP<TRoslynLspResult, TVsLspSource>(TVsLspSource? source)
{
return JsonSerializer.Deserialize<TRoslynLspResult>(JsonSerializer.SerializeToDocument(source, VsLspJsonSerializerOptions), RoslynLspJsonSerializerOptions);
}

/// <summary>
/// Adds VS Platform LSP converters for VSInternal variation of types (e.g. VSInternalClientCapability from ClientCapability)
/// </summary>
internal static void AddVSInternalExtensionConverters(JsonSerializerOptions serializerOptions)
{
// In its infinite wisdom, the LSP client has a public method that takes Newtonsoft.Json types, but an internal method that takes System.Text.Json types.
typeof(VSInternalExtensionUtilities).GetMethod("AddVSInternalExtensionConverters", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!.Invoke(null, [serializerOptions]);
}

private static JsonSerializerOptions CreateRoslynLspJsonSerializerOptions()
{
var serializerOptions = new JsonSerializerOptions();

foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters())
{
serializerOptions.Converters.Add(converter);
}

return serializerOptions;
}

private static JsonSerializerOptions CreateVsLspJsonSerializerOptions()
{
var serializerOptions = new JsonSerializerOptions();

AddVSInternalExtensionConverters(serializerOptions);
return serializerOptions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
using System.Collections.Generic;
using System.Composition;
using System.Diagnostics;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.CodeActions;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
Expand Down Expand Up @@ -56,16 +56,10 @@ public Task<string> GetFormattedNewFileContentsAsync(IProjectSnapshot projectSna
document = solution.GetRequiredDocument(documentIds.First(d => d.ProjectId == context.TextDocument.Project.Id));
}

var options = new JsonSerializerOptions();
foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters())
{
options.Converters.Add(converter);
}

var convertedEdit = JsonSerializer.Deserialize<RoslynTextEdit>(JsonSerializer.SerializeToDocument(edit, options), options).AssumeNotNull();
var convertedEdit = JsonHelpers.ToRoslynLSP<RoslynTextEdit, TextEdit>(edit).AssumeNotNull();

var edits = await ExternalHandlers.CodeActions.GetSimplifiedEditsAsync(document, convertedEdit, cancellationToken).ConfigureAwait(false);

return JsonSerializer.Deserialize<TextEdit[]>(JsonSerializer.SerializeToDocument(edits, options), options);
return JsonHelpers.ToVsLSP<TextEdit[], RoslynTextEdit[]>(edits);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
Expand All @@ -18,6 +17,7 @@
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Microsoft.VisualStudio.LanguageServer.Protocol.VSInternalCompletionList?>;
using RoslynCompletionContext = Roslyn.LanguageServer.Protocol.CompletionContext;
using RoslynCompletionList = Roslyn.LanguageServer.Protocol.CompletionList;
using RoslynCompletionSetting = Roslyn.LanguageServer.Protocol.CompletionSetting;

namespace Microsoft.CodeAnalysis.Remote.Razor;
Expand Down Expand Up @@ -175,20 +175,14 @@ private async ValueTask<Response> GetCompletionAsync(
}

// This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go.
var options = new JsonSerializerOptions();
foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters())
{
options.Converters.Add(converter);
}

if (JsonSerializer.Deserialize<RoslynCompletionContext>(JsonSerializer.SerializeToDocument(completionContext), options) is not { } roslynCompletionContext)
if (JsonHelpers.ToRoslynLSP<RoslynCompletionContext, CompletionContext>(completionContext) is not { } roslynCompletionContext)
{
Debug.Fail("Unable to convert VS to Roslyn LSP completion context");
return null;
}

var clientCapabilities = _clientCapabilitiesService.ClientCapabilities;
if (JsonSerializer.Deserialize<RoslynCompletionSetting>(JsonSerializer.SerializeToDocument(clientCapabilities.TextDocument?.Completion), options) is not { } roslynCompletionSetting)
if (JsonHelpers.ToRoslynLSP<RoslynCompletionSetting, CompletionSetting>(clientCapabilities.TextDocument?.Completion) is not { } roslynCompletionSetting)
{
Debug.Fail("Unable to convert VS to Roslyn LSP completion setting");
return null;
Expand Down Expand Up @@ -217,7 +211,7 @@ private async ValueTask<Response> GetCompletionAsync(
};
}

var vsPlatformCompletionList = JsonSerializer.Deserialize<VSInternalCompletionList>(JsonSerializer.SerializeToDocument(roslynCompletionList), options);
var vsPlatformCompletionList = JsonHelpers.ToVsLSP<VSInternalCompletionList, RoslynCompletionList>(roslynCompletionList);

var rewrittenResponse = await DelegatedCompletionHelper.RewriteCSharpResponseAsync(
vsPlatformCompletionList,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
Expand All @@ -12,6 +11,7 @@
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using ExternalHandlers = Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
using RoslynSymbolSumType = Roslyn.LanguageServer.Protocol.SumType<Roslyn.LanguageServer.Protocol.DocumentSymbol[], Roslyn.LanguageServer.Protocol.SymbolInformation[]>;

namespace Microsoft.CodeAnalysis.Remote.Razor;

Expand Down Expand Up @@ -49,13 +49,7 @@ protected override IRemoteDocumentSymbolService CreateService(in ServiceArgs arg
var csharpDocument = codeDocument.GetCSharpDocument();

// This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go.
var options = new JsonSerializerOptions();
foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters())
{
options.Converters.Add(converter);
}

var vsCSharpSymbols = JsonSerializer.Deserialize<SumType<DocumentSymbol[], SymbolInformation[]>?>(JsonSerializer.SerializeToDocument(csharpSymbols), options);
var vsCSharpSymbols = JsonHelpers.ToVsLSP<SumType<DocumentSymbol[], SymbolInformation[]>?, RoslynSymbolSumType>(csharpSymbols);
if (vsCSharpSymbols is not { } convertedSymbols)
{
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
Expand Down Expand Up @@ -127,18 +125,12 @@ private async Task<RazorVSInternalCodeAction[]> GetCSharpCodeActionsAsync(TextDo
return [];
}

var options = new JsonSerializerOptions();
foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters())
{
options.Converters.Add(converter);
}

var csharpRequest = JsonSerializer.Deserialize<Roslyn.LanguageServer.Protocol.CodeActionParams>(JsonSerializer.SerializeToDocument(request, options), options).AssumeNotNull();
var csharpRequest = JsonHelpers.ToRoslynLSP<Roslyn.LanguageServer.Protocol.CodeActionParams, VSCodeActionParams>(request).AssumeNotNull();

using var _ = _telemetryReporter.TrackLspRequest(Methods.TextDocumentCodeActionName, "Razor.ExternalAccess", TelemetryThresholds.CodeActionSubLSPTelemetryThreshold, correlationId);
var csharpCodeActions = await CodeActions.GetCodeActionsAsync(generatedDocument, csharpRequest, _clientCapabilitiesService.ClientCapabilities.SupportsVisualStudioExtensions, cancellationToken).ConfigureAwait(false);

return JsonSerializer.Deserialize<RazorVSInternalCodeAction[]>(JsonSerializer.SerializeToDocument(csharpCodeActions, options), options).AssumeNotNull();
return JsonHelpers.ToVsLSP<RazorVSInternalCodeAction[], Roslyn.LanguageServer.Protocol.CodeAction[]>(csharpCodeActions).AssumeNotNull();
}

private async Task<RazorVSInternalCodeAction[]> GetHtmlCodeActionsAsync(TextDocument razorDocument, VSCodeActionParams request, Guid correlationId, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
using Microsoft.CodeAnalysis.Razor.CodeActions;
Expand Down Expand Up @@ -118,19 +116,13 @@ private async Task<CodeAction> ResolveCSharpCodeActionAsync(TextDocument razorDo
return codeAction;
}

var options = new JsonSerializerOptions();
foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters())
{
options.Converters.Add(converter);
}

var resourceOptions = _clientCapabilitiesService.ClientCapabilities.Workspace?.WorkspaceEdit?.ResourceOperations ?? [];
var roslynCodeAction = JsonSerializer.Deserialize<Roslyn.LanguageServer.Protocol.VSInternalCodeAction>(JsonSerializer.SerializeToDocument(codeAction, options), options).AssumeNotNull();
var roslynResourceOptions = JsonSerializer.Deserialize<Roslyn.LanguageServer.Protocol.ResourceOperationKind[]>(JsonSerializer.SerializeToDocument(resourceOptions, options), options).AssumeNotNull();
var roslynCodeAction = JsonHelpers.ToRoslynLSP<Roslyn.LanguageServer.Protocol.VSInternalCodeAction, CodeAction>(codeAction).AssumeNotNull();
var roslynResourceOptions = JsonHelpers.ToRoslynLSP<Roslyn.LanguageServer.Protocol.ResourceOperationKind[], ResourceOperationKind[]>(resourceOptions).AssumeNotNull();

var resolvedCodeAction = await CodeActions.ResolveCodeActionAsync(generatedDocument, roslynCodeAction, roslynResourceOptions, cancellationToken).ConfigureAwait(false);

return JsonSerializer.Deserialize<RazorVSInternalCodeAction>(JsonSerializer.SerializeToDocument(resolvedCodeAction, options), options).AssumeNotNull();
return JsonHelpers.ToVsLSP<RazorVSInternalCodeAction, Roslyn.LanguageServer.Protocol.CodeAction>(resolvedCodeAction).AssumeNotNull();
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Completion;
using Microsoft.CodeAnalysis.Razor.Completion.Delegation;
Expand All @@ -25,6 +23,8 @@
using Response = Microsoft.CodeAnalysis.Razor.Remote.RemoteResponse<Microsoft.VisualStudio.LanguageServer.Protocol.VSInternalCompletionList?>;
using RoslynCompletionParams = Roslyn.LanguageServer.Protocol.CompletionParams;
using RoslynLspExtensions = Roslyn.LanguageServer.Protocol.RoslynLspExtensions;
using RoslynPosition = Roslyn.LanguageServer.Protocol.Position;
using RoslynCompletionContext = Roslyn.LanguageServer.Protocol.CompletionContext;

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

Expand Down Expand Up @@ -82,7 +82,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie

private async Task<VSInternalCompletionList?> HandleRequestAsync(RoslynCompletionParams request, TextDocument razorDocument, CancellationToken cancellationToken)
{
if (request.Context is null || ToVsLSP<VSInternalCompletionContext>(request.Context) is not VSInternalCompletionContext completionContext)
if (request.Context is null || JsonHelpers.ToVsLSP<VSInternalCompletionContext, RoslynCompletionContext>(request.Context) is not VSInternalCompletionContext completionContext)
{
_logger.LogError("Completion request context is null");
return null;
Expand All @@ -105,7 +105,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
solutionInfo,
razorDocument.Id,
completionContext,
ToVsLSP<Position>(request.Position).AssumeNotNull(),
JsonHelpers.ToVsLSP<Position, RoslynPosition>(request.Position).AssumeNotNull(),
cancellationToken),
cancellationToken).ConfigureAwait(false) is not { } completionPositionInfo)
{
Expand Down Expand Up @@ -218,23 +218,6 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
return rewrittenResponse;
}

private static T? ToVsLSP<T>(object source) where T : class
{
// This is, to say the least, not ideal. In future we're going to normalize on to Roslyn LSP types, and this can go.
var options = new JsonSerializerOptions();
foreach (var converter in RazorServiceDescriptorsWrapper.GetLspConverters())
{
options.Converters.Add(converter);
}

if (JsonSerializer.Deserialize<T>(JsonSerializer.SerializeToDocument(source), options) is not { } target)
{
return null;
}

return target;
}

private VSInternalCompletionList? AddSnippets(
VSInternalCompletionList? completionList,
RazorLanguageKind languageKind,
Expand Down
Loading