@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,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go scaffolding emitter — generates the LoadContext / SaveContext file.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `context.go.njk` Nunjucks template with a typed TypeScript
|
|
5
|
+
* function that produces identical Go source output.
|
|
6
|
+
*/
|
|
7
|
+
interface GoContextContext {
|
|
8
|
+
header: string;
|
|
9
|
+
packageName: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Emit the Go context file containing LoadContext and SaveContext structs.
|
|
13
|
+
*
|
|
14
|
+
* @param ctx - header string and package name
|
|
15
|
+
* @returns Complete Go source file as a string
|
|
16
|
+
*/
|
|
17
|
+
export declare function emitGoContext(ctx: GoContextContext): string;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go scaffolding emitter — generates the LoadContext / SaveContext file.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `context.go.njk` Nunjucks template with a typed TypeScript
|
|
5
|
+
* function that produces identical Go source output.
|
|
6
|
+
*/
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Main entry point
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Emit the Go context file containing LoadContext and SaveContext structs.
|
|
12
|
+
*
|
|
13
|
+
* @param ctx - header string and package name
|
|
14
|
+
* @returns Complete Go source file as a string
|
|
15
|
+
*/
|
|
16
|
+
export function emitGoContext(ctx) {
|
|
17
|
+
const lines = [];
|
|
18
|
+
lines.push("// Code generated by Typra emitter; DO NOT EDIT.");
|
|
19
|
+
lines.push(`// ${ctx.header}`);
|
|
20
|
+
lines.push("");
|
|
21
|
+
lines.push(`package ${ctx.packageName}`);
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push("// LoadContext provides context for loading operations");
|
|
24
|
+
lines.push("type LoadContext struct {");
|
|
25
|
+
lines.push("\t// Add any context fields needed for loading");
|
|
26
|
+
lines.push("\t// e.g., file paths, base directories, etc.");
|
|
27
|
+
lines.push("}");
|
|
28
|
+
lines.push("");
|
|
29
|
+
lines.push("// NewLoadContext creates a new LoadContext");
|
|
30
|
+
lines.push("func NewLoadContext() *LoadContext {");
|
|
31
|
+
lines.push("\treturn &LoadContext{}");
|
|
32
|
+
lines.push("}");
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push("// SaveContext provides context for saving operations");
|
|
35
|
+
lines.push("type SaveContext struct {");
|
|
36
|
+
lines.push("\t// Add any context fields needed for saving");
|
|
37
|
+
lines.push("\t// e.g., output directories, formatting options, etc.");
|
|
38
|
+
lines.push("}");
|
|
39
|
+
lines.push("");
|
|
40
|
+
lines.push("// NewSaveContext creates a new SaveContext");
|
|
41
|
+
lines.push("func NewSaveContext() *SaveContext {");
|
|
42
|
+
lines.push("\treturn &SaveContext{}");
|
|
43
|
+
lines.push("}");
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push("// ptrOf returns a pointer to the given value. Used by factory functions");
|
|
46
|
+
lines.push("// to set optional (pointer) fields in struct literals.");
|
|
47
|
+
lines.push("func ptrOf[T any](v T) *T {");
|
|
48
|
+
lines.push("\treturn &v");
|
|
49
|
+
lines.push("}");
|
|
50
|
+
lines.push("");
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=scaffolding.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go test emitter — BaseTestContext → Go test source code.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `test.go.njk` and `_macros.njk` Nunjucks templates with
|
|
5
|
+
* typed TypeScript functions that produce identical Go test output.
|
|
6
|
+
*
|
|
7
|
+
* Each test file covers one type and contains:
|
|
8
|
+
* - Per-example: LoadJSON, LoadYAML, Roundtrip, ToJSON, ToYAML tests
|
|
9
|
+
* - Per-coercion: From<Title> tests for scalar-to-object expansion
|
|
10
|
+
*/
|
|
11
|
+
import { BaseTestContext } from "../../ir/ast.js";
|
|
12
|
+
/**
|
|
13
|
+
* Emit a complete Go test file for a single type.
|
|
14
|
+
*
|
|
15
|
+
* @param ctx - test context built by `buildBaseTestContext()`
|
|
16
|
+
* @returns Complete Go test source file as a string
|
|
17
|
+
*/
|
|
18
|
+
export declare function emitGoTest(ctx: BaseTestContext & {
|
|
19
|
+
importPath: string;
|
|
20
|
+
}): string;
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go test emitter — BaseTestContext → Go test source code.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `test.go.njk` and `_macros.njk` Nunjucks templates with
|
|
5
|
+
* typed TypeScript functions that produce identical Go test output.
|
|
6
|
+
*
|
|
7
|
+
* Each test file covers one type and contains:
|
|
8
|
+
* - Per-example: LoadJSON, LoadYAML, Roundtrip, ToJSON, ToYAML tests
|
|
9
|
+
* - Per-coercion: From<Title> tests for scalar-to-object expansion
|
|
10
|
+
*/
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Helpers
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/** Nunjucks `capitalize` filter: upper-case first char, lower-case rest. */
|
|
15
|
+
function capitalize(s) {
|
|
16
|
+
if (!s)
|
|
17
|
+
return s;
|
|
18
|
+
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Emit a validation assertion for a property.
|
|
22
|
+
*
|
|
23
|
+
* @param varName - "instance" or "reloaded"
|
|
24
|
+
* @param v - property validation descriptor
|
|
25
|
+
*/
|
|
26
|
+
function emitValidation(lines, varName, v) {
|
|
27
|
+
// Determine the display value for the error message — includes quotes if delimiter is "
|
|
28
|
+
const displayQuote = v.delimiter === '"' ? '"' : '';
|
|
29
|
+
const display = `${displayQuote}${v.value}${displayQuote}`;
|
|
30
|
+
if (v.isOptional) {
|
|
31
|
+
lines.push(`if ${varName}.${v.key} == nil || *${varName}.${v.key} != ${v.delimiter}${v.value}${v.delimiter} {`);
|
|
32
|
+
lines.push(`t.Errorf(\`Expected ${v.key} to be ${display}, got %v\`, ${varName}.${v.key})`);
|
|
33
|
+
lines.push(`}`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
lines.push(`if ${varName}.${v.key} != ${v.delimiter}${v.value}${v.delimiter} {`);
|
|
37
|
+
lines.push(`t.Errorf(\`Expected ${v.key} to be ${display}, got %v\`, ${varName}.${v.key})`);
|
|
38
|
+
lines.push(`}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Main entry point
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Emit a complete Go test file for a single type.
|
|
46
|
+
*
|
|
47
|
+
* @param ctx - test context built by `buildBaseTestContext()`
|
|
48
|
+
* @returns Complete Go test source file as a string
|
|
49
|
+
*/
|
|
50
|
+
export function emitGoTest(ctx) {
|
|
51
|
+
const lines = [];
|
|
52
|
+
const typeName = ctx.node.typeName.name;
|
|
53
|
+
const pkg = ctx.package ?? "";
|
|
54
|
+
const isAbstract = ctx.isAbstract;
|
|
55
|
+
// File header (template lines 14-16)
|
|
56
|
+
lines.push("// Code generated by Typra emitter; DO NOT EDIT.");
|
|
57
|
+
lines.push("");
|
|
58
|
+
lines.push(`package ${pkg}_test`);
|
|
59
|
+
// Import block (template lines 17-26)
|
|
60
|
+
if (ctx.examples.length > 0 || ctx.coercions.length > 0) {
|
|
61
|
+
lines.push(""); // {% if true %}\n — blank line between package and import
|
|
62
|
+
lines.push(`import (`);
|
|
63
|
+
lines.push(`"encoding/json"`);
|
|
64
|
+
lines.push(`"testing"`);
|
|
65
|
+
lines.push(``);
|
|
66
|
+
lines.push(`"gopkg.in/yaml.v3"`);
|
|
67
|
+
lines.push(``);
|
|
68
|
+
lines.push(`"${ctx.importPath}"`);
|
|
69
|
+
lines.push(`)`);
|
|
70
|
+
}
|
|
71
|
+
lines.push(""); // {% endif %}\n (line 26)
|
|
72
|
+
lines.push(""); // blank line 27
|
|
73
|
+
// Per-example test functions (template lines 28-234)
|
|
74
|
+
for (let i = 0; i < ctx.examples.length; i++) {
|
|
75
|
+
const sample = ctx.examples[i];
|
|
76
|
+
const isFirst = i === 0;
|
|
77
|
+
const suffix = isFirst ? "" : String(i);
|
|
78
|
+
lines.push(""); // {% for %}\n — loop body starts with \n
|
|
79
|
+
emitLoadJSONTest(lines, typeName, pkg, suffix, sample, isAbstract);
|
|
80
|
+
lines.push("");
|
|
81
|
+
emitLoadYAMLTest(lines, typeName, pkg, suffix, sample, isAbstract);
|
|
82
|
+
lines.push("");
|
|
83
|
+
emitRoundtripTest(lines, typeName, pkg, suffix, sample, isAbstract);
|
|
84
|
+
lines.push("");
|
|
85
|
+
emitToJSONTest(lines, typeName, pkg, suffix, sample, isAbstract);
|
|
86
|
+
lines.push("");
|
|
87
|
+
emitToYAMLTest(lines, typeName, pkg, suffix, sample, isAbstract);
|
|
88
|
+
lines.push(""); // blank line 233
|
|
89
|
+
}
|
|
90
|
+
lines.push(""); // {% endfor %}\n (line 234)
|
|
91
|
+
// Coercion test functions (template lines 235-271)
|
|
92
|
+
if (ctx.coercions.length > 0) {
|
|
93
|
+
lines.push(""); // {% if true %}\n — if body starts with \n
|
|
94
|
+
for (let i = 0; i < ctx.coercions.length; i++) {
|
|
95
|
+
const alt = ctx.coercions[i];
|
|
96
|
+
const isFirst = i === 0;
|
|
97
|
+
const suffix = isFirst ? "" : String(i + 1);
|
|
98
|
+
lines.push(""); // {% for %}\n — loop body starts with \n
|
|
99
|
+
emitCoercionTest(lines, typeName, pkg, suffix, alt, isAbstract);
|
|
100
|
+
lines.push(""); // blank line 269
|
|
101
|
+
}
|
|
102
|
+
lines.push(""); // {% endfor %}\n (line 270)
|
|
103
|
+
lines.push(""); // {% endif %}\n (line 271)
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
lines.push(""); // {% if false %}...{% endif %}\n — 1 \n
|
|
107
|
+
}
|
|
108
|
+
return lines.join("\n") + "\n";
|
|
109
|
+
}
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// Per-example test emitters
|
|
112
|
+
// ============================================================================
|
|
113
|
+
function emitJsonDataBlock(lines, sample) {
|
|
114
|
+
lines.push("jsonData := `");
|
|
115
|
+
for (const line of sample.json) {
|
|
116
|
+
lines.push(line);
|
|
117
|
+
}
|
|
118
|
+
lines.push("`");
|
|
119
|
+
}
|
|
120
|
+
function emitYamlDataBlock(lines, sample) {
|
|
121
|
+
lines.push("yamlData := `");
|
|
122
|
+
for (const line of sample.yaml) {
|
|
123
|
+
lines.push(line);
|
|
124
|
+
}
|
|
125
|
+
lines.push("`");
|
|
126
|
+
}
|
|
127
|
+
function emitJsonUnmarshal(lines, varName = "data") {
|
|
128
|
+
lines.push(`var ${varName} map[string]interface{}`);
|
|
129
|
+
lines.push(`if err := json.Unmarshal([]byte(jsonData), &${varName}); err != nil {`);
|
|
130
|
+
lines.push(`t.Fatalf("Failed to parse JSON: %v", err)`);
|
|
131
|
+
lines.push(`}`);
|
|
132
|
+
}
|
|
133
|
+
function emitYamlUnmarshal(lines, varName = "data") {
|
|
134
|
+
lines.push(`var ${varName} map[string]interface{}`);
|
|
135
|
+
lines.push(`if err := yaml.Unmarshal([]byte(yamlData), &${varName}); err != nil {`);
|
|
136
|
+
lines.push(`t.Fatalf("Failed to parse YAML: %v", err)`);
|
|
137
|
+
lines.push(`}`);
|
|
138
|
+
}
|
|
139
|
+
function emitLoadCall(lines, typeName, pkg, ctxVar, dataVar, instanceVar) {
|
|
140
|
+
lines.push(`${ctxVar} := ${pkg}.NewLoadContext()`);
|
|
141
|
+
lines.push(`${instanceVar}, err := ${pkg}.Load${typeName}(${dataVar}, ${ctxVar})`);
|
|
142
|
+
lines.push(`if err != nil {`);
|
|
143
|
+
lines.push(`t.Fatalf("Failed to load ${typeName}: %v", err)`);
|
|
144
|
+
lines.push(`}`);
|
|
145
|
+
}
|
|
146
|
+
function emitAbstractExampleValidations(lines, hasValidations) {
|
|
147
|
+
lines.push("// Polymorphic types return interface{}, extract common fields via reflection or type-specific access");
|
|
148
|
+
lines.push("_ = instance // Load succeeded, exact type depends on discriminator");
|
|
149
|
+
if (hasValidations) {
|
|
150
|
+
lines.push("// Note: Validation skipped for polymorphic base types - test child types directly");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function emitConcreteExampleValidations(lines, varName, validations) {
|
|
154
|
+
if (validations.length > 0) {
|
|
155
|
+
for (const v of validations) {
|
|
156
|
+
emitValidation(lines, varName, v);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
lines.push(`_ = ${varName} // No scalar properties to validate`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// ---- LoadJSON ----
|
|
164
|
+
function emitLoadJSONTest(lines, typeName, pkg, suffix, sample, isAbstract) {
|
|
165
|
+
lines.push(`// Test${typeName}LoadJSON${suffix} tests loading ${typeName} from JSON`);
|
|
166
|
+
lines.push(`func Test${typeName}LoadJSON${suffix}(t *testing.T) {`);
|
|
167
|
+
emitJsonDataBlock(lines, sample);
|
|
168
|
+
emitJsonUnmarshal(lines);
|
|
169
|
+
lines.push("");
|
|
170
|
+
emitLoadCall(lines, typeName, pkg, "ctx", "data", "instance");
|
|
171
|
+
if (isAbstract) {
|
|
172
|
+
emitAbstractExampleValidations(lines, sample.validations.length > 0);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
emitConcreteExampleValidations(lines, "instance", sample.validations);
|
|
176
|
+
}
|
|
177
|
+
lines.push("}");
|
|
178
|
+
}
|
|
179
|
+
// ---- LoadYAML ----
|
|
180
|
+
function emitLoadYAMLTest(lines, typeName, pkg, suffix, sample, isAbstract) {
|
|
181
|
+
lines.push(`// Test${typeName}LoadYAML${suffix} tests loading ${typeName} from YAML`);
|
|
182
|
+
lines.push(`func Test${typeName}LoadYAML${suffix}(t *testing.T) {`);
|
|
183
|
+
emitYamlDataBlock(lines, sample);
|
|
184
|
+
emitYamlUnmarshal(lines);
|
|
185
|
+
lines.push("");
|
|
186
|
+
emitLoadCall(lines, typeName, pkg, "ctx", "data", "instance");
|
|
187
|
+
if (isAbstract) {
|
|
188
|
+
emitAbstractExampleValidations(lines, sample.validations.length > 0);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
emitConcreteExampleValidations(lines, "instance", sample.validations);
|
|
192
|
+
}
|
|
193
|
+
lines.push("}");
|
|
194
|
+
}
|
|
195
|
+
// ---- Roundtrip ----
|
|
196
|
+
function emitRoundtripTest(lines, typeName, pkg, suffix, sample, isAbstract) {
|
|
197
|
+
lines.push(`// Test${typeName}Roundtrip${suffix} tests load -> save -> load produces equivalent data`);
|
|
198
|
+
lines.push(`func Test${typeName}Roundtrip${suffix}(t *testing.T) {`);
|
|
199
|
+
emitJsonDataBlock(lines, sample);
|
|
200
|
+
emitJsonUnmarshal(lines);
|
|
201
|
+
lines.push("");
|
|
202
|
+
lines.push(`loadCtx := ${pkg}.NewLoadContext()`);
|
|
203
|
+
lines.push(`instance, err := ${pkg}.Load${typeName}(data, loadCtx)`);
|
|
204
|
+
lines.push(`if err != nil {`);
|
|
205
|
+
lines.push(`t.Fatalf("Failed to load ${typeName}: %v", err)`);
|
|
206
|
+
lines.push(`}`);
|
|
207
|
+
if (isAbstract) {
|
|
208
|
+
lines.push("// Polymorphic roundtrip testing requires type-specific handling");
|
|
209
|
+
lines.push("_ = instance // Load succeeded, exact type depends on discriminator");
|
|
210
|
+
lines.push("// Note: Roundtrip test skipped for polymorphic base types - test child types directly");
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
lines.push(`saveCtx := ${pkg}.NewSaveContext()`);
|
|
214
|
+
lines.push(`savedData := instance.Save(saveCtx)`);
|
|
215
|
+
lines.push("");
|
|
216
|
+
lines.push(`reloaded, err := ${pkg}.Load${typeName}(savedData, loadCtx)`);
|
|
217
|
+
lines.push(`if err != nil {`);
|
|
218
|
+
lines.push(`t.Fatalf("Failed to reload ${typeName}: %v", err)`);
|
|
219
|
+
lines.push(`}`);
|
|
220
|
+
emitConcreteExampleValidations(lines, "reloaded", sample.validations);
|
|
221
|
+
}
|
|
222
|
+
lines.push("}");
|
|
223
|
+
}
|
|
224
|
+
// ---- ToJSON ----
|
|
225
|
+
function emitToJSONTest(lines, typeName, pkg, suffix, sample, isAbstract) {
|
|
226
|
+
lines.push(`// Test${typeName}ToJSON${suffix} tests that ToJSON produces valid JSON`);
|
|
227
|
+
lines.push(`func Test${typeName}ToJSON${suffix}(t *testing.T) {`);
|
|
228
|
+
emitJsonDataBlock(lines, sample);
|
|
229
|
+
emitJsonUnmarshal(lines);
|
|
230
|
+
lines.push("");
|
|
231
|
+
emitLoadCall(lines, typeName, pkg, "ctx", "data", "instance");
|
|
232
|
+
if (isAbstract) {
|
|
233
|
+
lines.push("// Polymorphic ToJSON requires type-specific handling");
|
|
234
|
+
lines.push("_ = instance // Load succeeded, exact type depends on discriminator");
|
|
235
|
+
lines.push("// Note: ToJSON test skipped for polymorphic base types - test child types directly");
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
lines.push("jsonOutput, err := instance.ToJSON()");
|
|
239
|
+
lines.push("if err != nil {");
|
|
240
|
+
lines.push(`t.Fatalf("Failed to convert to JSON: %v", err)`);
|
|
241
|
+
lines.push("}");
|
|
242
|
+
lines.push("");
|
|
243
|
+
lines.push("var parsed map[string]interface{}");
|
|
244
|
+
lines.push("if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil {");
|
|
245
|
+
lines.push(`t.Fatalf("Failed to parse generated JSON: %v", err)`);
|
|
246
|
+
lines.push("}");
|
|
247
|
+
}
|
|
248
|
+
lines.push("}");
|
|
249
|
+
}
|
|
250
|
+
// ---- ToYAML ----
|
|
251
|
+
function emitToYAMLTest(lines, typeName, pkg, suffix, sample, isAbstract) {
|
|
252
|
+
lines.push(`// Test${typeName}ToYAML${suffix} tests that ToYAML produces valid YAML`);
|
|
253
|
+
lines.push(`func Test${typeName}ToYAML${suffix}(t *testing.T) {`);
|
|
254
|
+
emitJsonDataBlock(lines, sample);
|
|
255
|
+
emitJsonUnmarshal(lines);
|
|
256
|
+
lines.push("");
|
|
257
|
+
emitLoadCall(lines, typeName, pkg, "ctx", "data", "instance");
|
|
258
|
+
if (isAbstract) {
|
|
259
|
+
lines.push("// Polymorphic ToYAML requires type-specific handling");
|
|
260
|
+
lines.push("_ = instance // Load succeeded, exact type depends on discriminator");
|
|
261
|
+
lines.push("// Note: ToYAML test skipped for polymorphic base types - test child types directly");
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
lines.push("yamlOutput, err := instance.ToYAML()");
|
|
265
|
+
lines.push("if err != nil {");
|
|
266
|
+
lines.push(`t.Fatalf("Failed to convert to YAML: %v", err)`);
|
|
267
|
+
lines.push("}");
|
|
268
|
+
lines.push("");
|
|
269
|
+
lines.push("var parsed map[string]interface{}");
|
|
270
|
+
lines.push("if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil {");
|
|
271
|
+
lines.push(`t.Fatalf("Failed to parse generated YAML: %v", err)`);
|
|
272
|
+
lines.push("}");
|
|
273
|
+
}
|
|
274
|
+
lines.push("}");
|
|
275
|
+
}
|
|
276
|
+
// ============================================================================
|
|
277
|
+
// Coercion test emitter
|
|
278
|
+
// ============================================================================
|
|
279
|
+
function emitCoercionTest(lines, typeName, pkg, suffix, alt, isAbstract) {
|
|
280
|
+
const title = capitalize(alt.title);
|
|
281
|
+
lines.push(`// Test${typeName}From${title}${suffix} tests loading ${typeName} from ${alt.scalarType}`);
|
|
282
|
+
lines.push(`func Test${typeName}From${title}${suffix}(t *testing.T) {`);
|
|
283
|
+
lines.push(`ctx := ${pkg}.NewLoadContext()`);
|
|
284
|
+
lines.push(`instance, err := ${pkg}.Load${typeName}(${alt.value}, ctx)`);
|
|
285
|
+
lines.push(`if err != nil {`);
|
|
286
|
+
lines.push(`t.Fatalf("Failed to load ${typeName} from ${alt.scalarType}: %v", err)`);
|
|
287
|
+
lines.push(`}`);
|
|
288
|
+
if (isAbstract) {
|
|
289
|
+
lines.push("// Polymorphic alternate loading requires type-specific handling");
|
|
290
|
+
lines.push("_ = instance // Load succeeded, exact type depends on discriminator");
|
|
291
|
+
if (alt.validations.length > 0) {
|
|
292
|
+
lines.push("// Note: Validation skipped for polymorphic base types - test child types directly");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
emitConcreteExampleValidations(lines, "instance", alt.validations);
|
|
297
|
+
}
|
|
298
|
+
lines.push("}");
|
|
299
|
+
}
|
|
300
|
+
//# sourceMappingURL=test-emitter.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go expression visitor — Expr IR → Go source fragments.
|
|
3
|
+
*/
|
|
4
|
+
import { Expr, TypeRegistry } from "../../ir/expansion.js";
|
|
5
|
+
import { ExprVisitor } from "../../ir/visitor.js";
|
|
6
|
+
export declare class GoExprVisitor 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,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go expression visitor — Expr IR → Go source fragments.
|
|
3
|
+
*/
|
|
4
|
+
import { toPascalCase, assertNever } from "../../ir/visitor.js";
|
|
5
|
+
export class GoExprVisitor {
|
|
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 "nil";
|
|
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 `map[string]interface{}{${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 `${typeName}{}`;
|
|
40
|
+
}
|
|
41
|
+
// Look up the target type to check which fields are optional (pointer types in Go)
|
|
42
|
+
const typeNode = this.registry?.get(typeName);
|
|
43
|
+
const fields = expr.fields.map(f => {
|
|
44
|
+
const val = this.visitExpr(f.value);
|
|
45
|
+
const prop = typeNode?.properties.find(p => p.name === f.propertyName);
|
|
46
|
+
// Optional fields are pointers in Go — wrap scalar values with a helper
|
|
47
|
+
const needsAddr = prop?.isOptional && !prop.isCollection && !prop.isDict;
|
|
48
|
+
return `${toPascalCase(f.propertyName)}: ${needsAddr ? `ptrOf(${val})` : val}`;
|
|
49
|
+
}).join(", ");
|
|
50
|
+
return `${typeName}{ ${fields} }`;
|
|
51
|
+
}
|
|
52
|
+
visitVariant(expr) {
|
|
53
|
+
// Go child types are full structs with an explicit discriminator field.
|
|
54
|
+
const variantName = expr.variantTypeName.name;
|
|
55
|
+
// Include the discriminator (e.g., Kind: "text") since Go has no default field values.
|
|
56
|
+
const discField = `${toPascalCase(expr.discriminator)}: "${expr.discriminatorValue}"`;
|
|
57
|
+
const dataFields = expr.fields.map(f => `${toPascalCase(f.propertyName)}: ${this.visitExpr(f.value)}`);
|
|
58
|
+
const allFields = [discField, ...dataFields].join(", ");
|
|
59
|
+
return `${variantName}{ ${allFields} }`;
|
|
60
|
+
}
|
|
61
|
+
visitArray(expr) {
|
|
62
|
+
const elementType = expr.elementTypeName.name;
|
|
63
|
+
// Polymorphic types are stored as []interface{} in Go (no inheritance).
|
|
64
|
+
// A type is polymorphic if it has child types in the registry.
|
|
65
|
+
const typeNode = this.registry?.get(elementType);
|
|
66
|
+
const isPolymorphic = typeNode !== undefined && typeNode.childTypes.length > 0;
|
|
67
|
+
const goElementType = isPolymorphic ? "interface{}" : elementType;
|
|
68
|
+
if (expr.items.length === 0) {
|
|
69
|
+
return `[]${goElementType}{}`;
|
|
70
|
+
}
|
|
71
|
+
const items = expr.items.map(i => this.visitExpr(i)).join(", ");
|
|
72
|
+
return `[]${goElementType}{${items}}`;
|
|
73
|
+
}
|
|
74
|
+
escapeString(s) {
|
|
75
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=visitor.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EmitContext } from "@typespec/compiler";
|
|
2
|
+
import { EmitTarget, TypraEmitterOptions } from "../../lib.js";
|
|
3
|
+
import { PropertyNode, TypeNode } from "../../ir/ast.js";
|
|
4
|
+
import { GeneratorOptions } from "../../emitter.js";
|
|
5
|
+
export declare const generateMarkdown: (context: EmitContext<TypraEmitterOptions>, node: TypeNode, emitTarget: EmitTarget, options?: GeneratorOptions) => Promise<void>;
|
|
6
|
+
export declare const renderType: (prop: PropertyNode) => string;
|
|
7
|
+
export declare const renderChildTypes: (node: PropertyNode) => string;
|
|
8
|
+
export declare const getChildTypes: (node: TypeNode) => {
|
|
9
|
+
source: string;
|
|
10
|
+
target: string;
|
|
11
|
+
}[];
|
|
12
|
+
export declare const getCompositionTypes: (node: TypeNode) => TypeNode[];
|
|
13
|
+
export declare const generateCoercions: (node: TypeNode) => {
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
scalar: string;
|
|
17
|
+
simple: string;
|
|
18
|
+
expanded: string;
|
|
19
|
+
}[];
|