@typespec/emitter-framework 0.6.0-dev.1 → 0.6.0-dev.2

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.
@@ -0,0 +1,19 @@
1
+ import { Children } from "@alloy-js/core";
2
+ import { Value } from "@typespec/compiler";
3
+ /**
4
+ * Properties for the {@link ValueExpression} component.
5
+ */
6
+ interface ValueExpressionProps {
7
+ /**
8
+ * The TypeSpec value to be converted to a JavaScript expression.
9
+ */
10
+ value: Value;
11
+ }
12
+ /**
13
+ * Generates a JavaScript value expression from a TypeSpec value.
14
+ * @param props properties for the value expression
15
+ * @returns {@link Children} representing the JavaScript value expression
16
+ */
17
+ export declare function ValueExpression(props: Readonly<ValueExpressionProps>): Children;
18
+ export {};
19
+ //# sourceMappingURL=value-expression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value-expression.d.ts","sourceRoot":"","sources":["../../../../src/typescript/components/value-expression.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAkB,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;GAEG;AACH,UAAU,oBAAoB;IAC5B;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,QAAQ,CAoC/E"}
@@ -0,0 +1,72 @@
1
+ import { memo as _$memo } from "@alloy-js/core/jsx-runtime";
2
+ import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
3
+ import * as ts from "@alloy-js/typescript";
4
+ import { compilerAssert } from "@typespec/compiler";
5
+
6
+ /**
7
+ * Properties for the {@link ValueExpression} component.
8
+ */
9
+
10
+ /**
11
+ * Generates a JavaScript value expression from a TypeSpec value.
12
+ * @param props properties for the value expression
13
+ * @returns {@link Children} representing the JavaScript value expression
14
+ */
15
+ export function ValueExpression(props) {
16
+ switch (props.value.valueKind) {
17
+ case "StringValue":
18
+ case "BooleanValue":
19
+ case "NullValue":
20
+ return _$createComponent(ts.ValueExpression, {
21
+ get jsValue() {
22
+ return props.value.value;
23
+ }
24
+ });
25
+ case "NumericValue":
26
+ if (props.value.value.asNumber()) {
27
+ return _$createComponent(ts.ValueExpression, {
28
+ get jsValue() {
29
+ return props.value.value.asNumber();
30
+ }
31
+ });
32
+ }
33
+ compilerAssert(props.value.value.isInteger, "BigInt value must be an integer", props.value);
34
+ return _$createComponent(ts.ValueExpression, {
35
+ get jsValue() {
36
+ return props.value.value.asBigInt();
37
+ }
38
+ });
39
+ case "ArrayValue":
40
+ return _$createComponent(ts.ArrayExpression, {
41
+ get jsValue() {
42
+ return props.value.values.map(v => _$createComponent(ValueExpression, {
43
+ value: v
44
+ }));
45
+ }
46
+ });
47
+ case "ScalarValue":
48
+ compilerAssert(props.value.value.name === "fromISO", `Unsupported scalar constructor ${props.value.value.name}`, props.value);
49
+ return _$createComponent(ValueExpression, {
50
+ get value() {
51
+ return props.value.value.args[0];
52
+ }
53
+ });
54
+ case "ObjectValue":
55
+ const jsProperties = {};
56
+ for (const [key, value] of props.value.properties) {
57
+ jsProperties[key] = ValueExpression({
58
+ value: value.value
59
+ });
60
+ }
61
+ return _$createComponent(ts.ObjectExpression, {
62
+ jsValue: jsProperties
63
+ });
64
+ case "EnumValue":
65
+ return _$createComponent(ts.ValueExpression, {
66
+ get jsValue() {
67
+ return props.value.value.value ?? props.value.value.name;
68
+ }
69
+ });
70
+ }
71
+ }
72
+ //# sourceMappingURL=value-expression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value-expression.js","names":["ts","compilerAssert","ValueExpression","props","value","valueKind","_$createComponent","jsValue","asNumber","isInteger","asBigInt","ArrayExpression","values","map","v","name","args","jsProperties","key","properties","ObjectExpression"],"sources":["../../../../src/typescript/components/value-expression.tsx"],"sourcesContent":[null],"mappings":";;AACA,OAAO,KAAKA,EAAE,MAAM,sBAAsB;AAC1C,SAASC,cAAc,QAAe,oBAAoB;;AAE1D;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,eAAeA,CAACC,KAAqC,EAAY;EAC/E,QAAQA,KAAK,CAACC,KAAK,CAACC,SAAS;IAC3B,KAAK,aAAa;IAClB,KAAK,cAAc;IACnB,KAAK,WAAW;MACd,OAAAC,iBAAA,CAAQN,EAAE,CAACE,eAAe;QAAA,IAACK,OAAOA,CAAA;UAAA,OAAEJ,KAAK,CAACC,KAAK,CAACA,KAAK;QAAA;MAAA;IACvD,KAAK,cAAc;MACjB,IAAID,KAAK,CAACC,KAAK,CAACA,KAAK,CAACI,QAAQ,CAAC,CAAC,EAAE;QAChC,OAAAF,iBAAA,CAAQN,EAAE,CAACE,eAAe;UAAA,IAACK,OAAOA,CAAA;YAAA,OAAEJ,KAAK,CAACC,KAAK,CAACA,KAAK,CAACI,QAAQ,CAAC,CAAC;UAAA;QAAA;MAClE;MACAP,cAAc,CAACE,KAAK,CAACC,KAAK,CAACA,KAAK,CAACK,SAAS,EAAE,iCAAiC,EAAEN,KAAK,CAACC,KAAK,CAAC;MAC3F,OAAAE,iBAAA,CAAQN,EAAE,CAACE,eAAe;QAAA,IAACK,OAAOA,CAAA;UAAA,OAAEJ,KAAK,CAACC,KAAK,CAACA,KAAK,CAACM,QAAQ,CAAC,CAAC;QAAA;MAAA;IAClE,KAAK,YAAY;MACf,OAAAJ,iBAAA,CACGN,EAAE,CAACW,eAAe;QAAA,IACjBJ,OAAOA,CAAA;UAAA,OAAEJ,KAAK,CAACC,KAAK,CAACQ,MAAM,CAACC,GAAG,CAAEC,CAAC,IAAAR,iBAAA,CAC/BJ,eAAe;YAACE,KAAK,EAAEU;UAAC,EAC1B,CAAC;QAAA;MAAA;IAGR,KAAK,aAAa;MAChBb,cAAc,CACZE,KAAK,CAACC,KAAK,CAACA,KAAK,CAACW,IAAI,KAAK,SAAS,EACpC,kCAAkCZ,KAAK,CAACC,KAAK,CAACA,KAAK,CAACW,IAAI,EAAE,EAC1DZ,KAAK,CAACC,KACR,CAAC;MACD,OAAAE,iBAAA,CAAQJ,eAAe;QAAA,IAACE,KAAKA,CAAA;UAAA,OAAED,KAAK,CAACC,KAAK,CAACA,KAAK,CAACY,IAAI,CAAC,CAAC,CAAC;QAAA;MAAA;IAC1D,KAAK,aAAa;MAChB,MAAMC,YAAsC,GAAG,CAAC,CAAC;MACjD,KAAK,MAAM,CAACC,GAAG,EAAEd,KAAK,CAAC,IAAID,KAAK,CAACC,KAAK,CAACe,UAAU,EAAE;QACjDF,YAAY,CAACC,GAAG,CAAC,GAAGhB,eAAe,CAAC;UAAEE,KAAK,EAAEA,KAAK,CAACA;QAAM,CAAC,CAAC;MAC7D;MACA,OAAAE,iBAAA,CAAQN,EAAE,CAACoB,gBAAgB;QAACb,OAAO,EAAEU;MAAY;IACnD,KAAK,WAAW;MACd,OAAAX,iBAAA,CAAQN,EAAE,CAACE,eAAe;QAAA,IAACK,OAAOA,CAAA;UAAA,OAAEJ,KAAK,CAACC,KAAK,CAACA,KAAK,CAACA,KAAK,IAAID,KAAK,CAACC,KAAK,CAACA,KAAK,CAACW,IAAI;QAAA;MAAA;EACzF;AACF","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=value-expression.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value-expression.test.d.ts","sourceRoot":"","sources":["../../../../test/typescript/components/value-expression.test.tsx"],"names":[],"mappings":""}
@@ -8,4 +8,9 @@ export declare function createEmitterFrameworkTestRunner(options?: {
8
8
  export declare function getProgram(code: string, options?: {
9
9
  libraries: "Http"[];
10
10
  }): Promise<Program>;
11
+ /**
12
+ * Initializes an empty program in the compiler.
13
+ * This is useful when you want to initialize the default TypeKits without any code.
14
+ */
15
+ export declare function initEmptyProgram(): Promise<void>;
11
16
  //# sourceMappingURL=test-host.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-host.d.ts","sourceRoot":"","sources":["../../../test/typescript/test-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAQ7C,wBAAsB,yBAAyB,CAC7C,OAAO,GAAE;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAsB,0DASrD;AAED,wBAAsB,gCAAgC,CAAC,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,iEAK7F;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAsB,GACnD,OAAO,CAAC,OAAO,CAAC,CAUlB"}
1
+ {"version":3,"file":"test-host.d.ts","sourceRoot":"","sources":["../../../test/typescript/test-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAQ7C,wBAAsB,yBAAyB,CAC7C,OAAO,GAAE;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAsB,0DASrD;AAED,wBAAsB,gCAAgC,CAAC,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,iEAK7F;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAsB,GACnD,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEtD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typespec/emitter-framework",
3
- "version": "0.6.0-dev.1",
3
+ "version": "0.6.0-dev.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -0,0 +1,56 @@
1
+ import { Children } from "@alloy-js/core";
2
+ import * as ts from "@alloy-js/typescript";
3
+ import { compilerAssert, Value } from "@typespec/compiler";
4
+
5
+ /**
6
+ * Properties for the {@link ValueExpression} component.
7
+ */
8
+ interface ValueExpressionProps {
9
+ /**
10
+ * The TypeSpec value to be converted to a JavaScript expression.
11
+ */
12
+ value: Value;
13
+ }
14
+
15
+ /**
16
+ * Generates a JavaScript value expression from a TypeSpec value.
17
+ * @param props properties for the value expression
18
+ * @returns {@link Children} representing the JavaScript value expression
19
+ */
20
+ export function ValueExpression(props: Readonly<ValueExpressionProps>): Children {
21
+ switch (props.value.valueKind) {
22
+ case "StringValue":
23
+ case "BooleanValue":
24
+ case "NullValue":
25
+ return <ts.ValueExpression jsValue={props.value.value} />;
26
+ case "NumericValue":
27
+ if (props.value.value.asNumber()) {
28
+ return <ts.ValueExpression jsValue={props.value.value.asNumber()} />;
29
+ }
30
+ compilerAssert(props.value.value.isInteger, "BigInt value must be an integer", props.value);
31
+ return <ts.ValueExpression jsValue={props.value.value.asBigInt()} />;
32
+ case "ArrayValue":
33
+ return (
34
+ <ts.ArrayExpression
35
+ jsValue={props.value.values.map((v) => (
36
+ <ValueExpression value={v} />
37
+ ))}
38
+ />
39
+ );
40
+ case "ScalarValue":
41
+ compilerAssert(
42
+ props.value.value.name === "fromISO",
43
+ `Unsupported scalar constructor ${props.value.value.name}`,
44
+ props.value,
45
+ );
46
+ return <ValueExpression value={props.value.value.args[0]} />;
47
+ case "ObjectValue":
48
+ const jsProperties: Record<string, Children> = {};
49
+ for (const [key, value] of props.value.properties) {
50
+ jsProperties[key] = ValueExpression({ value: value.value });
51
+ }
52
+ return <ts.ObjectExpression jsValue={jsProperties} />;
53
+ case "EnumValue":
54
+ return <ts.ValueExpression jsValue={props.value.value.value ?? props.value.value.name} />;
55
+ }
56
+ }
@@ -0,0 +1,236 @@
1
+ import { Output, render } from "@alloy-js/core";
2
+ import { dedent } from "@alloy-js/core/testing";
3
+ import { SourceFile } from "@alloy-js/typescript";
4
+ import { EnumValue, Model, Namespace, Numeric, NumericValue, Value } from "@typespec/compiler";
5
+ import { $ } from "@typespec/compiler/experimental/typekit";
6
+ import { assert, beforeAll, describe, expect, it } from "vitest";
7
+ import { ValueExpression } from "../../../src/typescript/components/value-expression.js";
8
+ import { getProgram, initEmptyProgram } from "../test-host.js";
9
+
10
+ beforeAll(async () => {
11
+ await initEmptyProgram();
12
+ });
13
+
14
+ it("renders strings", async () => {
15
+ const value = $.value.createString("test");
16
+
17
+ await testValueExpression(value, `"test"`);
18
+ });
19
+
20
+ describe("numeric values", () => {
21
+ it("renders integers", async () => {
22
+ const value = $.value.createNumeric(42);
23
+
24
+ await testValueExpression(value, `42`);
25
+ });
26
+
27
+ it("renders decimals", async () => {
28
+ const value = $.value.createNumeric(42.5);
29
+
30
+ await testValueExpression(value, `42.5`);
31
+ });
32
+
33
+ it("renders bigints", async () => {
34
+ const digits = "1234567890123456789012345678901234567890";
35
+ const value: NumericValue = {
36
+ entityKind: "Value",
37
+ valueKind: "NumericValue",
38
+ value: Numeric(digits),
39
+ } as NumericValue;
40
+
41
+ await testValueExpression(value, `${digits}n`);
42
+ });
43
+
44
+ it("throws on invalid numbers", async () => {
45
+ const digits = "123456789123456789.112233445566778899";
46
+ const value: NumericValue = {
47
+ entityKind: "Value",
48
+ valueKind: "NumericValue",
49
+ value: Numeric(digits),
50
+ } as NumericValue;
51
+
52
+ await expect(testValueExpression(value, ``)).rejects.toThrow("BigInt value must be an integer");
53
+ });
54
+ });
55
+
56
+ it("renders booleans", async () => {
57
+ const value = $.value.createBoolean(true);
58
+ await testValueExpression(value, `true`);
59
+ });
60
+
61
+ it("renders nulls", async () => {
62
+ const value = {
63
+ entityKind: "Value",
64
+ valueKind: "NullValue",
65
+ value: null,
66
+ } as Value;
67
+ await testValueExpression(value, `null`);
68
+ });
69
+
70
+ it("renders empty arrays", async () => {
71
+ // Can be replaced with with TypeKit once #6976 is implemented
72
+ const value = {
73
+ entityKind: "Value",
74
+ valueKind: "ArrayValue",
75
+ values: [],
76
+ } as unknown as Value;
77
+ await testValueExpression(value, `[]`);
78
+ });
79
+
80
+ it("renders arrays with mixed values", async () => {
81
+ // Can be replaced with with TypeKit once #6976 is implemented
82
+ const value = {
83
+ entityKind: "Value",
84
+ valueKind: "ArrayValue",
85
+ values: [$.value.createString("foo"), $.value.createNumeric(42), $.value.createBoolean(true)],
86
+ } as Value;
87
+ await testValueExpression(value, `["foo", 42, true]`);
88
+ });
89
+
90
+ it("renders scalars", async () => {
91
+ const program = await getProgram(`
92
+ namespace DemoService;
93
+ model DateRange {
94
+ @encode("rfc7231")
95
+ minDate: utcDateTime = utcDateTime.fromISO("2024-02-15T18:36:03Z");
96
+ }
97
+ `);
98
+ const [namespace] = program.resolveTypeReference("DemoService");
99
+ const dateRange = (namespace as Namespace).models.get("DateRange");
100
+ const minDate = dateRange?.properties.get("minDate")?.defaultValue;
101
+ assert.exists(minDate, "unable to find minDate property");
102
+ await testValueExpression(minDate, `"2024-02-15T18:36:03Z"`);
103
+ });
104
+
105
+ it("throws on unsupported scalar", async () => {
106
+ const program = await getProgram(`
107
+ namespace DemoService;
108
+
109
+ scalar ipv4 extends string {
110
+ init fromInt(value: uint32);
111
+ }
112
+
113
+ @example (#{ip: ipv4.fromInt(2130706433)})
114
+ model IpAddress {
115
+ ip: ipv4;
116
+ }
117
+ `);
118
+ const [namespace] = program.resolveTypeReference("DemoService");
119
+ const model = (namespace as Namespace).models.get("IpAddress");
120
+ assert.exists(model, "unable to find IpAddress model");
121
+
122
+ const value = getExampleValue(model);
123
+ await expect(testValueExpression(value, ``)).rejects.toThrow(
124
+ /Unsupported scalar constructor fromInt/,
125
+ );
126
+ });
127
+
128
+ it("renders empty objects", async () => {
129
+ // Can be replaced with with TypeKit once #6976 is implemented
130
+ const program = await getProgram(`
131
+ namespace DemoService;
132
+ @example(#{})
133
+ model ObjectValue {};
134
+ `);
135
+ const [namespace] = program.resolveTypeReference("DemoService");
136
+ const model = (namespace as Namespace).models.get("ObjectValue");
137
+ assert.exists(model, "unable to find ObjectValue model");
138
+
139
+ const value = getExampleValue(model);
140
+ await testValueExpression(value, `{}`);
141
+ });
142
+
143
+ it("renders objects with properties", async () => {
144
+ // Can be replaced with with TypeKit once #6976 is implemented
145
+ const program = await getProgram(`
146
+ namespace DemoService;
147
+ @example(#{a: 5, b: "foo", c: true})
148
+ model ObjectValue {
149
+ a: int32;
150
+ b: string;
151
+ c: boolean;
152
+ };
153
+ `);
154
+ const [namespace] = program.resolveTypeReference("DemoService");
155
+ const model = (namespace as Namespace).models.get("ObjectValue");
156
+ assert.exists(model, "unable to find ObjectValue model");
157
+
158
+ const value = getExampleValue(model);
159
+ await testValueExpression(
160
+ value,
161
+ dedent(`
162
+ {
163
+ a: 5,
164
+ b: "foo",
165
+ c: true,
166
+ }`),
167
+ );
168
+ });
169
+
170
+ it("renders enums", async () => {
171
+ // Can be replaced with with TypeKit once #6976 is implemented
172
+ const program = await getProgram(`
173
+ namespace DemoService;
174
+ enum Color {
175
+ Red,
176
+ Green: 3,
177
+ Blue
178
+ }
179
+ `);
180
+ const [namespace] = program.resolveTypeReference("DemoService");
181
+ const colors = (namespace as Namespace).enums.get("Color");
182
+ assert.exists(colors, "unable to find Color enum");
183
+
184
+ const red = colors?.members.get("Red");
185
+ assert.exists(red, "unable to find Red enum member");
186
+ await testValueExpression(
187
+ {
188
+ valueKind: "EnumValue",
189
+ value: red,
190
+ } as EnumValue,
191
+ `"Red"`,
192
+ );
193
+
194
+ const green = colors?.members.get("Green");
195
+ assert.exists(green, "unable to find Green enum member");
196
+ await testValueExpression(
197
+ {
198
+ valueKind: "EnumValue",
199
+ value: green,
200
+ } as EnumValue,
201
+ `3`,
202
+ );
203
+ });
204
+
205
+ /**
206
+ * Helper that renders a value expression and checks the output against the expected value.
207
+ */
208
+ async function testValueExpression(value: Value, expected: string) {
209
+ const prefix = "const val = ";
210
+ const res = render(
211
+ <Output>
212
+ <SourceFile path="test.ts">
213
+ {prefix}
214
+ <ValueExpression value={value} />
215
+ </SourceFile>
216
+ </Output>,
217
+ );
218
+ const testFile = res.contents.find((file) => file.path === "test.ts");
219
+
220
+ assert.exists(testFile, "test.ts file not rendered");
221
+
222
+ assert.equal(
223
+ testFile.contents,
224
+ `${prefix}${expected}`,
225
+ "test.ts file contents do not match expected",
226
+ );
227
+ }
228
+
229
+ /**
230
+ * Extracts the value marked with the @example decorator from a model.
231
+ */
232
+ function getExampleValue(model: Model): Value {
233
+ const decorator = model?.decorators.find((d) => d.definition?.name === "@example");
234
+ assert.exists(decorator?.args[0]?.value, "unable to find example decorator");
235
+ return decorator.args[0].value as Value;
236
+ }
@@ -39,3 +39,11 @@ export async function getProgram(
39
39
  expectDiagnosticEmpty(diagnostics);
40
40
  return wrapper.program;
41
41
  }
42
+
43
+ /**
44
+ * Initializes an empty program in the compiler.
45
+ * This is useful when you want to initialize the default TypeKits without any code.
46
+ */
47
+ export async function initEmptyProgram(): Promise<void> {
48
+ await getProgram("");
49
+ }