@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,274 @@
1
+ /**
2
+ * C# test emitter — TypeNode → xUnit test file.
3
+ *
4
+ * Replaces `test.cs.njk` Nunjucks template with a typed TypeScript function
5
+ * that produces a complete C# xUnit test class.
6
+ *
7
+ * Each TypeNode with samples/coercions/factories gets one test file
8
+ * containing LoadYaml, LoadJson, roundtrip, and validity tests.
9
+ */
10
+ import { toPascalCase } from "../../ir/visitor.js";
11
+ // ============================================================================
12
+ // Main entry point
13
+ // ============================================================================
14
+ /**
15
+ * Emit a complete C# xUnit test file for a type node.
16
+ */
17
+ export function emitCSharpTest(ctx) {
18
+ const typeName = ctx.node.typeName.name;
19
+ const L = [];
20
+ L.push('using Xunit;');
21
+ L.push('');
22
+ L.push('#pragma warning disable IDE0130');
23
+ L.push(`namespace ${ctx.namespace};`);
24
+ L.push('#pragma warning restore IDE0130');
25
+ L.push('');
26
+ L.push('');
27
+ L.push(`public class ${typeName}ConversionTests`);
28
+ L.push('{');
29
+ // --- Example tests (6 per example) ---
30
+ ctx.examples.forEach((sample, i) => {
31
+ const suffix = i === 0 ? '' : `${i}`;
32
+ // LoadYamlInput
33
+ L.push(' [Fact]');
34
+ L.push(` public void LoadYamlInput${suffix}()`);
35
+ L.push(' {');
36
+ L.push(...emitRawStringLiteral('yamlData', sample.yaml));
37
+ L.push('');
38
+ L.push(` var instance = ${typeName}.FromYaml(yamlData);`);
39
+ L.push('');
40
+ L.push(' Assert.NotNull(instance);');
41
+ const yamlAssertions = emitExampleAssertions(sample.validations, 'instance');
42
+ if (yamlAssertions)
43
+ L.push(yamlAssertions);
44
+ L.push(' }');
45
+ L.push('');
46
+ // LoadJsonInput
47
+ L.push(' [Fact]');
48
+ L.push(` public void LoadJsonInput${suffix}()`);
49
+ L.push(' {');
50
+ L.push(...emitRawStringLiteral('jsonData', sample.json));
51
+ L.push('');
52
+ L.push(` var instance = ${typeName}.FromJson(jsonData);`);
53
+ L.push(' Assert.NotNull(instance);');
54
+ const jsonAssertions = emitExampleAssertions(sample.validations, 'instance');
55
+ if (jsonAssertions)
56
+ L.push(jsonAssertions);
57
+ L.push(' }');
58
+ L.push('');
59
+ // RoundtripJson
60
+ L.push(' [Fact]');
61
+ L.push(` public void RoundtripJson${suffix}()`);
62
+ L.push(' {');
63
+ L.push(' // Test that FromJson -> ToJson -> FromJson produces equivalent data');
64
+ L.push(...emitRawStringLiteral('jsonData', sample.json));
65
+ L.push('');
66
+ L.push(` var original = ${typeName}.FromJson(jsonData);`);
67
+ L.push(' Assert.NotNull(original);');
68
+ L.push('');
69
+ L.push(' var json = original.ToJson();');
70
+ L.push(' Assert.False(string.IsNullOrEmpty(json));');
71
+ L.push('');
72
+ L.push(` var reloaded = ${typeName}.FromJson(json);`);
73
+ L.push(' Assert.NotNull(reloaded);');
74
+ const rtJsonAssertions = emitExampleAssertions(sample.validations, 'reloaded');
75
+ if (rtJsonAssertions)
76
+ L.push(rtJsonAssertions);
77
+ L.push(' }');
78
+ L.push('');
79
+ // RoundtripYaml
80
+ L.push(' [Fact]');
81
+ L.push(` public void RoundtripYaml${suffix}()`);
82
+ L.push(' {');
83
+ L.push(' // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data');
84
+ L.push(...emitRawStringLiteral('yamlData', sample.yaml));
85
+ L.push('');
86
+ L.push(` var original = ${typeName}.FromYaml(yamlData);`);
87
+ L.push(' Assert.NotNull(original);');
88
+ L.push('');
89
+ L.push(' var yaml = original.ToYaml();');
90
+ L.push(' Assert.False(string.IsNullOrEmpty(yaml));');
91
+ L.push('');
92
+ L.push(` var reloaded = ${typeName}.FromYaml(yaml);`);
93
+ L.push(' Assert.NotNull(reloaded);');
94
+ const rtYamlAssertions = emitExampleAssertions(sample.validations, 'reloaded');
95
+ if (rtYamlAssertions)
96
+ L.push(rtYamlAssertions);
97
+ L.push(' }');
98
+ L.push('');
99
+ // ToJsonProducesValidJson
100
+ L.push(' [Fact]');
101
+ L.push(` public void ToJsonProducesValidJson${suffix}()`);
102
+ L.push(' {');
103
+ L.push(...emitRawStringLiteral('jsonData', sample.json));
104
+ L.push('');
105
+ L.push(` var instance = ${typeName}.FromJson(jsonData);`);
106
+ L.push(' var json = instance.ToJson();');
107
+ L.push('');
108
+ L.push(' // Verify it\'s valid JSON by parsing it');
109
+ L.push(' var parsed = System.Text.Json.JsonDocument.Parse(json);');
110
+ L.push(' Assert.NotNull(parsed);');
111
+ L.push(' }');
112
+ L.push('');
113
+ // ToYamlProducesValidYaml
114
+ L.push(' [Fact]');
115
+ L.push(` public void ToYamlProducesValidYaml${suffix}()`);
116
+ L.push(' {');
117
+ L.push(...emitRawStringLiteral('yamlData', sample.yaml));
118
+ L.push('');
119
+ L.push(` var instance = ${typeName}.FromYaml(yamlData);`);
120
+ L.push(' var yaml = instance.ToYaml();');
121
+ L.push('');
122
+ L.push(' // Verify it\'s valid YAML by parsing it');
123
+ L.push(' var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build();');
124
+ L.push(' var parsed = deserializer.Deserialize<object>(yaml);');
125
+ L.push(' Assert.NotNull(parsed);');
126
+ L.push(' }');
127
+ });
128
+ // --- Coercion tests (2 per coercion) ---
129
+ if (ctx.coercions.length > 0) {
130
+ for (const alt of ctx.coercions) {
131
+ const titleScalar = alt.scalar.charAt(0).toUpperCase() + alt.scalar.slice(1);
132
+ // Build the C# data literal
133
+ let dataLine;
134
+ if (alt.scalar === 'string') {
135
+ dataLine = ` var data = "${alt.value.toString().replace(/\\/g, '\\\\').replace(/"/g, '\\"')}";`;
136
+ }
137
+ else {
138
+ let dataValue;
139
+ if (alt.value.toString() === "True")
140
+ dataValue = "true";
141
+ else if (alt.value.toString() === "False")
142
+ dataValue = "false";
143
+ else
144
+ dataValue = alt.value.toString();
145
+ dataLine = ` var data = "${dataValue}";`;
146
+ }
147
+ // LoadJsonFrom{Scalar}
148
+ L.push('');
149
+ L.push(' [Fact]');
150
+ L.push(` public void LoadJsonFrom${titleScalar}()`);
151
+ L.push(' {');
152
+ L.push(` // alternate representation as ${alt.scalar}`);
153
+ L.push(dataLine);
154
+ L.push(` var instance = ${typeName}.FromJson(data);`);
155
+ L.push(' Assert.NotNull(instance);');
156
+ const jsonCoercionAssertions = emitCoercionAssertions(alt.validations);
157
+ if (jsonCoercionAssertions)
158
+ L.push(jsonCoercionAssertions);
159
+ L.push(' }');
160
+ // LoadYamlFrom{Scalar}
161
+ L.push('');
162
+ L.push(' [Fact]');
163
+ L.push(` public void LoadYamlFrom${titleScalar}()`);
164
+ L.push(' {');
165
+ L.push(` // alternate representation as ${alt.scalar}`);
166
+ L.push(dataLine);
167
+ L.push(` var instance = ${typeName}.FromYaml(data);`);
168
+ L.push(' Assert.NotNull(instance);');
169
+ L.push('');
170
+ const yamlCoercionAssertions = emitCoercionAssertions(alt.validations);
171
+ if (yamlCoercionAssertions)
172
+ L.push(yamlCoercionAssertions);
173
+ L.push(' }');
174
+ }
175
+ }
176
+ // --- Factory tests (1 per factory) ---
177
+ if (ctx.factories.length > 0) {
178
+ for (const factory of ctx.factories) {
179
+ const methodName = ctx.renderCsharpFactoryMethodName(factory.name);
180
+ const paramValues = Object.values(factory.params)
181
+ .map(pType => ctx.renderCsharpFactoryTestValue(pType))
182
+ .join(', ');
183
+ L.push('');
184
+ L.push(' [Fact]');
185
+ L.push(` public void Factory${methodName}()`);
186
+ L.push(' {');
187
+ L.push(` var instance = ${typeName}.${methodName}(${paramValues});`);
188
+ L.push(' Assert.NotNull(instance);');
189
+ for (const [propName, value] of Object.entries(factory.sets)) {
190
+ if (value === true) {
191
+ L.push(` Assert.True(instance.${ctx.renderName(propName)});`);
192
+ }
193
+ else if (value === false) {
194
+ L.push(` Assert.False(instance.${ctx.renderName(propName)});`);
195
+ }
196
+ else if (typeof value === 'number') {
197
+ L.push(` Assert.Equal(${value}, instance.${ctx.renderName(propName)});`);
198
+ }
199
+ else if (typeof value === 'string') {
200
+ // Check if this property is a closed enum (skip discriminator fields)
201
+ const prop = ctx.node.properties.find(p => p.name === propName);
202
+ const isDiscriminator = ctx.node.discriminator === propName;
203
+ if (prop && prop.enumName && !prop.isOpenEnum && !isDiscriminator) {
204
+ const csEnumName = toPascalCase(prop.enumName);
205
+ const memberName = toPascalCase(value);
206
+ L.push(` Assert.Equal(${csEnumName}.${memberName}, instance.${ctx.renderName(propName)});`);
207
+ }
208
+ else {
209
+ L.push(` Assert.Equal("${value}", instance.${ctx.renderName(propName)});`);
210
+ }
211
+ }
212
+ }
213
+ L.push(' }');
214
+ }
215
+ }
216
+ L.push('}');
217
+ L.push('');
218
+ return L.join('\n');
219
+ }
220
+ // ============================================================================
221
+ // Assertion helpers
222
+ // ============================================================================
223
+ /** Render Assert.Equal / Assert.True / Assert.False lines for example validations. */
224
+ function emitExampleAssertions(validations, varName) {
225
+ return validations.map(v => {
226
+ if (v.value === "True" || v.value === "False") {
227
+ return ` Assert.${v.value === "False" ? "False" : "True"}(${varName}.${v.key});`;
228
+ }
229
+ if (v.startDelim === '@"') {
230
+ return ` Assert.Equal(${v.startDelim}${v.value}${v.endDelim}.Replace("\\r\\n", "\\n"), ${varName}.${v.key});`;
231
+ }
232
+ return ` Assert.Equal(${v.startDelim}${v.value}${v.endDelim}, ${varName}.${v.key});`;
233
+ }).join('\n');
234
+ }
235
+ /** Render assertion lines for coercion validations (with isFloat / bool / normal dispatch). */
236
+ function emitCoercionAssertions(validations) {
237
+ return validations.map(v => {
238
+ const valueStr = v.value.toString();
239
+ if (valueStr === "True" || valueStr === "False") {
240
+ return [
241
+ ` Assert.NotNull(instance.${v.key});`,
242
+ ` Assert.IsType<bool>(instance.${v.key});`,
243
+ ` Assert.${valueStr === "False" ? "False" : "True"}((bool)instance.${v.key});`,
244
+ ].join('\n');
245
+ }
246
+ // Enum values (e.g., McpApprovalModeKind.Never) — emit direct assertion
247
+ if (v.delimiter === '' && /^[A-Z]/.test(valueStr)) {
248
+ return ` Assert.Equal(${v.value}, instance.${v.key});`;
249
+ }
250
+ // isFloat: value string contains '.'
251
+ if (valueStr.includes('.')) {
252
+ return [
253
+ ` Assert.NotNull(instance.${v.key});`,
254
+ ` Assert.True(instance.${v.key} is float || instance.${v.key} is double || instance.${v.key} is int || instance.${v.key} is long);`,
255
+ ` Assert.Equal(${v.value}, Convert.ToDouble(instance.${v.key}), 5);`,
256
+ ].join('\n');
257
+ }
258
+ return ` Assert.Equal(${v.delimiter}${v.value}${v.delimiter}, instance.${v.key});`;
259
+ }).join('\n');
260
+ }
261
+ // ============================================================================
262
+ // Raw string literal helper
263
+ // ============================================================================
264
+ /** Emit a raw-string-literal block for C# (lines at column 0, delimiters on own lines). */
265
+ function emitRawStringLiteral(varName, dataLines) {
266
+ const lines = [];
267
+ lines.push(` string ${varName} = """`);
268
+ for (const line of dataLines) {
269
+ lines.push(line);
270
+ }
271
+ lines.push('""";');
272
+ return lines;
273
+ }
274
+ //# sourceMappingURL=test-emitter.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * C# expression visitor — Expr IR → C# source fragments.
3
+ */
4
+ import { Expr, TypeRegistry } from "../../ir/expansion.js";
5
+ import { ExprVisitor } from "../../ir/visitor.js";
6
+ export declare class CSharpExprVisitor 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,79 @@
1
+ /**
2
+ * C# expression visitor — Expr IR → C# source fragments.
3
+ */
4
+ import { toPascalCase, assertNever } from "../../ir/visitor.js";
5
+ export class CSharpExprVisitor {
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 "null";
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 `new Dictionary<string, object?> { ${expr.entries.map(e => `{ "${e.key}", ${this.visitExpr(e.value)} }`).join(", ")} }`;
30
+ case "field_read":
31
+ return `${expr.objectName}.${toPascalCase(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
+ // C# uses object initializer syntax: new Type { Prop = value }
42
+ const fields = expr.fields.map(f => {
43
+ let val = this.visitExpr(f.value);
44
+ // For enum fields, convert string literals to EnumName.MemberName
45
+ if (f.value.kind === "string" && this.registry) {
46
+ const typeNode = this.registry.get(typeName);
47
+ if (typeNode) {
48
+ const prop = typeNode.properties.find(p => p.name === f.propertyName);
49
+ if (prop?.enumName) {
50
+ val = `${prop.enumName}.${toPascalCase(f.value.value)}`;
51
+ }
52
+ }
53
+ }
54
+ return `${toPascalCase(f.propertyName)} = ${val}`;
55
+ }).join(", ");
56
+ return `new ${typeName} { ${fields} }`;
57
+ }
58
+ visitVariant(expr) {
59
+ // In C#, polymorphic children are full classes
60
+ const variantName = expr.variantTypeName.name;
61
+ if (expr.fields.length === 0) {
62
+ return `new ${variantName}()`;
63
+ }
64
+ const fields = expr.fields.map(f => `${toPascalCase(f.propertyName)} = ${this.visitExpr(f.value)}`).join(", ");
65
+ return `new ${variantName} { ${fields} }`;
66
+ }
67
+ visitArray(expr) {
68
+ const elementType = expr.elementTypeName.name;
69
+ if (expr.items.length === 0) {
70
+ return `new List<${elementType}>()`;
71
+ }
72
+ const items = expr.items.map(i => this.visitExpr(i)).join(", ");
73
+ return `new List<${elementType}> { ${items} }`;
74
+ }
75
+ escapeString(s) {
76
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
77
+ }
78
+ }
79
+ //# sourceMappingURL=visitor.js.map
@@ -0,0 +1,12 @@
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 Go types.
7
+ */
8
+ export declare const goTypeMapper: Record<string, string>;
9
+ /**
10
+ * Main entry point for Go code generation.
11
+ */
12
+ export declare const generateGo: (context: EmitContext<TypraEmitterOptions>, node: TypeNode, emitTarget: EmitTarget, options?: GeneratorOptions) => Promise<void>;
@@ -0,0 +1,128 @@
1
+ import { resolvePath } from "@typespec/compiler";
2
+ import { execFileSync } from "child_process";
3
+ import { resolve } from "path";
4
+ import { enumerateTypes, } from "../../ir/ast.js";
5
+ import { filterNodes } from "../../emitter.js";
6
+ import { buildBaseTestContext, goTestOptions } from "../../testing/test-context.js";
7
+ import { toSnakeCase } from "../../ir/utilities.js";
8
+ import { TypeRegistry } from "../../ir/expansion.js";
9
+ import { GoExprVisitor } from "./visitor.js";
10
+ import { lowerFile } from "../../ir/lower.js";
11
+ import { emitGoFileContent } from "./emitter.js";
12
+ import { emitGoContext } from "./scaffolding.js";
13
+ import { emitGoTest } from "./test-emitter.js";
14
+ import { emitGeneratedFile } from "../../cleanup/generated-file.js";
15
+ /**
16
+ * Type mapping from TypeSpec scalar types to Go types.
17
+ */
18
+ export const goTypeMapper = {
19
+ "string": "string",
20
+ "number": "float64",
21
+ "array": "[]",
22
+ "object": "map[string]interface{}",
23
+ "boolean": "bool",
24
+ "int64": "int64",
25
+ "int32": "int32",
26
+ "float64": "float64",
27
+ "float32": "float32",
28
+ "integer": "int",
29
+ "float": "float64",
30
+ "numeric": "float64",
31
+ "any": "interface{}",
32
+ "dictionary": "map[string]interface{}",
33
+ };
34
+ /**
35
+ * Main entry point for Go code generation.
36
+ */
37
+ export const generateGo = async (context, node, emitTarget, options) => {
38
+ const allTypes = Array.from(enumerateTypes(node));
39
+ const nodes = filterNodes(allTypes, options);
40
+ // Build the expression IR infrastructure
41
+ const registry = TypeRegistry.fromTypeGraph(allTypes);
42
+ const visitor = new GoExprVisitor(registry);
43
+ // Determine package name from root node namespace (e.g., "Typra" -> "typra")
44
+ const packageName = node.typeName.namespace.toLowerCase().replace(/\./g, '');
45
+ // Collect all polymorphic type names across all nodes
46
+ const polymorphicTypeNames = new Set();
47
+ for (const n of nodes) {
48
+ const polyTypes = n.retrievePolymorphicTypes();
49
+ if (polyTypes) {
50
+ polymorphicTypeNames.add(n.typeName.name);
51
+ }
52
+ }
53
+ // Emit context file (LoadContext/SaveContext utilities)
54
+ const contextContent = emitGoContext({ header: "Typra Context", packageName });
55
+ await emitGoFile(context, 'context.go', contextContent, emitTarget["output-dir"]);
56
+ // Emit each base type and its children as a single file (Go stays flat — no subfolders)
57
+ for (const n of nodes) {
58
+ // Skip child types - they're rendered with their parent
59
+ if (!n.base) {
60
+ const fileDecl = lowerFile(n, registry, polymorphicTypeNames);
61
+ // Go stays flat: pass group as a header comment only, no subfolder emission
62
+ const fileContent = emitGoFileContent(fileDecl.types, packageName, visitor, polymorphicTypeNames, fileDecl.enums, n.group || "");
63
+ const fileName = toSnakeCase(n.typeName.name) + '.go';
64
+ await emitGoFile(context, fileName, fileContent, emitTarget["output-dir"]);
65
+ }
66
+ // Emit test file for each type (skip protocols — they have no data to test)
67
+ if (emitTarget["test-dir"] && !n.isProtocol) {
68
+ const importPath = emitTarget["import-path"] || packageName;
69
+ const testContext = { ...buildTestContext(n, packageName), importPath };
70
+ const testContent = emitGoTest(testContext);
71
+ const testFileName = toSnakeCase(n.typeName.name) + '_test.go';
72
+ await emitGoFile(context, testFileName, testContent, emitTarget["test-dir"]);
73
+ }
74
+ }
75
+ // Format emitted files if format option is enabled (default: true)
76
+ if (emitTarget.format !== false) {
77
+ const outputDir = emitTarget["output-dir"]
78
+ ? resolve(process.cwd(), emitTarget["output-dir"])
79
+ : context.emitterOutputDir;
80
+ const testDir = emitTarget["test-dir"]
81
+ ? resolve(process.cwd(), emitTarget["test-dir"])
82
+ : undefined;
83
+ formatGoFiles(outputDir, testDir);
84
+ }
85
+ };
86
+ /**
87
+ * Format Go files using gofmt and goimports.
88
+ */
89
+ function formatGoFiles(outputDir, testDir) {
90
+ const dirs = [outputDir, ...(testDir ? [testDir] : [])];
91
+ for (const dir of dirs) {
92
+ // Run gofmt — use execFileSync to avoid shell injection
93
+ try {
94
+ execFileSync("gofmt", ["-w", dir], {
95
+ stdio: 'pipe',
96
+ encoding: 'utf-8'
97
+ });
98
+ }
99
+ catch (error) {
100
+ console.warn(`Warning: gofmt formatting failed for ${dir}. You may need to install Go.`);
101
+ }
102
+ // Run goimports if available
103
+ try {
104
+ execFileSync("goimports", ["-w", dir], {
105
+ stdio: 'pipe',
106
+ encoding: 'utf-8'
107
+ });
108
+ }
109
+ catch (error) {
110
+ // goimports is optional, don't warn if not available
111
+ }
112
+ }
113
+ }
114
+ /**
115
+ * Build context for rendering a test file.
116
+ */
117
+ function buildTestContext(node, packageName) {
118
+ return buildBaseTestContext(node, packageName, goTestOptions);
119
+ }
120
+ /**
121
+ * Write generated Go content to file.
122
+ */
123
+ async function emitGoFile(context, filename, content, outputDir) {
124
+ outputDir = outputDir || `${context.emitterOutputDir}/go`;
125
+ const filePath = resolvePath(outputDir, filename);
126
+ await emitGeneratedFile(context, filePath, content);
127
+ }
128
+ //# sourceMappingURL=driver.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Go code emitter — Declaration IR → Go source code.
3
+ *
4
+ * Replaces `file.go.njk` Nunjucks template with a typed TypeScript function
5
+ * that walks the TypeDecl tree and produces a complete Go source file.
6
+ *
7
+ * Each file contains one type hierarchy (parent + children).
8
+ * Go has no inheritance — child structs duplicate parent fields.
9
+ * Polymorphic types use interface{} and type switches.
10
+ *
11
+ * Structural blocks emitted per type (in order):
12
+ * 1. Description comment
13
+ * 2. Struct definition
14
+ * 3. Load function
15
+ * 4. Save method
16
+ * 5. ToJSON method
17
+ * 6. ToYAML method
18
+ * 7. FromJSON function
19
+ * 8. FromYAML function
20
+ */
21
+ import { TypeDecl, EnumDef } from "../../ir/declarations.js";
22
+ import { ExprVisitor } from "../../ir/visitor.js";
23
+ /**
24
+ * Emit a complete Go source file for a type hierarchy.
25
+ *
26
+ * @param types - All TypeDecls in this file (parent first, then children)
27
+ * @param packageName - Go package name (lowercase)
28
+ * @param visitor - Expression visitor for coercion rendering
29
+ * @param polymorphicTypeNames - Set of type names that are polymorphic bases
30
+ * @param enums - Enum definitions used in this file
31
+ * @param group - Semantic group from TSP source subfolder (used as header comment only; Go stays flat)
32
+ */
33
+ export declare function emitGoFileContent(types: TypeDecl[], packageName: string, visitor: ExprVisitor, polymorphicTypeNames: Set<string>, enums?: EnumDef[], group?: string): string;