summaryrefslogtreecommitdiffstats
path: root/generator/plugins/dotnet
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:55:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:55:48 +0000
commit8be448d3881909fb0ce4b033cad71aa7575de0aa (patch)
treeda33caff06645347a08c3c9c56dd703e4acb5aa3 /generator/plugins/dotnet
parentInitial commit. (diff)
downloadlsprotocol-8be448d3881909fb0ce4b033cad71aa7575de0aa.tar.xz
lsprotocol-8be448d3881909fb0ce4b033cad71aa7575de0aa.zip
Adding upstream version 2023.0.0.upstream/2023.0.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'generator/plugins/dotnet')
-rw-r--r--generator/plugins/dotnet/__init__.py4
-rw-r--r--generator/plugins/dotnet/custom/CustomArrayConverter.cs42
-rw-r--r--generator/plugins/dotnet/custom/CustomObjectConverter.cs40
-rw-r--r--generator/plugins/dotnet/custom/CustomStringConverter.cs40
-rw-r--r--generator/plugins/dotnet/custom/Direction.cs12
-rw-r--r--generator/plugins/dotnet/custom/DocumentSelectorConverter.cs34
-rw-r--r--generator/plugins/dotnet/custom/IMessage.cs4
-rw-r--r--generator/plugins/dotnet/custom/INotification.cs6
-rw-r--r--generator/plugins/dotnet/custom/IOrType.cs4
-rw-r--r--generator/plugins/dotnet/custom/IPartialResultParams.cs15
-rw-r--r--generator/plugins/dotnet/custom/IRequest.cs9
-rw-r--r--generator/plugins/dotnet/custom/IResponse.cs9
-rw-r--r--generator/plugins/dotnet/custom/LSPAnyConverter.cs61
-rw-r--r--generator/plugins/dotnet/custom/LSPRequest.cs22
-rw-r--r--generator/plugins/dotnet/custom/LSPResponse.cs13
-rw-r--r--generator/plugins/dotnet/custom/MessageDirection.cs8
-rw-r--r--generator/plugins/dotnet/custom/OrType.cs138
-rw-r--r--generator/plugins/dotnet/custom/OrTypeArrayConverter.cs145
-rw-r--r--generator/plugins/dotnet/custom/OrTypeConverter.cs595
-rw-r--r--generator/plugins/dotnet/custom/Proposed.cs17
-rw-r--r--generator/plugins/dotnet/custom/ResponseError.cs27
-rw-r--r--generator/plugins/dotnet/custom/Since.cs17
-rw-r--r--generator/plugins/dotnet/custom/Validators.cs20
-rw-r--r--generator/plugins/dotnet/dotnet_classes.py1024
-rw-r--r--generator/plugins/dotnet/dotnet_commons.py60
-rw-r--r--generator/plugins/dotnet/dotnet_constants.py5
-rw-r--r--generator/plugins/dotnet/dotnet_enums.py51
-rw-r--r--generator/plugins/dotnet/dotnet_helpers.py215
-rw-r--r--generator/plugins/dotnet/dotnet_special_classes.py158
-rw-r--r--generator/plugins/dotnet/dotnet_utils.py58
30 files changed, 2853 insertions, 0 deletions
diff --git a/generator/plugins/dotnet/__init__.py b/generator/plugins/dotnet/__init__.py
new file mode 100644
index 0000000..8c6065b
--- /dev/null
+++ b/generator/plugins/dotnet/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+from .dotnet_utils import generate_from_spec as generate
diff --git a/generator/plugins/dotnet/custom/CustomArrayConverter.cs b/generator/plugins/dotnet/custom/CustomArrayConverter.cs
new file mode 100644
index 0000000..d34eb62
--- /dev/null
+++ b/generator/plugins/dotnet/custom/CustomArrayConverter.cs
@@ -0,0 +1,42 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System.Collections.Immutable;
+
+public class CustomArrayConverter<T> : JsonConverter<ImmutableArray<T>>
+{
+ public override ImmutableArray<T> ReadJson(JsonReader reader, Type objectType, ImmutableArray<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return default(ImmutableArray<T>);
+ }
+
+ JArray array = JArray.Load(reader);
+ ImmutableArray<T>.Builder builder = ImmutableArray.CreateBuilder<T>();
+
+ for (int i = 0; i < array.Count; i++)
+ {
+ builder.Add((T)array[i].ToObject(typeof(T))!);
+ }
+
+ return builder.ToImmutable();
+
+ }
+
+ public override void WriteJson(JsonWriter writer, ImmutableArray<T> value, JsonSerializer serializer)
+ {
+ if (value.IsDefault)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteStartArray();
+ foreach (var item in value)
+ {
+ serializer.Serialize(writer, item);
+ }
+ writer.WriteEndArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/CustomObjectConverter.cs b/generator/plugins/dotnet/custom/CustomObjectConverter.cs
new file mode 100644
index 0000000..e03b588
--- /dev/null
+++ b/generator/plugins/dotnet/custom/CustomObjectConverter.cs
@@ -0,0 +1,40 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+
+
+class CustomObjectConverter<T> : JsonConverter<T> where T : Dictionary<string, object?>
+{
+ public override T ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return default(T)!;
+ }
+
+ Dictionary<string, object?>? o = serializer.Deserialize<Dictionary<string, object?>>(reader);
+ if (o == null)
+ {
+ return default(T)!;
+ }
+ return (T)Activator.CreateInstance(typeof(T), o)! ?? default(T)!;
+ }
+
+ public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
+ {
+ if (value is null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteStartObject();
+ foreach (var kvp in value)
+ {
+ writer.WritePropertyName(kvp.Key);
+ serializer.Serialize(writer, kvp.Value);
+ }
+ writer.WriteEndObject();
+ }
+ }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/CustomStringConverter.cs b/generator/plugins/dotnet/custom/CustomStringConverter.cs
new file mode 100644
index 0000000..33a04df
--- /dev/null
+++ b/generator/plugins/dotnet/custom/CustomStringConverter.cs
@@ -0,0 +1,40 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+
+public class CustomStringConverter<T> : JsonConverter<T> where T : class
+{
+ public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.String && reader.Value is string str)
+ {
+ return Activator.CreateInstance(typeof(T), str) as T;
+ }
+ else if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ throw new JsonSerializationException($"Unexpected token type '{reader.TokenType}' while deserializing '{objectType.Name}'.");
+ }
+
+ public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
+ {
+ if (value is null)
+ {
+ writer.WriteNull();
+ }
+ else if (value is Uri u)
+ {
+ writer.WriteValue(u.AbsoluteUri);
+ }
+ else if (value is T t)
+ {
+ writer.WriteValue(t.ToString());
+ }
+ else
+ {
+ throw new ArgumentException($"{nameof(value)} must be of type {nameof(T)}.");
+ }
+ }
+}
diff --git a/generator/plugins/dotnet/custom/Direction.cs b/generator/plugins/dotnet/custom/Direction.cs
new file mode 100644
index 0000000..757acd3
--- /dev/null
+++ b/generator/plugins/dotnet/custom/Direction.cs
@@ -0,0 +1,12 @@
+using System;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Enum)]
+public class DirectionAttribute : Attribute
+{
+ public DirectionAttribute(MessageDirection direction)
+ {
+ Direction = direction;
+ }
+
+ public MessageDirection Direction { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/DocumentSelectorConverter.cs b/generator/plugins/dotnet/custom/DocumentSelectorConverter.cs
new file mode 100644
index 0000000..3124416
--- /dev/null
+++ b/generator/plugins/dotnet/custom/DocumentSelectorConverter.cs
@@ -0,0 +1,34 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+
+public class DocumentSelectorConverter : JsonConverter<DocumentSelector>
+{
+ public override void WriteJson(JsonWriter writer, DocumentSelector? value, JsonSerializer serializer)
+ {
+ if (value is null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ serializer.Serialize(writer, (DocumentFilter[])value);
+ }
+ }
+
+ public override DocumentSelector ReadJson(JsonReader reader, Type objectType, DocumentSelector? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null!;
+ }
+ var token = JToken.Load(reader);
+ if (token.Type == JTokenType.Array)
+ {
+ var filters = token.ToObject<DocumentFilter[]>(serializer);
+ return new DocumentSelector(filters ?? Array.Empty<DocumentFilter>());
+ }
+
+ throw new JsonSerializationException("Invalid JSON for DocumentSelector");
+ }
+}
diff --git a/generator/plugins/dotnet/custom/IMessage.cs b/generator/plugins/dotnet/custom/IMessage.cs
new file mode 100644
index 0000000..175c258
--- /dev/null
+++ b/generator/plugins/dotnet/custom/IMessage.cs
@@ -0,0 +1,4 @@
+public interface IMessage
+{
+ string JsonRPC { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/INotification.cs b/generator/plugins/dotnet/custom/INotification.cs
new file mode 100644
index 0000000..b287767
--- /dev/null
+++ b/generator/plugins/dotnet/custom/INotification.cs
@@ -0,0 +1,6 @@
+public interface INotification<TParams> : IMessage
+{
+ string Method { get; }
+
+ TParams? Params { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/IOrType.cs b/generator/plugins/dotnet/custom/IOrType.cs
new file mode 100644
index 0000000..217b64a
--- /dev/null
+++ b/generator/plugins/dotnet/custom/IOrType.cs
@@ -0,0 +1,4 @@
+public interface IOrType
+{
+ public object? Value { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/IPartialResultParams.cs b/generator/plugins/dotnet/custom/IPartialResultParams.cs
new file mode 100644
index 0000000..ef90c3b
--- /dev/null
+++ b/generator/plugins/dotnet/custom/IPartialResultParams.cs
@@ -0,0 +1,15 @@
+using System;
+
+/// <summary>
+/// Interface to describe parameters for requests that support streaming results.
+///
+/// See the <see href="https://microsoft.github.io/language-server-protocol/specifications/specification-current/#partialResultParams">Language Server Protocol specification</see> for additional information.
+/// </summary>
+/// <typeparam name="T">The type to be reported by <see cref="PartialResultToken"/>.</typeparam>
+public interface IPartialResultParams
+{
+ /// <summary>
+ /// An optional token that a server can use to report partial results (e.g. streaming) to the client.
+ /// </summary>
+ public ProgressToken? PartialResultToken { get; set; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/IRequest.cs b/generator/plugins/dotnet/custom/IRequest.cs
new file mode 100644
index 0000000..55fcbd8
--- /dev/null
+++ b/generator/plugins/dotnet/custom/IRequest.cs
@@ -0,0 +1,9 @@
+public interface IRequest<TParams> : IMessage
+{
+
+ OrType<string, int> Id { get; }
+
+ string Method { get; }
+
+ TParams Params { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/IResponse.cs b/generator/plugins/dotnet/custom/IResponse.cs
new file mode 100644
index 0000000..01ecf3e
--- /dev/null
+++ b/generator/plugins/dotnet/custom/IResponse.cs
@@ -0,0 +1,9 @@
+public interface IResponse<TResponse> : IMessage
+{
+
+ OrType<string, int> Id { get; }
+
+ TResponse? Result { get; }
+
+ ResponseError? Error { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/LSPAnyConverter.cs b/generator/plugins/dotnet/custom/LSPAnyConverter.cs
new file mode 100644
index 0000000..28781cb
--- /dev/null
+++ b/generator/plugins/dotnet/custom/LSPAnyConverter.cs
@@ -0,0 +1,61 @@
+using Newtonsoft.Json;
+
+public class LSPAnyConverter : JsonConverter
+{
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(LSPAny);
+ }
+
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+ {
+ reader = reader ?? throw new ArgumentNullException(nameof(reader));
+ switch (reader.TokenType)
+ {
+ case JsonToken.Null:
+ return null;
+
+ case JsonToken.Integer:
+ return new LSPAny(serializer.Deserialize<long>(reader));
+
+ case JsonToken.Float:
+ return new LSPAny(serializer.Deserialize<float>(reader));
+
+ case JsonToken.Boolean:
+ return new LSPAny(serializer.Deserialize<bool>(reader));
+
+ case JsonToken.String:
+ return new LSPAny(serializer.Deserialize<string>(reader));
+
+ case JsonToken.StartArray:
+ List<object>? l = serializer.Deserialize<List<object>>(reader);
+ if (l == null)
+ {
+ return null;
+ }
+ return new LSPAny(new LSPArray(l));
+
+ case JsonToken.StartObject:
+ Dictionary<string, object?>? o = serializer.Deserialize<Dictionary<string, object?>>(reader);
+ if (o == null)
+ {
+ return null;
+ }
+ return new LSPAny(new LSPObject(o));
+ }
+
+ throw new JsonSerializationException($"Unexpected token type '{reader.TokenType}' while deserializing '{objectType.Name}'.");
+ }
+
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ if (value is null)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ serializer.Serialize(writer, ((LSPAny)value).Value);
+ }
+ }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/LSPRequest.cs b/generator/plugins/dotnet/custom/LSPRequest.cs
new file mode 100644
index 0000000..69e3f0d
--- /dev/null
+++ b/generator/plugins/dotnet/custom/LSPRequest.cs
@@ -0,0 +1,22 @@
+using System;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class LSPRequestAttribute : Attribute
+{
+ public LSPRequestAttribute(string method, Type response)
+ {
+ Method = method;
+ Response = response;
+ }
+
+ public LSPRequestAttribute(string method, Type response, Type partialResponse)
+ {
+ Method = method;
+ Response = response;
+ PartialResponse = partialResponse;
+ }
+
+ public string Method { get; }
+ public Type Response { get; }
+ public Type? PartialResponse { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/LSPResponse.cs b/generator/plugins/dotnet/custom/LSPResponse.cs
new file mode 100644
index 0000000..4d2ca46
--- /dev/null
+++ b/generator/plugins/dotnet/custom/LSPResponse.cs
@@ -0,0 +1,13 @@
+using System;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class LSPResponseAttribute : Attribute
+{
+ public LSPResponseAttribute(Type request)
+ {
+ Request = request;
+ }
+
+
+ public Type Request { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/MessageDirection.cs b/generator/plugins/dotnet/custom/MessageDirection.cs
new file mode 100644
index 0000000..a2792ff
--- /dev/null
+++ b/generator/plugins/dotnet/custom/MessageDirection.cs
@@ -0,0 +1,8 @@
+using System.Runtime.Serialization;
+
+public enum MessageDirection
+{
+ [EnumMember(Value = "serverToClient")] ServerToClient,
+ [EnumMember(Value = "clientToServer")] ClientToServer,
+ [EnumMember(Value = "both")] Both,
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/OrType.cs b/generator/plugins/dotnet/custom/OrType.cs
new file mode 100644
index 0000000..2ddf320
--- /dev/null
+++ b/generator/plugins/dotnet/custom/OrType.cs
@@ -0,0 +1,138 @@
+using System;
+
+public record OrType<T, U> : IOrType
+{
+ public object? Value { get; }
+ public OrType(T t)
+ {
+ Value = t ?? throw new ArgumentNullException(nameof(t));
+ }
+
+ public OrType(U u)
+ {
+ Value = u ?? throw new ArgumentNullException(nameof(u));
+ }
+
+ public static explicit operator U?(OrType<T, U> obj)
+ {
+ return obj.Value is U x ? x : default;
+ }
+
+ public static explicit operator T?(OrType<T, U> obj)
+ {
+ return obj.Value is T x ? x : default;
+ }
+
+ public static explicit operator OrType<T, U>(U obj) => obj is null ? null! : new OrType<T, U>(obj);
+ public static explicit operator OrType<T, U>(T obj) => obj is null ? null! : new OrType<T, U>(obj);
+
+ public override string ToString()
+ {
+ return Value?.ToString()!;
+ }
+}
+
+public record OrType<T, U, V> : IOrType
+{
+ public object? Value { get; }
+
+ public OrType(T t)
+ {
+ Value = t ?? throw new ArgumentNullException(nameof(t));
+ }
+
+ public OrType(U u)
+ {
+ Value = u ?? throw new ArgumentNullException(nameof(u));
+ }
+
+ public OrType(V v)
+ {
+ Value = v ?? throw new ArgumentNullException(nameof(v));
+ }
+
+ public static explicit operator U?(OrType<T, U, V> obj)
+ {
+ return obj.Value is U x ? x : default;
+ }
+
+ public static explicit operator T?(OrType<T, U, V> obj)
+ {
+ return obj.Value is T x ? x : default;
+ }
+
+ public static explicit operator V?(OrType<T, U, V> obj)
+ {
+ return obj.Value is V x ? x : default;
+ }
+
+ public static explicit operator OrType<T, U, V>(U obj) => obj is null ? null! : new OrType<T, U, V>(obj);
+
+ public static explicit operator OrType<T, U, V>(T obj) => obj is null ? null! : new OrType<T, U, V>(obj);
+
+ public static explicit operator OrType<T, U, V>(V obj) => obj is null ? null! : new OrType<T, U, V>(obj);
+
+ public override string ToString()
+ {
+ return Value?.ToString()!;
+ }
+}
+
+
+public record OrType<T, U, V, W> : IOrType
+{
+ public object? Value { get; }
+
+ public OrType(T t)
+ {
+ Value = t ?? throw new ArgumentNullException(nameof(t));
+ }
+
+ public OrType(U u)
+ {
+ Value = u ?? throw new ArgumentNullException(nameof(u));
+ }
+
+ public OrType(V v)
+ {
+ Value = v ?? throw new ArgumentNullException(nameof(v));
+ }
+
+ public OrType(W w)
+ {
+ Value = w ?? throw new ArgumentNullException(nameof(w));
+ }
+
+ public static explicit operator U?(OrType<T, U, V, W> obj)
+ {
+ return obj.Value is U x ? x : default;
+ }
+
+ public static explicit operator T?(OrType<T, U, V, W> obj)
+ {
+ return obj.Value is T x ? x : default;
+ }
+
+ public static explicit operator V?(OrType<T, U, V, W> obj)
+ {
+ return obj.Value is V x ? x : default;
+ }
+
+ public static explicit operator W?(OrType<T, U, V, W> obj)
+ {
+ return obj.Value is W x ? x : default;
+ }
+
+ public static explicit operator OrType<T, U, V, W>(U obj) => obj is null ? null! : new OrType<T, U, V, W>(obj);
+
+ public static explicit operator OrType<T, U, V, W>(T obj) => obj is null ? null! : new OrType<T, U, V, W>(obj);
+
+ public static explicit operator OrType<T, U, V, W>(V obj) => obj is null ? null! : new OrType<T, U, V, W>(obj);
+
+ public static explicit operator OrType<T, U, V, W>(W obj) => obj is null ? null! : new OrType<T, U, V, W>(obj);
+
+ public override string ToString()
+ {
+ return Value?.ToString()!;
+ }
+}
diff --git a/generator/plugins/dotnet/custom/OrTypeArrayConverter.cs b/generator/plugins/dotnet/custom/OrTypeArrayConverter.cs
new file mode 100644
index 0000000..f2dbf23
--- /dev/null
+++ b/generator/plugins/dotnet/custom/OrTypeArrayConverter.cs
@@ -0,0 +1,145 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Immutable;
+
+public class OrTypeArrayConverter<T, U> : JsonConverter<ImmutableArray<OrType<T, U>>>
+{
+ private OrTypeConverter<T, U> _converter;
+
+ public OrTypeArrayConverter()
+ {
+ _converter = new OrTypeConverter<T, U>();
+ }
+
+ public override ImmutableArray<OrType<T, U>> ReadJson(JsonReader reader, Type objectType, ImmutableArray<OrType<T, U>> existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return default(ImmutableArray<OrType<T, U>>);
+ }
+
+ JArray array = JArray.Load(reader);
+ ImmutableArray<OrType<T, U>>.Builder builder = ImmutableArray.CreateBuilder<OrType<T, U>>();
+
+ for (int i = 0; i < array.Count; i++)
+ {
+ builder.Add((OrType<T, U>)_converter.ReadJson(array[i].CreateReader(), typeof(OrType<T, U>), null, serializer)!);
+ }
+
+ return builder.ToImmutable();
+ }
+
+ public override void WriteJson(JsonWriter writer, ImmutableArray<OrType<T, U>> value, JsonSerializer serializer)
+ {
+ if (value.IsDefault)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteStartArray();
+
+ foreach (var item in value)
+ {
+ _converter.WriteJson(writer, item, serializer);
+ }
+
+ writer.WriteEndArray();
+ }
+ }
+}
+public class OrTypeArrayConverter<T, U, V> : JsonConverter<ImmutableArray<OrType<T, U, V>>>
+{
+ private OrTypeConverter<T, U, V> _converter;
+
+ public OrTypeArrayConverter()
+ {
+ _converter = new OrTypeConverter<T, U, V>();
+ }
+
+ public override ImmutableArray<OrType<T, U, V>> ReadJson(JsonReader reader, Type objectType, ImmutableArray<OrType<T, U, V>> existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return default(ImmutableArray<OrType<T, U, V>>);
+ }
+
+ JArray array = JArray.Load(reader);
+ ImmutableArray<OrType<T, U, V>>.Builder builder = ImmutableArray.CreateBuilder<OrType<T, U, V>>();
+
+ for (int i = 0; i < array.Count; i++)
+ {
+ builder.Add((OrType<T, U, V>)_converter.ReadJson(array[i].CreateReader(), typeof(OrType<T, U, V>), null, serializer)!);
+ }
+
+ return builder.ToImmutable();
+ }
+
+ public override void WriteJson(JsonWriter writer, ImmutableArray<OrType<T, U, V>> value, JsonSerializer serializer)
+ {
+ if (value.IsDefault)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteStartArray();
+
+ foreach (var item in value)
+ {
+ _converter.WriteJson(writer, item, serializer);
+ }
+
+ writer.WriteEndArray();
+ }
+ }
+}
+
+
+public class OrTypeArrayConverter<T, U, V, W> : JsonConverter<ImmutableArray<OrType<T, U, V, W>>>
+{
+ private OrTypeConverter<T, U, V, W> _converter;
+
+ public OrTypeArrayConverter()
+ {
+ _converter = new OrTypeConverter<T, U, V, W>();
+ }
+
+ public override ImmutableArray<OrType<T, U, V, W>> ReadJson(JsonReader reader, Type objectType, ImmutableArray<OrType<T, U, V, W>> existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return default(ImmutableArray<OrType<T, U, V, W>>);
+ }
+
+ JArray array = JArray.Load(reader);
+ ImmutableArray<OrType<T, U, V, W>>.Builder builder = ImmutableArray.CreateBuilder<OrType<T, U, V, W>>();
+
+ for (int i = 0; i < array.Count; i++)
+ {
+ builder.Add((OrType<T, U, V, W>)_converter.ReadJson(array[i].CreateReader(), typeof(OrType<T, U, V, W>), null, serializer)!);
+ }
+
+ return builder.ToImmutable();
+ }
+
+ public override void WriteJson(JsonWriter writer, ImmutableArray<OrType<T, U, V, W>> value, JsonSerializer serializer)
+ {
+ if (value.IsDefault)
+ {
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WriteStartArray();
+
+ foreach (var item in value)
+ {
+ _converter.WriteJson(writer, item, serializer);
+ }
+
+ writer.WriteEndArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/OrTypeConverter.cs b/generator/plugins/dotnet/custom/OrTypeConverter.cs
new file mode 100644
index 0000000..f2aadbf
--- /dev/null
+++ b/generator/plugins/dotnet/custom/OrTypeConverter.cs
@@ -0,0 +1,595 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+internal class OrTypeConverterHelpers
+{
+ public static Type[] SortTypesByHeuristic(Type[] types, JToken jToken)
+ {
+ var typePropertyScores = new Dictionary<Type, int>();
+
+ string[] jTokenPropertyNames = jToken.Children<JProperty>().Select(p => p.Name.ToUpper()).ToArray();
+
+ foreach (Type type in types)
+ {
+ string[] typePropertyNames = type.GetProperties().Select(p => p.Name.ToUpper()).ToArray();
+
+ int score = jTokenPropertyNames.Count(propertyName => typePropertyNames.Contains(propertyName));
+ typePropertyScores[type] = score;
+ }
+
+ return types.OrderByDescending(type => typePropertyScores[type]).ToArray();
+ }
+}
+
+public class OrTypeConverter<T, U> : JsonConverter<OrType<T, U>>
+{
+ public override OrType<T, U>? ReadJson(JsonReader reader, Type objectType, OrType<T, U>? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ reader = reader ?? throw new ArgumentNullException(nameof(reader));
+
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ Type[] types = new Type[] { typeof(T), typeof(U) };
+
+ if (reader.TokenType == JsonToken.Integer && (Validators.HasType(types, typeof(uint)) || Validators.HasType(types, typeof(int))))
+ {
+ return ReadIntegerToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.Float && Validators.HasType(types, typeof(float)))
+ {
+ return ReadFloatToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.Boolean && Validators.HasType(types, typeof(bool)))
+ {
+ return ReadBooleanToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.String && Validators.HasType(types, typeof(string)))
+ {
+ return ReadStringToken(reader, serializer, types);
+ }
+
+ var token = JToken.Load(reader);
+ return OrTypeConverter<T, U>.ReadObjectToken(token, serializer, OrTypeConverterHelpers.SortTypesByHeuristic(types, token));
+ }
+
+ private static OrType<T, U> ReadIntegerToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ long integer = serializer.Deserialize<long>(reader);
+ if (Validators.InUIntegerRange(integer) && Validators.HasType(types, typeof(uint)))
+ {
+ if (typeof(T) == typeof(uint))
+ {
+ return new OrType<T, U>((T)(object)(uint)integer);
+ }
+ if (typeof(U) == typeof(uint))
+ {
+ return new OrType<T, U>((U)(object)(uint)integer);
+ }
+ }
+ if (Validators.InIntegerRange(integer) && Validators.HasType(types, typeof(int)))
+ {
+ if (typeof(T) == typeof(int))
+ {
+ return new OrType<T, U>((T)(object)(int)integer);
+ }
+ if (typeof(U) == typeof(int))
+ {
+ return new OrType<T, U>((U)(object)(int)integer);
+ }
+ }
+ throw new ArgumentOutOfRangeException($"Integer out-of-range of LSP Signed Integer[{int.MinValue}:{int.MaxValue}] and out-of-range of LSP Unsigned Integer [{uint.MinValue}:{uint.MaxValue}] => {integer}");
+ }
+
+ private static OrType<T, U> ReadFloatToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ float real = serializer.Deserialize<float>(reader);
+ if (typeof(T) == typeof(float))
+ {
+ return new OrType<T, U>((T)(object)real);
+ }
+ if (typeof(U) == typeof(float))
+ {
+ return new OrType<T, U>((U)(object)real);
+ }
+ throw new InvalidOperationException("Invalid token type for float");
+ }
+
+ private static OrType<T, U> ReadBooleanToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ bool boolean = serializer.Deserialize<bool>(reader);
+ if (typeof(T) == typeof(bool))
+ {
+ return new OrType<T, U>((T)(object)boolean);
+ }
+ if (typeof(U) == typeof(bool))
+ {
+ return new OrType<T, U>((U)(object)boolean);
+ }
+ throw new InvalidOperationException("Invalid token type for boolean");
+ }
+
+ private static OrType<T, U> ReadStringToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ string str = serializer.Deserialize<string>(reader)!;
+ if (typeof(T) == typeof(string))
+ {
+ return new OrType<T, U>((T)(object)str);
+ }
+ if (typeof(U) == typeof(string))
+ {
+ return new OrType<T, U>((U)(object)str);
+ }
+ throw new InvalidOperationException("Invalid token type for string");
+ }
+
+ private static OrType<T, U> ReadObjectToken(JToken token, JsonSerializer serializer, Type[] types)
+ {
+ var exceptions = new List<Exception>();
+ foreach (Type type in types)
+ {
+ try
+ {
+ object? value = null;
+ if (token.Type == JTokenType.Array && type == typeof((uint, uint)))
+ {
+ uint[]? o = token.ToObject<uint[]>(serializer);
+ if (o != null)
+ {
+ value = (o[0], o[1]);
+ }
+ }
+ else
+ {
+ value = token.ToObject(type, serializer);
+ }
+
+ if (value != null)
+ {
+ if (value is T t)
+ {
+ return new OrType<T, U>(t);
+ }
+ if (value is U u)
+ {
+ return new OrType<T, U>(u);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex);
+ continue;
+ }
+ }
+
+ throw new JsonSerializationException("Unable to deserialize object", new AggregateException(exceptions));
+ }
+
+
+
+ public override void WriteJson(JsonWriter writer, OrType<T, U>? value, JsonSerializer serializer)
+ {
+ if (value is null)
+ {
+ writer.WriteNull();
+ }
+ else if (value?.Value?.GetType() == typeof((uint, uint)))
+ {
+ ValueTuple<uint, uint> o = (ValueTuple<uint, uint>)(value.Value);
+ serializer.Serialize(writer, new uint[] { o.Item1, o.Item2 });
+ }
+ else
+ {
+ serializer.Serialize(writer, value?.Value);
+ }
+ }
+}
+
+public class OrTypeConverter<T, U, V> : JsonConverter<OrType<T, U, V>>
+{
+ public override OrType<T, U, V>? ReadJson(JsonReader reader, Type objectType, OrType<T, U, V>? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ reader = reader ?? throw new ArgumentNullException(nameof(reader));
+
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ Type[] types = new Type[] { typeof(T), typeof(U), typeof(V) };
+
+ if (reader.TokenType == JsonToken.Integer && (Validators.HasType(types, typeof(uint)) || Validators.HasType(types, typeof(int))))
+ {
+ return ReadIntegerToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.Float && Validators.HasType(types, typeof(float)))
+ {
+ return ReadFloatToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.Boolean && Validators.HasType(types, typeof(bool)))
+ {
+ return ReadBooleanToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.String && Validators.HasType(types, typeof(string)))
+ {
+ return ReadStringToken(reader, serializer, types);
+ }
+
+ var token = JToken.Load(reader);
+ return OrTypeConverter<T, U, V>.ReadObjectToken(token, serializer, OrTypeConverterHelpers.SortTypesByHeuristic(types, token));
+ }
+
+ private static OrType<T, U, V> ReadIntegerToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ long integer = serializer.Deserialize<long>(reader);
+ if (Validators.InUIntegerRange(integer) && Validators.HasType(types, typeof(uint)))
+ {
+ if (typeof(T) == typeof(uint))
+ {
+ return new OrType<T, U, V>((T)(object)(uint)integer);
+ }
+ if (typeof(U) == typeof(uint))
+ {
+ return new OrType<T, U, V>((U)(object)(uint)integer);
+ }
+ if (typeof(V) == typeof(uint))
+ {
+ return new OrType<T, U, V>((V)(object)(uint)integer);
+ }
+ }
+ if (Validators.InIntegerRange(integer) && Validators.HasType(types, typeof(int)))
+ {
+ if (typeof(T) == typeof(int))
+ {
+ return new OrType<T, U, V>((T)(object)(int)integer);
+ }
+ if (typeof(U) == typeof(int))
+ {
+ return new OrType<T, U, V>((U)(object)(int)integer);
+ }
+ if (typeof(V) == typeof(int))
+ {
+ return new OrType<T, U, V>((V)(object)(int)integer);
+ }
+ }
+ throw new ArgumentOutOfRangeException($"Integer out-of-range of LSP Signed Integer[{int.MinValue}:{int.MaxValue}] and out-of-range of LSP Unsigned Integer [{uint.MinValue}:{uint.MaxValue}] => {integer}");
+ }
+
+ private static OrType<T, U, V> ReadFloatToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ float real = serializer.Deserialize<float>(reader);
+ if (typeof(T) == typeof(float))
+ {
+ return new OrType<T, U, V>((T)(object)real);
+ }
+ if (typeof(U) == typeof(float))
+ {
+ return new OrType<T, U, V>((U)(object)real);
+ }
+ if (typeof(V) == typeof(float))
+ {
+ return new OrType<T, U, V>((V)(object)real);
+ }
+ throw new InvalidOperationException("Invalid token type for float");
+ }
+
+ private static OrType<T, U, V> ReadBooleanToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ bool boolean = serializer.Deserialize<bool>(reader);
+ if (typeof(T) == typeof(bool))
+ {
+ return new OrType<T, U, V>((T)(object)boolean);
+ }
+ if (typeof(U) == typeof(bool))
+ {
+ return new OrType<T, U, V>((U)(object)boolean);
+ }
+ if (typeof(V) == typeof(bool))
+ {
+ return new OrType<T, U, V>((V)(object)boolean);
+ }
+ throw new InvalidOperationException("Invalid token type for boolean");
+ }
+
+ private static OrType<T, U, V> ReadStringToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ string str = serializer.Deserialize<string>(reader)!;
+ if (typeof(T) == typeof(string))
+ {
+ return new OrType<T, U, V>((T)(object)str);
+ }
+ if (typeof(U) == typeof(string))
+ {
+ return new OrType<T, U, V>((U)(object)str);
+ }
+ if (typeof(V) == typeof(string))
+ {
+ return new OrType<T, U, V>((V)(object)str);
+ }
+ throw new InvalidOperationException("Invalid token type for string");
+ }
+
+ private static OrType<T, U, V> ReadObjectToken(JToken token, JsonSerializer serializer, Type[] types)
+ {
+ var exceptions = new List<Exception>();
+ foreach (Type type in types)
+ {
+ try
+ {
+ object? value = null;
+ if (token.Type == JTokenType.Array && type == typeof((uint, uint)))
+ {
+ uint[]? o = token.ToObject<uint[]>(serializer);
+ if (o != null)
+ {
+ value = (o[0], o[1]);
+ }
+ }
+ else
+ {
+ value = token.ToObject(type, serializer);
+ }
+
+ if (value != null)
+ {
+ if (value is T t)
+ {
+ return new OrType<T, U, V>(t);
+ }
+ if (value is U u)
+ {
+ return new OrType<T, U, V>(u);
+ }
+ if (value is V v)
+ {
+ return new OrType<T, U, V>(v);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex);
+ continue;
+ }
+ }
+
+ throw new JsonSerializationException("Unable to deserialize object", new AggregateException(exceptions));
+ }
+
+ public override void WriteJson(JsonWriter writer, OrType<T, U, V>? value, JsonSerializer serializer)
+ {
+ if (value is null)
+ {
+ writer.WriteNull();
+ }
+ else if (value?.Value?.GetType() == typeof((uint, uint)))
+ {
+ ValueTuple<uint, uint> o = (ValueTuple<uint, uint>)(value.Value);
+ serializer.Serialize(writer, new uint[] { o.Item1, o.Item2 });
+ }
+ else
+ {
+ serializer.Serialize(writer, value?.Value);
+ }
+ }
+}
+
+public class OrTypeConverter<T, U, V, W> : JsonConverter<OrType<T, U, V, W>>
+{
+ public override OrType<T, U, V, W>? ReadJson(JsonReader reader, Type objectType, OrType<T, U, V, W>? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ reader = reader ?? throw new ArgumentNullException(nameof(reader));
+
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+
+ Type[] types = new Type[] { typeof(T), typeof(U), typeof(V), typeof(W) };
+
+ if (reader.TokenType == JsonToken.Integer && (Validators.HasType(types, typeof(uint)) || Validators.HasType(types, typeof(int))))
+ {
+ return ReadIntegerToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.Float && Validators.HasType(types, typeof(float)))
+ {
+ return ReadFloatToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.Boolean && Validators.HasType(types, typeof(bool)))
+ {
+ return ReadBooleanToken(reader, serializer, types);
+ }
+ if (reader.TokenType == JsonToken.String && Validators.HasType(types, typeof(string)))
+ {
+ return ReadStringToken(reader, serializer, types);
+ }
+
+ var token = JToken.Load(reader);
+ return OrTypeConverter<T, U, V, W>.ReadObjectToken(token, serializer, OrTypeConverterHelpers.SortTypesByHeuristic(types, token));
+ }
+
+ private static OrType<T, U, V, W> ReadIntegerToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ long integer = serializer.Deserialize<long>(reader);
+ if (Validators.InUIntegerRange(integer) && Validators.HasType(types, typeof(uint)))
+ {
+ if (typeof(T) == typeof(uint))
+ {
+ return new OrType<T, U, V, W>((T)(object)(uint)integer);
+ }
+ if (typeof(U) == typeof(uint))
+ {
+ return new OrType<T, U, V, W>((U)(object)(uint)integer);
+ }
+ if (typeof(V) == typeof(uint))
+ {
+ return new OrType<T, U, V, W>((V)(object)(uint)integer);
+ }
+ if (typeof(W) == typeof(uint))
+ {
+ return new OrType<T, U, V, W>((W)(object)(uint)integer);
+ }
+ }
+ if (Validators.InIntegerRange(integer) && Validators.HasType(types, typeof(int)))
+ {
+ if (typeof(T) == typeof(int))
+ {
+ return new OrType<T, U, V, W>((T)(object)(int)integer);
+ }
+ if (typeof(U) == typeof(int))
+ {
+ return new OrType<T, U, V, W>((U)(object)(int)integer);
+ }
+ if (typeof(V) == typeof(int))
+ {
+ return new OrType<T, U, V, W>((V)(object)(int)integer);
+ }
+ if (typeof(W) == typeof(int))
+ {
+ return new OrType<T, U, V, W>((W)(object)(int)integer);
+ }
+ }
+ throw new ArgumentOutOfRangeException($"Integer out-of-range of LSP Signed Integer[{int.MinValue}:{int.MaxValue}] and out-of-range of LSP Unsigned Integer [{uint.MinValue}:{uint.MaxValue}] => {integer}");
+ }
+
+ private static OrType<T, U, V, W> ReadFloatToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ float real = serializer.Deserialize<float>(reader);
+ if (typeof(T) == typeof(float))
+ {
+ return new OrType<T, U, V, W>((T)(object)real);
+ }
+ if (typeof(U) == typeof(float))
+ {
+ return new OrType<T, U, V, W>((U)(object)real);
+ }
+ if (typeof(V) == typeof(float))
+ {
+ return new OrType<T, U, V, W>((V)(object)real);
+ }
+ if (typeof(W) == typeof(float))
+ {
+ return new OrType<T, U, V, W>((W)(object)real);
+ }
+ throw new InvalidOperationException("Invalid token type for float");
+ }
+
+ private static OrType<T, U, V, W> ReadBooleanToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ bool boolean = serializer.Deserialize<bool>(reader);
+ if (typeof(T) == typeof(bool))
+ {
+ return new OrType<T, U, V, W>((T)(object)boolean);
+ }
+ if (typeof(U) == typeof(bool))
+ {
+ return new OrType<T, U, V, W>((U)(object)boolean);
+ }
+ if (typeof(V) == typeof(bool))
+ {
+ return new OrType<T, U, V, W>((V)(object)boolean);
+ }
+ if (typeof(W) == typeof(bool))
+ {
+ return new OrType<T, U, V, W>((W)(object)boolean);
+ }
+ throw new InvalidOperationException("Invalid token type for boolean");
+ }
+
+ private static OrType<T, U, V, W> ReadStringToken(JsonReader reader, JsonSerializer serializer, Type[] types)
+ {
+ string str = serializer.Deserialize<string>(reader)!;
+ if (typeof(T) == typeof(string))
+ {
+ return new OrType<T, U, V, W>((T)(object)str);
+ }
+ if (typeof(U) == typeof(string))
+ {
+ return new OrType<T, U, V, W>((U)(object)str);
+ }
+ if (typeof(V) == typeof(string))
+ {
+ return new OrType<T, U, V, W>((V)(object)str);
+ }
+ if (typeof(W) == typeof(string))
+ {
+ return new OrType<T, U, V, W>((W)(object)str);
+ }
+ throw new InvalidOperationException("Invalid token type for string");
+ }
+
+ private static OrType<T, U, V, W> ReadObjectToken(JToken token, JsonSerializer serializer, Type[] types)
+ {
+ var exceptions = new List<Exception>();
+ foreach (Type type in types)
+ {
+ try
+ {
+ object? value = null;
+ if (token.Type == JTokenType.Array && type == typeof((uint, uint)))
+ {
+ uint[]? o = token.ToObject<uint[]>(serializer);
+ if (o != null)
+ {
+ value = (o[0], o[1]);
+ }
+ }
+ else
+ {
+ value = token.ToObject(type, serializer);
+ }
+
+ if (value != null)
+ {
+ if (value is T t)
+ {
+ return new OrType<T, U, V, W>(t);
+ }
+ if (value is U u)
+ {
+ return new OrType<T, U, V, W>(u);
+ }
+ if (value is V v)
+ {
+ return new OrType<T, U, V, W>(v);
+ }
+ if (value is W w)
+ {
+ return new OrType<T, U, V, W>(w);
+ }
+ }
+
+ }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex);
+ continue;
+ }
+ }
+
+ throw new JsonSerializationException("Unable to deserialize object", new AggregateException(exceptions));
+ }
+
+ public override void WriteJson(JsonWriter writer, OrType<T, U, V, W>? value, JsonSerializer serializer)
+ {
+ if (value is null)
+ {
+ writer.WriteNull();
+ }
+ else if (value?.Value?.GetType() == typeof((uint, uint)))
+ {
+ ValueTuple<uint, uint> o = (ValueTuple<uint, uint>)(value.Value);
+ serializer.Serialize(writer, new uint[] { o.Item1, o.Item2 });
+ }
+ else
+ {
+ serializer.Serialize(writer, value?.Value);
+ }
+ }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/Proposed.cs b/generator/plugins/dotnet/custom/Proposed.cs
new file mode 100644
index 0000000..eb0ed75
--- /dev/null
+++ b/generator/plugins/dotnet/custom/Proposed.cs
@@ -0,0 +1,17 @@
+using System;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Enum)]
+public class ProposedAttribute : Attribute
+{
+ public ProposedAttribute()
+ {
+ Version = null;
+ }
+
+ public ProposedAttribute(string version)
+ {
+ Version = version;
+ }
+
+ public string? Version { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/ResponseError.cs b/generator/plugins/dotnet/custom/ResponseError.cs
new file mode 100644
index 0000000..5151783
--- /dev/null
+++ b/generator/plugins/dotnet/custom/ResponseError.cs
@@ -0,0 +1,27 @@
+using Newtonsoft.Json;
+using System.Runtime.Serialization;
+
+[DataContract]
+public class ResponseError
+{
+ [JsonConstructor]
+ public ResponseError(
+ int code,
+ string message,
+ LSPObject? data = null
+ )
+ {
+ Code = code;
+ Message = message;
+ Data = data;
+ }
+
+ [DataMember(Name = "code")]
+ int Code { get; }
+
+ [DataMember(Name = "message")]
+ string Message { get; }
+
+ [DataMember(Name = "data")]
+ LSPObject? Data { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/Since.cs b/generator/plugins/dotnet/custom/Since.cs
new file mode 100644
index 0000000..b13da74
--- /dev/null
+++ b/generator/plugins/dotnet/custom/Since.cs
@@ -0,0 +1,17 @@
+using System;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Enum | AttributeTargets.Interface)]
+public class SinceAttribute : Attribute
+{
+ public SinceAttribute()
+ {
+ Version = null;
+ }
+
+ public SinceAttribute(string version)
+ {
+ Version = version;
+ }
+
+ public string? Version { get; }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/custom/Validators.cs b/generator/plugins/dotnet/custom/Validators.cs
new file mode 100644
index 0000000..9edc18f
--- /dev/null
+++ b/generator/plugins/dotnet/custom/Validators.cs
@@ -0,0 +1,20 @@
+
+using System;
+
+public static class Validators
+{
+ public static bool HasType(Type[] types, Type type)
+ {
+ return types.Contains(type);
+ }
+
+ public static bool InIntegerRange(long value)
+ {
+ return value >= int.MinValue && value <= int.MaxValue;
+ }
+
+ public static bool InUIntegerRange(long value)
+ {
+ return value >= uint.MinValue && value <= uint.MaxValue;
+ }
+} \ No newline at end of file
diff --git a/generator/plugins/dotnet/dotnet_classes.py b/generator/plugins/dotnet/dotnet_classes.py
new file mode 100644
index 0000000..0a705cd
--- /dev/null
+++ b/generator/plugins/dotnet/dotnet_classes.py
@@ -0,0 +1,1024 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+import re
+from typing import Any, Dict, List, Optional, Tuple, Union
+
+import cattrs
+
+from generator import model
+
+from .dotnet_commons import TypeData
+from .dotnet_constants import NAMESPACE
+from .dotnet_helpers import (
+ class_wrapper,
+ generate_extras,
+ get_doc,
+ get_special_case_class_name,
+ get_special_case_property_name,
+ get_usings,
+ indent_lines,
+ lsp_method_to_name,
+ namespace_wrapper,
+ to_camel_case,
+ to_upper_camel_case,
+)
+
+ORTYPE_CONVERTER_RE = re.compile(r"OrType<(?P<parts>.*)>")
+IMMUTABLE_ARRAY_CONVERTER_RE = re.compile(r"ImmutableArray<(?P<elements>.*)>")
+
+
+def _get_enum(name: str, spec: model.LSPModel) -> Optional[model.Enum]:
+ for enum in spec.enumerations:
+ if enum.name == name:
+ return enum
+ return None
+
+
+def _get_struct(name: str, spec: model.LSPModel) -> Optional[model.Structure]:
+ for struct in spec.structures:
+ if struct.name == name:
+ return struct
+ return None
+
+
+def _is_str_enum(enum_def: model.Enum) -> bool:
+ return all(isinstance(item.value, str) for item in enum_def.values)
+
+
+def _is_int_enum(enum_def: model.Enum) -> bool:
+ return all(isinstance(item.value, int) for item in enum_def.values)
+
+
+def lsp_to_base_types(lsp_type: model.BaseType):
+ if lsp_type.name in ["string", "RegExp"]:
+ return "string"
+ elif lsp_type.name in ["DocumentUri", "URI"]:
+ return "Uri"
+ elif lsp_type.name in ["decimal"]:
+ return "float"
+ elif lsp_type.name in ["integer"]:
+ return "int"
+ elif lsp_type.name in ["uinteger"]:
+ return "uint"
+ elif lsp_type.name in ["boolean"]:
+ return "bool"
+ elif lsp_type.name in ["null"]:
+ return "object"
+
+ # null should be handled by the caller as an Option<> type
+ raise ValueError(f"Unknown base type: {lsp_type.name}")
+
+
+def get_types_for_usings(code: List[str]) -> List[str]:
+ immutable = []
+ for line in code:
+ if "ImmutableArray<" in line:
+ immutable.append("ImmutableArray")
+ if "ImmutableDictionary<" in line:
+ immutable.append("ImmutableDictionary")
+ return list(set(immutable))
+
+
+def has_null_base_type(items: List[model.LSP_TYPE_SPEC]) -> bool:
+ return any(item.kind == "base" and item.name == "null" for item in items)
+
+
+def filter_null_base_type(
+ items: List[model.LSP_TYPE_SPEC],
+) -> List[model.LSP_TYPE_SPEC]:
+ return [item for item in items if not (item.kind == "base" and item.name == "null")]
+
+
+def get_type_name(
+ type_def: model.LSP_TYPE_SPEC,
+ types: TypeData,
+ spec: model.LSPModel,
+ name_context: Optional[str] = None,
+) -> str:
+ name = None
+ if type_def.kind == "reference":
+ enum_def = _get_enum(type_def.name, spec)
+ if enum_def and enum_def.supportsCustomValues:
+ if _is_str_enum(enum_def):
+ name = f"string"
+ elif _is_int_enum(enum_def):
+ name = f"int"
+ else:
+ name = get_special_case_class_name(type_def.name)
+ elif type_def.kind == "array":
+ name = f"ImmutableArray<{get_type_name(type_def.element, types, spec, name_context)}>"
+ elif type_def.kind == "map":
+ name = generate_map_type(type_def, types, spec, name_context)
+ elif type_def.kind == "base":
+ name = lsp_to_base_types(type_def)
+ elif type_def.kind == "literal":
+ name = generate_literal_type(type_def, types, spec, name_context)
+ elif type_def.kind == "stringLiteral":
+ name = "string"
+ elif type_def.kind == "tuple":
+ subset = filter_null_base_type(type_def.items)
+ subset_types = [
+ get_type_name(item, types, spec, name_context) for item in subset
+ ]
+ name = f"({', '.join(subset_types)})"
+ elif type_def.kind == "or":
+ subset = filter_null_base_type(type_def.items)
+ if len(subset) == 1:
+ name = get_type_name(subset[0], types, spec, name_context)
+ elif len(subset) >= 2:
+ if are_variant_literals(subset):
+ name = generate_class_from_variant_literals(
+ subset, spec, types, name_context
+ )
+ else:
+ subset_types = [
+ get_type_name(item, types, spec, name_context) for item in subset
+ ]
+ name = f"OrType<{', '.join(subset_types)}>"
+ else:
+ raise ValueError(f"Unknown type kind: {type_def.kind}")
+ else:
+ raise ValueError(f"Unknown type kind: {type_def.kind}")
+ return name
+
+
+def generate_map_type(
+ type_def: model.LSP_TYPE_SPEC,
+ types: TypeData,
+ spec: model.LSPModel,
+ name_context: Optional[str] = None,
+) -> str:
+ key_type = get_type_name(type_def.key, types, spec, name_context)
+
+ if type_def.value.kind == "or":
+ subset = filter_null_base_type(type_def.value.items)
+ if len(subset) == 1:
+ value_type = get_type_name(type_def.value, types, spec, name_context)
+ else:
+ value_type = to_upper_camel_case(f"{name_context}Value")
+ type_alias = model.TypeAlias(
+ **{
+ "name": value_type,
+ "type": type_def.value,
+ }
+ )
+ generate_class_from_type_alias(type_alias, spec, types)
+
+ else:
+ value_type = get_type_name(type_def.value, types, spec, name_context)
+ return f"ImmutableDictionary<{key_type}, {value_type}>"
+
+
+def get_converter(type_def: model.LSP_TYPE_SPEC, type_name: str) -> Optional[str]:
+ if type_def.kind == "base" and type_def.name in ["DocumentUri", "URI"]:
+ return "[JsonConverter(typeof(CustomStringConverter<Uri>))]"
+ elif type_def.kind == "reference" and type_def.name in [
+ "Pattern",
+ "ChangeAnnotationIdentifier",
+ ]:
+ return f"[JsonConverter(typeof(CustomStringConverter<{type_def.name}>))]"
+ elif type_def.kind == "reference" and type_def.name == "DocumentSelector":
+ return "[JsonConverter(typeof(DocumentSelectorConverter))]"
+ elif type_def.kind == "or":
+ subset = filter_null_base_type(type_def.items)
+ if len(subset) == 1:
+ return get_converter(subset[0], type_name)
+ elif len(subset) >= 2:
+ converter = type_name.replace("OrType<", "OrTypeConverter<")
+ return f"[JsonConverter(typeof({converter}))]"
+ elif type_def.kind == "array" and type_name.startswith("OrType<"):
+ matches = ORTYPE_CONVERTER_RE.match(type_name).groupdict()
+ if "parts" in matches:
+ converter = f"OrTypeArrayConverter<{matches['parts']}>"
+ return f"[JsonConverter(typeof({converter}))]"
+ elif type_def.kind == "array":
+ matches = IMMUTABLE_ARRAY_CONVERTER_RE.match(type_name).groupdict()
+ elements = matches["elements"]
+ if elements.startswith("OrType<"):
+ matches = ORTYPE_CONVERTER_RE.match(elements).groupdict()
+ converter = f"OrTypeArrayConverter<{matches['parts']}>"
+ return f"[JsonConverter(typeof({converter}))]"
+ else:
+ converter = f"CustomArrayConverter<{elements}>"
+ return f"[JsonConverter(typeof({converter}))]"
+ return None
+
+
+def generate_property(
+ prop_def: model.Property,
+ spec: model.LSPModel,
+ types: TypeData,
+ usings: List[str],
+ class_name: str = "",
+) -> Tuple[List[str], str]:
+ if prop_def.name == "jsonrpc":
+ name = "JsonRPC"
+ else:
+ name = to_upper_camel_case(prop_def.name)
+ type_name = get_type_name(
+ prop_def.type, types, spec, f"{class_name}_{prop_def.name}"
+ )
+ converter = get_converter(prop_def.type, type_name)
+ special_optional = prop_def.type.kind == "or" and has_null_base_type(
+ prop_def.type.items
+ )
+ optional = (
+ "?"
+ if (prop_def.optional or special_optional)
+ and not (
+ type_name.startswith("ImmutableArray<")
+ or type_name.startswith("ImmutableDictionary<")
+ )
+ else ""
+ )
+ lines = (
+ get_doc(prop_def.documentation)
+ + generate_extras(prop_def)
+ + ([converter] if converter else [])
+ + (
+ ["[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]"]
+ if optional and not special_optional
+ else []
+ )
+ + [
+ f'[DataMember(Name = "{prop_def.name}")]',
+ ]
+ )
+
+ if prop_def.type.kind == "stringLiteral":
+ lines.append(
+ f'public {type_name}{optional} {name} {{ get; init; }} = "{prop_def.type.value}";'
+ )
+ else:
+ lines.append(f"public {type_name}{optional} {name} {{ get; init; }}")
+
+ usings.append("DataMember")
+ if converter:
+ usings.append("JsonConverter")
+ if optional and not special_optional:
+ usings.append("JsonProperty")
+
+ return lines, type_name
+
+
+def generate_name(name_context: str, types: TypeData) -> str:
+ # If name context has a '_' it is likely a property.
+ # Try name generation using just the property name
+ parts = [to_upper_camel_case(p) for p in name_context.split("_") if len(p) > 3]
+
+ # Try the last part of the name context
+ name = parts[-1]
+ if not types.get_by_name(name) and "info" in name_context.lower():
+ return name
+
+ # Combine all parts and try again
+ name = "".join(parts)
+ if not types.get_by_name(name):
+ return name
+
+ raise ValueError(f"Unable to generate name for {name_context}")
+
+
+def generate_literal_type(
+ literal: model.LiteralType,
+ types: TypeData,
+ spec: model.LSPModel,
+ name_context: Optional[str] = None,
+) -> str:
+ if len(literal.value.properties) == 0:
+ return "LSPObject"
+
+ if types.get_by_name(literal.name) and not _get_struct(literal.name, spec):
+ return literal.name
+
+ if name_context is None:
+ raise ValueError("name_context must be provided for literal types")
+
+ if name_context.startswith("I") and name_context[1].isupper():
+ # This is a interface name ISomething => Something
+ name_context = name_context[1:]
+
+ if "_" not in name_context:
+ name_context = f"{name_context}_{get_context_from_literal(literal)}"
+
+ literal.name = generate_name(name_context, types)
+
+ usings = ["DataContract"]
+ inner = []
+ for prop in literal.value.properties:
+ prop_code, _ = generate_property(prop, spec, types, usings, literal.name)
+ inner += prop_code
+
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(usings + get_types_for_usings(inner)),
+ class_wrapper(literal, inner),
+ )
+ types.add_type_info(literal, literal.name, lines)
+ return literal.name
+
+
+def generate_constructor(
+ struct: model.Structure,
+ types: TypeData,
+ properties: List[Tuple[model.Property, str]],
+) -> List[str]:
+ class_name = get_special_case_class_name(struct.name)
+ constructor = [
+ "[JsonConstructor]",
+ f"public {class_name}(",
+ ]
+
+ arguments = []
+ optional_args = []
+ assignments = []
+ ctor_data = []
+ for prop, prop_type in properties:
+ name = get_special_case_property_name(to_camel_case(prop.name))
+ special_optional = prop.type.kind == "or" and has_null_base_type(
+ prop.type.items
+ )
+ if prop.optional or special_optional:
+ if prop_type.startswith("ImmutableArray<") or prop_type.startswith(
+ "ImmutableDictionary<"
+ ):
+ optional_args += [f"{prop_type} {name} = default!"]
+ else:
+ optional_args += [f"{prop_type}? {name} = null"]
+ ctor_data += [(prop_type, name, True)]
+ elif prop.name == "jsonrpc":
+ optional_args += [f'{prop_type} {name} = "2.0"']
+ ctor_data += [(prop_type, name, True)]
+ else:
+ arguments += [f"{prop_type} {name}"]
+ ctor_data += [(prop_type, name, False)]
+
+ if prop.name == "jsonrpc":
+ assignments += [f"JsonRPC = {name};"]
+ else:
+ assignments += [f"{to_upper_camel_case(prop.name)} = {name};"]
+
+ # combine args with a '\n' to get comma with indent
+ all_args = (",\n".join(indent_lines(arguments + optional_args))).splitlines()
+ types.add_ctor(struct.name, ctor_data)
+
+ # re-split args to get the right coma placement and indent
+ constructor += all_args
+ constructor += [")", "{"]
+ constructor += indent_lines(assignments)
+ constructor += ["}"]
+ return constructor
+
+
+def generate_class_from_struct(
+ struct: model.Structure,
+ spec: model.LSPModel,
+ types: TypeData,
+ derived: Optional[str] = None,
+ attributes: Optional[List[str]] = None,
+):
+ if types.get_by_name(struct.name) or struct.name.startswith("_"):
+ return
+
+ if attributes is None:
+ attributes = []
+
+ inner = []
+ usings = ["DataContract", "JsonConstructor"]
+
+ properties = get_all_properties(struct, spec)
+ prop_types = []
+ for prop in properties:
+ prop_code, prop_type = generate_property(prop, spec, types, usings, struct.name)
+ inner += prop_code
+ prop_types += [prop_type]
+
+ ctor = generate_constructor(struct, types, zip(properties, prop_types))
+ inner = ctor + inner
+
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(usings + get_types_for_usings(inner + attributes)),
+ class_wrapper(struct, inner, derived, attributes),
+ )
+ types.add_type_info(struct, struct.name, lines)
+
+
+def get_context_from_literal(literal: model.LiteralType) -> str:
+ if len(literal.value.properties) == 0:
+ return "LSPObject"
+
+ skipped = 0
+ skip = [
+ "range",
+ "rangeLength",
+ "position",
+ "position",
+ "location",
+ "locationLink",
+ "text",
+ ]
+ for prop in literal.value.properties:
+ if prop.name in skip:
+ skipped += 1
+ continue
+ return prop.name
+
+ if skipped == len(literal.value.properties):
+ # pick property with longest name
+ names = sorted([p.name for p in literal.value.properties])
+ return sorted(names, key=lambda n: len(n))[-1]
+
+ return ""
+
+
+def generate_type_alias_constructor(
+ type_def: model.TypeAlias, spec: model.LSPModel, types: TypeData
+) -> List[str]:
+ constructor = []
+
+ if type_def.type.kind == "or":
+ subset = filter_null_base_type(type_def.type.items)
+ if len(subset) == 1:
+ raise ValueError("Unable to generate constructor for single item union")
+ elif len(subset) >= 2:
+ type_name = to_upper_camel_case(type_def.name)
+ for t in subset:
+ sub_type = get_type_name(t, types, spec, type_def.name)
+ arg = get_special_case_property_name(to_camel_case(sub_type))
+ matches = re.match(r"ImmutableArray<(?P<arg>\w+)>", arg)
+ if matches:
+ arg = f"{matches['arg']}s"
+
+ constructor += [
+ f"public {type_name}({sub_type} {arg}): base({arg}) {{}}",
+ ]
+ else:
+ raise ValueError("Unable to generate constructor for empty union")
+ elif type_def.type.kind == "reference":
+ type_name = to_upper_camel_case(type_def.name)
+ ctor_data = types.get_ctor(type_def.type.name)
+ required = [
+ (prop_type, prop_name)
+ for prop_type, prop_name, optional in ctor_data
+ if not optional
+ ]
+ optional = [
+ (prop_type, prop_name)
+ for prop_type, prop_name, optional in ctor_data
+ if optional
+ ]
+
+ ctor_args = [f"{prop_type} {prop_name}" for prop_type, prop_name in required]
+ ctor_args += [
+ f"{prop_type}? {prop_name} = null" for prop_type, prop_name in optional
+ ]
+
+ base_args = [f"{prop_name}" for _, prop_name in required + optional]
+ constructor += [
+ f"public {type_name}({','.join(ctor_args)}): base({','.join(base_args)}) {{}}",
+ ]
+
+ return constructor
+
+
+def generate_type_alias_converter(
+ type_def: model.TypeAlias, spec: model.LSPModel, types: TypeData
+) -> None:
+ assert type_def.type.kind == "or"
+ subset_types = [
+ get_type_name(i, types, spec, type_def.name)
+ for i in filter_null_base_type(type_def.type.items)
+ ]
+ converter = f"{type_def.name}Converter"
+ or_type_converter = f"OrTypeConverter<{','.join(subset_types)}>"
+ or_type = f"OrType<{','.join(subset_types)}>"
+ code = [
+ f"public class {converter} : JsonConverter<{type_def.name}>",
+ "{",
+ f"private {or_type_converter} _orType;",
+ f"public {converter}()",
+ "{",
+ f"_orType = new {or_type_converter}();",
+ "}",
+ f"public override {type_def.name}? ReadJson(JsonReader reader, Type objectType, {type_def.name}? existingValue, bool hasExistingValue, JsonSerializer serializer)",
+ "{",
+ "reader = reader ?? throw new ArgumentNullException(nameof(reader));",
+ "if (reader.TokenType == JsonToken.Null) { return null; }",
+ f"var o = _orType.ReadJson(reader, objectType, existingValue, serializer);",
+ f"if (o is {or_type} orType)",
+ "{",
+ ]
+ for t in subset_types:
+ code += [
+ f"if (orType.Value?.GetType() == typeof({t}))",
+ "{",
+ f"return new {type_def.name}(({t})orType.Value);",
+ "}",
+ ]
+ code += [
+ "}",
+ 'throw new JsonSerializationException($"Unexpected token type.");',
+ "}",
+ f"public override void WriteJson(JsonWriter writer, {type_def.name}? value, JsonSerializer serializer)",
+ "{",
+ "_orType.WriteJson(writer, value, serializer);",
+ "}",
+ "}",
+ ]
+
+ code = namespace_wrapper(
+ NAMESPACE, get_usings(["JsonConverter"] + get_types_for_usings(code)), code
+ )
+
+ ref = model.Structure(**{"name": converter, "properties": []})
+ types.add_type_info(ref, converter, code)
+ return converter
+
+
+def generate_class_from_type_alias(
+ type_def: model.TypeAlias, spec: model.LSPModel, types: TypeData
+) -> None:
+ if types.get_by_name(type_def.name):
+ return
+
+ usings = ["DataContract"]
+ type_name = get_type_name(type_def.type, types, spec, type_def.name)
+ class_attributes = []
+ if type_def.type.kind == "or":
+ converter = generate_type_alias_converter(type_def, spec, types)
+ class_attributes += [f"[JsonConverter(typeof({converter}))]"]
+ usings.append("JsonConverter")
+
+ inner = generate_type_alias_constructor(type_def, spec, types)
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(usings + get_types_for_usings(inner)),
+ class_wrapper(type_def, inner, type_name, class_attributes),
+ )
+ types.add_type_info(type_def, type_def.name, lines)
+
+
+def generate_class_from_variant_literals(
+ literals: List[model.LiteralType],
+ spec: model.LSPModel,
+ types: TypeData,
+ name_context: Optional[str] = None,
+) -> str:
+ name = generate_name(name_context, types)
+ if types.get_by_name(name):
+ raise ValueError(f"Name {name} already exists")
+
+ struct = model.Structure(
+ **{
+ "name": name,
+ "properties": get_properties_from_literals(literals),
+ }
+ )
+
+ lines = generate_code_for_variant_struct(struct, spec, types)
+ types.add_type_info(struct, struct.name, lines)
+ return struct.name
+
+
+def get_properties_from_literals(literals: List[model.LiteralType]) -> Dict[str, Any]:
+ properties = []
+ for literal in literals:
+ assert literal.kind == "literal"
+ for prop in literal.value.properties:
+ if prop.name not in [p["name"] for p in properties]:
+ properties.append(
+ {
+ "name": prop.name,
+ "type": cattrs.unstructure(prop.type),
+ "optional": has_optional_variant(literals, prop.name), #
+ }
+ )
+ return properties
+
+
+def generate_code_for_variant_struct(
+ struct: model.Structure,
+ spec: model.LSPModel,
+ types: TypeData,
+) -> None:
+ prop_types = []
+ inner = []
+ usings = ["DataContract", "JsonConstructor"]
+ for prop in struct.properties:
+ prop_code, prop_type = generate_property(prop, spec, types, usings, struct.name)
+ inner += prop_code
+ prop_types += [prop_type]
+
+ ctor_data = []
+ constructor_args = []
+ conditions = []
+ for prop, prop_type in zip(struct.properties, prop_types):
+ name = get_special_case_property_name(to_camel_case(prop.name))
+ immutable = prop_type.startswith("ImmutableArray<") or prop_type.startswith(
+ "ImmutableDictionary<"
+ )
+ constructor_args += [
+ f"{prop_type} {name}" if immutable else f"{prop_type}? {name}"
+ ]
+ ctor_data = [(prop_type)]
+ if immutable:
+ conditions += [f"({name}.IsDefault)"]
+ else:
+ conditions += [f"({name} is null)"]
+
+ sig = ", ".join(constructor_args)
+ types.add_ctor(struct.name, ctor_data)
+ ctor = [
+ f"[JsonConstructor]",
+ f"public {struct.name}({sig})",
+ "{",
+ *indent_lines(
+ [
+ f"if ({'&&'.join(conditions)})",
+ "{",
+ *indent_lines(
+ [
+ f'throw new ArgumentException("At least one of the arguments must be non-null");'
+ ]
+ ),
+ "}",
+ ]
+ ),
+ *indent_lines(
+ [
+ f"{to_upper_camel_case(prop.name)} = {get_special_case_property_name(to_camel_case(prop.name))};"
+ for prop in struct.properties
+ ]
+ ),
+ "}",
+ ]
+
+ inner = ctor + inner
+
+ return namespace_wrapper(
+ NAMESPACE,
+ get_usings(usings + get_types_for_usings(inner)),
+ class_wrapper(struct, inner, None),
+ )
+
+
+def generate_class_from_variant_type_alias(
+ type_def: model.TypeAlias,
+ spec: model.LSPModel,
+ types: TypeData,
+ name_context: Optional[str] = None,
+) -> None:
+ struct = model.Structure(
+ **{
+ "name": type_def.name,
+ "properties": get_properties_from_literals(type_def.type.items),
+ "documentation": type_def.documentation,
+ "since": type_def.since,
+ "deprecated": type_def.deprecated,
+ "proposed": type_def.proposed,
+ }
+ )
+
+ lines = generate_code_for_variant_struct(struct, spec, types)
+ types.add_type_info(type_def, type_def.name, lines)
+
+
+def has_optional_variant(literals: List[model.LiteralType], property_name: str) -> bool:
+ count = 0
+ optional = False
+ for literal in literals:
+ for prop in literal.value.properties:
+ if prop.name == property_name:
+ count += 1
+ optional = optional or prop.optional
+ return optional and count == len(literals)
+
+
+def are_variant_literals(literals: List[model.LiteralType]) -> bool:
+ if all(i.kind == "literal" for i in literals):
+ return all(
+ has_optional_variant(literals, prop.name)
+ for prop in literals[0].value.properties
+ )
+ return False
+
+
+def is_variant_type_alias(type_def: model.TypeAlias) -> bool:
+ if type_def.type.kind == "or" and all(
+ i.kind == "literal" for i in type_def.type.items
+ ):
+ literals = type_def.type.items
+ return all(
+ has_optional_variant(literals, prop.name)
+ for prop in literals[0].value.properties
+ )
+ return False
+
+
+def copy_struct(struct_def: model.Structure, new_name: str):
+ converter = cattrs.GenConverter()
+ obj = converter.unstructure(struct_def, model.Structure)
+ obj["name"] = new_name
+ return model.Structure(**obj)
+
+
+def copy_property(prop_def: model.Property):
+ converter = cattrs.GenConverter()
+ obj = converter.unstructure(prop_def, model.Property)
+ return model.Property(**obj)
+
+
+def get_all_extends(struct_def: model.Structure, spec) -> List[model.Structure]:
+ extends = []
+ for extend in struct_def.extends:
+ extends.append(_get_struct(extend.name, spec))
+ for struct in get_all_extends(_get_struct(extend.name, spec), spec):
+ if not any(struct.name == e.name for e in extends):
+ extends.append(struct)
+ return extends
+
+
+def get_all_properties(struct: model.Structure, spec) -> List[model.Structure]:
+ properties = []
+ for prop in struct.properties:
+ properties.append(copy_property(prop))
+
+ for extend in get_all_extends(struct, spec):
+ for prop in get_all_properties(extend, spec):
+ if not any(prop.name == p.name for p in properties):
+ properties.append(copy_property(prop))
+
+ if not all(mixin.kind == "reference" for mixin in struct.mixins):
+ raise ValueError(f"Struct {struct.name} has non-reference mixins")
+ for mixin in [_get_struct(mixin.name, spec) for mixin in struct.mixins]:
+ for prop in get_all_properties(mixin, spec):
+ if not any(prop.name == p.name for p in properties):
+ properties.append(copy_property(prop))
+
+ return properties
+
+
+def generate_code_for_request(request: model.Request):
+ lines = get_doc(request.documentation) + generate_extras(request)
+ lines.append(
+ f'public static string {lsp_method_to_name(request.method)} {{ get; }} = "{request.method}";'
+ )
+ return lines
+
+
+def generate_code_for_notification(notify: model.Notification):
+ lines = get_doc(notify.documentation) + generate_extras(notify)
+ lines.append(
+ f'public static string {lsp_method_to_name(notify.method)} {{ get; }} = "{notify.method}";'
+ )
+ return lines
+
+
+def generate_request_notification_methods(spec: model.LSPModel, types: TypeData):
+ inner_lines = []
+ for request in spec.requests:
+ inner_lines += generate_code_for_request(request)
+
+ for notification in spec.notifications:
+ inner_lines += generate_code_for_notification(notification)
+
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(["System"] + get_types_for_usings(inner_lines)),
+ ["public static class LSPMethods", "{", *indent_lines(inner_lines), "}"],
+ )
+ enum_type = model.Enum(
+ **{
+ "name": "LSPMethods",
+ "type": {"kind": "base", "name": "string"},
+ "values": [],
+ "documentation": "LSP methods as defined in the LSP spec",
+ }
+ )
+ types.add_type_info(enum_type, "LSPMethods", lines)
+
+
+def get_message_template(
+ obj: Union[model.Request, model.Notification],
+ is_request: bool,
+) -> model.Structure:
+ text = "Request" if is_request else "Notification"
+ properties = [
+ {
+ "name": "jsonrpc",
+ "type": {"kind": "stringLiteral", "value": "2.0"},
+ "documentation": "The jsonrpc version.",
+ }
+ ]
+ if is_request:
+ properties += [
+ {
+ "name": "id",
+ "type": {
+ "kind": "or",
+ "items": [
+ {"kind": "base", "name": "string"},
+ {"kind": "base", "name": "integer"},
+ ],
+ },
+ "documentation": f"The {text} id.",
+ }
+ ]
+ properties += [
+ {
+ "name": "method",
+ "type": {"kind": "base", "name": "string"},
+ "documentation": f"The {text} method.",
+ },
+ ]
+ if obj.params:
+ properties.append(
+ {
+ "name": "params",
+ "type": cattrs.unstructure(obj.params),
+ "documentation": f"The {text} parameters.",
+ }
+ )
+ else:
+ properties.append(
+ {
+ "name": "params",
+ "type": {"kind": "reference", "name": "LSPAny"},
+ "documentation": f"The {text} parameters.",
+ "optional": True,
+ }
+ )
+
+ class_template = {
+ "name": f"{lsp_method_to_name(obj.method)}{text}",
+ "properties": properties,
+ "documentation": obj.documentation,
+ "since": obj.since,
+ "deprecated": obj.deprecated,
+ "proposed": obj.proposed,
+ }
+ return model.Structure(**class_template)
+
+
+def get_response_template(
+ obj: model.Request, spec: model.LSPModel, types: TypeData
+) -> model.Structure:
+ properties = [
+ {
+ "name": "jsonrpc",
+ "type": {"kind": "stringLiteral", "value": "2.0"},
+ "documentation": "The jsonrpc version.",
+ },
+ {
+ "name": "id",
+ "type": {
+ "kind": "or",
+ "items": [
+ {"kind": "base", "name": "string"},
+ {"kind": "base", "name": "integer"},
+ ],
+ },
+ "documentation": f"The Request id.",
+ },
+ ]
+ if obj.result:
+ properties.append(
+ {
+ "name": "result",
+ "type": cattrs.unstructure(obj.result),
+ "documentation": f"Results for the request.",
+ "optional": True,
+ }
+ )
+ else:
+ properties.append(
+ {
+ "name": "result",
+ "type": {"kind": "base", "name": "null"},
+ "documentation": f"Results for the request.",
+ "optional": True,
+ }
+ )
+ properties.append(
+ {
+ "name": "error",
+ "type": {"kind": "reference", "name": "ResponseError"},
+ "documentation": f"Error while handling the request.",
+ "optional": True,
+ }
+ )
+ class_template = {
+ "name": f"{lsp_method_to_name(obj.method)}Response",
+ "properties": properties,
+ "documentation": obj.documentation,
+ "since": obj.since,
+ "deprecated": obj.deprecated,
+ "proposed": obj.proposed,
+ }
+ return model.Structure(**class_template)
+
+
+def get_registration_options_template(
+ obj: Union[model.Request, model.Notification],
+ spec: model.LSPModel,
+ types: TypeData,
+) -> model.Structure:
+ if obj.registrationOptions and obj.registrationOptions.kind != "reference":
+ if obj.registrationOptions.kind == "and":
+ structs = [_get_struct(s.name, spec) for s in obj.registrationOptions.items]
+ properties = []
+ for struct in structs:
+ properties += get_all_properties(struct, spec)
+
+ class_template = {
+ "name": f"{lsp_method_to_name(obj.method)}RegistrationOptions",
+ "properties": [
+ cattrs.unstructure(p, model.Property) for p in properties
+ ],
+ }
+ return model.Structure(**class_template)
+ else:
+ raise ValueError(
+ f"Unexpected registrationOptions type: {obj.registrationOptions.type.kind}"
+ )
+ return None
+
+
+def generate_all_classes(spec: model.LSPModel, types: TypeData):
+ for struct in spec.structures:
+ generate_class_from_struct(struct, spec, types)
+
+ for type_alias in spec.typeAliases:
+ if is_variant_type_alias(type_alias):
+ generate_class_from_variant_type_alias(type_alias, spec, types)
+ else:
+ generate_class_from_type_alias(type_alias, spec, types)
+
+ generate_request_notification_methods(spec, types)
+
+ for request in spec.requests:
+ partial_result_name = None
+ if request.partialResult:
+ partial_result_name = get_type_name(request.partialResult, types, spec)
+
+ struct = get_message_template(request, is_request=True)
+ generate_class_from_struct(
+ struct,
+ spec,
+ types,
+ (
+ f"IRequest<{get_type_name(request.params, types, spec)}>"
+ if request.params
+ else "IRequest<LSPAny?>"
+ ),
+ [
+ f"[Direction(MessageDirection.{to_upper_camel_case(request.messageDirection)})]",
+ f'[LSPRequest("{request.method}", typeof({lsp_method_to_name(request.method)}Response), typeof({partial_result_name}))]'
+ if partial_result_name
+ else f'[LSPRequest("{request.method}", typeof({lsp_method_to_name(request.method)}Response))]',
+ ],
+ )
+ response = get_response_template(request, spec, types)
+ generate_class_from_struct(
+ response,
+ spec,
+ types,
+ f"IResponse<{get_type_name(request.result, types, spec)}>",
+ [
+ f"[LSPResponse(typeof({lsp_method_to_name(request.method)}Request))]",
+ ],
+ )
+ registration_options = get_registration_options_template(request, spec, types)
+ if registration_options:
+ generate_class_from_struct(
+ registration_options,
+ spec,
+ types,
+ )
+
+ for notification in spec.notifications:
+ struct = get_message_template(notification, is_request=False)
+ generate_class_from_struct(
+ struct,
+ spec,
+ types,
+ (
+ f"INotification<{get_type_name(notification.params, types, spec)}>"
+ if notification.params
+ else "INotification<LSPAny>"
+ ),
+ [
+ f"[Direction(MessageDirection.{to_upper_camel_case(request.messageDirection)})]",
+ ],
+ )
+ registration_options = get_registration_options_template(
+ notification, spec, types
+ )
+ if registration_options:
+ generate_class_from_struct(
+ registration_options,
+ spec,
+ types,
+ )
diff --git a/generator/plugins/dotnet/dotnet_commons.py b/generator/plugins/dotnet/dotnet_commons.py
new file mode 100644
index 0000000..9c21eca
--- /dev/null
+++ b/generator/plugins/dotnet/dotnet_commons.py
@@ -0,0 +1,60 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+from typing import Dict, List, Tuple, Union
+
+from generator import model
+
+TypesWithId = Union[
+ model.Request,
+ model.TypeAlias,
+ model.Enum,
+ model.Structure,
+ model.Notification,
+ model.LiteralType,
+ model.ReferenceType,
+ model.ReferenceMapKeyType,
+ model.Property,
+ model.EnumItem,
+]
+
+
+class TypeData:
+ def __init__(self) -> None:
+ self._id_data: Dict[str, Tuple[str, TypesWithId, List[str]]] = {}
+ self._ctor_data: Dict[str, Tuple[str, str]] = {}
+
+ def add_type_info(
+ self,
+ type_def: TypesWithId,
+ type_name: str,
+ impl: List[str],
+ ) -> None:
+ if type_def.id_ in self._id_data:
+ raise Exception(f"Duplicate id {type_def.id_} for type {type_name}")
+ self._id_data[type_def.id_] = (type_name, type_def, impl)
+
+ def has_id(
+ self,
+ type_def: TypesWithId,
+ ) -> bool:
+ return type_def.id_ in self._id_data
+
+ def has_name(self, type_name: str) -> bool:
+ return any(type_name == name for name, _, _ in self._id_data.values())
+
+ def get_by_name(self, type_name: str) -> List[TypesWithId]:
+ return [
+ type_def
+ for name, type_def, _ in self._id_data.values()
+ if name == type_name
+ ]
+
+ def get_all(self) -> List[Tuple[str, List[str]]]:
+ return [(name, lines) for name, _, lines in self._id_data.values()]
+
+ def add_ctor(self, type_name: str, ctor: Tuple[str, str, bool]) -> None:
+ self._ctor_data[type_name] = ctor
+
+ def get_ctor(self, type_name: str) -> Tuple[str, str, bool]:
+ return self._ctor_data[type_name]
diff --git a/generator/plugins/dotnet/dotnet_constants.py b/generator/plugins/dotnet/dotnet_constants.py
new file mode 100644
index 0000000..f66c96b
--- /dev/null
+++ b/generator/plugins/dotnet/dotnet_constants.py
@@ -0,0 +1,5 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+NAMESPACE = "Microsoft.LanguageServer.Protocol"
+PACKAGE_DIR_NAME = "lsprotocol"
diff --git a/generator/plugins/dotnet/dotnet_enums.py b/generator/plugins/dotnet/dotnet_enums.py
new file mode 100644
index 0000000..c5e7a6c
--- /dev/null
+++ b/generator/plugins/dotnet/dotnet_enums.py
@@ -0,0 +1,51 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+from typing import Dict, List, Union
+
+from generator import model
+
+from .dotnet_commons import TypeData
+from .dotnet_constants import NAMESPACE
+from .dotnet_helpers import (
+ indent_lines,
+ lines_to_doc_comments,
+ namespace_wrapper,
+ to_upper_camel_case,
+)
+
+
+def generate_enums(spec: model.LSPModel, types: TypeData) -> None:
+ """Generate the code for the given spec."""
+ for enum_def in spec.enumerations:
+ types.add_type_info(enum_def, enum_def.name, generate_enum(enum_def))
+
+
+def _get_enum_doc(enum: Union[model.Enum, model.EnumItem]) -> List[str]:
+ doc = enum.documentation.splitlines(keepends=False) if enum.documentation else []
+ return lines_to_doc_comments(doc)
+
+
+def generate_enum(enum: model.Enum) -> List[str]:
+ use_enum_member = all(isinstance(item.value, str) for item in enum.values)
+ imports = ["using System.Runtime.Serialization;"]
+ if use_enum_member:
+ imports += ["using Newtonsoft.Json;", "using Newtonsoft.Json.Converters;"]
+
+ lines = _get_enum_doc(enum)
+ if use_enum_member:
+ lines += ["[JsonConverter(typeof(StringEnumConverter))]"]
+ lines += [f"public enum {enum.name}", "{"]
+
+ for item in enum.values:
+ name = to_upper_camel_case(item.name)
+ inner = _get_enum_doc(item)
+ if use_enum_member:
+ inner += [f'[EnumMember(Value = "{item.value}")]{name},']
+ else:
+ inner += [f"{name} = {item.value},"]
+ lines += indent_lines(inner) + [""]
+
+ lines += ["}"]
+
+ return namespace_wrapper(NAMESPACE, (imports if use_enum_member else []), lines)
diff --git a/generator/plugins/dotnet/dotnet_helpers.py b/generator/plugins/dotnet/dotnet_helpers.py
new file mode 100644
index 0000000..2eb357a
--- /dev/null
+++ b/generator/plugins/dotnet/dotnet_helpers.py
@@ -0,0 +1,215 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+import re
+from typing import List, Optional, Union
+
+from generator import model
+
+BASIC_LINK_RE = re.compile(r"{@link +(\w+) ([\w ]+)}")
+BASIC_LINK_RE2 = re.compile(r"{@link +(\w+)\.(\w+) ([\w \.`]+)}")
+BASIC_LINK_RE3 = re.compile(r"{@link +(\w+)}")
+BASIC_LINK_RE4 = re.compile(r"{@link +(\w+)\.(\w+)}")
+PARTS_RE = re.compile(r"(([a-z0-9])([A-Z]))")
+
+
+def _fix_links(line: str) -> str:
+ line = BASIC_LINK_RE.sub(r'<see cref="\1">\2</see>', line)
+ line = BASIC_LINK_RE2.sub(r'<see cref="\1.\2">\3</see>', line)
+ line = BASIC_LINK_RE3.sub(r'<see cref="\1" />', line)
+ line = BASIC_LINK_RE4.sub(r'<see cref="\1.\2" />', line)
+ return line
+
+
+def lines_to_doc_comments(lines: List[str]) -> List[str]:
+ if not lines:
+ return []
+
+ return (
+ ["/// <summary>"]
+ + [f"/// {_fix_links(line)}" for line in lines if not line.startswith("@")]
+ + ["/// </summary>"]
+ )
+
+
+def get_parts(name: str) -> List[str]:
+ name = name.replace("_", " ")
+ return PARTS_RE.sub(r"\2 \3", name).split()
+
+
+def to_camel_case(name: str) -> str:
+ parts = get_parts(name)
+ return parts[0] + "".join([p.capitalize() for p in parts[1:]])
+
+
+def to_upper_camel_case(name: str) -> str:
+ return "".join([c.capitalize() for c in get_parts(name)])
+
+
+def lsp_method_to_name(method: str) -> str:
+ if method.startswith("$"):
+ method = method[1:]
+ method = method.replace("/", "_")
+ return to_upper_camel_case(method)
+
+
+def file_header() -> List[str]:
+ return [
+ "// Copyright (c) Microsoft Corporation. All rights reserved.",
+ "// Licensed under the MIT License.",
+ "// ",
+ "// THIS FILE IS AUTOGENERATED, DO NOT MODIFY IT",
+ "",
+ ]
+
+
+def namespace_wrapper(
+ namespace: str, imports: List[str], lines: List[str]
+) -> List[str]:
+ indent = " " * 4
+ return (
+ file_header()
+ + imports
+ + [""]
+ + ["namespace " + namespace + " {"]
+ + [(f"{indent}{line}" if line else line) for line in lines]
+ + ["}", ""]
+ )
+
+
+def get_doc(doc: Optional[str]) -> str:
+ if doc:
+ return lines_to_doc_comments(doc.splitlines(keepends=False))
+ return []
+
+
+def get_special_case_class_name(name: str) -> str:
+ # This is because C# does not allow class name and property name to be the same.
+ # public class Command{ public string Command { get; set; }} is not valid.
+ if name == "Command":
+ return "CommandAction"
+ return name
+
+
+def get_special_case_property_name(name: str) -> str:
+ if name == "string":
+ return "stringValue"
+ if name == "int":
+ return "intValue"
+ if name == "event":
+ return "eventArgs"
+ if name == "params":
+ return "paramsValue"
+ return name
+
+
+def class_wrapper(
+ type_def: Union[model.Structure, model.Notification, model.Request],
+ inner: List[str],
+ derived: Optional[str] = None,
+ class_attributes: Optional[List[str]] = None,
+ is_record=True,
+) -> List[str]:
+ if hasattr(type_def, "name"):
+ name = get_special_case_class_name(type_def.name)
+ else:
+ raise ValueError(f"Unknown type: {type_def}")
+
+ rec_or_cls = "record" if is_record else "class"
+ lines = (
+ get_doc(type_def.documentation)
+ + generate_extras(type_def)
+ + (class_attributes if class_attributes else [])
+ + [
+ "[DataContract]",
+ f"public {rec_or_cls} {name}: {derived}"
+ if derived
+ else f"public {rec_or_cls} {name}",
+ "{",
+ ]
+ )
+ lines += indent_lines(inner)
+ lines += ["}", ""]
+ return lines
+
+
+def property_wrapper(prop_def: model.Property, content: List[str]) -> List[str]:
+ lines = (get_doc(prop_def.documentation) + generate_extras(prop_def) + content,)
+ lines += indent_lines(content)
+ return lines
+
+
+def indent_lines(lines: List[str], indent: str = " " * 4) -> List[str]:
+ return [(f"{indent}{line}" if line else line) for line in lines]
+
+
+def cleanup_str(text: str) -> str:
+ return text.replace("\r", "").replace("\n", "")
+
+
+def get_deprecated(text: Optional[str]) -> Optional[str]:
+ if not text:
+ return None
+
+ lines = text.splitlines(keepends=False)
+ for line in lines:
+ if line.startswith("@deprecated"):
+ return line.replace("@deprecated", "").strip()
+ return None
+
+
+def generate_extras(
+ type_def: Union[
+ model.Enum,
+ model.EnumItem,
+ model.Property,
+ model.TypeAlias,
+ model.Structure,
+ model.Request,
+ model.Notification,
+ ]
+) -> List[str]:
+ deprecated = get_deprecated(type_def.documentation)
+ extras = []
+ if type_def.deprecated:
+ extras += [f'[Obsolete("{cleanup_str(type_def.deprecated)}")]']
+ elif deprecated:
+ extras += [f'[Obsolete("{cleanup_str(deprecated)}")]']
+ if type_def.proposed:
+ extras += [f"[Proposed]"]
+ if type_def.since:
+ extras += [f'[Since("{cleanup_str(type_def.since)}")]']
+
+ if hasattr(type_def, "messageDirection"):
+ if type_def.since:
+ extras += [
+ f"[Direction(MessageDirection.{to_upper_camel_case(type_def.messageDirection)})]"
+ ]
+
+ return extras
+
+
+def get_usings(types: List[str]) -> List[str]:
+ usings = []
+
+ for t in ["DataMember", "DataContract"]:
+ if t in types:
+ usings.append("using System.Runtime.Serialization;")
+
+ for t in ["JsonConverter", "JsonConstructor", "JsonProperty", "NullValueHandling"]:
+ if t in types:
+ usings.append("using Newtonsoft.Json;")
+
+ for t in ["JToken", "JObject", "JArray"]:
+ if t in types:
+ usings.append("using Newtonsoft.Json.Linq;")
+
+ for t in ["List", "Dictionary"]:
+ if t in types:
+ usings.append("using System.Collections.Generic;")
+
+ for t in ["ImmutableArray", "ImmutableDictionary"]:
+ if t in types:
+ usings.append("using System.Collections.Immutable;")
+
+ return sorted(list(set(usings)))
diff --git a/generator/plugins/dotnet/dotnet_special_classes.py b/generator/plugins/dotnet/dotnet_special_classes.py
new file mode 100644
index 0000000..a656b6d
--- /dev/null
+++ b/generator/plugins/dotnet/dotnet_special_classes.py
@@ -0,0 +1,158 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+from typing import Dict, List, Union
+
+from generator import model
+
+from .dotnet_commons import TypeData
+from .dotnet_constants import NAMESPACE
+from .dotnet_helpers import class_wrapper, get_usings, namespace_wrapper
+
+SPECIAL_CLASSES = [
+ "LSPObject",
+ "LSPAny",
+ "LSPArray",
+ "ChangeAnnotationIdentifier",
+ "Pattern",
+ "DocumentSelector",
+ "InitializedParams",
+]
+
+
+def generate_special_classes(spec: model.LSPModel, types: TypeData) -> None:
+ """Generate code for special classes in LSP."""
+ for special_class in SPECIAL_CLASSES:
+ for class_def in spec.structures + spec.typeAliases:
+ if class_def.name == special_class:
+ generate_special_class(class_def, spec, types)
+
+
+def generate_special_class(
+ type_def: Union[model.Structure, model.TypeAlias],
+ spec: model.LSPModel,
+ types: TypeData,
+) -> Dict[str, str]:
+ """Generate code for a special class."""
+ lines: List[str] = []
+
+ if type_def.name == "LSPObject":
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(["Dictionary", "DataContract", "JsonConverter"]),
+ class_wrapper(
+ type_def,
+ ["public LSPObject(Dictionary<string, object?> value):base(value){}"],
+ "Dictionary<string, object?>",
+ ["[JsonConverter(typeof(CustomObjectConverter<LSPObject>))]"],
+ is_record=False,
+ ),
+ )
+ if type_def.name == "InitializedParams":
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(["Dictionary", "DataContract", "JsonConverter"]),
+ class_wrapper(
+ type_def,
+ [
+ "public InitializedParams(Dictionary<string, object?> value):base(value){}"
+ ],
+ "Dictionary<string, object?>",
+ ["[JsonConverter(typeof(CustomObjectConverter<InitializedParams>))]"],
+ is_record=False,
+ ),
+ )
+ if type_def.name == "LSPAny":
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(["DataContract", "JsonConverter"]),
+ class_wrapper(
+ type_def,
+ [
+ "public LSPAny(object? value){this.Value = value;}",
+ "public object? Value { get; set; }",
+ ],
+ "object",
+ ["[JsonConverter(typeof(LSPAnyConverter))]"],
+ ),
+ )
+ if type_def.name == "LSPArray":
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(["DataContract", "List"]),
+ class_wrapper(
+ type_def,
+ ["public LSPArray(List<object> value):base(value){}"],
+ "List<object>",
+ is_record=False,
+ ),
+ )
+
+ if type_def.name == "Pattern":
+ inner = [
+ "private string pattern;",
+ "public Pattern(string value){pattern = value;}",
+ "public static implicit operator Pattern(string value) => new Pattern(value);",
+ "public static implicit operator string(Pattern pattern) => pattern.pattern;",
+ "public override string ToString() => pattern;",
+ ]
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(["JsonConverter", "DataContract"]),
+ class_wrapper(
+ type_def,
+ inner,
+ None,
+ [f"[JsonConverter(typeof(CustomStringConverter<{type_def.name}>))]"],
+ ),
+ )
+
+ if type_def.name == "ChangeAnnotationIdentifier":
+ inner = [
+ "private string identifier;",
+ "public ChangeAnnotationIdentifier(string value){identifier = value;}",
+ "public static implicit operator ChangeAnnotationIdentifier(string value) => new ChangeAnnotationIdentifier(value);",
+ "public static implicit operator string(ChangeAnnotationIdentifier identifier) => identifier.identifier;",
+ "public override string ToString() => identifier;",
+ ]
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(["JsonConverter", "DataContract"]),
+ class_wrapper(
+ type_def,
+ inner,
+ None,
+ [f"[JsonConverter(typeof(CustomStringConverter<{type_def.name}>))]"],
+ ),
+ )
+
+ if type_def.name == "DocumentSelector":
+ inner = [
+ "private DocumentFilter[] Filters { get; set; }",
+ "public DocumentSelector(params DocumentFilter[] filters)",
+ "{",
+ " Filters = filters ?? Array.Empty<DocumentFilter>();",
+ "}",
+ "public DocumentFilter this[int index]",
+ "{",
+ " get { return Filters[index]; }",
+ " set { Filters[index] = value; }",
+ "}",
+ "public int Length => Filters.Length;",
+ "public static implicit operator DocumentSelector(DocumentFilter[] filters) => new(filters);",
+ "public static implicit operator DocumentFilter[](DocumentSelector selector) => selector.Filters;",
+ "public IEnumerator<DocumentFilter> GetEnumerator() => ((IEnumerable<DocumentFilter>)Filters).GetEnumerator();",
+ "System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => Filters.GetEnumerator();",
+ ]
+ lines = namespace_wrapper(
+ NAMESPACE,
+ get_usings(["JsonConverter", "DataContract"]),
+ class_wrapper(
+ type_def,
+ inner,
+ "IEnumerable<DocumentFilter>",
+ [f"[JsonConverter(typeof(DocumentSelectorConverter))]"],
+ ),
+ )
+
+ types.add_type_info(type_def, type_def.name, lines)
diff --git a/generator/plugins/dotnet/dotnet_utils.py b/generator/plugins/dotnet/dotnet_utils.py
new file mode 100644
index 0000000..4510527
--- /dev/null
+++ b/generator/plugins/dotnet/dotnet_utils.py
@@ -0,0 +1,58 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+import logging
+import os
+import pathlib
+import subprocess
+from typing import Dict, List
+
+import generator.model as model
+
+from .dotnet_classes import generate_all_classes
+from .dotnet_commons import TypeData
+from .dotnet_constants import NAMESPACE, PACKAGE_DIR_NAME
+from .dotnet_enums import generate_enums
+from .dotnet_helpers import namespace_wrapper
+from .dotnet_special_classes import generate_special_classes
+
+LOGGER = logging.getLogger("dotnet")
+
+
+def generate_from_spec(spec: model.LSPModel, output_dir: str) -> None:
+ """Generate the code for the given spec."""
+ output_path = pathlib.Path(output_dir, PACKAGE_DIR_NAME)
+ if not output_path.exists():
+ output_path.mkdir(parents=True, exist_ok=True)
+
+ cleanup(output_path)
+ copy_custom_classes(output_path)
+
+ LOGGER.info("Generating code in C#")
+ types = TypeData()
+ generate_package_code(spec, types)
+
+ for name, lines in types.get_all():
+ file_name = f"{name}.cs"
+ (output_path / file_name).write_text("\n".join(lines), encoding="utf-8")
+
+
+def generate_package_code(spec: model.LSPModel, types: TypeData) -> Dict[str, str]:
+ generate_enums(spec, types)
+ generate_special_classes(spec, types)
+ generate_all_classes(spec, types)
+
+
+def cleanup(output_path: pathlib.Path) -> None:
+ """Cleanup the generated C# files."""
+ for file in output_path.glob("*.cs"):
+ file.unlink()
+
+
+def copy_custom_classes(output_path: pathlib.Path) -> None:
+ """Copy the custom classes to the output directory."""
+ custom = pathlib.Path(__file__).parent / "custom"
+ for file in custom.glob("*.cs"):
+ lines = file.read_text(encoding="utf-8").splitlines()
+ lines = namespace_wrapper(NAMESPACE, [], lines)
+ (output_path / file.name).write_text("\n".join(lines), encoding="utf-8")