@typra/emitter 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/src/cleanup/generated-file.d.ts +6 -0
  2. package/dist/src/cleanup/generated-file.js +61 -0
  3. package/dist/src/cli.d.ts +2 -0
  4. package/dist/src/cli.js +110 -0
  5. package/dist/src/decorators.d.ts +56 -0
  6. package/dist/src/decorators.js +177 -0
  7. package/dist/src/emitter.d.ts +13 -0
  8. package/dist/src/emitter.js +137 -0
  9. package/dist/src/generate.d.ts +86 -0
  10. package/dist/src/generate.js +104 -0
  11. package/dist/src/index.d.ts +4 -0
  12. package/dist/src/index.js +5 -0
  13. package/dist/src/ir/ast.d.ts +235 -0
  14. package/dist/src/ir/ast.js +589 -0
  15. package/dist/src/ir/declarations.d.ts +364 -0
  16. package/dist/src/ir/declarations.js +23 -0
  17. package/dist/src/ir/expansion.d.ts +140 -0
  18. package/dist/src/ir/expansion.js +407 -0
  19. package/dist/src/ir/lower.d.ts +53 -0
  20. package/dist/src/ir/lower.js +480 -0
  21. package/dist/src/ir/utilities.d.ts +12 -0
  22. package/dist/src/ir/utilities.js +39 -0
  23. package/dist/src/ir/visitor.d.ts +29 -0
  24. package/dist/src/ir/visitor.js +48 -0
  25. package/dist/src/languages/csharp/driver.d.ts +5 -0
  26. package/dist/src/languages/csharp/driver.js +315 -0
  27. package/dist/src/languages/csharp/emitter.d.ts +33 -0
  28. package/dist/src/languages/csharp/emitter.js +1140 -0
  29. package/dist/src/languages/csharp/scaffolding.d.ts +18 -0
  30. package/dist/src/languages/csharp/scaffolding.js +591 -0
  31. package/dist/src/languages/csharp/test-emitter.d.ts +43 -0
  32. package/dist/src/languages/csharp/test-emitter.js +274 -0
  33. package/dist/src/languages/csharp/visitor.d.ts +14 -0
  34. package/dist/src/languages/csharp/visitor.js +79 -0
  35. package/dist/src/languages/go/driver.d.ts +12 -0
  36. package/dist/src/languages/go/driver.js +128 -0
  37. package/dist/src/languages/go/emitter.d.ts +33 -0
  38. package/dist/src/languages/go/emitter.js +879 -0
  39. package/dist/src/languages/go/scaffolding.d.ts +18 -0
  40. package/dist/src/languages/go/scaffolding.js +53 -0
  41. package/dist/src/languages/go/test-emitter.d.ts +20 -0
  42. package/dist/src/languages/go/test-emitter.js +300 -0
  43. package/dist/src/languages/go/visitor.d.ts +14 -0
  44. package/dist/src/languages/go/visitor.js +78 -0
  45. package/dist/src/languages/markdown/driver.d.ts +19 -0
  46. package/dist/src/languages/markdown/driver.js +408 -0
  47. package/dist/src/languages/python/driver.d.ts +14 -0
  48. package/dist/src/languages/python/driver.js +372 -0
  49. package/dist/src/languages/python/emitter.d.ts +31 -0
  50. package/dist/src/languages/python/emitter.js +856 -0
  51. package/dist/src/languages/python/scaffolding.d.ts +33 -0
  52. package/dist/src/languages/python/scaffolding.js +279 -0
  53. package/dist/src/languages/python/test-emitter.d.ts +29 -0
  54. package/dist/src/languages/python/test-emitter.js +388 -0
  55. package/dist/src/languages/python/visitor.d.ts +14 -0
  56. package/dist/src/languages/python/visitor.js +65 -0
  57. package/dist/src/languages/rust/driver.d.ts +13 -0
  58. package/dist/src/languages/rust/driver.js +624 -0
  59. package/dist/src/languages/rust/emitter.d.ts +45 -0
  60. package/dist/src/languages/rust/emitter.js +1596 -0
  61. package/dist/src/languages/rust/visitor.d.ts +25 -0
  62. package/dist/src/languages/rust/visitor.js +153 -0
  63. package/dist/src/languages/typescript/driver.d.ts +8 -0
  64. package/dist/src/languages/typescript/driver.js +209 -0
  65. package/dist/src/languages/typescript/emitter.d.ts +42 -0
  66. package/dist/src/languages/typescript/emitter.js +904 -0
  67. package/dist/src/languages/typescript/scaffolding.d.ts +32 -0
  68. package/dist/src/languages/typescript/scaffolding.js +303 -0
  69. package/dist/src/languages/typescript/test-emitter.d.ts +23 -0
  70. package/dist/src/languages/typescript/test-emitter.js +204 -0
  71. package/dist/src/languages/typescript/visitor.d.ts +14 -0
  72. package/dist/src/languages/typescript/visitor.js +64 -0
  73. package/dist/src/lib.d.ts +33 -0
  74. package/dist/src/lib.js +101 -0
  75. package/dist/src/testing/index.d.ts +2 -0
  76. package/dist/src/testing/index.js +8 -0
  77. package/dist/src/testing/test-context.d.ts +63 -0
  78. package/dist/src/testing/test-context.js +355 -0
  79. package/fixtures/shapes/main.tsp +43 -0
  80. package/fixtures/tspconfig.yaml +13 -0
  81. package/package.json +76 -0
  82. package/src/lib/main.tsp +110 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * C# scaffolding emitter — static support files.
3
+ *
4
+ * Replaces `context.cs.njk` and `utils.cs.njk` Nunjucks templates with typed
5
+ * TypeScript functions that produce the same C# source code.
6
+ *
7
+ * Emitted files:
8
+ * - Context.cs — LoadContext / SaveContext helper classes
9
+ * - Utils.cs — JsonUtils, YamlUtils, and internal Utils extension methods
10
+ */
11
+ /**
12
+ * Emit the C# LoadContext / SaveContext file.
13
+ */
14
+ export declare function emitCSharpContext(namespace: string): string;
15
+ /**
16
+ * Emit the C# utility classes file (JsonUtils, YamlUtils, Utils).
17
+ */
18
+ export declare function emitCSharpUtils(namespace: string): string;
@@ -0,0 +1,591 @@
1
+ /**
2
+ * C# scaffolding emitter — static support files.
3
+ *
4
+ * Replaces `context.cs.njk` and `utils.cs.njk` Nunjucks templates with typed
5
+ * TypeScript functions that produce the same C# source code.
6
+ *
7
+ * Emitted files:
8
+ * - Context.cs — LoadContext / SaveContext helper classes
9
+ * - Utils.cs — JsonUtils, YamlUtils, and internal Utils extension methods
10
+ */
11
+ // ============================================================================
12
+ // Context.cs
13
+ // ============================================================================
14
+ /**
15
+ * Emit the C# LoadContext / SaveContext file.
16
+ */
17
+ export function emitCSharpContext(namespace) {
18
+ return `// Copyright (c) Microsoft. All rights reserved.
19
+ using System.Text.Json;
20
+ using YamlDotNet.Serialization;
21
+
22
+ #pragma warning disable IDE0130
23
+ namespace ${namespace};
24
+ #pragma warning restore IDE0130
25
+
26
+ /// <summary>
27
+ /// Context for customizing the loading process of agent definitions.
28
+ /// Provides hooks for pre-processing input data before parsing and
29
+ /// post-processing output data after instantiation.
30
+ /// </summary>
31
+ public class LoadContext
32
+ {
33
+ /// <summary>
34
+ /// Optional callback to transform input data before parsing.
35
+ /// </summary>
36
+ public Func<Dictionary<string, object?>, Dictionary<string, object?>>? PreProcess { get; set; }
37
+
38
+ /// <summary>
39
+ /// Optional callback to transform the result after instantiation.
40
+ /// </summary>
41
+ public Func<object, object>? PostProcess { get; set; }
42
+
43
+ /// <summary>
44
+ /// Apply pre-processing to input data if a PreProcess callback is set.
45
+ /// </summary>
46
+ /// <param name="data">The raw input dictionary to process.</param>
47
+ /// <returns>The processed dictionary, or the original if no callback is set.</returns>
48
+ public Dictionary<string, object?> ProcessInput(Dictionary<string, object?> data)
49
+ {
50
+ if (PreProcess is not null)
51
+ {
52
+ return PreProcess(data);
53
+ }
54
+ return data;
55
+ }
56
+
57
+ /// <summary>
58
+ /// Apply post-processing to the result if a PostProcess callback is set.
59
+ /// </summary>
60
+ /// <typeparam name="T">The type of the result.</typeparam>
61
+ /// <param name="result">The instantiated object to process.</param>
62
+ /// <returns>The processed result, or the original if no callback is set.</returns>
63
+ public T ProcessOutput<T>(T result) where T : class
64
+ {
65
+ if (PostProcess is not null)
66
+ {
67
+ return (T)PostProcess(result);
68
+ }
69
+ return result;
70
+ }
71
+ }
72
+
73
+ /// <summary>
74
+ /// Context for customizing the serialization process of agent definitions.
75
+ /// Provides hooks for pre-processing the object before serialization and
76
+ /// post-processing the dictionary after serialization.
77
+ /// </summary>
78
+ public class SaveContext
79
+ {
80
+ /// <summary>
81
+ /// Optional callback to transform the object before serialization.
82
+ /// </summary>
83
+ public Func<object, object>? PreSave { get; set; }
84
+
85
+ /// <summary>
86
+ /// Optional callback to transform the dictionary after serialization.
87
+ /// </summary>
88
+ public Func<Dictionary<string, object?>, Dictionary<string, object?>>? PostSave { get; set; }
89
+
90
+ /// <summary>
91
+ /// Output format for collections: "object" (name as key) or "array" (list of dicts).
92
+ /// Defaults to "object".
93
+ /// </summary>
94
+ public string CollectionFormat { get; set; } = "object";
95
+
96
+ /// <summary>
97
+ /// Use shorthand scalar representation when possible (e.g., {"myTool": "function"}).
98
+ /// Defaults to true.
99
+ /// </summary>
100
+ public bool UseShorthand { get; set; } = true;
101
+
102
+ /// <summary>
103
+ /// Apply pre-processing to the object if a PreSave callback is set.
104
+ /// </summary>
105
+ /// <typeparam name="T">The type of the object.</typeparam>
106
+ /// <param name="obj">The object to process before serialization.</param>
107
+ /// <returns>The processed object, or the original if no callback is set.</returns>
108
+ public T ProcessObject<T>(T obj) where T : class
109
+ {
110
+ if (PreSave is not null)
111
+ {
112
+ return (T)PreSave(obj);
113
+ }
114
+ return obj;
115
+ }
116
+
117
+ /// <summary>
118
+ /// Apply post-processing to the dictionary if a PostSave callback is set.
119
+ /// </summary>
120
+ /// <param name="data">The serialized dictionary to process.</param>
121
+ /// <returns>The processed dictionary, or the original if no callback is set.</returns>
122
+ public Dictionary<string, object?> ProcessDict(Dictionary<string, object?> data)
123
+ {
124
+ if (PostSave is not null)
125
+ {
126
+ return PostSave(data);
127
+ }
128
+ return data;
129
+ }
130
+
131
+ private static readonly JsonSerializerOptions s_jsonOptions = new()
132
+ {
133
+ WriteIndented = true,
134
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
135
+ };
136
+
137
+ private static readonly ISerializer s_yamlSerializer = new SerializerBuilder()
138
+ .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull)
139
+ .Build();
140
+
141
+ /// <summary>
142
+ /// Convert the dictionary to a YAML string.
143
+ /// </summary>
144
+ /// <param name="data">The dictionary to convert.</param>
145
+ /// <returns>The YAML string representation.</returns>
146
+ public string ToYaml(Dictionary<string, object?> data)
147
+ {
148
+ return s_yamlSerializer.Serialize(data);
149
+ }
150
+
151
+ /// <summary>
152
+ /// Convert the dictionary to a JSON string.
153
+ /// </summary>
154
+ /// <param name="data">The dictionary to convert.</param>
155
+ /// <param name="indent">Whether to indent the output. Defaults to true.</param>
156
+ /// <returns>The JSON string representation.</returns>
157
+ public string ToJson(Dictionary<string, object?> data, bool indent = true)
158
+ {
159
+ var options = indent ? s_jsonOptions : new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
160
+ return JsonSerializer.Serialize(data, options);
161
+ }
162
+ }
163
+ `;
164
+ }
165
+ // ============================================================================
166
+ // Utils.cs
167
+ // ============================================================================
168
+ /**
169
+ * Emit the C# utility classes file (JsonUtils, YamlUtils, Utils).
170
+ */
171
+ export function emitCSharpUtils(namespace) {
172
+ return `// Copyright (c) Microsoft. All rights reserved.
173
+ using System.Collections;
174
+ using System.Reflection;
175
+ using System.Text.Json;
176
+ using System.Text.Json.Serialization;
177
+ using YamlDotNet.Serialization;
178
+ using YamlDotNet.Serialization.NamingConventions;
179
+
180
+ #pragma warning disable IDE0130
181
+ namespace ${namespace};
182
+ #pragma warning restore IDE0130
183
+
184
+ /// <summary>
185
+ /// JSON serialization utilities.
186
+ /// </summary>
187
+ public static class JsonUtils
188
+ {
189
+ /// <summary>
190
+ /// Default JSON serializer options with support for nested dictionaries.
191
+ /// </summary>
192
+ public static readonly JsonSerializerOptions Options = new()
193
+ {
194
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
195
+ WriteIndented = true,
196
+ Converters = { new DictionaryJsonConverter() }
197
+ };
198
+
199
+ /// <summary>
200
+ /// Extract a value from a JsonElement.
201
+ /// </summary>
202
+ public static object? GetJsonElementValue(JsonElement element)
203
+ {
204
+ return element.ValueKind switch
205
+ {
206
+ JsonValueKind.String => element.GetString(),
207
+ JsonValueKind.Number => GetNumericValue(element),
208
+ JsonValueKind.True => true,
209
+ JsonValueKind.False => false,
210
+ JsonValueKind.Null => null,
211
+ JsonValueKind.Undefined => null,
212
+ _ => element.GetRawText()
213
+ };
214
+ }
215
+
216
+ /// <summary>
217
+ /// Get the appropriate numeric type from a JSON element.
218
+ /// </summary>
219
+ private static object GetNumericValue(JsonElement element)
220
+ {
221
+ // Try int first (most common case for small integers)
222
+ if (element.TryGetInt32(out var i))
223
+ return i;
224
+ // Then try long for larger integers
225
+ if (element.TryGetInt64(out var l))
226
+ return l;
227
+ // Fall back to double for decimals
228
+ return element.GetDouble();
229
+ }
230
+
231
+ /// <summary>
232
+ /// Custom converter to properly deserialize nested objects as dictionaries.
233
+ /// </summary>
234
+ private class DictionaryJsonConverter : JsonConverter<Dictionary<string, object?>>
235
+ {
236
+ public override Dictionary<string, object?> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
237
+ {
238
+ if (reader.TokenType != JsonTokenType.StartObject)
239
+ throw new JsonException("Expected StartObject token");
240
+
241
+ var dict = new Dictionary<string, object?>();
242
+ while (reader.Read())
243
+ {
244
+ if (reader.TokenType == JsonTokenType.EndObject)
245
+ return dict;
246
+
247
+ if (reader.TokenType != JsonTokenType.PropertyName)
248
+ throw new JsonException("Expected PropertyName token");
249
+
250
+ var key = reader.GetString()!;
251
+ reader.Read();
252
+ dict[key] = ReadValue(ref reader, options);
253
+ }
254
+ throw new JsonException("Expected EndObject token");
255
+ }
256
+
257
+ private object? ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options)
258
+ {
259
+ return reader.TokenType switch
260
+ {
261
+ JsonTokenType.String => reader.GetString(),
262
+ JsonTokenType.Number => GetNumericValue(reader),
263
+ JsonTokenType.True => true,
264
+ JsonTokenType.False => false,
265
+ JsonTokenType.Null => null,
266
+ JsonTokenType.StartObject => Read(ref reader, typeof(Dictionary<string, object?>), options),
267
+ JsonTokenType.StartArray => ReadArray(ref reader, options),
268
+ _ => throw new JsonException($"Unexpected token type: {reader.TokenType}")
269
+ };
270
+ }
271
+
272
+ private static object GetNumericValue(Utf8JsonReader reader)
273
+ {
274
+ // Try int first (most common case for small integers)
275
+ if (reader.TryGetInt32(out var i))
276
+ return i;
277
+ // Then try long for larger integers
278
+ if (reader.TryGetInt64(out var l))
279
+ return l;
280
+ // Fall back to double for decimals
281
+ return reader.GetDouble();
282
+ }
283
+
284
+ private List<object?> ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options)
285
+ {
286
+ var list = new List<object?>();
287
+ while (reader.Read())
288
+ {
289
+ if (reader.TokenType == JsonTokenType.EndArray)
290
+ return list;
291
+ list.Add(ReadValue(ref reader, options));
292
+ }
293
+ throw new JsonException("Expected EndArray token");
294
+ }
295
+
296
+ public override void Write(Utf8JsonWriter writer, Dictionary<string, object?> value, JsonSerializerOptions options)
297
+ {
298
+ JsonSerializer.Serialize(writer, value, options);
299
+ }
300
+ }
301
+ }
302
+
303
+ /// <summary>
304
+ /// YAML serialization utilities.
305
+ /// </summary>
306
+ public static class YamlUtils
307
+ {
308
+ /// <summary>
309
+ /// Default YAML deserializer.
310
+ /// </summary>
311
+ public static readonly IDeserializer Deserializer = new DeserializerBuilder()
312
+ .WithNamingConvention(CamelCaseNamingConvention.Instance)
313
+ .Build();
314
+
315
+ /// <summary>
316
+ /// Default YAML serializer.
317
+ /// </summary>
318
+ public static readonly ISerializer Serializer = new SerializerBuilder()
319
+ .WithNamingConvention(CamelCaseNamingConvention.Instance)
320
+ .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull)
321
+ .Build();
322
+
323
+ /// <summary>
324
+ /// Parse a YAML scalar string to a typed value.
325
+ /// Uses YAML deserialization to properly handle quoted strings and types.
326
+ /// Returns the properly typed value (bool, int, double, or string).
327
+ /// </summary>
328
+ public static object? ParseScalar(string yaml)
329
+ {
330
+ // Handle null/empty
331
+ if (string.IsNullOrWhiteSpace(yaml))
332
+ return null;
333
+
334
+ // Use YAML deserializer to properly handle quoted strings and type inference
335
+ try
336
+ {
337
+ var result = Deserializer.Deserialize<object>(yaml);
338
+ // YamlDotNet returns strings for everything when deserializing to object
339
+ // We need to do additional type parsing
340
+ if (result is string str)
341
+ {
342
+ if (str == "null" || str == "~" || str == "")
343
+ return null;
344
+ if (str == "true" || str == "True" || str == "TRUE")
345
+ return true;
346
+ if (str == "false" || str == "False" || str == "FALSE")
347
+ return false;
348
+ if (int.TryParse(str, out var intValue))
349
+ return intValue;
350
+ if (double.TryParse(str, out var doubleValue))
351
+ return doubleValue;
352
+ return str;
353
+ }
354
+ return result;
355
+ }
356
+ catch
357
+ {
358
+ return yaml;
359
+ }
360
+ }
361
+ }
362
+
363
+ /// <summary>
364
+ /// Utilities for retrieving property values and working with dictionaries.
365
+ /// </summary>
366
+ internal static class Utils
367
+ {
368
+ public static object? GetScalarValue(this JsonElement obj)
369
+ {
370
+ return obj.ValueKind switch
371
+ {
372
+ JsonValueKind.String => obj.GetString(),
373
+ JsonValueKind.Number => obj.GetRawText().Contains('.') ? obj.GetSingle() : obj.GetInt32(),
374
+ JsonValueKind.True => true,
375
+ JsonValueKind.False => false,
376
+ JsonValueKind.Array => obj.EnumerateArray().Select(static x => x.GetScalarValue()).ToArray(),
377
+ JsonValueKind.Null => null,
378
+ JsonValueKind.Object => null,
379
+ JsonValueKind.Undefined => null,
380
+ _ => null,
381
+ };
382
+ }
383
+
384
+ /// <summary>
385
+ /// Retrieves a value from the dictionary by key and attempts to convert it to the specified type T.
386
+ /// </summary>
387
+ /// <typeparam name="T">The type to convert the value to.</typeparam>
388
+ /// <param name="dict">The dictionary to search.</param>
389
+ /// <param name="key">The key of the value to retrieve.</param>
390
+ /// <returns>The value converted to type T, or default if not found.</returns>
391
+ public static T? GetValue<T>(this Dictionary<string, object?> dict, string key)
392
+ {
393
+ if (dict.TryGetValue(key, out var value) && value is not null)
394
+ {
395
+ if (value is T typedValue)
396
+ {
397
+ return typedValue;
398
+ }
399
+ try
400
+ {
401
+ return (T)Convert.ChangeType(value, typeof(T));
402
+ }
403
+ catch
404
+ {
405
+ return default;
406
+ }
407
+ }
408
+ return default;
409
+ }
410
+
411
+ /// <summary>
412
+ /// Retrieves a nested dictionary from the dictionary by key.
413
+ /// </summary>
414
+ /// <param name="dict">The dictionary to search.</param>
415
+ /// <param name="key">The key of the nested dictionary.</param>
416
+ /// <returns>Dictionary if found; otherwise, an empty dictionary.</returns>
417
+ public static Dictionary<string, object?> GetDictionary(this Dictionary<string, object?> dict, string key)
418
+ {
419
+ if (dict.TryGetValue(key, out var value))
420
+ {
421
+ return value.GetDictionary();
422
+ }
423
+ return new Dictionary<string, object?>();
424
+ }
425
+
426
+ /// <summary>
427
+ /// Retrieves a nested dictionary from any object.
428
+ /// Handles both Dictionary&lt;string, object?&gt; and Dictionary&lt;object, object&gt; (from YAML).
429
+ /// </summary>
430
+ /// <param name="obj">The object that should be a dictionary.</param>
431
+ /// <returns>Dictionary if the object is a dictionary; otherwise, an empty dictionary.</returns>
432
+ public static Dictionary<string, object?> GetDictionary(this object? obj)
433
+ {
434
+ if (obj is Dictionary<string, object?> dict)
435
+ {
436
+ return dict;
437
+ }
438
+ // Handle YAML's Dictionary<object, object>
439
+ if (obj is IDictionary<object, object> objDict)
440
+ {
441
+ return objDict.ToDictionary(
442
+ kvp => kvp.Key?.ToString() ?? string.Empty,
443
+ kvp => (object?)kvp.Value);
444
+ }
445
+ return new Dictionary<string, object?>();
446
+ }
447
+
448
+ /// <summary>
449
+ /// Retrieves a nested dictionary from any object, with shorthand property support.
450
+ /// If the object is not a dictionary and a shorthand property is specified,
451
+ /// wraps the scalar value as { shorthandProperty: value }.
452
+ /// </summary>
453
+ /// <param name="obj">The object that should be a dictionary.</param>
454
+ /// <param name="shorthandProperty">Optional shorthand property name for scalar wrapping.</param>
455
+ /// <returns>Dictionary if the object is a dictionary; shorthand-wrapped dict for scalars; otherwise, an empty dictionary.</returns>
456
+ public static Dictionary<string, object?> GetDictionary(this object? obj, string? shorthandProperty)
457
+ {
458
+ var dict = obj.GetDictionary();
459
+ if (dict.Count > 0) return dict;
460
+ if (shorthandProperty is not null && obj is not null)
461
+ return new Dictionary<string, object?> { [shorthandProperty] = obj };
462
+ return dict;
463
+ }
464
+
465
+ /// <summary>
466
+ /// Retrieves a value from the dictionary by key and attempts to convert it to the specified type T.
467
+ /// </summary>
468
+ /// <typeparam name="T">The type to convert the value to.</typeparam>
469
+ /// <param name="dict">The dictionary to search.</param>
470
+ /// <param name="key">The key of the value to retrieve.</param>
471
+ /// <returns></returns>
472
+ public static T? GetValueOrDefault<T>(this IDictionary<string, object> dict, string key)
473
+ {
474
+ // check if T is a class and use .ctor recursively
475
+ if (dict.TryGetValue(key, out var value))
476
+ {
477
+ return (T?)Convert.ChangeType(value, typeof(T));
478
+ }
479
+ return default;
480
+ }
481
+
482
+ /// <summary>
483
+ /// Converts a named dictionary or list of dictionaries into a list of dictionaries (for normalizing Named objects into List objects).
484
+ /// </summary>
485
+ /// <param name="data"></param>
486
+ /// <returns>List of dictionaries</returns>
487
+ public static IList<IDictionary<string, object>> GetNamedDictionaryList(this object data)
488
+ {
489
+ if (data is IDictionary<string, object> dict)
490
+ {
491
+ return [.. dict
492
+ .Where(kvp => kvp.Value is IDictionary<string, object>)
493
+ .Select(kvp =>
494
+ {
495
+ var newDict = new Dictionary<string, object>((IDictionary<string, object>)kvp.Value!)
496
+ {
497
+ { "name", kvp.Key }
498
+ };
499
+ return (IDictionary<string, object>)newDict;
500
+ })];
501
+ }
502
+ if (data is IEnumerable<object> enumerable)
503
+ {
504
+ return [.. enumerable.OfType<IDictionary<string, object>>()];
505
+ }
506
+ return [];
507
+ }
508
+
509
+ /// <summary>
510
+ /// Retrieves a nested dictionary from the dictionary by key.
511
+ /// </summary>
512
+ /// <param name="dict">The dictionary to search.</param>
513
+ /// <param name="key">The key of the nested dictionary.</param>
514
+ /// <returns>Dictionary&lt;string, object&gt; if found; otherwise, an empty dictionary.</returns>
515
+ public static IDictionary<string, object> GetDictionaryOrDefault(this IDictionary<string, object> dict, string key)
516
+ {
517
+ if (dict.TryGetValue(key, out var value) && value is IDictionary<string, object> nestedDict)
518
+ {
519
+ return nestedDict;
520
+ }
521
+ return new Dictionary<string, object>();
522
+ }
523
+
524
+ /// <summary>
525
+ /// Expands a dictionary by converting its keys and values to strings and more usable formats.
526
+ /// </summary>
527
+ /// <param name="dictionary">The dictionary to expand.</param>
528
+ /// <returns>A new dictionary with expanded keys and values.</returns>
529
+ private static Dictionary<string, object> Expand(IDictionary dictionary)
530
+ {
531
+ var dict = new Dictionary<string, object>();
532
+ foreach (DictionaryEntry entry in dictionary)
533
+ {
534
+ if (entry.Value != null)
535
+ dict.Add(entry.Key.ToString()!, GetValue(entry.Value));
536
+ }
537
+ return dict;
538
+ }
539
+
540
+ /// <summary>
541
+ /// Expands a dictionary by converting its values to a more usable format.
542
+ /// </summary>
543
+ /// <param name="o">The object to convert.</param>
544
+ /// <returns>A more usable object.</returns>
545
+ private static object GetValue(object o)
546
+ {
547
+ return Type.GetTypeCode(o.GetType()) switch
548
+ {
549
+ TypeCode.Object => o switch
550
+ {
551
+
552
+ IDictionary dict => Expand(dict),
553
+ IList list => Enumerable.Range(0, list.Count).Where(i => list[i] != null).Select(i => list[i]!.ToParamDictionary()).ToArray(),
554
+ _ => o.ToParamDictionary(),
555
+ },
556
+ _ => o,
557
+ };
558
+ }
559
+
560
+ /// <summary>
561
+ /// Converts an object to a dictionary of parameters.
562
+ /// </summary>
563
+ /// <param name="obj">The object to convert.</param>
564
+ /// <returns>A dictionary of parameters.</returns>
565
+ public static IDictionary<string, object> ToParamDictionary(this object obj)
566
+ {
567
+ if (obj == null)
568
+ return new Dictionary<string, object>();
569
+
570
+ else if (obj is IDictionary<string, object> dictionary)
571
+ return dictionary;
572
+
573
+ var items = obj.GetType()
574
+ .GetProperties(BindingFlags.Public | BindingFlags.Instance)
575
+ .Where(prop => prop.GetGetMethod() != null);
576
+
577
+ var dict = new Dictionary<string, object>();
578
+
579
+ foreach (var item in items)
580
+ {
581
+ var value = item.GetValue(obj);
582
+ if (value != null)
583
+ dict.Add(item.Name, GetValue(value));
584
+ }
585
+
586
+ return dict;
587
+ }
588
+ }
589
+ `;
590
+ }
591
+ //# sourceMappingURL=scaffolding.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * C# test emitter — TypeNode → xUnit test file.
3
+ *
4
+ * Replaces `test.cs.njk` Nunjucks template with a typed TypeScript function
5
+ * that produces a complete C# xUnit test class.
6
+ *
7
+ * Each TypeNode with samples/coercions/factories gets one test file
8
+ * containing LoadYaml, LoadJson, roundtrip, and validity tests.
9
+ */
10
+ import { FactoryEntry } from "../../decorators.js";
11
+ import { TypeNode } from "../../ir/ast.js";
12
+ export interface CSharpTestContext {
13
+ node: TypeNode;
14
+ namespace: string;
15
+ examples: Array<{
16
+ json: string[];
17
+ yaml: string[];
18
+ validations: Array<{
19
+ key: string;
20
+ value: any;
21
+ startDelim: string;
22
+ endDelim: string;
23
+ }>;
24
+ }>;
25
+ coercions: Array<{
26
+ title: string;
27
+ scalar: string;
28
+ value: string | number;
29
+ validations: Array<{
30
+ key: string;
31
+ value: any;
32
+ delimiter: string;
33
+ }>;
34
+ }>;
35
+ factories: FactoryEntry[];
36
+ renderName: (name: string) => string;
37
+ renderCsharpFactoryMethodName: (factoryName: string) => string;
38
+ renderCsharpFactoryTestValue: (typeStr: string) => string;
39
+ }
40
+ /**
41
+ * Emit a complete C# xUnit test file for a type node.
42
+ */
43
+ export declare function emitCSharpTest(ctx: CSharpTestContext): string;