@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.
- package/dist/src/cleanup/generated-file.d.ts +6 -0
- package/dist/src/cleanup/generated-file.js +61 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +110 -0
- package/dist/src/decorators.d.ts +56 -0
- package/dist/src/decorators.js +177 -0
- package/dist/src/emitter.d.ts +13 -0
- package/dist/src/emitter.js +137 -0
- package/dist/src/generate.d.ts +86 -0
- package/dist/src/generate.js +104 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +5 -0
- package/dist/src/ir/ast.d.ts +235 -0
- package/dist/src/ir/ast.js +589 -0
- package/dist/src/ir/declarations.d.ts +364 -0
- package/dist/src/ir/declarations.js +23 -0
- package/dist/src/ir/expansion.d.ts +140 -0
- package/dist/src/ir/expansion.js +407 -0
- package/dist/src/ir/lower.d.ts +53 -0
- package/dist/src/ir/lower.js +480 -0
- package/dist/src/ir/utilities.d.ts +12 -0
- package/dist/src/ir/utilities.js +39 -0
- package/dist/src/ir/visitor.d.ts +29 -0
- package/dist/src/ir/visitor.js +48 -0
- package/dist/src/languages/csharp/driver.d.ts +5 -0
- package/dist/src/languages/csharp/driver.js +315 -0
- package/dist/src/languages/csharp/emitter.d.ts +33 -0
- package/dist/src/languages/csharp/emitter.js +1140 -0
- package/dist/src/languages/csharp/scaffolding.d.ts +18 -0
- package/dist/src/languages/csharp/scaffolding.js +591 -0
- package/dist/src/languages/csharp/test-emitter.d.ts +43 -0
- package/dist/src/languages/csharp/test-emitter.js +274 -0
- package/dist/src/languages/csharp/visitor.d.ts +14 -0
- package/dist/src/languages/csharp/visitor.js +79 -0
- package/dist/src/languages/go/driver.d.ts +12 -0
- package/dist/src/languages/go/driver.js +128 -0
- package/dist/src/languages/go/emitter.d.ts +33 -0
- package/dist/src/languages/go/emitter.js +879 -0
- package/dist/src/languages/go/scaffolding.d.ts +18 -0
- package/dist/src/languages/go/scaffolding.js +53 -0
- package/dist/src/languages/go/test-emitter.d.ts +20 -0
- package/dist/src/languages/go/test-emitter.js +300 -0
- package/dist/src/languages/go/visitor.d.ts +14 -0
- package/dist/src/languages/go/visitor.js +78 -0
- package/dist/src/languages/markdown/driver.d.ts +19 -0
- package/dist/src/languages/markdown/driver.js +408 -0
- package/dist/src/languages/python/driver.d.ts +14 -0
- package/dist/src/languages/python/driver.js +372 -0
- package/dist/src/languages/python/emitter.d.ts +31 -0
- package/dist/src/languages/python/emitter.js +856 -0
- package/dist/src/languages/python/scaffolding.d.ts +33 -0
- package/dist/src/languages/python/scaffolding.js +279 -0
- package/dist/src/languages/python/test-emitter.d.ts +29 -0
- package/dist/src/languages/python/test-emitter.js +388 -0
- package/dist/src/languages/python/visitor.d.ts +14 -0
- package/dist/src/languages/python/visitor.js +65 -0
- package/dist/src/languages/rust/driver.d.ts +13 -0
- package/dist/src/languages/rust/driver.js +624 -0
- package/dist/src/languages/rust/emitter.d.ts +45 -0
- package/dist/src/languages/rust/emitter.js +1596 -0
- package/dist/src/languages/rust/visitor.d.ts +25 -0
- package/dist/src/languages/rust/visitor.js +153 -0
- package/dist/src/languages/typescript/driver.d.ts +8 -0
- package/dist/src/languages/typescript/driver.js +209 -0
- package/dist/src/languages/typescript/emitter.d.ts +42 -0
- package/dist/src/languages/typescript/emitter.js +904 -0
- package/dist/src/languages/typescript/scaffolding.d.ts +32 -0
- package/dist/src/languages/typescript/scaffolding.js +303 -0
- package/dist/src/languages/typescript/test-emitter.d.ts +23 -0
- package/dist/src/languages/typescript/test-emitter.js +204 -0
- package/dist/src/languages/typescript/visitor.d.ts +14 -0
- package/dist/src/languages/typescript/visitor.js +64 -0
- package/dist/src/lib.d.ts +33 -0
- package/dist/src/lib.js +101 -0
- package/dist/src/testing/index.d.ts +2 -0
- package/dist/src/testing/index.js +8 -0
- package/dist/src/testing/test-context.d.ts +63 -0
- package/dist/src/testing/test-context.js +355 -0
- package/fixtures/shapes/main.tsp +43 -0
- package/fixtures/tspconfig.yaml +13 -0
- package/package.json +76 -0
- 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>;
|