@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
package/dist/src/lib.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createTypeSpecLibrary } from "@typespec/compiler";
|
|
2
|
+
const TypraEmitterOptionsSchema = {
|
|
3
|
+
type: "object",
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
properties: {
|
|
6
|
+
"emit-targets": {
|
|
7
|
+
type: "array",
|
|
8
|
+
items: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
"type": {
|
|
12
|
+
type: "string"
|
|
13
|
+
},
|
|
14
|
+
"output-dir": {
|
|
15
|
+
type: "string",
|
|
16
|
+
nullable: true
|
|
17
|
+
},
|
|
18
|
+
"test-dir": {
|
|
19
|
+
type: "string",
|
|
20
|
+
nullable: true
|
|
21
|
+
},
|
|
22
|
+
"alias": {
|
|
23
|
+
type: "object",
|
|
24
|
+
additionalProperties: true,
|
|
25
|
+
nullable: true
|
|
26
|
+
},
|
|
27
|
+
"format": {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
nullable: true,
|
|
30
|
+
default: true,
|
|
31
|
+
description: "Run formatters on emitted files"
|
|
32
|
+
},
|
|
33
|
+
"namespace": {
|
|
34
|
+
type: "string",
|
|
35
|
+
nullable: true,
|
|
36
|
+
description: "Override the namespace for the emitted code"
|
|
37
|
+
},
|
|
38
|
+
"import-path": {
|
|
39
|
+
type: "string",
|
|
40
|
+
nullable: true,
|
|
41
|
+
description: "Import path for generated code in tests. Defaults vary by language."
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
required: ["type"]
|
|
45
|
+
},
|
|
46
|
+
nullable: true,
|
|
47
|
+
description: "List of target languages to emit code for"
|
|
48
|
+
},
|
|
49
|
+
"root-namespace": {
|
|
50
|
+
type: "string",
|
|
51
|
+
nullable: true,
|
|
52
|
+
description: "Root namespace for the emitted code"
|
|
53
|
+
},
|
|
54
|
+
"root-object": {
|
|
55
|
+
type: "string",
|
|
56
|
+
nullable: false,
|
|
57
|
+
description: "Root object for the emitted artifacts"
|
|
58
|
+
},
|
|
59
|
+
"root-alias": {
|
|
60
|
+
type: "string",
|
|
61
|
+
nullable: true,
|
|
62
|
+
description: "Alias for the root object"
|
|
63
|
+
},
|
|
64
|
+
"omit-models": {
|
|
65
|
+
type: "array",
|
|
66
|
+
items: { type: "string" },
|
|
67
|
+
nullable: true,
|
|
68
|
+
description: "List of model names to omit from generation"
|
|
69
|
+
},
|
|
70
|
+
"schema-output-dir": {
|
|
71
|
+
type: "string",
|
|
72
|
+
nullable: true,
|
|
73
|
+
description: "Directory containing JSON schema files. Reserved for future manifest-based cleanup of omitted models."
|
|
74
|
+
},
|
|
75
|
+
"additional-roots": {
|
|
76
|
+
type: "array",
|
|
77
|
+
items: { type: "string" },
|
|
78
|
+
nullable: true,
|
|
79
|
+
description: "Additional root types to resolve and generate alongside the main root object. These types need not be referenced from the main root. Specified as fully-qualified names (e.g., 'Typra.Message')."
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
required: ["root-object"],
|
|
83
|
+
};
|
|
84
|
+
export const $lib = createTypeSpecLibrary({
|
|
85
|
+
name: "typra-emitter",
|
|
86
|
+
diagnostics: {},
|
|
87
|
+
emitter: { options: TypraEmitterOptionsSchema },
|
|
88
|
+
state: {
|
|
89
|
+
samples: { description: "Sample values for properties" },
|
|
90
|
+
coercions: { description: "Scalar-to-object implicit conversions" },
|
|
91
|
+
abstracts: { description: "Abstract models" },
|
|
92
|
+
factories: { description: "Factory methods for model construction" },
|
|
93
|
+
methods: { description: "Method stubs for model types" },
|
|
94
|
+
knownAs: { description: "Wire field name mappings per target system" },
|
|
95
|
+
defaultFor: { description: "Per-target required default values" },
|
|
96
|
+
protocols: { description: "Pipeline interface markers" }
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
export const { reportDiagnostic, createDiagnostic } = $lib;
|
|
100
|
+
export const StateKeys = $lib.stateKeys;
|
|
101
|
+
//# sourceMappingURL=lib.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { resolvePath } from "@typespec/compiler";
|
|
2
|
+
import { createTestLibrary } from "@typespec/compiler/testing";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
export const TypraEmitTestLibrary = createTestLibrary({
|
|
5
|
+
name: "typra-emitter",
|
|
6
|
+
packageRoot: resolvePath(fileURLToPath(import.meta.url), "../../../../"),
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Test Context Builder
|
|
3
|
+
* ===========================
|
|
4
|
+
* Provides standardized helper functions for building test contexts across all language emitters.
|
|
5
|
+
* This ensures consistency in how tests are generated from @sample decorators.
|
|
6
|
+
*/
|
|
7
|
+
import { TypeNode, BaseTestContext } from "../ir/ast.js";
|
|
8
|
+
/**
|
|
9
|
+
* Options for building test context - language-specific transformations.
|
|
10
|
+
*/
|
|
11
|
+
export interface TestContextOptions {
|
|
12
|
+
/** Transform property name to target language casing (e.g., PascalCase, snake_case) */
|
|
13
|
+
renderKey: (key: string) => string;
|
|
14
|
+
/** Render boolean value as language-specific literal (e.g., "True"/"False" for Python) */
|
|
15
|
+
renderBoolean: (val: boolean) => string;
|
|
16
|
+
/** Escape string for use in language-specific string literal */
|
|
17
|
+
escapeString: (str: string) => string;
|
|
18
|
+
/** Get string delimiter based on content (e.g., '"' or '"""' for multiline) */
|
|
19
|
+
getDelimiter: (str: string) => string;
|
|
20
|
+
/** Escape JSON for embedding in test template (optional - for languages that need it) */
|
|
21
|
+
escapeJsonForTemplate?: (json: string) => string;
|
|
22
|
+
/** Escape YAML for embedding in test template (optional - for languages that need it) */
|
|
23
|
+
escapeYamlForTemplate?: (yaml: string) => string;
|
|
24
|
+
/** Default scalar values for each type (used when @sample doesn't provide example) */
|
|
25
|
+
scalarValues: Record<string, string>;
|
|
26
|
+
/** Type mapper for scalar types */
|
|
27
|
+
typeMapper: Record<string, string>;
|
|
28
|
+
/**
|
|
29
|
+
* Render an enum assertion value for a closed enum field.
|
|
30
|
+
* Called with (enumName, rawStringValue, fieldName).
|
|
31
|
+
* If provided and returns non-null, overrides default string/bool/number rendering.
|
|
32
|
+
* The returned value+delimiter replace the default.
|
|
33
|
+
*/
|
|
34
|
+
renderEnumValue?: (enumName: string, rawValue: string, fieldName: string) => {
|
|
35
|
+
value: string;
|
|
36
|
+
delimiter: string;
|
|
37
|
+
} | null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build a standardized test context from a TypeNode.
|
|
41
|
+
* All language emitters should use this to ensure consistent test generation.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildBaseTestContext(node: TypeNode, packageName: string | undefined, options: TestContextOptions): BaseTestContext;
|
|
44
|
+
/**
|
|
45
|
+
* C# test context options.
|
|
46
|
+
*/
|
|
47
|
+
export declare const csharpTestOptions: TestContextOptions;
|
|
48
|
+
/**
|
|
49
|
+
* Python test context options.
|
|
50
|
+
*/
|
|
51
|
+
export declare const pythonTestOptions: TestContextOptions;
|
|
52
|
+
/**
|
|
53
|
+
* TypeScript test context options.
|
|
54
|
+
*/
|
|
55
|
+
export declare const typescriptTestOptions: TestContextOptions;
|
|
56
|
+
/**
|
|
57
|
+
* Rust test context options.
|
|
58
|
+
*/
|
|
59
|
+
export declare const rustTestOptions: TestContextOptions;
|
|
60
|
+
/**
|
|
61
|
+
* Go test context options.
|
|
62
|
+
*/
|
|
63
|
+
export declare const goTestOptions: TestContextOptions;
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Test Context Builder
|
|
3
|
+
* ===========================
|
|
4
|
+
* Provides standardized helper functions for building test contexts across all language emitters.
|
|
5
|
+
* This ensures consistency in how tests are generated from @sample decorators.
|
|
6
|
+
*/
|
|
7
|
+
import { getCombinations, toSnakeCase } from "../ir/utilities.js";
|
|
8
|
+
import { toPascalCase } from "../ir/visitor.js";
|
|
9
|
+
import * as YAML from "yaml";
|
|
10
|
+
const RUST_KEYWORDS = new Set([
|
|
11
|
+
"as", "break", "const", "continue", "crate", "else", "enum", "extern",
|
|
12
|
+
"false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod",
|
|
13
|
+
"move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct",
|
|
14
|
+
"super", "trait", "true", "type", "unsafe", "use", "where", "while",
|
|
15
|
+
"async", "await", "dyn",
|
|
16
|
+
]);
|
|
17
|
+
function rustFieldName(name) {
|
|
18
|
+
const snake = toSnakeCase(name);
|
|
19
|
+
return RUST_KEYWORDS.has(snake) ? `r#${snake}` : snake;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build a standardized test context from a TypeNode.
|
|
23
|
+
* All language emitters should use this to ensure consistent test generation.
|
|
24
|
+
*/
|
|
25
|
+
export function buildBaseTestContext(node, packageName, options) {
|
|
26
|
+
const examples = buildExamples(node, options);
|
|
27
|
+
const coercions = buildCoercions(node, options);
|
|
28
|
+
const isAbstract = node.isAbstract || (node.discriminator !== undefined && node.discriminator.length > 0);
|
|
29
|
+
return {
|
|
30
|
+
node,
|
|
31
|
+
isAbstract,
|
|
32
|
+
package: packageName,
|
|
33
|
+
examples,
|
|
34
|
+
coercions,
|
|
35
|
+
factories: node.factories,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build test examples from @sample decorators on properties.
|
|
40
|
+
*/
|
|
41
|
+
function buildExamples(node, options) {
|
|
42
|
+
// Get sample properties and generate combinations
|
|
43
|
+
const samples = node.properties
|
|
44
|
+
.filter(p => p.samples && p.samples.length > 0)
|
|
45
|
+
.map(p => p.samples?.map(s => ({ ...s.sample })));
|
|
46
|
+
const combinations = samples.length > 0 ? getCombinations(samples) : [];
|
|
47
|
+
return combinations.map(c => {
|
|
48
|
+
const sample = Object.assign({}, ...c);
|
|
49
|
+
// Create YAML document with proper string escaping
|
|
50
|
+
const doc = new YAML.Document(sample);
|
|
51
|
+
YAML.visit(doc, {
|
|
52
|
+
Scalar(key, yamlNode) {
|
|
53
|
+
if (typeof yamlNode.value === 'string') {
|
|
54
|
+
const str = yamlNode.value;
|
|
55
|
+
if (str.includes('\n') || str.includes('\t') || str.includes('#') || str.includes(':') || str.includes('"')) {
|
|
56
|
+
yamlNode.type = 'QUOTE_DOUBLE';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
// Generate JSON and optionally escape for embedding in template strings
|
|
62
|
+
let jsonStr = JSON.stringify(sample, null, 2);
|
|
63
|
+
if (options.escapeJsonForTemplate) {
|
|
64
|
+
jsonStr = options.escapeJsonForTemplate(jsonStr);
|
|
65
|
+
}
|
|
66
|
+
// Generate YAML and optionally escape for embedding in template strings
|
|
67
|
+
let yamlStr = doc.toString({ indent: 2, lineWidth: 0 });
|
|
68
|
+
if (options.escapeYamlForTemplate) {
|
|
69
|
+
yamlStr = options.escapeYamlForTemplate(yamlStr);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
json: jsonStr.split('\n'),
|
|
73
|
+
yaml: yamlStr.split('\n'),
|
|
74
|
+
validations: buildValidations(sample, node, options),
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build property validations from a sample object.
|
|
80
|
+
*/
|
|
81
|
+
function buildValidations(sample, node, options) {
|
|
82
|
+
return Object.keys(sample)
|
|
83
|
+
.filter(key => typeof sample[key] !== 'object')
|
|
84
|
+
.map(key => {
|
|
85
|
+
const prop = node.properties.find(p => p.name === key);
|
|
86
|
+
const rawValue = sample[key];
|
|
87
|
+
// Check for enum field (skip discriminator fields)
|
|
88
|
+
const isDiscriminator = node.discriminator === key;
|
|
89
|
+
if (prop && prop.enumName && !isDiscriminator && typeof rawValue === 'string' && options.renderEnumValue) {
|
|
90
|
+
const enumResult = options.renderEnumValue(prop.enumName, rawValue, key);
|
|
91
|
+
if (enumResult) {
|
|
92
|
+
return {
|
|
93
|
+
key: options.renderKey(key),
|
|
94
|
+
value: enumResult.value,
|
|
95
|
+
delimiter: enumResult.delimiter,
|
|
96
|
+
isOptional: prop?.isOptional || false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
let value;
|
|
101
|
+
let delimiter = '';
|
|
102
|
+
if (typeof rawValue === 'boolean') {
|
|
103
|
+
value = options.renderBoolean(rawValue);
|
|
104
|
+
}
|
|
105
|
+
else if (typeof rawValue === 'string') {
|
|
106
|
+
value = options.escapeString(rawValue);
|
|
107
|
+
delimiter = options.getDelimiter(rawValue);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
value = rawValue;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
key: options.renderKey(key),
|
|
114
|
+
value,
|
|
115
|
+
delimiter,
|
|
116
|
+
isOptional: prop?.isOptional || false,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Build coercion (scalar-to-object) test cases from node coercions.
|
|
122
|
+
*/
|
|
123
|
+
function buildCoercions(node, options) {
|
|
124
|
+
if (!node.coercions || node.coercions.length === 0) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
return node.coercions.map(alt => {
|
|
128
|
+
// Get example value - use provided example or default scalar value
|
|
129
|
+
const example = alt.example
|
|
130
|
+
? (typeof alt.example === "string" ? '"' + alt.example + '"' : alt.example.toString())
|
|
131
|
+
: options.scalarValues[alt.scalar] || "null";
|
|
132
|
+
// Build validations for expanded properties
|
|
133
|
+
const validations = Object.keys(alt.expansion)
|
|
134
|
+
.filter(key => typeof alt.expansion[key] !== 'object')
|
|
135
|
+
.map(key => {
|
|
136
|
+
const prop = node.properties.find(p => p.name === key);
|
|
137
|
+
const rawValue = alt.expansion[key];
|
|
138
|
+
const isValuePlaceholder = rawValue === "{value}";
|
|
139
|
+
const value = isValuePlaceholder ? example : rawValue;
|
|
140
|
+
// Check for closed enum field (skip discriminator fields)
|
|
141
|
+
const isDiscriminator = node.discriminator === key;
|
|
142
|
+
if (prop && prop.enumName && !isDiscriminator && options.renderEnumValue) {
|
|
143
|
+
// Extract the raw string value (strip quotes if present from example substitution)
|
|
144
|
+
const strValue = typeof value === 'string' ? value.replace(/^"|"$/g, '') : String(value);
|
|
145
|
+
const enumResult = options.renderEnumValue(prop.enumName, strValue, key);
|
|
146
|
+
if (enumResult) {
|
|
147
|
+
return {
|
|
148
|
+
key: options.renderKey(key),
|
|
149
|
+
value: enumResult.value,
|
|
150
|
+
delimiter: enumResult.delimiter,
|
|
151
|
+
isOptional: prop?.isOptional || false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Determine delimiter - don't add quotes if it's the {value} placeholder (already has quotes)
|
|
156
|
+
const needsQuotes = typeof value === 'string' && !value.includes('"') && !isValuePlaceholder;
|
|
157
|
+
return {
|
|
158
|
+
key: options.renderKey(key),
|
|
159
|
+
value: needsQuotes ? options.escapeString(value) : value,
|
|
160
|
+
delimiter: needsQuotes ? '"' : '',
|
|
161
|
+
isOptional: prop?.isOptional || false,
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
title: alt.title || alt.scalar,
|
|
166
|
+
scalarType: options.typeMapper[alt.scalar] || alt.scalar,
|
|
167
|
+
value: example,
|
|
168
|
+
validations,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// Language-Specific Presets
|
|
174
|
+
// =============================================================================
|
|
175
|
+
/**
|
|
176
|
+
* C# test context options.
|
|
177
|
+
*/
|
|
178
|
+
export const csharpTestOptions = {
|
|
179
|
+
renderKey: (key) => {
|
|
180
|
+
// Convert snake_case to PascalCase
|
|
181
|
+
const pascal = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
182
|
+
return pascal.charAt(0).toUpperCase() + pascal.slice(1);
|
|
183
|
+
},
|
|
184
|
+
renderBoolean: (val) => val ? "True" : "False",
|
|
185
|
+
escapeString: (str) => str.replace(/\\/g, "\\\\").replace(/"/g, '\\"'),
|
|
186
|
+
getDelimiter: (str) => str.includes('\n') ? '@"' : '"',
|
|
187
|
+
scalarValues: {
|
|
188
|
+
"boolean": "false",
|
|
189
|
+
"float": "3.14f",
|
|
190
|
+
"float32": "3.14f",
|
|
191
|
+
"float64": "3.14",
|
|
192
|
+
"number": "3.14f",
|
|
193
|
+
"int32": "3",
|
|
194
|
+
"int64": "3L",
|
|
195
|
+
"integer": "3",
|
|
196
|
+
"string": '"example"',
|
|
197
|
+
},
|
|
198
|
+
typeMapper: {
|
|
199
|
+
"string": "string",
|
|
200
|
+
"boolean": "bool",
|
|
201
|
+
"int32": "int",
|
|
202
|
+
"int64": "long",
|
|
203
|
+
"float32": "float",
|
|
204
|
+
"float64": "double",
|
|
205
|
+
"number": "float",
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Python test context options.
|
|
210
|
+
*/
|
|
211
|
+
export const pythonTestOptions = {
|
|
212
|
+
renderKey: (key) => toSnakeCase(key), // camelCase from TypeSpec → snake_case for Python
|
|
213
|
+
renderBoolean: (val) => val ? "True" : "False",
|
|
214
|
+
escapeString: (str) => str.replace(/\\/g, "\\\\").replace(/"/g, '\\"'),
|
|
215
|
+
getDelimiter: (str) => str.includes('\n') ? '"""' : '"',
|
|
216
|
+
scalarValues: {
|
|
217
|
+
"boolean": "False",
|
|
218
|
+
"float": "3.14",
|
|
219
|
+
"float32": "3.14",
|
|
220
|
+
"float64": "3.14",
|
|
221
|
+
"number": "3.14",
|
|
222
|
+
"int32": "3",
|
|
223
|
+
"int64": "3",
|
|
224
|
+
"integer": "3",
|
|
225
|
+
"string": '"example"',
|
|
226
|
+
},
|
|
227
|
+
typeMapper: {
|
|
228
|
+
"string": "str",
|
|
229
|
+
"boolean": "bool",
|
|
230
|
+
"int32": "int",
|
|
231
|
+
"int64": "int",
|
|
232
|
+
"float32": "float",
|
|
233
|
+
"float64": "float",
|
|
234
|
+
"number": "float",
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* TypeScript test context options.
|
|
239
|
+
*/
|
|
240
|
+
export const typescriptTestOptions = {
|
|
241
|
+
renderKey: (key) => key, // camelCase - already correct from TypeSpec
|
|
242
|
+
renderBoolean: (val) => val ? "true" : "false",
|
|
243
|
+
escapeString: (str) => str
|
|
244
|
+
.replace(/\\/g, "\\\\")
|
|
245
|
+
.replace(/\n/g, "\\n")
|
|
246
|
+
.replace(/\r/g, "\\r")
|
|
247
|
+
.replace(/\t/g, "\\t")
|
|
248
|
+
.replace(/"/g, '\\"'),
|
|
249
|
+
getDelimiter: (str) => '"',
|
|
250
|
+
// Escape backslashes in JSON so escape sequences like \n remain as literals in template strings
|
|
251
|
+
escapeJsonForTemplate: (json) => json.replace(/\\/g, "\\\\"),
|
|
252
|
+
// Escape backslashes in YAML so escape sequences remain as literals in template strings
|
|
253
|
+
escapeYamlForTemplate: (yaml) => yaml.replace(/\\/g, "\\\\"),
|
|
254
|
+
scalarValues: {
|
|
255
|
+
"boolean": "false",
|
|
256
|
+
"float": "3.14",
|
|
257
|
+
"float32": "3.14",
|
|
258
|
+
"float64": "3.14",
|
|
259
|
+
"number": "3.14",
|
|
260
|
+
"int32": "3",
|
|
261
|
+
"int64": "3",
|
|
262
|
+
"integer": "3",
|
|
263
|
+
"string": '"example"',
|
|
264
|
+
},
|
|
265
|
+
typeMapper: {
|
|
266
|
+
"string": "string",
|
|
267
|
+
"boolean": "boolean",
|
|
268
|
+
"int32": "number",
|
|
269
|
+
"int64": "number",
|
|
270
|
+
"float32": "number",
|
|
271
|
+
"float64": "number",
|
|
272
|
+
"number": "number",
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Rust test context options.
|
|
277
|
+
*/
|
|
278
|
+
export const rustTestOptions = {
|
|
279
|
+
renderKey: (key) => {
|
|
280
|
+
return rustFieldName(key);
|
|
281
|
+
},
|
|
282
|
+
renderBoolean: (val) => val ? "true" : "false",
|
|
283
|
+
escapeString: (str) => str
|
|
284
|
+
.replace(/\\/g, '\\\\')
|
|
285
|
+
.replace(/"/g, '\\"')
|
|
286
|
+
.replace(/\n/g, '\\n')
|
|
287
|
+
.replace(/\r/g, '\\r')
|
|
288
|
+
.replace(/\t/g, '\\t'),
|
|
289
|
+
getDelimiter: (str) => '"',
|
|
290
|
+
renderEnumValue: (enumName, rawValue) => ({
|
|
291
|
+
value: `${enumName}::${toPascalCase(rawValue)}`,
|
|
292
|
+
delimiter: '',
|
|
293
|
+
}),
|
|
294
|
+
escapeJsonForTemplate: undefined,
|
|
295
|
+
escapeYamlForTemplate: undefined,
|
|
296
|
+
scalarValues: {
|
|
297
|
+
"boolean": "false",
|
|
298
|
+
"float": "3.14",
|
|
299
|
+
"float32": "3.14",
|
|
300
|
+
"float64": "3.14",
|
|
301
|
+
"number": "3.14",
|
|
302
|
+
"int32": "3",
|
|
303
|
+
"int64": "3",
|
|
304
|
+
"integer": "3",
|
|
305
|
+
"string": '"example"',
|
|
306
|
+
},
|
|
307
|
+
typeMapper: {
|
|
308
|
+
"string": "String",
|
|
309
|
+
"boolean": "bool",
|
|
310
|
+
"int32": "i32",
|
|
311
|
+
"int64": "i64",
|
|
312
|
+
"float32": "f32",
|
|
313
|
+
"float64": "f64",
|
|
314
|
+
"number": "f64",
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Go test context options.
|
|
319
|
+
*/
|
|
320
|
+
export const goTestOptions = {
|
|
321
|
+
renderKey: (key) => {
|
|
322
|
+
// Convert snake_case to PascalCase for exported Go fields
|
|
323
|
+
const pascal = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
324
|
+
return pascal.charAt(0).toUpperCase() + pascal.slice(1);
|
|
325
|
+
},
|
|
326
|
+
renderBoolean: (val) => val ? "true" : "false",
|
|
327
|
+
escapeString: (str) => str
|
|
328
|
+
.replace(/\\/g, '\\\\')
|
|
329
|
+
.replace(/"/g, '\\"')
|
|
330
|
+
.replace(/\n/g, '\\n')
|
|
331
|
+
.replace(/\r/g, '\\r')
|
|
332
|
+
.replace(/\t/g, '\\t'),
|
|
333
|
+
getDelimiter: (str) => '"',
|
|
334
|
+
scalarValues: {
|
|
335
|
+
"boolean": "false",
|
|
336
|
+
"float": "3.14",
|
|
337
|
+
"float32": "3.14",
|
|
338
|
+
"float64": "3.14",
|
|
339
|
+
"number": "3.14",
|
|
340
|
+
"int32": "3",
|
|
341
|
+
"int64": "3",
|
|
342
|
+
"integer": "3",
|
|
343
|
+
"string": '"example"',
|
|
344
|
+
},
|
|
345
|
+
typeMapper: {
|
|
346
|
+
"string": "string",
|
|
347
|
+
"boolean": "bool",
|
|
348
|
+
"int32": "int32",
|
|
349
|
+
"int64": "int64",
|
|
350
|
+
"float32": "float32",
|
|
351
|
+
"float64": "float64",
|
|
352
|
+
"number": "float64",
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
//# sourceMappingURL=test-context.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "@typra/emitter";
|
|
2
|
+
|
|
3
|
+
namespace Typra.Fixtures;
|
|
4
|
+
|
|
5
|
+
model FixtureRoot {
|
|
6
|
+
@doc("Required scalar field")
|
|
7
|
+
name: string;
|
|
8
|
+
|
|
9
|
+
@doc("Optional scalar field")
|
|
10
|
+
description?: string;
|
|
11
|
+
|
|
12
|
+
@doc("Array of scalar tags")
|
|
13
|
+
tags: string[];
|
|
14
|
+
|
|
15
|
+
@doc("Dictionary-shaped metadata")
|
|
16
|
+
metadata?: Record<unknown>;
|
|
17
|
+
|
|
18
|
+
@doc("Nested object field")
|
|
19
|
+
owner: FixtureOwner;
|
|
20
|
+
|
|
21
|
+
@doc("Discriminated union field")
|
|
22
|
+
content: FixtureContent;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
model FixtureOwner {
|
|
26
|
+
id: string;
|
|
27
|
+
displayName?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@discriminator("kind")
|
|
31
|
+
model FixtureContent {
|
|
32
|
+
kind: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
model TextContent extends FixtureContent {
|
|
36
|
+
kind: "text";
|
|
37
|
+
text: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
model ImageContent extends FixtureContent {
|
|
41
|
+
kind: "image";
|
|
42
|
+
url: string;
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
emit:
|
|
2
|
+
- "@typra/emitter"
|
|
3
|
+
options:
|
|
4
|
+
"@typra/emitter":
|
|
5
|
+
emitter-output-dir: "{cwd}/generated/fixtures"
|
|
6
|
+
root-object: "Typra.Fixtures.FixtureRoot"
|
|
7
|
+
root-namespace: "Typra.Fixtures"
|
|
8
|
+
emit-targets:
|
|
9
|
+
- type: TypeScript
|
|
10
|
+
output-dir: "generated/fixtures/typescript"
|
|
11
|
+
test-dir: "generated/fixtures/typescript/tests"
|
|
12
|
+
import-path: "../index"
|
|
13
|
+
format: false
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@typra/emitter",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Generic TypeSpec emitter for generating multi-runtime model surfaces",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/sethjuarez/typra"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "dist/src/index.js",
|
|
12
|
+
"types": "dist/src/index.d.ts",
|
|
13
|
+
"tspMain": "src/lib/main.tsp",
|
|
14
|
+
"bin": {
|
|
15
|
+
"typra-generate": "./dist/src/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/src/index.d.ts",
|
|
20
|
+
"default": "./dist/src/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./generate": {
|
|
23
|
+
"types": "./dist/src/generate.d.ts",
|
|
24
|
+
"default": "./dist/src/generate.js"
|
|
25
|
+
},
|
|
26
|
+
"./testing": {
|
|
27
|
+
"types": "./dist/src/testing/index.d.ts",
|
|
28
|
+
"default": "./dist/src/testing/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist/src/**/*.js",
|
|
33
|
+
"dist/src/**/*.d.ts",
|
|
34
|
+
"dist/src/**/*.json",
|
|
35
|
+
"fixtures/**/*.tsp",
|
|
36
|
+
"src/lib/*.tsp",
|
|
37
|
+
"tspconfig.yaml",
|
|
38
|
+
"fixtures/tspconfig.yaml"
|
|
39
|
+
],
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@typespec/compiler": "latest",
|
|
42
|
+
"@typespec/json-schema": "latest"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^24.7.0",
|
|
46
|
+
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
|
48
|
+
"@typescript-eslint/parser": "^8.15.0",
|
|
49
|
+
"@typespec/compiler": "latest",
|
|
50
|
+
"@typespec/json-schema": "^1.8.0",
|
|
51
|
+
"eslint": "^9.15.0",
|
|
52
|
+
"markdownlint-cli": "^0.48.0",
|
|
53
|
+
"prettier": "^3.3.3",
|
|
54
|
+
"typescript": "^5.3.3",
|
|
55
|
+
"typescript-eslint": "^8.54.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc",
|
|
59
|
+
"watch": "tsc --watch",
|
|
60
|
+
"generate:fixtures": "tsp compile ./fixtures/shapes/main.tsp --config ./fixtures/tspconfig.yaml",
|
|
61
|
+
"test": "node --test \"dist/test/*.test.js\"",
|
|
62
|
+
"lint": "eslint src/ test/ --report-unused-disable-directives --max-warnings=0",
|
|
63
|
+
"lint:fix": "eslint . --report-unused-disable-directives --fix",
|
|
64
|
+
"format": "prettier . --write",
|
|
65
|
+
"format:check": "prettier --check ."
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
},
|
|
70
|
+
"packageManager": "npm@11.5.1+sha512.232e6f5d9e799bcb486920b3e9ba907fdf96e576cf7e8c9446c8162e33a416096a1d37a9e910d9a918f6b1f606791c99bc6bb61ee2569b496ec74af13d0dbd95",
|
|
71
|
+
"dependencies": {
|
|
72
|
+
|
|
73
|
+
"xml-formatter": "^3.6.7",
|
|
74
|
+
"yaml": "^2.8.1"
|
|
75
|
+
}
|
|
76
|
+
}
|