@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,388 @@
1
+ /**
2
+ * Python test file emitter — BaseTestContext → pytest source code.
3
+ *
4
+ * Replaces the Nunjucks templates:
5
+ * - `test.py.njk` → emitPythonTest()
6
+ * - `test_context.py.njk` → emitPythonTestContext()
7
+ * - `_macros.njk` → factoryParamTestValue(), renderValidation()
8
+ *
9
+ * The emitter produces pytest functions for:
10
+ * - JSON loading (load_json per example)
11
+ * - YAML loading (load_yaml per example)
12
+ * - Round-trip (load → save → load per example)
13
+ * - Serialization (to_json, to_yaml per example)
14
+ * - Alternate representations (scalar coercions)
15
+ * - Factory methods
16
+ */
17
+ import { toSnakeCase } from "../../ir/utilities.js";
18
+ // ============================================================================
19
+ // Macro replacements
20
+ // ============================================================================
21
+ /**
22
+ * Get test value for a factory parameter type.
23
+ * Replaces the factoryParamTestValue macro from _macros.njk.
24
+ */
25
+ function factoryParamTestValue(typeStr) {
26
+ switch (typeStr) {
27
+ case "string": return '"test"';
28
+ case "boolean": return "True";
29
+ case "integer":
30
+ case "int32":
31
+ case "int64": return "42";
32
+ case "float":
33
+ case "float64":
34
+ case "float32": return "3.14";
35
+ case "unknown":
36
+ default: return '"test"';
37
+ }
38
+ }
39
+ /**
40
+ * Render a single validation assertion line for a Python test.
41
+ */
42
+ function renderValidation(v, varName) {
43
+ if (v.value === "True") {
44
+ return ` assert ${varName}.${v.key}`;
45
+ }
46
+ else if (v.value === "False") {
47
+ return ` assert not ${varName}.${v.key}`;
48
+ }
49
+ else {
50
+ return ` assert ${varName}.${v.key} == ${v.delimiter}${v.value}${v.delimiter}`;
51
+ }
52
+ }
53
+ // ============================================================================
54
+ // Test emitters
55
+ // ============================================================================
56
+ /**
57
+ * Emit the test_context.py file content (tests for LoadContext + SaveContext).
58
+ * Replaces test_context.py.njk template.
59
+ */
60
+ export function emitPythonTestContext(header, packageName) {
61
+ const headerLine = header ? `# ${header}\n` : '';
62
+ return `${headerLine}from ${packageName}._context import LoadContext, SaveContext
63
+
64
+
65
+ class TestLoadContext:
66
+ """Tests for LoadContext class."""
67
+
68
+ def test_default_values(self) -> None:
69
+ """Test that LoadContext has correct default values."""
70
+ context = LoadContext()
71
+ assert context.pre_process is None
72
+ assert context.post_process is None
73
+
74
+ def test_process_input_without_callback(self) -> None:
75
+ """Test process_input returns original data when no callback set."""
76
+ context = LoadContext()
77
+ data = {"key": "value", "nested": {"a": 1}}
78
+ result = context.process_input(data)
79
+ assert result is data
80
+
81
+ def test_process_input_with_callback(self) -> None:
82
+ """Test process_input applies the pre_process callback."""
83
+ def add_field(data: dict) -> dict:
84
+ return {**data, "added": True}
85
+
86
+ context = LoadContext(pre_process=add_field)
87
+ data = {"key": "value"}
88
+ result = context.process_input(data)
89
+ assert result == {"key": "value", "added": True}
90
+ assert result is not data
91
+
92
+ def test_process_output_without_callback(self) -> None:
93
+ """Test process_output returns original result when no callback set."""
94
+ context = LoadContext()
95
+ result = {"some": "result"}
96
+ processed = context.process_output(result)
97
+ assert processed is result
98
+
99
+ def test_process_output_with_callback(self) -> None:
100
+ """Test process_output applies the post_process callback."""
101
+ def wrap_result(result: dict) -> dict:
102
+ return {"wrapped": result}
103
+
104
+ context = LoadContext(post_process=wrap_result)
105
+ result = {"key": "value"}
106
+ processed = context.process_output(result)
107
+ assert processed == {"wrapped": {"key": "value"}}
108
+
109
+ def test_both_callbacks(self) -> None:
110
+ """Test using both pre_process and post_process callbacks."""
111
+ def normalize_keys(data: dict) -> dict:
112
+ return {k.lower(): v for k, v in data.items()}
113
+
114
+ def add_metadata(result: dict) -> dict:
115
+ return {**result, "_processed": True}
116
+
117
+ context = LoadContext(pre_process=normalize_keys, post_process=add_metadata)
118
+
119
+ input_data = {"Key": "value", "UPPER": "case"}
120
+ processed_input = context.process_input(input_data)
121
+ assert processed_input == {"key": "value", "upper": "case"}
122
+
123
+ final_result = context.process_output(processed_input)
124
+ assert final_result == {"key": "value", "upper": "case", "_processed": True}
125
+
126
+ def test_pre_process_can_modify_structure(self) -> None:
127
+ """Test that pre_process can fundamentally transform data structure."""
128
+ def flatten_nested(data: dict) -> dict:
129
+ result = {}
130
+ for key, value in data.items():
131
+ if isinstance(value, dict):
132
+ for nested_key, nested_value in value.items():
133
+ result[f"{key}_{nested_key}"] = nested_value
134
+ else:
135
+ result[key] = value
136
+ return result
137
+
138
+ context = LoadContext(pre_process=flatten_nested)
139
+ data = {"top": "level", "nested": {"a": 1, "b": 2}}
140
+ result = context.process_input(data)
141
+ assert result == {"top": "level", "nested_a": 1, "nested_b": 2}
142
+
143
+
144
+ class TestSaveContext:
145
+ """Tests for SaveContext class."""
146
+
147
+ def test_default_values(self) -> None:
148
+ """Test that SaveContext has correct default values."""
149
+ context = SaveContext()
150
+ assert context.pre_save is None
151
+ assert context.post_save is None
152
+
153
+ def test_process_object_without_callback(self) -> None:
154
+ """Test process_object returns original object when no callback set."""
155
+ context = SaveContext()
156
+ obj = {"key": "value"}
157
+ result = context.process_object(obj)
158
+ assert result is obj
159
+
160
+ def test_process_object_with_callback(self) -> None:
161
+ """Test process_object applies the pre_save callback."""
162
+ def add_timestamp(obj: dict) -> dict:
163
+ return {**obj, "timestamp": "2024-01-01"}
164
+
165
+ context = SaveContext(pre_save=add_timestamp)
166
+ obj = {"key": "value"}
167
+ result = context.process_object(obj)
168
+ assert result == {"key": "value", "timestamp": "2024-01-01"}
169
+
170
+ def test_process_dict_without_callback(self) -> None:
171
+ """Test process_dict returns original dict when no callback set."""
172
+ context = SaveContext()
173
+ data = {"key": "value"}
174
+ result = context.process_dict(data)
175
+ assert result is data
176
+
177
+ def test_process_dict_with_callback(self) -> None:
178
+ """Test process_dict applies the post_save callback."""
179
+ def remove_internal_fields(data: dict) -> dict:
180
+ return {k: v for k, v in data.items() if not k.startswith("_")}
181
+
182
+ context = SaveContext(post_save=remove_internal_fields)
183
+ data = {"key": "value", "_internal": "secret"}
184
+ result = context.process_dict(data)
185
+ assert result == {"key": "value"}
186
+
187
+ def test_both_callbacks(self) -> None:
188
+ """Test using both pre_save and post_save callbacks."""
189
+ def mark_for_export(obj: dict) -> dict:
190
+ return {**obj, "_exporting": True}
191
+
192
+ def clean_markers(data: dict) -> dict:
193
+ return {k: v for k, v in data.items() if not k.startswith("_")}
194
+
195
+ context = SaveContext(pre_save=mark_for_export, post_save=clean_markers)
196
+
197
+ obj = {"name": "test", "value": 42}
198
+ processed_obj = context.process_object(obj)
199
+ assert processed_obj == {"name": "test", "value": 42, "_exporting": True}
200
+
201
+ final_dict = context.process_dict(processed_obj)
202
+ assert final_dict == {"name": "test", "value": 42}
203
+
204
+ def test_to_yaml(self) -> None:
205
+ """Test to_yaml produces valid YAML string."""
206
+ context = SaveContext()
207
+ data = {"name": "test", "items": ["a", "b"]}
208
+ result = context.to_yaml(data)
209
+ assert "name: test" in result
210
+ assert "items:" in result
211
+ assert "- a" in result
212
+ assert "- b" in result
213
+
214
+ def test_to_json(self) -> None:
215
+ """Test to_json produces valid JSON string."""
216
+ import json
217
+ context = SaveContext()
218
+ data = {"name": "test", "items": ["a", "b"]}
219
+ result = context.to_json(data)
220
+ parsed = json.loads(result)
221
+ assert parsed == data
222
+
223
+ def test_to_json_custom_indent(self) -> None:
224
+ """Test to_json respects custom indent."""
225
+ context = SaveContext()
226
+ data = {"name": "test"}
227
+ result_2 = context.to_json(data, indent=2)
228
+ result_4 = context.to_json(data, indent=4)
229
+ # 4-space indent should have more characters
230
+ assert len(result_4) > len(result_2)
231
+
232
+ def test_collection_format_default(self) -> None:
233
+ """Test that default collection_format is 'object'."""
234
+ context = SaveContext()
235
+ assert context.collection_format == "object"
236
+
237
+ def test_collection_format_array(self) -> None:
238
+ """Test collection_format can be set to 'array'."""
239
+ context = SaveContext(collection_format="array")
240
+ assert context.collection_format == "array"
241
+
242
+ def test_use_shorthand_default(self) -> None:
243
+ """Test that default use_shorthand is True."""
244
+ context = SaveContext()
245
+ assert context.use_shorthand is True
246
+
247
+ def test_use_shorthand_disabled(self) -> None:
248
+ """Test use_shorthand can be disabled."""
249
+ context = SaveContext(use_shorthand=False)
250
+ assert context.use_shorthand is False
251
+ `;
252
+ }
253
+ /**
254
+ * Emit a pytest test file for a type.
255
+ * Replaces test.py.njk template.
256
+ */
257
+ export function emitPythonTest(ctx) {
258
+ const { node, examples, coercions, factories, classCtx } = ctx;
259
+ const packageName = ctx.package || '';
260
+ const typeName = node.typeName.name;
261
+ const typeNameLower = typeName.toLowerCase();
262
+ const lines = [];
263
+ // Imports
264
+ if (examples.length > 0) {
265
+ lines.push('import json');
266
+ lines.push('import yaml');
267
+ lines.push('');
268
+ }
269
+ lines.push(`from ${packageName} import ${typeName}`);
270
+ lines.push('');
271
+ // Example tests: load_json, load_yaml, roundtrip_json, to_json, to_yaml
272
+ for (let i = 0; i < examples.length; i++) {
273
+ const sample = examples[i];
274
+ const suffix = i === 0 ? '' : `_${i}`;
275
+ const jsonBlock = sample.json.map(line => line.length > 0 ? ` ${line}` : '').join('\n');
276
+ const yamlBlock = sample.yaml.map(line => line.length > 0 ? ` ${line}` : '').join('\n');
277
+ // test_load_json
278
+ lines.push(`def test_load_json_${typeNameLower}${suffix}():`);
279
+ lines.push(` json_data = r'''`);
280
+ lines.push(jsonBlock);
281
+ lines.push(` '''`);
282
+ lines.push(` data = json.loads(json_data, strict=False)`);
283
+ lines.push(` instance = ${typeName}.load(data)`);
284
+ lines.push(` assert instance is not None`);
285
+ for (const v of sample.validations) {
286
+ lines.push(renderValidation(v, 'instance'));
287
+ }
288
+ lines.push('');
289
+ // test_load_yaml
290
+ lines.push(`def test_load_yaml_${typeNameLower}${suffix}():`);
291
+ lines.push(` yaml_data = r'''`);
292
+ lines.push(yamlBlock);
293
+ lines.push(` '''`);
294
+ lines.push(` data = yaml.load(yaml_data, Loader=yaml.FullLoader)`);
295
+ lines.push(` instance = ${typeName}.load(data)`);
296
+ lines.push(` assert instance is not None`);
297
+ for (const v of sample.validations) {
298
+ lines.push(renderValidation(v, 'instance'));
299
+ }
300
+ lines.push('');
301
+ // test_roundtrip_json
302
+ lines.push(`def test_roundtrip_json_${typeNameLower}${suffix}():`);
303
+ lines.push(` """Test that load -> save -> load produces equivalent data."""`);
304
+ lines.push(` json_data = r'''`);
305
+ lines.push(jsonBlock);
306
+ lines.push(` '''`);
307
+ lines.push(` original_data = json.loads(json_data, strict=False)`);
308
+ lines.push(` instance = ${typeName}.load(original_data)`);
309
+ lines.push(` saved_data = instance.save()`);
310
+ lines.push(` reloaded = ${typeName}.load(saved_data)`);
311
+ lines.push(` assert reloaded is not None`);
312
+ for (const v of sample.validations) {
313
+ lines.push(renderValidation(v, 'reloaded'));
314
+ }
315
+ lines.push('');
316
+ // test_to_json
317
+ lines.push(`def test_to_json_${typeNameLower}${suffix}():`);
318
+ lines.push(` """Test that to_json produces valid JSON."""`);
319
+ lines.push(` json_data = r'''`);
320
+ lines.push(jsonBlock);
321
+ lines.push(` '''`);
322
+ lines.push(` data = json.loads(json_data, strict=False)`);
323
+ lines.push(` instance = ${typeName}.load(data)`);
324
+ lines.push(` json_output = instance.to_json()`);
325
+ lines.push(` assert json_output is not None`);
326
+ lines.push(` parsed = json.loads(json_output)`);
327
+ lines.push(` assert isinstance(parsed, dict)`);
328
+ lines.push('');
329
+ // test_to_yaml
330
+ lines.push(`def test_to_yaml_${typeNameLower}${suffix}():`);
331
+ lines.push(` """Test that to_yaml produces valid YAML."""`);
332
+ lines.push(` json_data = r'''`);
333
+ lines.push(jsonBlock);
334
+ lines.push(` '''`);
335
+ lines.push(` data = json.loads(json_data, strict=False)`);
336
+ lines.push(` instance = ${typeName}.load(data)`);
337
+ lines.push(` yaml_output = instance.to_yaml()`);
338
+ lines.push(` assert yaml_output is not None`);
339
+ lines.push(` parsed = yaml.safe_load(yaml_output)`);
340
+ lines.push(` assert isinstance(parsed, dict)`);
341
+ lines.push('');
342
+ }
343
+ // Coercion tests
344
+ if (coercions.length > 0) {
345
+ for (const alt of coercions) {
346
+ lines.push(`def test_load_${typeNameLower}_from_${alt.scalarType}():`);
347
+ lines.push(` instance = ${typeName}.load(${alt.value})`);
348
+ lines.push(` assert instance is not None`);
349
+ for (const v of alt.validations) {
350
+ lines.push(renderValidation(v, 'instance'));
351
+ }
352
+ lines.push('');
353
+ }
354
+ }
355
+ // Factory tests
356
+ if (factories.length > 0) {
357
+ for (const factory of factories) {
358
+ const safeName = classCtx.factoryNameMap[factory.name];
359
+ const factorySnake = toSnakeCase(factory.name);
360
+ const params = Object.entries(factory.params)
361
+ .map(([_, pType]) => factoryParamTestValue(pType))
362
+ .join(', ');
363
+ lines.push(`def test_factory_${factorySnake}_${typeNameLower}():`);
364
+ lines.push(` """Test that ${factory.name}() factory creates a valid instance."""`);
365
+ lines.push(` instance = ${typeName}.${safeName}(${params})`);
366
+ lines.push(` assert instance is not None`);
367
+ lines.push(` assert isinstance(instance, ${typeName})`);
368
+ for (const [propName, value] of Object.entries(factory.sets)) {
369
+ const snakeProp = toSnakeCase(propName);
370
+ if (value === true) {
371
+ lines.push(` assert instance.${snakeProp}`);
372
+ }
373
+ else if (value === false) {
374
+ lines.push(` assert not instance.${snakeProp}`);
375
+ }
376
+ else if (typeof value === 'number') {
377
+ lines.push(` assert instance.${snakeProp} == ${value}`);
378
+ }
379
+ else if (typeof value === 'string') {
380
+ lines.push(` assert instance.${snakeProp} == "${value}"`);
381
+ }
382
+ }
383
+ lines.push('');
384
+ }
385
+ }
386
+ return lines.join('\n') + '\n';
387
+ }
388
+ //# sourceMappingURL=test-emitter.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Python expression visitor — Expr IR → Python source fragments.
3
+ */
4
+ import { Expr, TypeRegistry } from "../../ir/expansion.js";
5
+ import { ExprVisitor } from "../../ir/visitor.js";
6
+ export declare class PythonExprVisitor implements ExprVisitor {
7
+ registry?: TypeRegistry;
8
+ constructor(registry?: TypeRegistry);
9
+ visitExpr(expr: Expr): string;
10
+ private visitConstruct;
11
+ private visitVariant;
12
+ private visitArray;
13
+ private escapeString;
14
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Python expression visitor — Expr IR → Python source fragments.
3
+ */
4
+ import { assertNever } from "../../ir/visitor.js";
5
+ import { toSnakeCase } from "../../ir/utilities.js";
6
+ export class PythonExprVisitor {
7
+ registry;
8
+ constructor(registry) {
9
+ this.registry = registry;
10
+ }
11
+ visitExpr(expr) {
12
+ switch (expr.kind) {
13
+ case "string":
14
+ return `"${this.escapeString(expr.value)}"`;
15
+ case "number":
16
+ return String(expr.value);
17
+ case "boolean":
18
+ return expr.value ? "True" : "False";
19
+ case "null":
20
+ return "None";
21
+ case "param":
22
+ return toSnakeCase(expr.name);
23
+ case "construct":
24
+ return this.visitConstruct(expr);
25
+ case "variant":
26
+ return this.visitVariant(expr);
27
+ case "array":
28
+ return this.visitArray(expr);
29
+ case "dict":
30
+ return `{${expr.entries.map(e => `"${e.key}": ${this.visitExpr(e.value)}`).join(", ")}}`;
31
+ case "field_read":
32
+ return `${toSnakeCase(expr.objectName)}.${toSnakeCase(expr.fieldName)}`;
33
+ default:
34
+ return assertNever(expr);
35
+ }
36
+ }
37
+ visitConstruct(expr) {
38
+ const typeName = expr.typeName.name;
39
+ if (expr.fields.length === 0) {
40
+ return `${typeName}()`;
41
+ }
42
+ const fields = expr.fields.map(f => `${toSnakeCase(f.propertyName)}=${this.visitExpr(f.value)}`).join(", ");
43
+ return `${typeName}(${fields})`;
44
+ }
45
+ visitVariant(expr) {
46
+ // In Python, polymorphic children are full classes
47
+ const variantName = expr.variantTypeName.name;
48
+ if (expr.fields.length === 0) {
49
+ return `${variantName}()`;
50
+ }
51
+ const fields = expr.fields.map(f => `${toSnakeCase(f.propertyName)}=${this.visitExpr(f.value)}`).join(", ");
52
+ return `${variantName}(${fields})`;
53
+ }
54
+ visitArray(expr) {
55
+ if (expr.items.length === 0) {
56
+ return "[]";
57
+ }
58
+ const items = expr.items.map(i => this.visitExpr(i)).join(", ");
59
+ return `[${items}]`;
60
+ }
61
+ escapeString(s) {
62
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
63
+ }
64
+ }
65
+ //# sourceMappingURL=visitor.js.map
@@ -0,0 +1,13 @@
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
+ /**
6
+ * Type mapping from TypeSpec scalar types to Rust types.
7
+ * Retained for use by the test template context.
8
+ */
9
+ export declare const rustTypeMapper: Record<string, string>;
10
+ /**
11
+ * Main entry point for Rust code generation.
12
+ */
13
+ export declare const generateRust: (context: EmitContext<TypraEmitterOptions>, node: TypeNode, emitTarget: EmitTarget, options?: GeneratorOptions) => Promise<void>;