From 8abfdeadbb3c06e5c651b27e9aa4c87e05f9f968 Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sun, 25 Oct 2026 14:23:39 -0400 Subject: [PATCH 1/2] fix(csharp): shared DecimalWire and MetaNetValidation helpers carry the auto-generated marker (#460) --- CHANGELOG.md | 6 +++ .../DecimalWireTests.cs | 50 +++++++++++++++++++ .../Generators/EntityGenerator.cs | 2 + .../Runtime/DecimalWire.cs | 1 + .../Generated/DecimalWire.g.cs | 1 + .../Generated/MetaNetValidation.g.cs | 1 + 6 files changed, 61 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c504e9c66..b3dfa3a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ here.** ### Fixed +- **C#: the two shared helpers now carry the `// ` marker (#460).** + `DecimalWire[.].g.cs` and `MetaNetValidation[.].g.cs` were the only + emitted files without it, so a runner with no `GenStateDir` wrote them once and then logged + `refusing to overwrite hand-written file` on every later run, leaving a stale copy (and the + `@precision`/`@scale` input check inside it) behind. Regenerate once; the marker is the only + change to the helpers' text. - The 1.1.0 entry's handler-code figure of 439 lines retired is corrected to about 460, as measured in one adopter's pull request. diff --git a/server/csharp/MetaObjects.Codegen.Tests/DecimalWireTests.cs b/server/csharp/MetaObjects.Codegen.Tests/DecimalWireTests.cs index beae89d55..2fb9c2817 100644 --- a/server/csharp/MetaObjects.Codegen.Tests/DecimalWireTests.cs +++ b/server/csharp/MetaObjects.Codegen.Tests/DecimalWireTests.cs @@ -539,4 +539,54 @@ public void Helpers_for_packages_bound_to_different_namespaces_get_distinct_file Assert.Contains("MetaNetValidation.Acme.Shipping.g.cs", paths); Compile(files); } + + [Fact] + public void Shared_helpers_carry_the_generated_marker_on_their_first_line() + { + var files = EmitFitness(out _); + var helpers = files.Where(f => + f.Path.StartsWith("DecimalWire", StringComparison.Ordinal) || + f.Path.StartsWith("MetaNetValidation", StringComparison.Ordinal)).ToList(); + Assert.Contains(helpers, f => f.Path.StartsWith("DecimalWire", StringComparison.Ordinal)); + Assert.Contains(helpers, f => f.Path.StartsWith("MetaNetValidation", StringComparison.Ordinal)); + foreach (var f in helpers) + Assert.Equal(CodegenRunner.GeneratedMarker, f.Content.Split('\n')[0].Replace("// ", "")); + } + + [Fact] + public void Rerun_without_a_state_dir_rewrites_the_helpers_in_every_namespace() + { + // #460: with no GenStateDir the runner falls back to the marker rule, and the helpers + // lacked the marker, so every run after the first refused to overwrite them. + var dir = Path.Combine(Path.GetTempPath(), "mo-helper-marker-" + Guid.NewGuid().ToString("N")); + try + { + var r = new MetaDataLoader().Load([ + new InMemoryStringSource(TwoNamespaceModel, id: "billing.json"), + new InMemoryStringSource(SecondNamespaceModel, id: "shipping.json")]); + Assert.Empty(r.Errors); + var config = new GenConfig + { + OutDir = dir, + Namespace = "Acme", + PackageNamespaces = new() { ["acme::billing"] = "Acme.Billing", ["acme::shipping"] = "Acme.Shipping" }, + }; + string[] helpers = + [ + "DecimalWire.Acme.Billing.g.cs", "DecimalWire.Acme.Shipping.g.cs", + "MetaNetValidation.Acme.Billing.g.cs", "MetaNetValidation.Acme.Shipping.g.cs", + ]; + CodegenRunner.Run(config, r.Root, [new EntityGenerator()]); + foreach (var h in helpers) File.WriteAllText(Path.Combine(dir, h), File.ReadAllText(Path.Combine(dir, h)) + "// stale\n"); + + var second = CodegenRunner.Run(config, r.Root, [new EntityGenerator()]); + Assert.DoesNotContain(second.Warnings, w => w.Contains("refusing to overwrite", StringComparison.Ordinal)); + foreach (var h in helpers) + { + Assert.Contains(second.Files, f => f.Path == h && f.Status == "written"); + Assert.DoesNotContain("// stale", File.ReadAllText(Path.Combine(dir, h)), StringComparison.Ordinal); + } + } + finally { try { Directory.Delete(dir, true); } catch { } } + } } diff --git a/server/csharp/MetaObjects.Codegen/Generators/EntityGenerator.cs b/server/csharp/MetaObjects.Codegen/Generators/EntityGenerator.cs index d823ea6cc..bef3a811f 100644 --- a/server/csharp/MetaObjects.Codegen/Generators/EntityGenerator.cs +++ b/server/csharp/MetaObjects.Codegen/Generators/EntityGenerator.cs @@ -1049,6 +1049,7 @@ private static void AppendNetConverterAttribute(StringBuilder sb, MetaField fiel // the namespace swap. It lives HERE (not read from the HelperRuntime resource) so an // ejected EntityGenerator stays self-contained, the way the MetaNetValidation text is. private const string DecimalWireSourceTemplate = """ +// // DecimalWire — the wire form of a field.decimal (ADR-0019 Amendments 1 and 2, W1-W4). // // Output follows the project's `wire.decimals` setting (Amendment 2). Under `native`, the @@ -1417,6 +1418,7 @@ private static void AppendDecimalConverterAttribute(StringBuilder sb, MetaField private static EmittedFile EmitNetValidationFile(string ns, string fileName) { var sb = new StringBuilder(); + sb.AppendLine("// "); sb.AppendLine("// Generated by MetaObjects entity-generator. Do not edit by hand."); sb.AppendLine("#nullable enable"); sb.AppendLine("using System;"); diff --git a/server/csharp/MetaObjects.Codegen/Runtime/DecimalWire.cs b/server/csharp/MetaObjects.Codegen/Runtime/DecimalWire.cs index 41083c658..c587c91a0 100644 --- a/server/csharp/MetaObjects.Codegen/Runtime/DecimalWire.cs +++ b/server/csharp/MetaObjects.Codegen/Runtime/DecimalWire.cs @@ -1,3 +1,4 @@ +// // DecimalWire — the wire form of a field.decimal (ADR-0019 Amendments 1 and 2, W1-W4). // // Output follows the project's `wire.decimals` setting (Amendment 2). Under `native`, the diff --git a/server/csharp/MetaObjects.IntegrationTests/Generated/DecimalWire.g.cs b/server/csharp/MetaObjects.IntegrationTests/Generated/DecimalWire.g.cs index efd47a183..12630ac1a 100644 --- a/server/csharp/MetaObjects.IntegrationTests/Generated/DecimalWire.g.cs +++ b/server/csharp/MetaObjects.IntegrationTests/Generated/DecimalWire.g.cs @@ -1,3 +1,4 @@ +// // DecimalWire — the wire form of a field.decimal (ADR-0019 Amendments 1 and 2, W1-W4). // // Output follows the project's `wire.decimals` setting (Amendment 2). Under `native`, the diff --git a/server/csharp/MetaObjects.IntegrationTests/Generated/MetaNetValidation.g.cs b/server/csharp/MetaObjects.IntegrationTests/Generated/MetaNetValidation.g.cs index df770557a..f254cb0a6 100644 --- a/server/csharp/MetaObjects.IntegrationTests/Generated/MetaNetValidation.g.cs +++ b/server/csharp/MetaObjects.IntegrationTests/Generated/MetaNetValidation.g.cs @@ -1,3 +1,4 @@ +// // Generated by MetaObjects entity-generator. Do not edit by hand. #nullable enable using System; From 920cf391e5370265d5a29d7dde288ae303f284ef Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sun, 25 Oct 2026 14:25:38 -0400 Subject: [PATCH 2/2] no-mistakes(review): Correct CHANGELOG upgrade note: delete old helpers first --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3dfa3a96..a47f6b071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,10 @@ here.** `DecimalWire[.].g.cs` and `MetaNetValidation[.].g.cs` were the only emitted files without it, so a runner with no `GenStateDir` wrote them once and then logged `refusing to overwrite hand-written file` on every later run, leaving a stale copy (and the - `@precision`/`@scale` input check inside it) behind. Regenerate once; the marker is the only - change to the helpers' text. + `@precision`/`@scale` input check inside it) behind. A copy already on disk has no marker, so + regenerating alone keeps refusing it: delete the existing `DecimalWire*.g.cs` and + `MetaNetValidation*.g.cs` files, then regenerate. The marker is the only change to the + helpers' text. - The 1.1.0 entry's handler-code figure of 439 lines retired is corrected to about 460, as measured in one adopter's pull request.