From 1a4c3f429fe13a2e928c800cebbf93154447095a Mon Sep 17 00:00:00 2001 From: David Barbet Date: Thu, 2 May 2024 14:51:36 -0700 Subject: [PATCH] Use Newtonsoft for client side serialization in Document Outline as we have not switched to STJ on the client side --- eng/Directory.Packages.props | 6 +- ...rverProtocolTests.InitializationOptions.cs | 3 + .../AbstractLanguageServerProtocolTests.cs | 20 ++- .../Symbols/RoslynDocumentSymbolParams.cs | 5 +- .../DocumentOutlineTestsBase.cs | 58 ++++---- .../DocumentOutlineViewModel.cs | 1 + .../DocumentOutlineViewModel_Utilities.cs | 61 ++++----- .../DocumentSymbolNewtonsoft.cs | 127 ++++++++++++++++++ 8 files changed, 211 insertions(+), 70 deletions(-) create mode 100644 src/VisualStudio/Core/Def/DocumentOutline/DocumentSymbolNewtonsoft.cs diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 8adaa8740c2ac..ce2c48d67d235 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -56,7 +56,7 @@ Visual Studio --> - + @@ -99,7 +99,7 @@ - + @@ -138,7 +138,7 @@ - + diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs index a817940d49017..8998fcec76edd 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.Options; +using StreamJsonRpc; using LSP = Roslyn.LanguageServer.Protocol; namespace Roslyn.Test.Utilities @@ -22,10 +23,12 @@ internal readonly record struct InitializationOptions() internal LSP.ClientCapabilities ClientCapabilities { get; init; } = new LSP.ClientCapabilities(); internal WellKnownLspServerKinds ServerKind { get; init; } = WellKnownLspServerKinds.AlwaysActiveVSLspServer; internal Action? OptionUpdater { get; init; } = null; + internal bool CallInitialize { get; init; } = true; internal bool CallInitialized { get; init; } = true; internal object? ClientTarget { get; init; } = null; internal string? Locale { get; init; } = null; internal IEnumerable? AdditionalAnalyzers { get; init; } = null; + internal IJsonRpcMessageFormatter? ClientMessageFormatter { get; init; } = null; } } } diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 0cdfa7bd0739d..1fa9da38fb528 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -531,7 +531,8 @@ private TestLspServer( LSP.ClientCapabilities clientCapabilities, RoslynLanguageServer target, Stream clientStream, - object? clientTarget = null) + object? clientTarget = null, + IJsonRpcMessageFormatter? clientMessageFormatter = null) { TestWorkspace = testWorkspace; ClientCapabilities = clientCapabilities; @@ -540,7 +541,9 @@ private TestLspServer( LanguageServer = target; - _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, RoslynLanguageServer.CreateJsonMessageFormatter()), clientTarget) + clientMessageFormatter ??= RoslynLanguageServer.CreateJsonMessageFormatter(); + + _clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientStream, clientStream, clientMessageFormatter), clientTarget) { ExceptionStrategy = ExceptionProcessing.ISerializable, }; @@ -566,13 +569,16 @@ internal static async Task CreateAsync(EditorTestWorkspace testWo var (clientStream, serverStream) = FullDuplexStream.CreatePair(); var languageServer = CreateLanguageServer(serverStream, serverStream, testWorkspace, initializationOptions.ServerKind, logger); - var server = new TestLspServer(testWorkspace, locations, initializationOptions.ClientCapabilities, languageServer, clientStream, initializationOptions.ClientTarget); + var server = new TestLspServer(testWorkspace, locations, initializationOptions.ClientCapabilities, languageServer, clientStream, initializationOptions.ClientTarget, initializationOptions.ClientMessageFormatter); - await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams + if (initializationOptions.CallInitialize) { - Capabilities = initializationOptions.ClientCapabilities, - Locale = initializationOptions.Locale, - }, CancellationToken.None); + await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams + { + Capabilities = initializationOptions.ClientCapabilities, + Locale = initializationOptions.Locale, + }, CancellationToken.None); + } if (initializationOptions.CallInitialized) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/RoslynDocumentSymbolParams.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/RoslynDocumentSymbolParams.cs index ac79269e2d44b..15f26944b6da3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/RoslynDocumentSymbolParams.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/RoslynDocumentSymbolParams.cs @@ -2,8 +2,8 @@ // 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.Text.Json.Serialization; using Roslyn.LanguageServer.Protocol; -using Newtonsoft.Json; namespace Microsoft.CodeAnalysis.LanguageServer.Handler { @@ -18,7 +18,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler /// internal class RoslynDocumentSymbolParams : DocumentSymbolParams { - [JsonProperty(PropertyName = "useHierarchicalSymbols", DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonPropertyName("useHierarchicalSymbols")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool UseHierarchicalSymbols { get; set; } } } diff --git a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs index d386add5b3e2e..9e67add1eaba5 100644 --- a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs +++ b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTestsBase.cs @@ -2,9 +2,8 @@ // 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; -using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -12,8 +11,6 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.UnitTests; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -22,9 +19,7 @@ using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.LanguageServices.DocumentOutline; using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Threading; -using Moq; -using Newtonsoft.Json.Linq; +using StreamJsonRpc; using Xunit.Abstractions; using static Roslyn.Test.Utilities.AbstractLanguageServerProtocolTests; using IAsyncDisposable = System.IAsyncDisposable; @@ -49,7 +44,7 @@ protected class DocumentOutlineTestMocks : IAsyncDisposable private readonly IAsyncDisposable _disposable; internal DocumentOutlineTestMocks( - LanguageServiceBrokerCallback languageServiceBrokerCallback, + LanguageServiceBrokerCallback languageServiceBrokerCallback, IThreadingContext threadingContext, EditorTestWorkspace workspace, IAsyncDisposable disposable) @@ -61,7 +56,7 @@ internal DocumentOutlineTestMocks( TextBuffer = workspace.Documents.Single().GetTextBuffer(); } - internal LanguageServiceBrokerCallback LanguageServiceBrokerCallback { get; } + internal LanguageServiceBrokerCallback LanguageServiceBrokerCallback { get; } internal IThreadingContext ThreadingContext { get; } @@ -84,27 +79,26 @@ protected async Task CreateMocksAsync(string code) var workspace = EditorTestWorkspace.CreateCSharp(code, composition: s_composition); var threadingContext = workspace.GetService(); - var clientCapabilities = new LSP.ClientCapabilities() + var testLspServer = await CreateTestLspServerAsync(workspace, new InitializationOptions { - TextDocument = new LSP.TextDocumentClientCapabilities() - { - DocumentSymbol = new LSP.DocumentSymbolSetting() - { - HierarchicalDocumentSymbolSupport = true - } - } - }; - - var testLspServer = await CreateTestLspServerAsync(workspace, new InitializationOptions { ClientCapabilities = clientCapabilities }); + // Set the message formatter to use newtonsoft on the client side to match real behavior. + // Also avoid calling initialize / initialized as the test harness uses types only compatible with STJ. + // TODO - switch back to STJ with https://github.com/dotnet/roslyn/issues/73317 + ClientMessageFormatter = new JsonMessageFormatter(), + CallInitialize = false, + CallInitialized = false + }); var mocks = new DocumentOutlineTestMocks(RequestAsync, threadingContext, workspace, testLspServer); return mocks; - async Task RequestAsync(ITextBuffer textBuffer, Func capabilitiesFilter, string languageServerName, string method, Func parameterFactory, CancellationToken cancellationToken) + async Task RequestAsync(Request request, CancellationToken cancellationToken) { - var request = parameterFactory(textBuffer.CurrentSnapshot).ToObject(); - var response = await testLspServer.ExecuteRequestAsync(method, request!, cancellationToken); - return new ManualInvocationResponse(string.Empty, JToken.FromObject(response!)); + var docRequest = (DocumentRequest)request; + var parameters = docRequest.ParameterFactory(docRequest.TextBuffer.CurrentSnapshot); + var response = await testLspServer.ExecuteRequestAsync(request.Method, parameters, cancellationToken); + + return response; } } @@ -139,7 +133,21 @@ private async Task CreateTestLspServerAsync(EditorTestWorkspace w var workspaceWaiter = operations.GetWaiter(FeatureAttribute.Workspace); await workspaceWaiter.ExpeditedWaitAsync(); - return await TestLspServer.CreateAsync(workspace, initializationOptions, _logger); + var server = await TestLspServer.CreateAsync(workspace, initializationOptions, _logger); + + // We disable the default test initialize call because the default test harness intialize types only support STJ (not newtonsoft). + // We only care that initialize has been called with some capability, so call with simple objects. + // TODO - remove with switch to STJ in https://github.com/dotnet/roslyn/issues/73317 + await server.ExecuteRequestAsync(Roslyn.LanguageServer.Protocol.Methods.InitializeName, new NewtonsoftInitializeParams() { Capabilities = new object() }, CancellationToken.None); + + return server; + } + + [DataContract] + private class NewtonsoftInitializeParams + { + [DataMember(Name = "capabilities")] + internal object? Capabilities { get; set; } } } } diff --git a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel.cs b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel.cs index e9143db12bc2c..5be44e0a29717 100644 --- a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel.cs +++ b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; +using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; diff --git a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs index 7b0668f518478..4d632cf85c644 100644 --- a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs +++ b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Linq; +using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -12,6 +13,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.Text; using Newtonsoft.Json.Linq; @@ -23,8 +25,7 @@ namespace Microsoft.VisualStudio.LanguageServices.DocumentOutline; using LspDocumentSymbol = DocumentSymbol; using Range = Roslyn.LanguageServer.Protocol.Range; -internal delegate Task LanguageServiceBrokerCallback( - ITextBuffer textBuffer, Func capabilitiesFilter, string languageServerName, string method, Func parameterFactory, CancellationToken cancellationToken); +internal delegate Task LanguageServiceBrokerCallback(Request request, CancellationToken cancellationToken); internal sealed partial class DocumentOutlineViewModel { @@ -32,34 +33,29 @@ internal sealed partial class DocumentOutlineViewModel /// Makes an LSP document symbol request and returns the response and the text snapshot used at /// the time the LSP client sends the request to the server. /// - public static async Task<(JToken response, ITextSnapshot snapshot)?> DocumentSymbolsRequestAsync( + public static async Task<(DocumentSymbolNewtonsoft.NewtonsoftRoslynDocumentSymbol[] response, ITextSnapshot snapshot)?> DocumentSymbolsRequestAsync( ITextBuffer textBuffer, - LanguageServiceBrokerCallback callbackAsync, + LanguageServiceBrokerCallback callbackAsync, string textViewFilePath, CancellationToken cancellationToken) { ITextSnapshot? requestSnapshot = null; - JToken ParameterFunction(ITextSnapshot snapshot) + + var request = new DocumentRequest() { - requestSnapshot = snapshot; - return JToken.FromObject(new RoslynDocumentSymbolParams() + Method = Methods.TextDocumentDocumentSymbolName, + LanguageServerName = WellKnownLspServerKinds.AlwaysActiveVSLspServer.ToUserVisibleString(), + TextBuffer = textBuffer, + ParameterFactory = (snapshot) => { - UseHierarchicalSymbols = true, - TextDocument = new TextDocumentIdentifier() - { - Uri = ProtocolConversions.CreateAbsoluteUri(textViewFilePath) - } - }); - } + requestSnapshot = snapshot; + return new DocumentSymbolNewtonsoft.NewtonsoftRoslynDocumentSymbolParams( + new DocumentSymbolNewtonsoft.NewtonsoftTextDocumentIdentifier(ProtocolConversions.CreateAbsoluteUri(textViewFilePath)), + UseHierarchicalSymbols: true); + } + }; - var manualResponse = await callbackAsync( - textBuffer: textBuffer, - method: Methods.TextDocumentDocumentSymbolName, - capabilitiesFilter: _ => true, - languageServerName: WellKnownLspServerKinds.AlwaysActiveVSLspServer.ToUserVisibleString(), - parameterFactory: ParameterFunction, - cancellationToken: cancellationToken).ConfigureAwait(false); - var response = manualResponse?.Response; + var response = await callbackAsync(request, cancellationToken).ConfigureAwait(false); // The request snapshot or response can be null if there is no LSP server implementation for // the document symbol request for that language. @@ -100,12 +96,8 @@ JToken ParameterFunction(ITextSnapshot snapshot) /// ] /// } /// ] - public static ImmutableArray CreateDocumentSymbolData(JToken token, ITextSnapshot textSnapshot) + public static ImmutableArray CreateDocumentSymbolData(DocumentSymbolNewtonsoft.NewtonsoftRoslynDocumentSymbol[] documentSymbols, ITextSnapshot textSnapshot) { - // If we get no value results back, treat that as empty results. That way we don't keep showing stale - // results if the server starts returning nothing. - var documentSymbols = token.ToObject() ?? []; - // Obtain a flat list of all the document symbols sorted by location in the document. var allSymbols = documentSymbols .SelectMany(x => x.Children) @@ -124,7 +116,7 @@ public static ImmutableArray CreateDocumentSymbolData(JToken // Returns the symbol in the list at index start (the parent symbol) with the following symbols in the list // (descendants) appropriately nested into the parent. - DocumentSymbolData NestDescendantSymbols(ImmutableArray allSymbols, int start, out int newStart) + DocumentSymbolData NestDescendantSymbols(ImmutableArray allSymbols, int start, out int newStart) { var currentParent = allSymbols[start]; start++; @@ -149,7 +141,7 @@ DocumentSymbolData NestDescendantSymbols(ImmutableArray al // Return the nested parent symbol. return new DocumentSymbolData( currentParent.Detail ?? currentParent.Name, - currentParent.Kind, + (Roslyn.LanguageServer.Protocol.SymbolKind)currentParent.Kind, (Glyph)currentParent.Glyph, GetSymbolRangeSpan(currentParent.Range), GetSymbolRangeSpan(currentParent.SelectionRange), @@ -157,15 +149,18 @@ DocumentSymbolData NestDescendantSymbols(ImmutableArray al } // Returns whether the child symbol is in range of the parent symbol. - static bool Contains(LspDocumentSymbol parent, LspDocumentSymbol child) + static bool Contains(DocumentSymbolNewtonsoft.NewtonsoftRoslynDocumentSymbol parent, DocumentSymbolNewtonsoft.NewtonsoftRoslynDocumentSymbol child) { - var parentRange = ProtocolConversions.RangeToLinePositionSpan(parent.Range); - var childRange = ProtocolConversions.RangeToLinePositionSpan(child.Range); + var parentRange = RangeToLinePositionSpan(parent.Range); + var childRange = RangeToLinePositionSpan(child.Range); return childRange.Start > parentRange.Start && childRange.End <= parentRange.End; + + static LinePositionSpan RangeToLinePositionSpan(DocumentSymbolNewtonsoft.NewtonsoftRange range) + => new(new LinePosition(range.Start.Line, range.Start.Character), new LinePosition(range.End.Line, range.End.Character)); } // Converts a Document Symbol Range to a SnapshotSpan within the text snapshot used for the LSP request. - SnapshotSpan GetSymbolRangeSpan(Range symbolRange) + SnapshotSpan GetSymbolRangeSpan(DocumentSymbolNewtonsoft.NewtonsoftRange symbolRange) { var originalStartPosition = textSnapshot.GetLineFromLineNumber(symbolRange.Start.Line).Start.Position + symbolRange.Start.Character; var originalEndPosition = textSnapshot.GetLineFromLineNumber(symbolRange.End.Line).Start.Position + symbolRange.End.Character; diff --git a/src/VisualStudio/Core/Def/DocumentOutline/DocumentSymbolNewtonsoft.cs b/src/VisualStudio/Core/Def/DocumentOutline/DocumentSymbolNewtonsoft.cs new file mode 100644 index 0000000000000..962a451c2c5a5 --- /dev/null +++ b/src/VisualStudio/Core/Def/DocumentOutline/DocumentSymbolNewtonsoft.cs @@ -0,0 +1,127 @@ +// 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; +using System.Globalization; +using System.Runtime.Serialization; +using Microsoft.CodeAnalysis.LanguageServer; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.VisualStudio.LanguageServices.DocumentOutline; + +/// +/// These are very temporary types that we need in order to serialize document symbol data +/// using Newtonsoft instead of System.Text.Json +/// +/// We currently must support Newtonsoft serialization here because we have not yet opted into using STJ +/// in the VS language server client (and so the client will serialize the request using Newtonsoft). +/// +/// https://github.com/dotnet/roslyn/pull/72675 tracks opting in the client to STJ. +/// TODO - everything in this type should be deleted once the client side is using STJ. +/// +internal class DocumentSymbolNewtonsoft +{ + private class NewtonsoftDocumentUriConverter : JsonConverter + { + /// + public override bool CanConvert(Type objectType) + { + return true; + } + + /// + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + reader = reader ?? throw new ArgumentNullException(nameof(reader)); + if (reader.TokenType == JsonToken.String) + { + var token = JToken.ReadFrom(reader); + var uri = new Uri(token.ToObject()); + + return uri; + } + else if (reader.TokenType == JsonToken.Null) + { + return null; + } + + throw new JsonSerializationException(string.Format(CultureInfo.InvariantCulture, LanguageServerProtocolResources.DocumentUriSerializationError, reader.Value)); + } + + /// + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + writer = writer ?? throw new ArgumentNullException(nameof(writer)); + + if (value is Uri uri) + { + var token = JToken.FromObject(uri.AbsoluteUri); + token.WriteTo(writer); + } + else + { + throw new ArgumentException($"{nameof(value)} must be of type {nameof(Uri)}"); + } + } + } + + [DataContract] + internal record NewtonsoftTextDocumentIdentifier([property: DataMember(Name = "uri"), JsonConverter(typeof(NewtonsoftDocumentUriConverter))] Uri Uri); + + [DataContract] + internal record NewtonsoftRoslynDocumentSymbolParams( + [property: DataMember(Name = "textDocument")] NewtonsoftTextDocumentIdentifier TextDocument, + [property: DataMember(Name = "useHierarchicalSymbols"), JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] bool UseHierarchicalSymbols); + + [DataContract] + internal record NewtonsoftRoslynDocumentSymbol( + [property: DataMember(IsRequired = true, Name = "name")] string Name, + [property: DataMember(Name = "detail")][property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Detail, + [property: DataMember(Name = "kind")] NewtonsoftSymbolKind Kind, + [property: DataMember(Name = "deprecated")][property: JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] bool Deprecated, + [property: DataMember(IsRequired = true, Name = "range")] NewtonsoftRange Range, + [property: DataMember(IsRequired = true, Name = "selectionRange")] NewtonsoftRange SelectionRange, + [property: DataMember(Name = "children")][property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] NewtonsoftRoslynDocumentSymbol[]? Children, + [property: DataMember(Name = "glyph")] int Glyph); + + [DataContract] + internal record NewtonsoftRange( + [property: DataMember(Name = "start"), JsonProperty(Required = Required.Always)] NewtonsoftPosition Start, + [property: DataMember(Name = "end"), JsonProperty(Required = Required.Always)] NewtonsoftPosition End); + + [DataContract] + internal record NewtonsoftPosition([property: DataMember(Name = "line")] int Line, [property: DataMember(Name = "character")] int Character); + + [DataContract] + internal enum NewtonsoftSymbolKind + { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26, + } +}