@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,32 @@
1
+ /**
2
+ * TypeScript scaffolding emitter — static/structural files.
3
+ *
4
+ * Replaces the Nunjucks templates:
5
+ * - `context.ts.njk` → emitTypeScriptContext()
6
+ * - `index.ts.njk` → emitTypeScriptIndex()
7
+ * - `eslint.config.js.njk` → emitEslintConfig()
8
+ *
9
+ * These emit files whose content depends only on the type graph
10
+ * shape (not on the Declaration IR used for per-type files).
11
+ */
12
+ import { TypeNode } from "../../ir/ast.js";
13
+ /**
14
+ * Emit static TypeScript context classes (LoadContext, SaveContext).
15
+ */
16
+ export declare function emitTypeScriptContext(): string;
17
+ /**
18
+ * Emit the barrel export index.ts file.
19
+ *
20
+ * When types are organised into group subfolders, the root index.ts re-exports
21
+ * from each group's index.ts (e.g. `export * from "./connection/index"`).
22
+ */
23
+ export declare function emitTypeScriptIndex(baseTypes: TypeNode[], _types: TypeNode[]): string;
24
+ /**
25
+ * Emit a group-level index.ts barrel file.
26
+ * Lives at `model/{group}/index.ts` and re-exports all types in that group.
27
+ */
28
+ export declare function emitTypeScriptGroupIndex(group: string, groupNodes: TypeNode[]): string;
29
+ /**
30
+ * Emit the static ESLint configuration file.
31
+ */
32
+ export declare function emitEslintConfig(): string;
@@ -0,0 +1,303 @@
1
+ /**
2
+ * TypeScript scaffolding emitter — static/structural files.
3
+ *
4
+ * Replaces the Nunjucks templates:
5
+ * - `context.ts.njk` → emitTypeScriptContext()
6
+ * - `index.ts.njk` → emitTypeScriptIndex()
7
+ * - `eslint.config.js.njk` → emitEslintConfig()
8
+ *
9
+ * These emit files whose content depends only on the type graph
10
+ * shape (not on the Declaration IR used for per-type files).
11
+ */
12
+ import { toKebabCase } from "../../ir/utilities.js";
13
+ // ============================================================================
14
+ // Context classes (LoadContext, SaveContext)
15
+ // ============================================================================
16
+ /**
17
+ * Emit static TypeScript context classes (LoadContext, SaveContext).
18
+ */
19
+ export function emitTypeScriptContext() {
20
+ return `// Copyright (c) Microsoft. All rights reserved.
21
+ // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY.
22
+
23
+ import * as yaml from "yaml";
24
+
25
+ /**
26
+ * Context for customizing the loading process of agent definitions.
27
+ *
28
+ * Provides hooks for pre-processing input data before parsing and
29
+ * post-processing output data after instantiation.
30
+ */
31
+ export class LoadContext {
32
+ /**
33
+ * Optional callback to transform input data before parsing.
34
+ */
35
+ preProcess?: (data: Record<string, unknown>) => Record<string, unknown>;
36
+
37
+ /**
38
+ * Optional callback to transform the result after instantiation.
39
+ */
40
+ postProcess?: (result: unknown) => unknown;
41
+
42
+ constructor(init?: Partial<LoadContext>) {
43
+ if (init?.preProcess) {
44
+ this.preProcess = init.preProcess;
45
+ }
46
+ if (init?.postProcess) {
47
+ this.postProcess = init.postProcess;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Apply pre-processing to input data if a preProcess callback is set.
53
+ * @param data - The raw input dictionary to process.
54
+ * @returns The processed dictionary, or the original if no callback is set.
55
+ */
56
+ processInput(data: Record<string, unknown>): Record<string, unknown> {
57
+ if (this.preProcess) {
58
+ return this.preProcess(data);
59
+ }
60
+ return data;
61
+ }
62
+
63
+ /**
64
+ * Apply post-processing to the result if a postProcess callback is set.
65
+ * @param result - The instantiated object to process.
66
+ * @returns The processed result, or the original if no callback is set.
67
+ */
68
+ processOutput<T>(result: T): T {
69
+ if (this.postProcess) {
70
+ return this.postProcess(result) as T;
71
+ }
72
+ return result;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Context for customizing the serialization process of agent definitions.
78
+ *
79
+ * Provides hooks for pre-processing the object before serialization and
80
+ * post-processing the dictionary after serialization.
81
+ */
82
+ export class SaveContext {
83
+ /**
84
+ * Optional callback to transform the object before serialization.
85
+ */
86
+ preSave?: (obj: unknown) => unknown;
87
+
88
+ /**
89
+ * Optional callback to transform the dictionary after serialization.
90
+ */
91
+ postSave?: (data: Record<string, unknown>) => Record<string, unknown>;
92
+
93
+ /**
94
+ * Output format for collections: "object" (name as key) or "array" (list of dicts).
95
+ * Defaults to "object".
96
+ */
97
+ collectionFormat: "object" | "array" = "object";
98
+
99
+ /**
100
+ * Use shorthand scalar representation when possible.
101
+ * Defaults to true.
102
+ */
103
+ useShorthand: boolean = true;
104
+
105
+ constructor(init?: Partial<SaveContext>) {
106
+ if (init?.preSave) {
107
+ this.preSave = init.preSave;
108
+ }
109
+ if (init?.postSave) {
110
+ this.postSave = init.postSave;
111
+ }
112
+ if (init?.collectionFormat) {
113
+ this.collectionFormat = init.collectionFormat;
114
+ }
115
+ if (init?.useShorthand !== undefined) {
116
+ this.useShorthand = init.useShorthand;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Apply pre-processing to the object if a preSave callback is set.
122
+ * @param obj - The object to process before serialization.
123
+ * @returns The processed object, or the original if no callback is set.
124
+ */
125
+ processObject<T>(obj: T): T {
126
+ if (this.preSave) {
127
+ return this.preSave(obj) as T;
128
+ }
129
+ return obj;
130
+ }
131
+
132
+ /**
133
+ * Apply post-processing to the dictionary if a postSave callback is set.
134
+ * @param data - The serialized dictionary to process.
135
+ * @returns The processed dictionary, or the original if no callback is set.
136
+ */
137
+ processDict(data: Record<string, unknown>): Record<string, unknown> {
138
+ if (this.postSave) {
139
+ return this.postSave(data);
140
+ }
141
+ return data;
142
+ }
143
+
144
+ /**
145
+ * Convert a dictionary to a YAML string.
146
+ * @param data - The dictionary to convert.
147
+ * @returns The YAML string representation.
148
+ */
149
+ toYaml(data: Record<string, unknown>): string {
150
+ return yaml.stringify(data, { indent: 2 });
151
+ }
152
+
153
+ /**
154
+ * Convert a dictionary to a JSON string.
155
+ * @param data - The dictionary to convert.
156
+ * @param indent - Number of spaces for indentation.
157
+ * @returns The JSON string representation.
158
+ */
159
+ toJson(data: Record<string, unknown>, indent: number = 2): string {
160
+ return JSON.stringify(data, null, indent);
161
+ }
162
+ }
163
+ `;
164
+ }
165
+ // ============================================================================
166
+ // Barrel export index.ts
167
+ // ============================================================================
168
+ /**
169
+ * Emit the barrel export index.ts file.
170
+ *
171
+ * When types are organised into group subfolders, the root index.ts re-exports
172
+ * from each group's index.ts (e.g. `export * from "./connection/index"`).
173
+ */
174
+ export function emitTypeScriptIndex(baseTypes, _types) {
175
+ const lines = [];
176
+ lines.push("// Copyright (c) Microsoft. All rights reserved.");
177
+ lines.push("// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY.");
178
+ lines.push("");
179
+ lines.push('export { LoadContext, SaveContext } from "./context";');
180
+ // Group root types by their semantic group
181
+ const groupMap = new Map();
182
+ for (const type of baseTypes) {
183
+ const g = type.group || "";
184
+ if (!groupMap.has(g))
185
+ groupMap.set(g, []);
186
+ groupMap.get(g).push(type);
187
+ }
188
+ const sortedGroups = Array.from(groupMap.keys()).sort();
189
+ for (const group of sortedGroups) {
190
+ const groupTypes = groupMap.get(group);
191
+ if (!group) {
192
+ // Root-level types (no group) — emit inline re-exports
193
+ for (const type of groupTypes) {
194
+ const exportKeyword = type.isProtocol ? "export type" : "export";
195
+ if (type.childTypes.length > 0) {
196
+ const exports = [type.typeName.name, ...type.childTypes.map((c) => c.typeName.name)];
197
+ lines.push("");
198
+ lines.push(`${exportKeyword} {`);
199
+ for (const name of exports) {
200
+ lines.push(` ${name},`);
201
+ }
202
+ lines.push(`} from "./${toKebabCase(type.typeName.name)}";`);
203
+ }
204
+ else {
205
+ lines.push(`${exportKeyword} { ${type.typeName.name} } from "./${toKebabCase(type.typeName.name)}";`);
206
+ }
207
+ }
208
+ }
209
+ else {
210
+ // Group subfolder — re-export from the group's index.ts
211
+ lines.push("");
212
+ for (const type of groupTypes) {
213
+ const exportKeyword = type.isProtocol ? "export type" : "export";
214
+ const allNames = [type.typeName.name, ...type.childTypes.map(c => c.typeName.name)];
215
+ if (allNames.length > 1) {
216
+ lines.push(`${exportKeyword} { ${allNames.join(", ")} } from "./${group}/${toKebabCase(type.typeName.name)}";`);
217
+ }
218
+ else {
219
+ lines.push(`${exportKeyword} { ${type.typeName.name} } from "./${group}/${toKebabCase(type.typeName.name)}";`);
220
+ }
221
+ }
222
+ }
223
+ }
224
+ lines.push("");
225
+ return lines.join("\n");
226
+ }
227
+ /**
228
+ * Emit a group-level index.ts barrel file.
229
+ * Lives at `model/{group}/index.ts` and re-exports all types in that group.
230
+ */
231
+ export function emitTypeScriptGroupIndex(group, groupNodes) {
232
+ const lines = [];
233
+ lines.push("// Copyright (c) Microsoft. All rights reserved.");
234
+ lines.push("// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY.");
235
+ lines.push("");
236
+ for (const type of groupNodes) {
237
+ const exportKeyword = type.isProtocol ? "export type" : "export";
238
+ const allNames = [type.typeName.name, ...type.childTypes.map(c => c.typeName.name)];
239
+ if (allNames.length > 1) {
240
+ lines.push(`${exportKeyword} {`);
241
+ for (const name of allNames) {
242
+ lines.push(` ${name},`);
243
+ }
244
+ lines.push(`} from "./${toKebabCase(type.typeName.name)}";`);
245
+ }
246
+ else {
247
+ lines.push(`${exportKeyword} { ${type.typeName.name} } from "./${toKebabCase(type.typeName.name)}";`);
248
+ }
249
+ }
250
+ lines.push("");
251
+ return lines.join("\n");
252
+ }
253
+ // ============================================================================
254
+ // ESLint configuration
255
+ // ============================================================================
256
+ /**
257
+ * Emit the static ESLint configuration file.
258
+ */
259
+ export function emitEslintConfig() {
260
+ return `// ESLint configuration for auto-generated Typra TypeScript code
261
+ // This file is auto-generated by the Typra emitter
262
+ import js from "@eslint/js";
263
+ import tseslint from "typescript-eslint";
264
+
265
+ export default tseslint.config(
266
+ js.configs.recommended,
267
+ ...tseslint.configs.recommended,
268
+ {
269
+ files: ["src/**/*.ts"],
270
+ rules: {
271
+ // Allow unused vars prefixed with underscore
272
+ "@typescript-eslint/no-unused-vars": [
273
+ "error",
274
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
275
+ ],
276
+ // Allow explicit any in generated code
277
+ "@typescript-eslint/no-explicit-any": "off",
278
+ // Allow non-null assertions in generated code
279
+ "@typescript-eslint/no-non-null-assertion": "off",
280
+ // Allow require() for dynamic YAML imports in generated code
281
+ "@typescript-eslint/no-require-imports": "off",
282
+ // Allow empty blocks in generated shorthand parsing patterns
283
+ "no-empty": "off",
284
+ },
285
+ },
286
+ {
287
+ files: ["tests/**/*.ts"],
288
+ rules: {
289
+ // Allow explicit any in test code
290
+ "@typescript-eslint/no-explicit-any": "off",
291
+ // Allow unused vars in generated tests
292
+ "@typescript-eslint/no-unused-vars": "off",
293
+ // Allow require() for dynamic YAML imports
294
+ "@typescript-eslint/no-require-imports": "off",
295
+ },
296
+ },
297
+ {
298
+ ignores: ["dist/**", "node_modules/**", "*.config.*"],
299
+ }
300
+ );
301
+ `;
302
+ }
303
+ //# sourceMappingURL=scaffolding.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * TypeScript test file emitter — BaseTestContext → vitest source code.
3
+ *
4
+ * Replaces the Nunjucks templates:
5
+ * - `test.ts.njk` → emitTypeScriptTest()
6
+ * - `_macros.njk` → factoryParamTestValue() (test-specific macro)
7
+ *
8
+ * The emitter produces vitest `describe`/`it` blocks for:
9
+ * - Construction (default + partial)
10
+ * - JSON serialization (load + round-trip per example)
11
+ * - YAML serialization (load + round-trip per example)
12
+ * - Alternate representations (scalar coercions)
13
+ * - Factory methods
14
+ * - Load/save (dictionary round-trip)
15
+ */
16
+ import { BaseTestContext } from "../../ir/ast.js";
17
+ /**
18
+ * Emit a vitest test file for a TypeNode.
19
+ */
20
+ export declare function emitTypeScriptTest(ctx: BaseTestContext & {
21
+ importPath: string;
22
+ namespace: string;
23
+ }): string;
@@ -0,0 +1,204 @@
1
+ /**
2
+ * TypeScript test file emitter — BaseTestContext → vitest source code.
3
+ *
4
+ * Replaces the Nunjucks templates:
5
+ * - `test.ts.njk` → emitTypeScriptTest()
6
+ * - `_macros.njk` → factoryParamTestValue() (test-specific macro)
7
+ *
8
+ * The emitter produces vitest `describe`/`it` blocks for:
9
+ * - Construction (default + partial)
10
+ * - JSON serialization (load + round-trip per example)
11
+ * - YAML serialization (load + round-trip per example)
12
+ * - Alternate representations (scalar coercions)
13
+ * - Factory methods
14
+ * - Load/save (dictionary round-trip)
15
+ */
16
+ // ============================================================================
17
+ // Macro replacements
18
+ // ============================================================================
19
+ /**
20
+ * Map a factory parameter type string to a TypeScript test value literal.
21
+ * Replaces the `factoryParamTestValue` macro from `_macros.njk`.
22
+ */
23
+ function factoryParamTestValue(typeStr) {
24
+ switch (typeStr) {
25
+ case "string":
26
+ return '"test"';
27
+ case "boolean":
28
+ return "true";
29
+ case "integer":
30
+ case "int32":
31
+ case "int64":
32
+ return "42";
33
+ case "float":
34
+ case "float64":
35
+ case "float32":
36
+ return "3.14";
37
+ case "unknown":
38
+ default:
39
+ return '"test"';
40
+ }
41
+ }
42
+ /**
43
+ * Render name in PascalCase (used by coercion test validation keys).
44
+ */
45
+ function renderName(name) {
46
+ const pascal = name.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
47
+ return pascal.charAt(0).toUpperCase() + pascal.slice(1);
48
+ }
49
+ // ============================================================================
50
+ // Main entry point
51
+ // ============================================================================
52
+ /**
53
+ * Emit a vitest test file for a TypeNode.
54
+ */
55
+ export function emitTypeScriptTest(ctx) {
56
+ const { node, isAbstract, examples, coercions, factories, importPath } = ctx;
57
+ const typeName = node.typeName.name;
58
+ const lines = [];
59
+ lines.push("// Copyright (c) Microsoft. All rights reserved.");
60
+ lines.push("// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY.");
61
+ lines.push("");
62
+ lines.push(`import { ${typeName} } from "${importPath}";`);
63
+ lines.push("");
64
+ lines.push(`describe("${typeName}", () => {`);
65
+ lines.push(` describe("construction", () => {`);
66
+ lines.push(` it("should create a new instance with defaults", () => {`);
67
+ lines.push(` const instance = new ${typeName}();`);
68
+ lines.push(` expect(instance).toBeDefined();`);
69
+ lines.push(` });`);
70
+ lines.push("");
71
+ lines.push(` it("should create a new instance with partial initialization", () => {`);
72
+ lines.push(` const instance = new ${typeName}({});`);
73
+ lines.push(` expect(instance).toBeDefined();`);
74
+ lines.push(` });`);
75
+ lines.push(` });`);
76
+ // JSON serialization
77
+ if (examples.length > 0) {
78
+ lines.push("");
79
+ lines.push(` describe("JSON serialization", () => {`);
80
+ for (let i = 0; i < examples.length; i++) {
81
+ const example = examples[i];
82
+ const exampleNum = i + 1;
83
+ lines.push(` it("should load from JSON - example ${exampleNum}", () => {`);
84
+ lines.push(" const json = `" + example.json.join("\\n") + "`;");
85
+ lines.push(` const instance = ${typeName}.fromJson(json);`);
86
+ lines.push(` expect(instance).toBeDefined();`);
87
+ for (const val of example.validations) {
88
+ lines.push(` expect(instance.${val.key}).toEqual(${val.delimiter}${val.value}${val.delimiter});`);
89
+ }
90
+ lines.push(` });`);
91
+ lines.push("");
92
+ lines.push(` it("should round-trip JSON - example ${exampleNum}", () => {`);
93
+ lines.push(" const json = `" + example.json.join("\\n") + "`;");
94
+ lines.push(` const instance = ${typeName}.fromJson(json);`);
95
+ lines.push(` const output = instance.toJson();`);
96
+ lines.push(` const reloaded = ${typeName}.fromJson(output);`);
97
+ for (const val of example.validations) {
98
+ lines.push(` expect(reloaded.${val.key}).toEqual(instance.${val.key});`);
99
+ }
100
+ lines.push(` });`);
101
+ }
102
+ lines.push(` });`);
103
+ // YAML serialization
104
+ lines.push("");
105
+ lines.push(` describe("YAML serialization", () => {`);
106
+ for (let i = 0; i < examples.length; i++) {
107
+ const example = examples[i];
108
+ const exampleNum = i + 1;
109
+ lines.push(` it("should load from YAML - example ${exampleNum}", () => {`);
110
+ lines.push(" const yaml = `" + example.yaml.join("\\n") + "`;");
111
+ lines.push(` const instance = ${typeName}.fromYaml(yaml);`);
112
+ lines.push(` expect(instance).toBeDefined();`);
113
+ for (const val of example.validations) {
114
+ lines.push(` expect(instance.${val.key}).toEqual(${val.delimiter}${val.value}${val.delimiter});`);
115
+ }
116
+ lines.push(` });`);
117
+ lines.push("");
118
+ lines.push(` it("should round-trip YAML - example ${exampleNum}", () => {`);
119
+ lines.push(" const yaml = `" + example.yaml.join("\\n") + "`;");
120
+ lines.push(` const instance = ${typeName}.fromYaml(yaml);`);
121
+ lines.push(` const output = instance.toYaml();`);
122
+ lines.push(` const reloaded = ${typeName}.fromYaml(output);`);
123
+ for (const val of example.validations) {
124
+ lines.push(` expect(reloaded.${val.key}).toEqual(instance.${val.key});`);
125
+ }
126
+ lines.push(` });`);
127
+ }
128
+ lines.push(` });`);
129
+ }
130
+ // Alternate representations (coercions)
131
+ if (coercions.length > 0) {
132
+ lines.push("");
133
+ lines.push(` describe("alternate representations", () => {`);
134
+ for (const alt of coercions) {
135
+ lines.push(` it("should handle ${alt.title} alternate representation", () => {`);
136
+ lines.push(` const value = ${alt.value};`);
137
+ lines.push(` const json = JSON.stringify(value);`);
138
+ lines.push(` const instance = ${typeName}.fromJson(json);`);
139
+ lines.push(` expect(instance).toBeDefined();`);
140
+ for (const val of alt.validations) {
141
+ const coercionKey = renderName(val.key).toLowerCase().replace(/\./g, "");
142
+ lines.push(` expect(instance.${coercionKey}).toEqual(${val.delimiter}${val.value}${val.delimiter});`);
143
+ }
144
+ lines.push(` });`);
145
+ }
146
+ lines.push(` });`);
147
+ }
148
+ // Factory methods
149
+ if (factories.length > 0) {
150
+ lines.push("");
151
+ lines.push(` describe("factory methods", () => {`);
152
+ for (const factory of factories) {
153
+ const paramValues = Object.values(factory.params)
154
+ .map((pType) => factoryParamTestValue(pType))
155
+ .join(", ");
156
+ lines.push(` it("should create instance via ${factory.name}() factory", () => {`);
157
+ lines.push(` const instance = ${typeName}.${factory.name}(${paramValues});`);
158
+ lines.push(` expect(instance).toBeDefined();`);
159
+ lines.push(` expect(instance).toBeInstanceOf(${typeName});`);
160
+ for (const [propName, value] of Object.entries(factory.sets)) {
161
+ if (value === true) {
162
+ lines.push(` expect(instance.${propName}).toBe(true);`);
163
+ }
164
+ else if (value === false) {
165
+ lines.push(` expect(instance.${propName}).toBe(false);`);
166
+ }
167
+ else if (typeof value === "number") {
168
+ lines.push(` expect(instance.${propName}).toBe(${value});`);
169
+ }
170
+ else if (typeof value === "string") {
171
+ lines.push(` expect(instance.${propName}).toBe("${value}");`);
172
+ }
173
+ }
174
+ lines.push(` });`);
175
+ }
176
+ lines.push(` });`);
177
+ }
178
+ // Load and save
179
+ lines.push("");
180
+ if (!(isAbstract && node.isAbstract)) {
181
+ lines.push(` describe("load and save", () => {`);
182
+ if (!isAbstract) {
183
+ lines.push(` it("should load from dictionary", () => {`);
184
+ lines.push(` const data: Record<string, unknown> = {};`);
185
+ lines.push(` const instance = ${typeName}.load(data);`);
186
+ lines.push(` expect(instance).toBeDefined();`);
187
+ lines.push(` });`);
188
+ }
189
+ lines.push("");
190
+ if (!node.isAbstract) {
191
+ lines.push(` it("should save to dictionary", () => {`);
192
+ lines.push(` const instance = new ${typeName}();`);
193
+ lines.push(` const data = instance.save();`);
194
+ lines.push(` expect(data).toBeDefined();`);
195
+ lines.push(` expect(typeof data).toBe("object");`);
196
+ lines.push(` });`);
197
+ }
198
+ lines.push(` });`);
199
+ }
200
+ lines.push("});");
201
+ lines.push("");
202
+ return lines.join("\n");
203
+ }
204
+ //# sourceMappingURL=test-emitter.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * TypeScript expression visitor — Expr IR → TypeScript source fragments.
3
+ */
4
+ import { Expr, TypeRegistry } from "../../ir/expansion.js";
5
+ import { ExprVisitor } from "../../ir/visitor.js";
6
+ export declare class TypeScriptExprVisitor 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,64 @@
1
+ /**
2
+ * TypeScript expression visitor — Expr IR → TypeScript source fragments.
3
+ */
4
+ import { assertNever } from "../../ir/visitor.js";
5
+ export class TypeScriptExprVisitor {
6
+ registry;
7
+ constructor(registry) {
8
+ this.registry = registry;
9
+ }
10
+ visitExpr(expr) {
11
+ switch (expr.kind) {
12
+ case "string":
13
+ return `"${this.escapeString(expr.value)}"`;
14
+ case "number":
15
+ return String(expr.value);
16
+ case "boolean":
17
+ return expr.value ? "true" : "false";
18
+ case "null":
19
+ return "undefined";
20
+ case "param":
21
+ return expr.name;
22
+ case "construct":
23
+ return this.visitConstruct(expr);
24
+ case "variant":
25
+ return this.visitVariant(expr);
26
+ case "array":
27
+ return this.visitArray(expr);
28
+ case "dict":
29
+ return `{ ${expr.entries.map(e => `${e.key}: ${this.visitExpr(e.value)}`).join(", ")} }`;
30
+ case "field_read":
31
+ return `${expr.objectName}.${expr.fieldName}`;
32
+ default:
33
+ return assertNever(expr);
34
+ }
35
+ }
36
+ visitConstruct(expr) {
37
+ const typeName = expr.typeName.name;
38
+ if (expr.fields.length === 0) {
39
+ return `new ${typeName}({})`;
40
+ }
41
+ const fields = expr.fields.map(f => `${f.propertyName}: ${this.visitExpr(f.value)}`).join(", ");
42
+ return `new ${typeName}({ ${fields} })`;
43
+ }
44
+ visitVariant(expr) {
45
+ // In TypeScript, polymorphic children are full classes — construct the variant type directly
46
+ const variantName = expr.variantTypeName.name;
47
+ if (expr.fields.length === 0) {
48
+ return `new ${variantName}({})`;
49
+ }
50
+ const fields = expr.fields.map(f => `${f.propertyName}: ${this.visitExpr(f.value)}`).join(", ");
51
+ return `new ${variantName}({ ${fields} })`;
52
+ }
53
+ visitArray(expr) {
54
+ if (expr.items.length === 0) {
55
+ return "[]";
56
+ }
57
+ const items = expr.items.map(i => this.visitExpr(i)).join(", ");
58
+ return `[${items}]`;
59
+ }
60
+ escapeString(s) {
61
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
62
+ }
63
+ }
64
+ //# sourceMappingURL=visitor.js.map
@@ -0,0 +1,33 @@
1
+ export interface EmitTarget {
2
+ "type": string;
3
+ "output-dir"?: string;
4
+ "test-dir"?: string;
5
+ "alias"?: {
6
+ [key: string]: any;
7
+ };
8
+ "format"?: boolean;
9
+ "namespace"?: string;
10
+ "import-path"?: string;
11
+ }
12
+ export interface TypraEmitterOptions {
13
+ "root-object": string;
14
+ "emit-targets"?: EmitTarget[];
15
+ "root-namespace"?: string;
16
+ "root-alias"?: string;
17
+ "omit-models"?: string[];
18
+ "schema-output-dir"?: string;
19
+ "additional-roots"?: string[];
20
+ }
21
+ export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{
22
+ [code: string]: import("@typespec/compiler").DiagnosticMessages;
23
+ }, TypraEmitterOptions, "samples" | "coercions" | "abstracts" | "factories" | "methods" | "knownAs" | "defaultFor" | "protocols">;
24
+ export declare const reportDiagnostic: <C extends string | number, M extends keyof {
25
+ [code: string]: import("@typespec/compiler").DiagnosticMessages;
26
+ }[C]>(program: import("@typespec/compiler").Program, diag: import("@typespec/compiler").DiagnosticReport<{
27
+ [code: string]: import("@typespec/compiler").DiagnosticMessages;
28
+ }, C, M>) => void, createDiagnostic: <C extends string | number, M extends keyof {
29
+ [code: string]: import("@typespec/compiler").DiagnosticMessages;
30
+ }[C]>(diag: import("@typespec/compiler").DiagnosticReport<{
31
+ [code: string]: import("@typespec/compiler").DiagnosticMessages;
32
+ }, C, M>) => import("@typespec/compiler").Diagnostic;
33
+ export declare const StateKeys: Record<"samples" | "coercions" | "abstracts" | "factories" | "methods" | "knownAs" | "defaultFor" | "protocols", symbol>;