From bde2046ea2ec6bc33f9c364f64da433b5c64bf26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulius=20Menkevi=C4=8Dius?= Date: Thu, 28 May 2026 10:01:02 +0300 Subject: [PATCH 1/7] tools/MetaModelGenerator: convert to STJ --- .../GenerateClientServer.fs | 16 +- tools/MetaModelGenerator/GenerateTypes.fs | 20 +- tools/MetaModelGenerator/MetaModel.fs | 194 ++++++++++++------ .../MetaModelGenerator.fsproj | 2 - tools/MetaModelGenerator/Program.fs | 4 +- 5 files changed, 160 insertions(+), 76 deletions(-) diff --git a/tools/MetaModelGenerator/GenerateClientServer.fs b/tools/MetaModelGenerator/GenerateClientServer.fs index 3f43b99..a5ded55 100644 --- a/tools/MetaModelGenerator/GenerateClientServer.fs +++ b/tools/MetaModelGenerator/GenerateClientServer.fs @@ -8,6 +8,20 @@ module GenerateClientServer = open Fantomas.Core + let private formatConfig = { + FormatConfig.Default with + IndentSize = 2 + MaxRecordWidth = 80 + MaxValueBindingWidth = 120 + MaxFunctionBindingWidth = 120 + MaxDotGetExpressionWidth = 120 + MaxInfixOperatorExpression = 10 + ArrayOrListMultilineFormatter = MultilineFormatterType.NumberOfItems + MultilineBracketStyle = MultilineBracketStyle.Stroustrup + MultiLineLambdaClosingNewline = true + InsertFinalNewline = false + } + let generateClientServer (parsedMetaModel: MetaModel.MetaModel) outputPath = async { printfn "Generating generateClientServer" @@ -312,7 +326,7 @@ module GenerateClientServer = let! formattedText = oak |> Gen.mkOak - |> CodeFormatter.FormatOakAsync + |> fun oak -> CodeFormatter.FormatOakAsync(oak, formatConfig) do! FileWriters.writeIfChanged outputPath formattedText diff --git a/tools/MetaModelGenerator/GenerateTypes.fs b/tools/MetaModelGenerator/GenerateTypes.fs index 01a4be1..9cfd54d 100644 --- a/tools/MetaModelGenerator/GenerateTypes.fs +++ b/tools/MetaModelGenerator/GenerateTypes.fs @@ -15,7 +15,7 @@ module GenerateTypes = open type Fabulous.AST.Ast open Fantomas.FCS.Syntax - open Newtonsoft.Json.Linq + let getIdent (x: IdentifierOrDot list) = x @@ -27,7 +27,7 @@ module GenerateTypes = |> String.concat "" - let JToken = LongIdent(nameof JToken) + let JToken = LongIdent "JToken" let createOption (t: WidgetBuilder) = Ast.OptionPostfix t @@ -866,6 +866,20 @@ module GenerateTypes = } + let private formatConfig = { + FormatConfig.Default with + IndentSize = 2 + MaxRecordWidth = 80 + MaxValueBindingWidth = 120 + MaxFunctionBindingWidth = 120 + MaxDotGetExpressionWidth = 120 + MaxInfixOperatorExpression = 10 + ArrayOrListMultilineFormatter = MultilineFormatterType.NumberOfItems + MultilineBracketStyle = MultilineBracketStyle.Stroustrup + MultiLineLambdaClosingNewline = true + InsertFinalNewline = false + } + /// The main entry point to generating types from a metaModel.json file let generateType (parsedMetaModel: MetaModel.MetaModel) outputPath = async { @@ -965,7 +979,7 @@ See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17 let! formattedText = oak |> Gen.mkOak - |> CodeFormatter.FormatOakAsync + |> fun oak -> CodeFormatter.FormatOakAsync(oak, formatConfig) do! FileWriters.writeIfChanged outputPath formattedText } \ No newline at end of file diff --git a/tools/MetaModelGenerator/MetaModel.fs b/tools/MetaModelGenerator/MetaModel.fs index f8b5d59..632bab0 100644 --- a/tools/MetaModelGenerator/MetaModel.fs +++ b/tools/MetaModelGenerator/MetaModel.fs @@ -18,36 +18,15 @@ module Proposed = else true -module Preconverts = - open Newtonsoft.Json - open Newtonsoft.Json.Linq - - type SingleOrArrayConverter<'T>() = - inherit JsonConverter() - - override x.CanConvert(tobject: System.Type) = tobject = typeof<'T array> - - override _.WriteJson(writer: JsonWriter, value, serializer: JsonSerializer) : unit = - failwith "Should never be writing this structure, it comes from Microsoft LSP Spec" - - override _.ReadJson(reader: JsonReader, objectType: System.Type, existingValue: obj, serializer: JsonSerializer) = - let token = JToken.Load reader - - match token.Type with - | JTokenType.Array -> serializer.Deserialize(reader, objectType) - | JTokenType.Null -> null - | _ -> Some [| token.ToObject<'T>(serializer) |] - module rec MetaModel = open System - open Newtonsoft.Json.Linq - open Newtonsoft.Json - open Ionide.LanguageServerProtocol + open System.Text.Json + open System.Text.Json.Serialization type MetaData = { Version: string } /// Indicates in which direction a message is sent in the protocol. - [)>] + [)>] type MessageDirection = | ClientToServer = 0 | ServerToClient = 1 @@ -66,7 +45,6 @@ module rec MetaModel = /// The request's method name. Method: string /// The parameter type(s) if any. - [>)>] Params: Type array option /// Optional partial result type if the request supports partial result reporting. PartialResult: Type option @@ -102,7 +80,6 @@ module rec MetaModel = /// The request's method name. Method: string /// The parameter type(s) if any. - [>)>] Params: Type array option /// Whether this is a proposed feature. If omitted the notification is final.", Proposed: bool option @@ -334,7 +311,7 @@ module rec MetaModel = x.Documentation |> Option.map StructuredDocs.parse - [)>] + [)>] type EnumerationTypeNameValues = | String = 0 | Integer = 1 @@ -349,6 +326,7 @@ module rec MetaModel = Name: string Proposed: bool option Since: string option + [)>] Value: string } with @@ -398,88 +376,168 @@ module rec MetaModel = module Converters = + /// Reads a JSON value that is either a JSON string or a JSON number, returning it as a string. + /// This is needed because integer/uinteger enumeration values are serialised as numbers in metaModel.json. + type StringOrNumberAsStringConverter() = + inherit JsonConverter() + + override _.Read(reader: byref, _typeToConvert: System.Type, _options: JsonSerializerOptions) = + match reader.TokenType with + | JsonTokenType.String -> reader.GetString() + | JsonTokenType.Number -> + // Preserve the raw number token as a string (e.g. "-32700") + reader.GetInt64() + |> string + | other -> failwithf "StringOrNumberAsStringConverter: unexpected token %A" other + + override _.Write(writer: Utf8JsonWriter, value: string, _options: JsonSerializerOptions) = + writer.WriteStringValue(value) + type MapKeyTypeConverter() = inherit JsonConverter() - override _.WriteJson(writer: JsonWriter, value: MapKeyType, serializer: JsonSerializer) : unit = - failwith "Should never be writing this structure, it comes from Microsoft LSP Spec" - - override _.ReadJson - ( - reader: JsonReader, - objectType: System.Type, - existingValue: MapKeyType, - hasExistingValue, - serializer: JsonSerializer - ) = - let jobj = JObject.Load(reader) - let kind = jobj.["kind"].Value() + override _.Read(reader: byref, _typeToConvert: System.Type, options: JsonSerializerOptions) = + let doc = JsonDocument.ParseValue(&reader) + let root = doc.RootElement + let kind = root.GetProperty("kind").GetString() match kind with | ReferenceTypeConst -> - let name = jobj.["name"].Value() + let name = root.GetProperty("name").GetString() MapKeyType.ReferenceType { Kind = kind; Name = name } | "base" -> - let name = jobj.["name"].Value() + let name = root.GetProperty("name").GetString() MapKeyType.Base {| Kind = kind; Name = MapKeyNameEnum.Parse name |} | _ -> failwithf "Unknown map key type: %s" kind - type TypeConverter() = - inherit JsonConverter() + override _.Write(_writer, _value, _options) = + failwith "Should never be writing this structure, it comes from Microsoft LSP Spec" - override _.WriteJson(writer: JsonWriter, value: MetaModel.Type, serializer: JsonSerializer) : unit = + /// Reads a value that may be either a single item or an array of items, + /// deserializing it as 'T array option (None when the JSON token is null). + type SingleOrArrayConverter<'T>() = + inherit JsonConverter<'T array option>() + + override _.Read(reader: byref, _typeToConvert: System.Type, options: JsonSerializerOptions) = + match reader.TokenType with + | JsonTokenType.Null -> + reader.Read() + |> ignore + + None + | JsonTokenType.StartArray -> + let arr = JsonSerializer.Deserialize<'T array>(&reader, options) + Some arr + | _ -> + let item = JsonSerializer.Deserialize<'T>(&reader, options) + Some [| item |] + + override _.Write(_writer, _value, _options) = failwith "Should never be writing this structure, it comes from Microsoft LSP Spec" - override _.ReadJson - (reader: JsonReader, objectType: System.Type, existingValue: Type, hasExistingValue, serializer: JsonSerializer) - = - let jobj = JObject.Load(reader) - let kind = jobj.["kind"].Value() + type TypeConverter() = + inherit JsonConverter() + + override _.Read(reader: byref, _typeToConvert: System.Type, options: JsonSerializerOptions) = + let doc = JsonDocument.ParseValue(&reader) + let root = doc.RootElement + let kind = root.GetProperty("kind").GetString() + // Helper: re-serialise a sub-element and deserialize as 'T + let deser (t: System.Type) (el: JsonElement) = JsonSerializer.Deserialize(el.GetRawText(), t, options) match kind with | BaseTypeConst -> - let name = jobj.["name"].Value() + let name = root.GetProperty("name").GetString() Type.BaseType { Kind = kind; Name = BaseTypes.Parse name } | ReferenceTypeConst -> - let name = jobj.["name"].Value() + let name = root.GetProperty("name").GetString() Type.ReferenceType { Kind = kind; Name = name } | ArrayTypeConst -> - let element = jobj.["element"].ToObject(serializer) + let element = deser typeof (root.GetProperty("element")) :?> Type Type.ArrayType { Kind = kind; Element = element } | MapTypeConst -> - let key = jobj.["key"].ToObject(serializer) - let value = jobj.["value"].ToObject(serializer) + let key = deser typeof (root.GetProperty("key")) :?> MapKeyType + let value = deser typeof (root.GetProperty("value")) :?> Type Type.MapType { Kind = kind; Key = key; Value = value } | AndTypeConst -> - let items = jobj.["items"].ToObject(serializer) + let items = deser typeof (root.GetProperty("items")) :?> Type[] Type.AndType { Kind = kind; Items = items } | OrTypeConst -> - let items = jobj.["items"].ToObject(serializer) + let items = deser typeof (root.GetProperty("items")) :?> Type[] Type.OrType { Kind = kind; Items = items } | TupleTypeConst -> - let items = jobj.["items"].ToObject(serializer) + let items = deser typeof (root.GetProperty("items")) :?> Type[] Type.TupleType { Kind = kind; Items = items } | StructureTypeLiteral -> - let value = jobj.["value"].ToObject(serializer) + let value = deser typeof (root.GetProperty("value")) :?> StructureLiteral Type.StructureLiteralType { Kind = kind; Value = value } | StringLiteralTypeConst -> - let value = jobj.["value"].Value() + let value = root.GetProperty("value").GetString() Type.StringLiteralType { Kind = kind; Value = value } | IntegerLiteralTypeConst -> - let value = jobj.["value"].Value() + let value = root.GetProperty("value").GetDecimal() Type.IntegerLiteralType { Kind = kind; Value = value } | BooleanLiteralTypeConst -> - let value = jobj.["value"].Value() + let value = root.GetProperty("value").GetBoolean() Type.BooleanLiteralType { Kind = kind; Value = value } | _ -> failwithf "Unknown type kind: %s" kind + override _.Write(_writer, _value, _options) = + failwith "Should never be writing this structure, it comes from Microsoft LSP Spec" - let metaModelSerializerSettings = - let settings = JsonSerializerSettings() - settings.Converters.Add(Converters.TypeConverter() :> JsonConverter) - settings.Converters.Add(Converters.MapKeyTypeConverter() :> JsonConverter) - settings.Converters.Add(JsonUtils.OptionConverter() :> JsonConverter) - settings + /// STJ converter for F# option types. + type OptionConverter<'T>() = + inherit JsonConverter<'T option>() + + override _.Read(reader: byref, _typeToConvert: System.Type, options: JsonSerializerOptions) = + if reader.TokenType = JsonTokenType.Null then + reader.Read() + |> ignore + + None + else + Some(JsonSerializer.Deserialize<'T>(&reader, options)) + + override _.Write(writer: Utf8JsonWriter, value: 'T option, options: JsonSerializerOptions) = + match value with + | None -> writer.WriteNullValue() + | Some v -> JsonSerializer.Serialize(writer, v, options) + + type OptionConverterFactory() = + inherit JsonConverterFactory() + + override _.CanConvert(t: System.Type) = + t.IsGenericType + && t.GetGenericTypeDefinition() = typedefof> + + override _.CreateConverter(t: System.Type, _options: JsonSerializerOptions) = + let innerType = t.GetGenericArguments()[0] + let converterType = typedefof>.MakeGenericType(innerType) + System.Activator.CreateInstance(converterType) :?> JsonConverter + + /// STJ converter for 'T array option fields that may be a single item or an array in JSON. + type SingleOrArrayConverterFactory() = + inherit JsonConverterFactory() + + override _.CanConvert(t: System.Type) = + t.IsGenericType + && t.GetGenericTypeDefinition() = typedefof> + && t.GetGenericArguments().[0].IsArray + + override _.CreateConverter(t: System.Type, _options: JsonSerializerOptions) = + let elementType = t.GetGenericArguments().[0].GetElementType() + let converterType = typedefof>.MakeGenericType([| elementType |]) + System.Activator.CreateInstance(converterType) :?> JsonConverter + + let metaModelSerializerOptions = + let options = JsonSerializerOptions(PropertyNamingPolicy = JsonNamingPolicy.CamelCase) + options.Converters.Add(Converters.TypeConverter()) + options.Converters.Add(Converters.MapKeyTypeConverter()) + // SingleOrArrayConverterFactory must come before OptionConverterFactory + // so that 'Type array option' fields hit the right converter + options.Converters.Add(Converters.SingleOrArrayConverterFactory()) + options.Converters.Add(Converters.OptionConverterFactory()) + options let isNullableType (t: MetaModel.Type) = match t with diff --git a/tools/MetaModelGenerator/MetaModelGenerator.fsproj b/tools/MetaModelGenerator/MetaModelGenerator.fsproj index d7b3d24..67e84bb 100644 --- a/tools/MetaModelGenerator/MetaModelGenerator.fsproj +++ b/tools/MetaModelGenerator/MetaModelGenerator.fsproj @@ -5,11 +5,9 @@ - - diff --git a/tools/MetaModelGenerator/Program.fs b/tools/MetaModelGenerator/Program.fs index 4299ef7..b69dd4c 100644 --- a/tools/MetaModelGenerator/Program.fs +++ b/tools/MetaModelGenerator/Program.fs @@ -3,7 +3,7 @@ module Main = open Argu open System - open Newtonsoft.Json + open System.Text.Json open System.IO type TypeArgs = @@ -50,7 +50,7 @@ module Main = printfn "Deserializing metaModel" let parsedMetaModel = - JsonConvert.DeserializeObject(metaModel, MetaModel.metaModelSerializerSettings) + JsonSerializer.Deserialize(metaModel, MetaModel.metaModelSerializerOptions) return parsedMetaModel } From 48fa73053fdded5656b45d5d91a1399be4085e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulius=20Menkevi=C4=8Dius?= Date: Thu, 28 May 2026 10:01:11 +0300 Subject: [PATCH 2/7] src/Types.cg.fs+ClientServer.cg.fs: update with minor trivia --- src/ClientServer.cg.fs | 4 ---- src/Types.cg.fs | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/ClientServer.cg.fs b/src/ClientServer.cg.fs index a899f79..e7e0f61 100644 --- a/src/ClientServer.cg.fs +++ b/src/ClientServer.cg.fs @@ -111,13 +111,11 @@ type ILspServer = /// response is of type {@link FoldingRangeList} or a Thenable /// that resolves to such. abstract TextDocumentFoldingRange: FoldingRangeParams -> AsyncLspResult>> - /// A request to resolve the type definition locations of a symbol at a given text /// document position. The request's parameter is of type {@link TextDocumentPositionParams} /// the response is of type {@link Declaration} or a typed array of {@link DeclarationLink} /// or a Thenable that resolves to such. abstract TextDocumentDeclaration: DeclarationParams -> AsyncLspResult>>> - /// A request to provide selection ranges in a document. The request's /// parameter is of type {@link SelectionRangeParams}, the /// response is of type {@link SelectionRange SelectionRange[]} or a Thenable @@ -239,7 +237,6 @@ type ILspServer = /// server constantly fails on this request. This is done to keep the save fast and /// reliable. abstract TextDocumentWillSaveWaitUntil: WillSaveTextDocumentParams -> AsyncLspResult>> - /// Request to request completion at a given text document position. The request's /// parameter is of type {@link TextDocumentPosition} the response /// is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList} @@ -250,7 +247,6 @@ type ILspServer = /// request. However, properties that are needed for the initial sorting and filtering, like `sortText`, /// `filterText`, `insertText`, and `textEdit`, must not be changed during resolve. abstract TextDocumentCompletion: CompletionParams -> AsyncLspResult, CompletionList>>> - /// Request to resolve additional information for a given completion item.The request's /// parameter is of type {@link CompletionItem} the response /// is of type {@link CompletionItem} or a Thenable that resolves to such. diff --git a/src/Types.cg.fs b/src/Types.cg.fs index f09ed4d..3ac9317 100644 --- a/src/Types.cg.fs +++ b/src/Types.cg.fs @@ -5,7 +5,6 @@ open System.Runtime.Serialization open System.Diagnostics open Newtonsoft.Json open Newtonsoft.Json.Linq - /// URI's are transferred as strings. The URI's format is defined in https://tools.ietf.org/html/rfc3986 /// /// See: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#uri @@ -1635,7 +1634,6 @@ type WorkspaceDiagnosticParams = { /// /// @since 3.17.0 type WorkspaceDiagnosticReport = { Items: WorkspaceDocumentDiagnosticReport[] } - /// A partial result for a workspace diagnostic report. /// /// @since 3.17.0 @@ -3326,7 +3324,6 @@ type WorkDoneProgressEnd = { } type SetTraceParams = { Value: TraceValues } - type LogTraceParams = { Message: string; Verbose: string option } type CancelParams = { @@ -4485,8 +4482,7 @@ type Diagnostic = { [] member x.DebuggerDisplay = - $"[{defaultArg x.Severity DiagnosticSeverity.Error}] ({x.Range.DebuggerDisplay}) {x.Message} ({Option.map string x.Code - |> Option.defaultValue String.Empty})" + $"[{defaultArg x.Severity DiagnosticSeverity.Error}] ({x.Range.DebuggerDisplay}) {x.Message} ({Option.map string x.Code |> Option.defaultValue String.Empty})" /// Contains additional information about the context in which a completion request is triggered. type CompletionContext = { From 636ebe14d68e73e974692afe2d798b5d2de7dc39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulius=20Menkevi=C4=8Dius?= Date: Thu, 28 May 2026 10:07:53 +0300 Subject: [PATCH 3/7] src: fix NU1202 when packing with tool project in context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RegenerateTypes MSBuild target calls Restore/Build on the MetaModelGenerator tool project. When invoked from dotnet pack src, the outer TargetFramework=netstandard2.0 is inherited, causing NuGet to validate the tool's packages against netstandard2.0 and fail on Fabulous.AST (which only supports net8.0+). Pass RemoveProperties="TargetFramework" so the tool project restores and builds against its own declared net9.0 TFM. 🤖 Generated with eca --- src/Ionide.LanguageServerProtocol.fsproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ionide.LanguageServerProtocol.fsproj b/src/Ionide.LanguageServerProtocol.fsproj index 61da048..9b7c28d 100644 --- a/src/Ionide.LanguageServerProtocol.fsproj +++ b/src/Ionide.LanguageServerProtocol.fsproj @@ -72,10 +72,10 @@ so if you happen to read some content during a Restore, that content won't be re-read during a subsequent Build --> - + - + From 70498a06ff86002883409b472d01b504d4ed7a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulius=20Menkevi=C4=8Dius?= Date: Thu, 28 May 2026 10:12:34 +0300 Subject: [PATCH 4/7] Bump .NET SDK to 10.0.x --- .github/workflows/build.yml | 2 +- .github/workflows/publish.yml | 2 +- global.json | 4 ++-- tests/Ionide.LanguageServerProtocol.Tests.fsproj | 4 ++-- tools/MetaModelGenerator/MetaModelGenerator.fsproj | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5b6f989..dc939c4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: global-json-file: global.json dotnet-version: | 8.x - 9.x + 10.x - name: Run build run: dotnet build -c Release src - name: Run tests diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 74730a5..7b76712 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,7 +28,7 @@ jobs: global-json-file: global.json dotnet-version: | 8.x - 9.x + 10.x - name: Restore tools run: dotnet tool restore diff --git a/global.json b/global.json index f15a959..1e7fdfa 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100", + "version": "10.0.100", "rollForward": "latestMinor" } -} \ No newline at end of file +} diff --git a/tests/Ionide.LanguageServerProtocol.Tests.fsproj b/tests/Ionide.LanguageServerProtocol.Tests.fsproj index 3d5520d..9b4006f 100644 --- a/tests/Ionide.LanguageServerProtocol.Tests.fsproj +++ b/tests/Ionide.LanguageServerProtocol.Tests.fsproj @@ -1,8 +1,8 @@ - + Exe - net8.0;net9.0 + net8.0;net10.0 false diff --git a/tools/MetaModelGenerator/MetaModelGenerator.fsproj b/tools/MetaModelGenerator/MetaModelGenerator.fsproj index 67e84bb..84e04d9 100644 --- a/tools/MetaModelGenerator/MetaModelGenerator.fsproj +++ b/tools/MetaModelGenerator/MetaModelGenerator.fsproj @@ -1,7 +1,7 @@ Exe - net9.0 + net10.0 From 17e9cf716831f6d76864a61c87164bd98457a65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulius=20Menkevi=C4=8Dius?= Date: Fri, 29 May 2026 09:00:41 +0300 Subject: [PATCH 5/7] tools/MetaModelGenerator/MetaModel.fs: dispose JsonDocument where appropriate --- tools/MetaModelGenerator/MetaModel.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/MetaModelGenerator/MetaModel.fs b/tools/MetaModelGenerator/MetaModel.fs index 632bab0..1ab921a 100644 --- a/tools/MetaModelGenerator/MetaModel.fs +++ b/tools/MetaModelGenerator/MetaModel.fs @@ -397,7 +397,7 @@ module rec MetaModel = inherit JsonConverter() override _.Read(reader: byref, _typeToConvert: System.Type, options: JsonSerializerOptions) = - let doc = JsonDocument.ParseValue(&reader) + use doc = JsonDocument.ParseValue(&reader) let root = doc.RootElement let kind = root.GetProperty("kind").GetString() @@ -439,7 +439,7 @@ module rec MetaModel = inherit JsonConverter() override _.Read(reader: byref, _typeToConvert: System.Type, options: JsonSerializerOptions) = - let doc = JsonDocument.ParseValue(&reader) + use doc = JsonDocument.ParseValue(&reader) let root = doc.RootElement let kind = root.GetProperty("kind").GetString() // Helper: re-serialise a sub-element and deserialize as 'T From 27c7ce6bfed8b9654699aa557cf270c21677acca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulius=20Menkevi=C4=8Dius?= Date: Fri, 29 May 2026 09:07:29 +0300 Subject: [PATCH 6/7] tools/MetaModelGenerator/Common.fs: add Formatting.formatConfig that is shared between GenerateTypes.fs and GenerateClientServer.fs --- tools/MetaModelGenerator/Common.fs | 17 +++++++++++++++++ .../MetaModelGenerator/GenerateClientServer.fs | 14 +------------- tools/MetaModelGenerator/GenerateTypes.fs | 14 +------------- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/tools/MetaModelGenerator/Common.fs b/tools/MetaModelGenerator/Common.fs index 4b24a57..bce160b 100644 --- a/tools/MetaModelGenerator/Common.fs +++ b/tools/MetaModelGenerator/Common.fs @@ -1,5 +1,22 @@ namespace MetaModelGenerator +module Formatting = + open Fantomas.Core + + let formatConfig = { + FormatConfig.Default with + IndentSize = 2 + MaxRecordWidth = 80 + MaxValueBindingWidth = 120 + MaxFunctionBindingWidth = 120 + MaxDotGetExpressionWidth = 120 + MaxInfixOperatorExpression = 10 + ArrayOrListMultilineFormatter = MultilineFormatterType.NumberOfItems + MultilineBracketStyle = MultilineBracketStyle.Stroustrup + MultiLineLambdaClosingNewline = true + InsertFinalNewline = false + } + module FileWriters = open System.IO diff --git a/tools/MetaModelGenerator/GenerateClientServer.fs b/tools/MetaModelGenerator/GenerateClientServer.fs index a5ded55..9e22800 100644 --- a/tools/MetaModelGenerator/GenerateClientServer.fs +++ b/tools/MetaModelGenerator/GenerateClientServer.fs @@ -8,19 +8,7 @@ module GenerateClientServer = open Fantomas.Core - let private formatConfig = { - FormatConfig.Default with - IndentSize = 2 - MaxRecordWidth = 80 - MaxValueBindingWidth = 120 - MaxFunctionBindingWidth = 120 - MaxDotGetExpressionWidth = 120 - MaxInfixOperatorExpression = 10 - ArrayOrListMultilineFormatter = MultilineFormatterType.NumberOfItems - MultilineBracketStyle = MultilineBracketStyle.Stroustrup - MultiLineLambdaClosingNewline = true - InsertFinalNewline = false - } + let private formatConfig = Formatting.formatConfig let generateClientServer (parsedMetaModel: MetaModel.MetaModel) outputPath = async { diff --git a/tools/MetaModelGenerator/GenerateTypes.fs b/tools/MetaModelGenerator/GenerateTypes.fs index 9cfd54d..ad13d75 100644 --- a/tools/MetaModelGenerator/GenerateTypes.fs +++ b/tools/MetaModelGenerator/GenerateTypes.fs @@ -866,19 +866,7 @@ module GenerateTypes = } - let private formatConfig = { - FormatConfig.Default with - IndentSize = 2 - MaxRecordWidth = 80 - MaxValueBindingWidth = 120 - MaxFunctionBindingWidth = 120 - MaxDotGetExpressionWidth = 120 - MaxInfixOperatorExpression = 10 - ArrayOrListMultilineFormatter = MultilineFormatterType.NumberOfItems - MultilineBracketStyle = MultilineBracketStyle.Stroustrup - MultiLineLambdaClosingNewline = true - InsertFinalNewline = false - } + let private formatConfig = Formatting.formatConfig /// The main entry point to generating types from a metaModel.json file let generateType (parsedMetaModel: MetaModel.MetaModel) outputPath = From 0e41cfb8f67284d07c145535608ab6f80b03a0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saulius=20Menkevi=C4=8Dius?= Date: Fri, 29 May 2026 09:17:00 +0300 Subject: [PATCH 7/7] tools/MetaModelGenerator/MetaModel.fs: fix JsonTokeType.Null branch in some of Converters --- tools/MetaModelGenerator/MetaModel.fs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tools/MetaModelGenerator/MetaModel.fs b/tools/MetaModelGenerator/MetaModel.fs index 1ab921a..077ba1c 100644 --- a/tools/MetaModelGenerator/MetaModel.fs +++ b/tools/MetaModelGenerator/MetaModel.fs @@ -420,11 +420,7 @@ module rec MetaModel = override _.Read(reader: byref, _typeToConvert: System.Type, options: JsonSerializerOptions) = match reader.TokenType with - | JsonTokenType.Null -> - reader.Read() - |> ignore - - None + | JsonTokenType.Null -> None | JsonTokenType.StartArray -> let arr = JsonSerializer.Deserialize<'T array>(&reader, options) Some arr @@ -490,13 +486,9 @@ module rec MetaModel = inherit JsonConverter<'T option>() override _.Read(reader: byref, _typeToConvert: System.Type, options: JsonSerializerOptions) = - if reader.TokenType = JsonTokenType.Null then - reader.Read() - |> ignore - - None - else - Some(JsonSerializer.Deserialize<'T>(&reader, options)) + match reader.TokenType with + | JsonTokenType.Null -> None + | _ -> Some(JsonSerializer.Deserialize<'T>(&reader, options)) override _.Write(writer: Utf8JsonWriter, value: 'T option, options: JsonSerializerOptions) = match value with