Skip to content
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 @@ -3,20 +3,19 @@

using System.Text.Json.Serialization;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Newtonsoft.Json;

namespace Microsoft.AspNetCore.Razor.LanguageServer.WrapWithTag;

/// <summary>
/// Class representing the parameters sent for a textDocument/_vsweb_wrapWithTag request.
/// </summary>
internal class WrapWithTagParams
internal class WrapWithTagParams(TextDocumentIdentifier textDocument)
{
/// <summary>
/// Gets or sets the identifier for the text document to be operate on.
/// </summary>
[JsonPropertyName("_vs_textDocument")]
public TextDocumentIdentifier TextDocument { get; set; }
public TextDocumentIdentifier TextDocument { get; set; } = textDocument;

/// <summary>
/// Gets or sets the selection range to be wrapped.
Expand All @@ -35,11 +34,6 @@ internal class WrapWithTagParams
/// </summary>
[JsonPropertyName("_vs_options")]
public FormattingOptions? Options { get; set; }

public WrapWithTagParams(TextDocumentIdentifier textDocument)
{
TextDocument = textDocument;
}
}

internal class DelegatedWrapWithTagParams : WrapWithTagParams
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
using System.Text.Json;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Newtonsoft.Json.Linq;

namespace Microsoft.CodeAnalysis.Razor.Completion;

internal static class CompletionListMerger
{
private static readonly string Data1Key = nameof(MergedCompletionListData.Data1);
private static readonly string Data2Key = nameof(MergedCompletionListData.Data2);
private static readonly object EmptyData = new object();
private static readonly string s_data1Key = nameof(MergedCompletionListData.Data1);
private static readonly string s_data2Key = nameof(MergedCompletionListData.Data2);
private static readonly object s_emptyData = new();

public static VSInternalCompletionList? Merge(VSInternalCompletionList? razorCompletionList, VSInternalCompletionList? delegatedCompletionList)
{
Expand Down Expand Up @@ -109,10 +108,7 @@ private static void Split(object data, ref PooledArrayBuilder<JsonElement> colle
return;
}

// We have to be agnostic to which serialization method the delegated servers use, including
// the scenario where they use different ones, so we normalize the data to JObject.
TrySplitJsonElement(data, ref collector);
TrySplitJObject(data, ref collector);
}

private static void TrySplitJsonElement(object data, ref PooledArrayBuilder<JsonElement> collector)
Expand All @@ -122,8 +118,8 @@ private static void TrySplitJsonElement(object data, ref PooledArrayBuilder<Json
return;
}

if (jsonElement.TryGetProperty(Data1Key, out _) || jsonElement.TryGetProperty(Data1Key.ToLowerInvariant(), out _) &&
jsonElement.TryGetProperty(Data2Key, out _) || jsonElement.TryGetProperty(Data2Key.ToLowerInvariant(), out _))
if ((jsonElement.TryGetProperty(s_data1Key, out _) || jsonElement.TryGetProperty(s_data1Key.ToLowerInvariant(), out _)) &&
(jsonElement.TryGetProperty(s_data2Key, out _) || jsonElement.TryGetProperty(s_data2Key.ToLowerInvariant(), out _)))
{
// Merged data
var mergedCompletionListData = jsonElement.Deserialize<MergedCompletionListData>();
Expand All @@ -143,39 +139,10 @@ private static void TrySplitJsonElement(object data, ref PooledArrayBuilder<Json
}
}

private static void TrySplitJObject(object data, ref PooledArrayBuilder<JsonElement> collector)
{
if (data is not JObject jObject)
{
return;
}

if ((jObject.ContainsKey(Data1Key) || jObject.ContainsKey(Data1Key.ToLowerInvariant())) &&
(jObject.ContainsKey(Data2Key) || jObject.ContainsKey(Data2Key.ToLowerInvariant())))
{
// Merged data
var mergedCompletionListData = jObject.ToObject<MergedCompletionListData>();

if (mergedCompletionListData is null)
{
Debug.Fail("Merged completion list data is null, this should never happen.");
return;
}

Split(mergedCompletionListData.Data1, ref collector);
Split(mergedCompletionListData.Data2, ref collector);
}
else
{
// Normal, non-merged data
collector.Add(JsonDocument.Parse(jObject.ToString()).RootElement);
}
}

private static void EnsureMergeableData(VSInternalCompletionList completionListA, VSInternalCompletionList completionListB)
{
if (completionListA.Data != completionListB.Data &&
completionListA.Data is null || completionListB.Data is null)
(completionListA.Data is null || completionListB.Data is null))
{
// One of the completion lists have data while the other does not, we need to ensure that any non-data centric items don't get incorrect data associated

Expand All @@ -185,10 +152,7 @@ private static void EnsureMergeableData(VSInternalCompletionList completionListA
for (var i = 0; i < candidateCompletionList.Items.Length; i++)
{
var item = candidateCompletionList.Items[i];
if (item.Data is null)
{
item.Data = EmptyData;
}
item.Data ??= s_emptyData;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,14 @@
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
/// </summary>
internal static object? TryConvertFromJObject(object? data)
{
if (data is JObject jObject)
{
jObject[s_convertedFlag] = true;
return JsonDocument.Parse(jObject.ToString()).RootElement;
}

return data;
}

/// <summary>
/// Converts from JObject back to JsonElement, but only if the original conversion was done with <see cref="TryConvertFromJObject(object?)"/>
/// </summary>
internal static object? TryConvertBackToJObject(object? data)
{
if (data is JsonElement jsonElement &&
jsonElement.TryGetProperty(s_convertedFlag, out _))
{
data = JObject.Parse(jsonElement.ToString());
}

return data;
}

/// <summary>
/// Serializer options to use when serializing or deserializing a Roslyn LSP type
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
(service, solutionInfo, cancellationToken) => service.GetCodeActionRequestInfoAsync(solutionInfo, razorDocument.Id, request, cancellationToken),
cancellationToken).ConfigureAwait(false);

if (requestInfo is null ||
requestInfo.LanguageKind == RazorLanguageKind.CSharp && requestInfo.CSharpRequest is null)
if (requestInfo is null or { LanguageKind: RazorLanguageKind.CSharp, CSharpRequest: null })
{
return null;
}
Expand Down Expand Up @@ -137,18 +136,7 @@ private async Task<RazorVSInternalCodeAction[]> GetHtmlCodeActionsAsync(TextDocu
request,
cancellationToken).ConfigureAwait(false);

if (result?.Response is null)
{
return [];
}

// WebTools is still using Newtonsoft, so we have to convert to STJ
foreach (var codeAction in result.Response)
{
codeAction.Data = JsonHelpers.TryConvertFromJObject(codeAction.Data);
}

return result.Response;
return result?.Response ?? [];
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,7 @@ internal partial class RazorCustomMessageTarget

if (response.Response != null)
{
foreach (var codeAction in response.Response)
{
codeAction.Data = JsonHelpers.TryConvertFromJObject(codeAction.Data);
codeActions.Add(codeAction);
}
codeActions.AddRange(response.Response);
}
}

Expand Down Expand Up @@ -124,7 +120,6 @@ internal partial class RazorCustomMessageTarget

var textBuffer = virtualDocumentSnapshot.Snapshot.TextBuffer;
var codeAction = resolveCodeActionParams.CodeAction;
codeAction.Data = JsonHelpers.TryConvertBackToJObject(codeAction.Data);

var requests = _requestInvoker.ReinvokeRequestOnMultipleServersAsync<CodeAction, VSInternalCodeAction?>(
textBuffer,
Expand All @@ -137,10 +132,7 @@ internal partial class RazorCustomMessageTarget
if (response.Response is not null)
{
// Only take the first response from a resolution
var resolved = response.Response;
resolved.Data = JsonHelpers.TryConvertFromJObject(resolved.Data);

return resolved;
return response.Response;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,6 @@ internal partial class RazorCustomMessageTarget

completionList.Items = builder.ToArray();

completionList.Data = JsonHelpers.TryConvertFromJObject(completionList.Data);
ConvertJsonElementToJObject(completionList);

return completionList;
}
finally
Expand All @@ -173,14 +170,6 @@ internal partial class RazorCustomMessageTarget
}
}

private void ConvertJsonElementToJObject(VSInternalCompletionList completionList)
{
foreach (var item in completionList.Items)
{
item.Data = JsonHelpers.TryConvertFromJObject(item.Data);
}
}

private static TextEdit BuildRevertedEdit(TextEdit provisionalTextEdit)
{
TextEdit? revertedProvisionalTextEdit;
Expand Down Expand Up @@ -222,7 +211,7 @@ private void UpdateVirtualDocument(
trackingDocumentManager.UpdateVirtualDocument<CSharpVirtualDocument>(
documentSnapshotUri,
virtualDocumentUri,
new[] { textChange },
[textChange],
hostDocumentVersion,
state: null);
}
Expand All @@ -231,7 +220,7 @@ private void UpdateVirtualDocument(
trackingDocumentManager.UpdateVirtualDocument<HtmlVirtualDocument>(
documentSnapshotUri,
virtualDocumentUri,
new[] { textChange },
[textChange],
hostDocumentVersion,
state: null);
}
Expand Down Expand Up @@ -291,25 +280,15 @@ private void UpdateVirtualDocument(
return null;
}

var completionResolveParams = request.CompletionItem;

completionResolveParams.Data = JsonHelpers.TryConvertBackToJObject(completionResolveParams.Data);

var textBuffer = virtualDocumentSnapshot.Snapshot.TextBuffer;
var response = await _requestInvoker.ReinvokeRequestOnServerAsync<VSInternalCompletionItem, CompletionItem?>(
textBuffer,
Methods.TextDocumentCompletionResolve.Name,
languageServerName,
completionResolveParams,
request.CompletionItem,
cancellationToken).ConfigureAwait(false);

var item = response?.Response;
if (item is not null)
{
item.Data = JsonHelpers.TryConvertFromJObject(item.Data);
}

return item;
return response?.Response;
}

[JsonRpcMethod(LanguageServerConstants.RazorGetFormattingOptionsEndpointName, UseSingleObjectParameterDeserialization = true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Nerdbank.Streams;
Expand Down Expand Up @@ -77,8 +78,7 @@ static SystemTextJsonFormatter CreateSystemTextJsonMessageFormatter(AbstractRazo
// Roslyn has its own converters since it doesn't use MS.VS.LS.Protocol
languageServerFactory.AddJsonConverters(messageFormatter.JsonSerializerOptions);

// 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);

return messageFormatter;
}
Expand Down