@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,480 @@
1
+ /**
2
+ * Lowering pass — TypeNode graph → Declaration IR.
3
+ *
4
+ * This module converts the emitter's type graph (TypeNode/PropertyNode)
5
+ * into the language-agnostic Declaration IR (FileDecl/TypeDecl).
6
+ *
7
+ * The lowering is shared across all 5 target languages. Per-language
8
+ * emitter functions consume the FileDecl tree and emit code.
9
+ *
10
+ * Key responsibilities:
11
+ * - Classify every property into a PropertyCategory
12
+ * - Build load/save method specifications
13
+ * - Resolve polymorphic dispatch
14
+ * - Resolve collection helpers
15
+ * - Resolve factory methods via the Expression IR
16
+ * - Compute file-level imports
17
+ */
18
+ import { resolveFactoryExpr, collectExprTypeRefs, } from "./expansion.js";
19
+ // ============================================================================
20
+ // Public API
21
+ // ============================================================================
22
+ /**
23
+ * Lower a base TypeNode (and all its children) into a FileDecl.
24
+ *
25
+ * This is the main entry point for the lowering pass. It produces a complete
26
+ * FileDecl containing one or more TypeDecls (parent + children for polymorphic types).
27
+ *
28
+ * The result is fully language-agnostic — per-language emitters handle rendering.
29
+ *
30
+ * @param node - The base TypeNode (must not have a parent — i.e., `node.base === null`)
31
+ * @param registry - TypeRegistry for resolving type references
32
+ * @param polymorphicTypeNames - Set of type names that are polymorphic bases
33
+ */
34
+ export function lowerFile(node, registry, polymorphicTypeNames) {
35
+ const polyNames = polymorphicTypeNames ?? collectPolymorphicTypeNames(node, registry);
36
+ // Lower all types in this file (parent + children)
37
+ const types = [
38
+ lowerType(node, registry, polyNames),
39
+ ...node.childTypes.map(ct => lowerType(ct, registry, polyNames)),
40
+ ];
41
+ // Resolve file-level imports
42
+ const imports = resolveImports(node, types, registry);
43
+ // Collect unique enum definitions from all fields across all types
44
+ const enums = collectEnums(types);
45
+ return {
46
+ typeName: node.typeName,
47
+ types,
48
+ imports,
49
+ containsAbstract: node.isAbstract || node.childTypes.some(c => c.isAbstract),
50
+ enums,
51
+ group: node.group,
52
+ };
53
+ }
54
+ /**
55
+ * Collect all polymorphic type names from a set of nodes.
56
+ */
57
+ export function collectPolymorphicTypeNames(rootNode, registry) {
58
+ const names = new Set();
59
+ function walk(node) {
60
+ if (node.discriminator && node.childTypes.length > 0) {
61
+ names.add(node.typeName.name);
62
+ }
63
+ for (const prop of node.properties) {
64
+ if (prop.type)
65
+ walk(prop.type);
66
+ }
67
+ for (const child of node.childTypes) {
68
+ walk(child);
69
+ }
70
+ }
71
+ walk(rootNode);
72
+ return names;
73
+ }
74
+ // ============================================================================
75
+ // Type lowering
76
+ // ============================================================================
77
+ /**
78
+ * Lower a single TypeNode into a TypeDecl.
79
+ */
80
+ export function lowerType(node, registry, polymorphicTypeNames) {
81
+ const fields = node.properties.map(p => lowerField(p, polymorphicTypeNames));
82
+ const collectionHelpers = lowerCollectionHelpers(node);
83
+ const polymorphicDispatch = lowerPolymorphicDispatch(node);
84
+ const factories = lowerFactories(node, registry);
85
+ const coercionProperty = findCoercionProperty(node);
86
+ // Clear enum metadata from discriminator fields — they're handled by polymorphic dispatch
87
+ if (polymorphicDispatch) {
88
+ for (const field of fields) {
89
+ if (field.name === polymorphicDispatch.discriminatorField) {
90
+ field.enumName = null;
91
+ field.isOpenEnum = false;
92
+ }
93
+ }
94
+ }
95
+ // Build load/save method specs
96
+ const load = lowerLoad(node, fields, polymorphicDispatch);
97
+ const save = lowerSave(node, fields);
98
+ return {
99
+ typeName: node.typeName,
100
+ base: node.base,
101
+ isAbstract: node.isAbstract,
102
+ isProtocol: node.isProtocol,
103
+ description: node.description,
104
+ fields,
105
+ coercionProperty,
106
+ load,
107
+ save,
108
+ factories,
109
+ collectionHelpers,
110
+ polymorphicDispatch,
111
+ methods: lowerMethods(node),
112
+ wire: lowerWire(node, fields),
113
+ };
114
+ }
115
+ // ============================================================================
116
+ // Property classification — the core insight
117
+ // ============================================================================
118
+ /**
119
+ * Classify a property into one of 5 categories.
120
+ * This is the fundamental decision that drives ALL code generation.
121
+ *
122
+ * Decision tree:
123
+ * isDict → "dict"
124
+ * isCollection && isScalar → "collection_scalar"
125
+ * isCollection && !isScalar → "collection_complex"
126
+ * isScalar → "scalar"
127
+ * !isScalar → "complex"
128
+ */
129
+ export function classifyProperty(prop, polymorphicTypeNames) {
130
+ if (prop.isDict) {
131
+ return { kind: "dict" };
132
+ }
133
+ if (prop.isCollection) {
134
+ if (prop.isScalar) {
135
+ return { kind: "collection_scalar", scalarType: prop.typeName.name };
136
+ }
137
+ return { kind: "collection_complex", typeName: prop.typeName.name };
138
+ }
139
+ if (prop.isScalar) {
140
+ return { kind: "scalar", scalarType: prop.typeName.name };
141
+ }
142
+ return { kind: "complex", typeName: prop.typeName.name };
143
+ }
144
+ // ============================================================================
145
+ // Field lowering
146
+ // ============================================================================
147
+ /**
148
+ * Lower a PropertyNode into a FieldDecl.
149
+ */
150
+ function lowerField(prop, polymorphicTypeNames) {
151
+ const knownAs = {};
152
+ for (const entry of prop.knownAs) {
153
+ knownAs[entry.provider] = entry.name;
154
+ }
155
+ return {
156
+ name: prop.name,
157
+ typeName: prop.typeName,
158
+ category: classifyProperty(prop, polymorphicTypeNames),
159
+ isOptional: prop.isOptional,
160
+ defaultValue: prop.defaultValue,
161
+ allowedValues: prop.allowedValues,
162
+ enumName: prop.enumName,
163
+ isOpenEnum: prop.isOpenEnum,
164
+ description: prop.description,
165
+ knownAs,
166
+ };
167
+ }
168
+ // ============================================================================
169
+ // Load method lowering
170
+ // ============================================================================
171
+ /**
172
+ * Lower the load/deserialization method specification.
173
+ *
174
+ * Produces language-agnostic coercion and assignment data. Each emitter
175
+ * decides variable names, rendering, and expression formatting.
176
+ */
177
+ function lowerLoad(node, fields, polymorphicDispatch) {
178
+ // Determine if this type has a discriminator with child variants
179
+ const hasDiscriminatorWithChildren = node.discriminator != null &&
180
+ (node.childTypes?.length ?? 0) > 0;
181
+ const coercions = (node.coercions || []).map(c => {
182
+ // Build structured assignments from the expansion dict
183
+ const assignments = Object.entries(c.expansion).map(([key, value]) => ({
184
+ fieldName: key,
185
+ isInput: value === "{value}",
186
+ literalValue: value === "{value}" ? undefined : String(value),
187
+ }));
188
+ // Determine if this coercion needs runtime dispatch:
189
+ // only when the discriminator field is set dynamically AND child types exist
190
+ const setsDiscriminator = node.discriminator != null &&
191
+ assignments.some(a => a.fieldName === node.discriminator && a.isInput);
192
+ const needsDispatch = setsDiscriminator && hasDiscriminatorWithChildren;
193
+ return {
194
+ scalarType: c.scalar,
195
+ assignments,
196
+ needsDispatch,
197
+ };
198
+ });
199
+ // Per-property load assignments
200
+ const assignments = fields.map(f => ({
201
+ sourceName: f.name,
202
+ fieldName: f.name,
203
+ category: f.category,
204
+ isOptional: f.isOptional,
205
+ parentTypeName: node.typeName.name,
206
+ enumName: f.enumName,
207
+ allowedValues: f.allowedValues,
208
+ defaultValue: f.defaultValue,
209
+ isOpenEnum: f.isOpenEnum,
210
+ }));
211
+ return {
212
+ coercions,
213
+ assignments,
214
+ hasPolymorphicDispatch: polymorphicDispatch !== null,
215
+ hasContextHooks: true, // All types support context hooks
216
+ };
217
+ }
218
+ // ============================================================================
219
+ // Save method lowering
220
+ // ============================================================================
221
+ /**
222
+ * Lower the save/serialization method specification.
223
+ */
224
+ function lowerSave(node, fields) {
225
+ const assignments = fields.map(f => ({
226
+ targetName: f.name,
227
+ fieldName: f.name,
228
+ category: f.category,
229
+ isOptional: f.isOptional,
230
+ parentTypeName: node.typeName.name,
231
+ enumName: f.enumName,
232
+ isOpenEnum: f.isOpenEnum,
233
+ }));
234
+ return {
235
+ assignments,
236
+ hasBase: node.base !== null,
237
+ hasContextHooks: true,
238
+ };
239
+ }
240
+ // ============================================================================
241
+ // Polymorphic dispatch lowering
242
+ // ============================================================================
243
+ /**
244
+ * Lower polymorphic dispatch specification from TypeNode.
245
+ * Returns null if the type is not polymorphic (no discriminator or no children).
246
+ */
247
+ function lowerPolymorphicDispatch(node) {
248
+ const polyTypes = node.retrievePolymorphicTypes();
249
+ if (!polyTypes)
250
+ return null;
251
+ const variants = polyTypes.types.map((t) => ({
252
+ value: t.value,
253
+ typeName: t.instance.typeName,
254
+ }));
255
+ let defaultVariant = null;
256
+ if (polyTypes.default) {
257
+ const defaultNode = polyTypes.default.instance;
258
+ defaultVariant = {
259
+ typeName: defaultNode.typeName,
260
+ isSelfReference: defaultNode.typeName.name === node.typeName.name,
261
+ };
262
+ }
263
+ const baseDispatch = {
264
+ discriminatorField: node.discriminator,
265
+ variants,
266
+ defaultVariant,
267
+ isAbstract: node.isAbstract,
268
+ };
269
+ return baseDispatch;
270
+ }
271
+ // ============================================================================
272
+ // Collection helper lowering
273
+ // ============================================================================
274
+ /**
275
+ * Lower collection helpers for complex collection properties.
276
+ * These are properties like `tools: Tool[]` or `parts: ContentPart[]`
277
+ * that need dedicated load/save helper methods for dict↔array conversion.
278
+ */
279
+ function lowerCollectionHelpers(node) {
280
+ return node.properties
281
+ .filter(p => p.isCollection && !p.isScalar && !p.isDict)
282
+ .map(p => ({
283
+ propertyName: p.name,
284
+ elementTypeName: p.typeName,
285
+ innerFields: p.type?.properties.filter(t => t.name !== "name").map(t => t.name) || [],
286
+ hasNameProperty: p.type?.properties.some(t => t.name === "name") || false,
287
+ }));
288
+ }
289
+ // ============================================================================
290
+ // Factory method lowering
291
+ // ============================================================================
292
+ /**
293
+ * Lower factory methods. Resolves the Expr tree via the Expression IR
294
+ * but stores it as a typed Expr (not pre-rendered string).
295
+ * Emitters will visit the Expr with their own visitor for language-specific output.
296
+ */
297
+ function lowerFactories(node, registry) {
298
+ if (!node.factories || node.factories.length === 0)
299
+ return [];
300
+ return node.factories.map(f => {
301
+ const expr = resolveFactoryExpr(f.sets, f.params, node, registry);
302
+ return {
303
+ name: f.name,
304
+ params: f.params,
305
+ body: expr,
306
+ };
307
+ });
308
+ }
309
+ // ============================================================================
310
+ // Method stub lowering
311
+ // ============================================================================
312
+ function lowerMethods(node) {
313
+ return (node.methods || []).map(m => ({
314
+ name: m.name,
315
+ returns: m.returns,
316
+ description: m.description,
317
+ params: m.params || {},
318
+ optional: m.optional ?? false,
319
+ sync: m.sync ?? false,
320
+ }));
321
+ }
322
+ // ============================================================================
323
+ // Wire conversion lowering
324
+ // ============================================================================
325
+ /**
326
+ * Lower wire conversion data from knownAs mappings on fields.
327
+ * Returns null if no field has wire mappings.
328
+ */
329
+ function lowerWire(node, fields) {
330
+ const providerSet = new Set();
331
+ const mappings = [];
332
+ for (const field of fields) {
333
+ if (Object.keys(field.knownAs).length > 0) {
334
+ for (const provider of Object.keys(field.knownAs)) {
335
+ providerSet.add(provider);
336
+ }
337
+ mappings.push({
338
+ fieldName: field.name,
339
+ category: field.category,
340
+ isOptional: field.isOptional,
341
+ parentTypeName: node.typeName.name,
342
+ wireNames: field.knownAs,
343
+ });
344
+ }
345
+ }
346
+ if (mappings.length === 0)
347
+ return null;
348
+ return {
349
+ providers: Array.from(providerSet).sort(),
350
+ mappings,
351
+ };
352
+ }
353
+ // ============================================================================
354
+ // Coercion property detection
355
+ // ============================================================================
356
+ /**
357
+ * Find the property that receives "{value}" in coercion expansions.
358
+ */
359
+ function findCoercionProperty(node) {
360
+ if (!node.coercions || node.coercions.length === 0)
361
+ return null;
362
+ for (const alt of node.coercions) {
363
+ for (const [key, value] of Object.entries(alt.expansion)) {
364
+ if (value === "{value}") {
365
+ return key;
366
+ }
367
+ }
368
+ }
369
+ return null;
370
+ }
371
+ // ============================================================================
372
+ // Enum collection
373
+ // ============================================================================
374
+ /**
375
+ * Collect unique enum definitions from all fields across all types in a file.
376
+ * Deduplicates by enum name — same-named enums with the same values share one definition.
377
+ */
378
+ function collectEnums(types) {
379
+ const seen = new Map();
380
+ // Collect discriminator field names to skip — these are handled by polymorphic dispatch
381
+ const discriminatorFields = new Set();
382
+ for (const type of types) {
383
+ if (type.polymorphicDispatch) {
384
+ discriminatorFields.add(type.polymorphicDispatch.discriminatorField);
385
+ }
386
+ }
387
+ for (const type of types) {
388
+ for (const field of type.fields) {
389
+ // Skip discriminator fields — they use the polymorphic Kind enum instead
390
+ if (discriminatorFields.has(field.name))
391
+ continue;
392
+ if (field.enumName && field.allowedValues.length > 0 && !seen.has(field.enumName)) {
393
+ seen.set(field.enumName, {
394
+ name: field.enumName,
395
+ values: field.allowedValues,
396
+ isOpen: field.isOpenEnum,
397
+ });
398
+ }
399
+ }
400
+ }
401
+ return Array.from(seen.values()).sort((a, b) => a.name.localeCompare(b.name));
402
+ }
403
+ // ============================================================================
404
+ // Import resolution
405
+ // ============================================================================
406
+ /**
407
+ * Resolve file-level imports from type references.
408
+ * Groups imports by module: each module maps to the symbols imported from it.
409
+ */
410
+ function resolveImports(rootNode, types, registry) {
411
+ // Types defined in this file (excluded from imports)
412
+ const definedInFile = new Set([
413
+ rootNode.typeName.name,
414
+ ...rootNode.childTypes.map(c => c.typeName.name),
415
+ ]);
416
+ const importMap = new Map();
417
+ const addImport = (typeName, module) => {
418
+ if (definedInFile.has(typeName))
419
+ return;
420
+ // Determine which module this type lives in
421
+ const refNode = registry.get(typeName);
422
+ const mod = module ?? (refNode?.base ? refNode.base.name : typeName);
423
+ if (!importMap.has(mod))
424
+ importMap.set(mod, new Set());
425
+ importMap.get(mod).add(typeName);
426
+ };
427
+ // Collect import refs from all properties across all types in this file
428
+ for (const type of types) {
429
+ for (const field of type.fields) {
430
+ // Only import non-scalar, non-dict types
431
+ if (field.category.kind === "complex" || field.category.kind === "collection_complex") {
432
+ addImport(field.typeName.name);
433
+ }
434
+ }
435
+ // Factory-referenced imports (may include child types like TextPart)
436
+ for (const factory of type.factories) {
437
+ for (const ref of collectExprTypeRefs(factory.body)) {
438
+ if (definedInFile.has(ref.name))
439
+ continue;
440
+ addImport(ref.name);
441
+ }
442
+ }
443
+ // Protocol method type references (param types and return types)
444
+ for (const method of type.methods) {
445
+ for (const typeName of extractMethodTypeRefs(method)) {
446
+ addImport(typeName);
447
+ }
448
+ }
449
+ }
450
+ return Array.from(importMap.entries())
451
+ .map(([module, names]) => {
452
+ // Look up the group of the module's root node in the registry
453
+ const modNode = registry.get(module);
454
+ const group = modNode?.group ?? "";
455
+ return { module, names: Array.from(names).sort(), group };
456
+ })
457
+ .sort((a, b) => a.module.localeCompare(b.module));
458
+ }
459
+ /**
460
+ * Extract type names referenced in method parameter types and return type.
461
+ * Handles formats like "Prompty", "Message[]", "Record<unknown>", "string", "unknown".
462
+ */
463
+ function extractMethodTypeRefs(method) {
464
+ const SCALARS = new Set(["string", "int32", "float32", "float64", "boolean", "unknown"]);
465
+ const refs = [];
466
+ const extract = (typeStr) => {
467
+ // Strip nullable suffix and array suffix: "string?" → "string", "Message[]" → "Message"
468
+ const base = typeStr.replace(/\?$/, "").replace(/\[\]$/, "");
469
+ // Skip scalars, Record<>, and generic types
470
+ if (SCALARS.has(base) || base.startsWith("Record<"))
471
+ return;
472
+ refs.push(base);
473
+ };
474
+ extract(method.returns);
475
+ for (const pType of Object.values(method.params)) {
476
+ extract(pType);
477
+ }
478
+ return refs;
479
+ }
480
+ //# sourceMappingURL=lower.js.map
@@ -0,0 +1,12 @@
1
+ export declare const scalarValue: Record<string, string>;
2
+ /**
3
+ * Convert PascalCase to snake_case.
4
+ * Used for idiomatic file naming in Python and Go.
5
+ */
6
+ export declare function toSnakeCase(str: string): string;
7
+ /**
8
+ * Convert PascalCase to kebab-case.
9
+ * Used for idiomatic file naming in TypeScript.
10
+ */
11
+ export declare function toKebabCase(str: string): string;
12
+ export declare const getCombinations: (arrays: any[][]) => any[][];
@@ -0,0 +1,39 @@
1
+ export const scalarValue = {
2
+ "boolean": 'False',
3
+ "float": "3.14",
4
+ "float32": "3.14",
5
+ "float64": "3.14",
6
+ "number": "3.14",
7
+ "int32": "3",
8
+ "int64": "3",
9
+ "integer": "3",
10
+ "string": '"example"',
11
+ };
12
+ /**
13
+ * Convert PascalCase to snake_case.
14
+ * Used for idiomatic file naming in Python and Go.
15
+ */
16
+ export function toSnakeCase(str) {
17
+ return str
18
+ .replace(/([a-z])([A-Z])/g, "$1_$2")
19
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
20
+ .toLowerCase();
21
+ }
22
+ /**
23
+ * Convert PascalCase to kebab-case.
24
+ * Used for idiomatic file naming in TypeScript.
25
+ */
26
+ export function toKebabCase(str) {
27
+ return str
28
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
29
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
30
+ .toLowerCase();
31
+ }
32
+ export const getCombinations = (arrays) => {
33
+ if (arrays.length === 0)
34
+ return [[]];
35
+ const [firstArray, ...restArrays] = arrays;
36
+ const combinationsOfRest = getCombinations(restArrays);
37
+ return firstArray.flatMap(item => combinationsOfRest.map(combination => [item, ...combination]));
38
+ };
39
+ //# sourceMappingURL=utilities.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Expression visitor interface and shared helpers.
3
+ *
4
+ * The ExprVisitor interface is the contract between the shared IR and
5
+ * per-language code emitters. Each language implements its own visitor
6
+ * in `languages/<lang>/visitor.ts`.
7
+ */
8
+ import { Expr, Construct, VariantConstruct, TypeRegistry } from "./expansion.js";
9
+ export interface ExprVisitor {
10
+ visitExpr(expr: Expr): string;
11
+ /** Optional type registry for wire format generation and type-aware codegen. */
12
+ registry?: TypeRegistry;
13
+ }
14
+ /**
15
+ * Render a Construct expression's fields as a plain object literal.
16
+ * Used by TypeScript, Python, C#, Go coercions where the template wraps
17
+ * the literal in language-specific loading logic (e.g., `TypeName.load({...}, ctx)`).
18
+ *
19
+ * @param expr - A Construct or VariantConstruct expression
20
+ * @param visitor - The language-specific visitor for rendering field values
21
+ * @param format - How to format the fields:
22
+ * - "js": `{ field: value }` (TypeScript, Go)
23
+ * - "py": `{"field": value}` (Python)
24
+ */
25
+ export declare function renderObjectLiteral(expr: Construct | VariantConstruct, visitor: ExprVisitor, format?: "js" | "py"): string;
26
+ /** camelCase → PascalCase (first char uppercase). */
27
+ export declare function toPascalCase(name: string): string;
28
+ /** Exhaustive check helper — TypeScript enforces all Expr.kind arms are handled. */
29
+ export declare function assertNever(x: never): never;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Expression visitor interface and shared helpers.
3
+ *
4
+ * The ExprVisitor interface is the contract between the shared IR and
5
+ * per-language code emitters. Each language implements its own visitor
6
+ * in `languages/<lang>/visitor.ts`.
7
+ */
8
+ /**
9
+ * Render a Construct expression's fields as a plain object literal.
10
+ * Used by TypeScript, Python, C#, Go coercions where the template wraps
11
+ * the literal in language-specific loading logic (e.g., `TypeName.load({...}, ctx)`).
12
+ *
13
+ * @param expr - A Construct or VariantConstruct expression
14
+ * @param visitor - The language-specific visitor for rendering field values
15
+ * @param format - How to format the fields:
16
+ * - "js": `{ field: value }` (TypeScript, Go)
17
+ * - "py": `{"field": value}` (Python)
18
+ */
19
+ export function renderObjectLiteral(expr, visitor, format = "js") {
20
+ const fields = expr.fields.map(f => {
21
+ const val = visitor.visitExpr(f.value);
22
+ switch (format) {
23
+ case "js":
24
+ return `${f.propertyName}: ${val}`;
25
+ case "py":
26
+ return `"${f.propertyName}": ${val}`;
27
+ }
28
+ });
29
+ return `{ ${fields.join(", ")} }`;
30
+ }
31
+ // ============================================================================
32
+ // Naming helpers — reusable across visitors
33
+ // ============================================================================
34
+ /** camelCase → PascalCase (first char uppercase). */
35
+ export function toPascalCase(name) {
36
+ // Handle snake_case first
37
+ if (name.includes("_")) {
38
+ return name
39
+ .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
40
+ .replace(/^(.)/, (_, char) => char.toUpperCase());
41
+ }
42
+ return name.charAt(0).toUpperCase() + name.slice(1);
43
+ }
44
+ /** Exhaustive check helper — TypeScript enforces all Expr.kind arms are handled. */
45
+ export function assertNever(x) {
46
+ throw new Error(`Unexpected expression kind: ${x.kind}`);
47
+ }
48
+ //# sourceMappingURL=visitor.js.map
@@ -0,0 +1,5 @@
1
+ import { EmitContext } from "@typespec/compiler";
2
+ import { EmitTarget, TypraEmitterOptions } from "../../lib.js";
3
+ import { TypeNode } from "../../ir/ast.js";
4
+ import { GeneratorOptions } from "../../emitter.js";
5
+ export declare const generateCsharp: (context: EmitContext<TypraEmitterOptions>, node: TypeNode, emitTarget: EmitTarget, options?: GeneratorOptions) => Promise<void>;