@typespec/emitter-framework 0.9.0-dev.1 → 0.9.0-dev.3

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 (41) hide show
  1. package/dist/src/csharp/components/class-declaration.d.ts +9 -0
  2. package/dist/src/csharp/components/class-declaration.d.ts.map +1 -0
  3. package/dist/src/csharp/components/class-declaration.js +97 -0
  4. package/dist/src/csharp/components/enum-declaration.d.ts +9 -0
  5. package/dist/src/csharp/components/enum-declaration.d.ts.map +1 -0
  6. package/dist/src/csharp/components/enum-declaration.js +52 -0
  7. package/dist/src/csharp/components/index.d.ts +4 -0
  8. package/dist/src/csharp/components/index.d.ts.map +1 -0
  9. package/dist/src/csharp/components/index.js +3 -0
  10. package/dist/src/csharp/components/type-expression.d.ts +11 -0
  11. package/dist/src/csharp/components/type-expression.d.ts.map +1 -0
  12. package/dist/src/csharp/components/type-expression.js +129 -0
  13. package/dist/src/csharp/components/utils/refkey.d.ts +23 -0
  14. package/dist/src/csharp/components/utils/refkey.d.ts.map +1 -0
  15. package/dist/src/csharp/components/utils/refkey.js +35 -0
  16. package/dist/src/csharp/index.d.ts +2 -0
  17. package/dist/src/csharp/index.d.ts.map +1 -0
  18. package/dist/src/csharp/index.js +1 -0
  19. package/dist/test/csharp/components/class-declaration.test.d.ts +2 -0
  20. package/dist/test/csharp/components/class-declaration.test.d.ts.map +1 -0
  21. package/dist/test/csharp/components/class-declaration.test.js +421 -0
  22. package/dist/test/csharp/components/enum-declaration.test.d.ts +2 -0
  23. package/dist/test/csharp/components/enum-declaration.test.d.ts.map +1 -0
  24. package/dist/test/csharp/components/enum-declaration.test.js +355 -0
  25. package/dist/test/csharp/test-host.d.ts +11 -0
  26. package/dist/test/csharp/test-host.d.ts.map +1 -0
  27. package/dist/test/csharp/test-host.js +32 -0
  28. package/dist/test/csharp/utils.d.ts +3 -0
  29. package/dist/test/csharp/utils.d.ts.map +1 -0
  30. package/dist/test/csharp/utils.js +6 -0
  31. package/package.json +5 -1
  32. package/src/csharp/components/class-declaration.tsx +87 -0
  33. package/src/csharp/components/enum-declaration.tsx +48 -0
  34. package/src/csharp/components/index.ts +3 -0
  35. package/src/csharp/components/type-expression.tsx +112 -0
  36. package/src/csharp/components/utils/refkey.ts +36 -0
  37. package/src/csharp/index.ts +1 -0
  38. package/test/csharp/components/class-declaration.test.tsx +344 -0
  39. package/test/csharp/components/enum-declaration.test.tsx +296 -0
  40. package/test/csharp/test-host.ts +42 -0
  41. package/test/csharp/utils.ts +8 -0
@@ -0,0 +1,36 @@
1
+ import { refkey as ayRefkey, type Refkey } from "@alloy-js/core";
2
+
3
+ const refKeyPrefix = Symbol.for("emitter-framework:csharp");
4
+
5
+ /**
6
+ * A wrapper around `refkey` that uses a custom symbol to avoid collisions with
7
+ * other libraries that use `refkey`.
8
+ *
9
+ * @remarks
10
+ *
11
+ * The underlying refkey function is called with the {@link refKeyPrefix} symbol as the first argument.
12
+ *
13
+ * @param args The parameters of the refkey.
14
+ * @returns A refkey object that can be used to identify the value.
15
+ */
16
+ export function efRefkey(...args: unknown[]): Refkey {
17
+ if (args.length === 0) {
18
+ return ayRefkey(); // Generates a unique refkey
19
+ }
20
+ return ayRefkey(refKeyPrefix, ...args);
21
+ }
22
+
23
+ /**
24
+ * Creates a refkey for a declaration by combining the provided refkey with an internal
25
+ * refkey generated from the provided arguments.
26
+ *
27
+ * @param refkey The refkey provided by the user to be passed as is.
28
+ * @param args The parameters of the refkey.
29
+ * @returns An array of refkeys that can be passed to an Alloy declaration.
30
+ */
31
+ export function declarationRefkeys(refkey?: Refkey | Refkey[], ...args: unknown[]): Refkey[] {
32
+ if (refkey) {
33
+ return [refkey, efRefkey(...args)].flat();
34
+ }
35
+ return [efRefkey(...args)];
36
+ }
@@ -0,0 +1 @@
1
+ export * from "./components/index.js";
@@ -0,0 +1,344 @@
1
+ import { render } from "@alloy-js/core";
2
+ import { d } from "@alloy-js/core/testing";
3
+ import * as cs from "@alloy-js/csharp";
4
+ import { Namespace, SourceFile } from "@alloy-js/csharp";
5
+ import { Enum, Interface, Model } from "@typespec/compiler";
6
+ import { BasicTestRunner } from "@typespec/compiler/testing";
7
+ import { beforeEach, describe, it } from "vitest";
8
+ import { Output } from "../../../src/core/index.js";
9
+ import { ClassDeclaration, EnumDeclaration } from "../../../src/csharp/index.js";
10
+ import { createEmitterFrameworkTestRunner } from "../test-host.js";
11
+ import { assertFileContents } from "../utils.js";
12
+
13
+ let runner: BasicTestRunner;
14
+
15
+ beforeEach(async () => {
16
+ runner = await createEmitterFrameworkTestRunner();
17
+ });
18
+
19
+ it("renders an empty class declaration", async () => {
20
+ const { TestModel } = (await runner.compile(`
21
+ @test model TestModel {}
22
+ `)) as { TestModel: Model };
23
+
24
+ const res = render(
25
+ <Output program={runner.program}>
26
+ <Namespace name="TestNamespace">
27
+ <SourceFile path="test.cs">
28
+ <ClassDeclaration type={TestModel} />
29
+ </SourceFile>
30
+ </Namespace>
31
+ </Output>,
32
+ );
33
+
34
+ assertFileContents(
35
+ res,
36
+ d`
37
+ namespace TestNamespace
38
+ {
39
+ class TestModel
40
+ {
41
+
42
+ }
43
+ }
44
+ `,
45
+ );
46
+ });
47
+
48
+ it("renders a class declaration with properties", async () => {
49
+ const { TestModel } = (await runner.compile(`
50
+ @test model TestModel {
51
+ @test Prop1: string;
52
+ @test Prop2: int32;
53
+ }
54
+ `)) as { TestModel: Model };
55
+
56
+ const res = render(
57
+ <Output program={runner.program}>
58
+ <Namespace name="TestNamespace">
59
+ <SourceFile path="test.cs">
60
+ <ClassDeclaration type={TestModel} />
61
+ </SourceFile>
62
+ </Namespace>
63
+ </Output>,
64
+ );
65
+
66
+ assertFileContents(
67
+ res,
68
+ d`
69
+ namespace TestNamespace
70
+ {
71
+ class TestModel
72
+ {
73
+ public string Prop1
74
+ {
75
+ get;
76
+ set;
77
+ }
78
+ public int Prop2
79
+ {
80
+ get;
81
+ set;
82
+ }
83
+ }
84
+ }
85
+ `,
86
+ );
87
+ });
88
+
89
+ it("can override class name", async () => {
90
+ const { TestModel } = (await runner.compile(`
91
+ @test model TestModel {}
92
+ `)) as { TestModel: Model };
93
+
94
+ const res = render(
95
+ <Output program={runner.program}>
96
+ <Namespace name="TestNamespace">
97
+ <SourceFile path="test.cs">
98
+ <ClassDeclaration type={TestModel} name="CustomClassName" />
99
+ </SourceFile>
100
+ </Namespace>
101
+ </Output>,
102
+ );
103
+
104
+ assertFileContents(
105
+ res,
106
+ d`
107
+ namespace TestNamespace
108
+ {
109
+ class CustomClassName
110
+ {
111
+
112
+ }
113
+ }
114
+ `,
115
+ );
116
+ });
117
+
118
+ it("renders a class with access modifiers", async () => {
119
+ const { TestModel } = (await runner.compile(`
120
+ @test model TestModel {
121
+ }
122
+ `)) as { TestModel: Model };
123
+
124
+ const res = render(
125
+ <Output program={runner.program}>
126
+ <Namespace name="TestNamespace">
127
+ <SourceFile path="test.cs">
128
+ <ClassDeclaration type={TestModel} accessModifier="protected" />
129
+ </SourceFile>
130
+ </Namespace>
131
+ </Output>,
132
+ );
133
+
134
+ assertFileContents(
135
+ res,
136
+ d`
137
+ namespace TestNamespace
138
+ {
139
+ protected class TestModel
140
+ {
141
+
142
+ }
143
+ }
144
+ `,
145
+ );
146
+ });
147
+
148
+ describe("from an interface", () => {
149
+ it("renders an empty class", async () => {
150
+ const { TestInterface } = (await runner.compile(`
151
+ @test interface TestInterface {
152
+ }
153
+ `)) as { TestInterface: Interface };
154
+
155
+ const res = render(
156
+ <Output program={runner.program}>
157
+ <Namespace name="TestNamespace">
158
+ <SourceFile path="test.cs">
159
+ <ClassDeclaration type={TestInterface} />
160
+ </SourceFile>
161
+ </Namespace>
162
+ </Output>,
163
+ );
164
+
165
+ assertFileContents(
166
+ res,
167
+ d`
168
+ namespace TestNamespace
169
+ {
170
+ class TestInterface
171
+ {
172
+
173
+ }
174
+ }
175
+ `,
176
+ );
177
+ });
178
+
179
+ it("renders a class with operations", async () => {
180
+ const { TestInterface } = (await runner.compile(`
181
+ @test interface TestInterface {
182
+ op getName(id: string): string;
183
+ }
184
+ `)) as { TestInterface: Interface };
185
+
186
+ const res = render(
187
+ <Output program={runner.program} namePolicy={cs.createCSharpNamePolicy()}>
188
+ <Namespace name="TestNamespace">
189
+ <SourceFile path="test.cs">
190
+ <ClassDeclaration type={TestInterface} />
191
+ </SourceFile>
192
+ </Namespace>
193
+ </Output>,
194
+ );
195
+
196
+ assertFileContents(
197
+ res,
198
+ d`
199
+ namespace TestNamespace
200
+ {
201
+ class TestInterface
202
+ {
203
+ public abstract string GetName(string id) {}
204
+ }
205
+ }
206
+ `,
207
+ );
208
+ });
209
+ });
210
+
211
+ it("renders a class with model members", async () => {
212
+ const { TestModel, TestReference } = (await runner.compile(`
213
+ @test model TestReference {
214
+ }
215
+ @test model TestModel {
216
+ @test prop1: TestReference;
217
+ }
218
+ `)) as { TestModel: Model; TestReference: Model };
219
+
220
+ const res = render(
221
+ <Output program={runner.program} namePolicy={cs.createCSharpNamePolicy()}>
222
+ <Namespace name="TestNamespace">
223
+ <SourceFile path="test.cs">
224
+ <ClassDeclaration type={TestReference} />
225
+ <hbr />
226
+ <ClassDeclaration type={TestModel} />
227
+ </SourceFile>
228
+ </Namespace>
229
+ </Output>,
230
+ );
231
+
232
+ assertFileContents(
233
+ res,
234
+ d`
235
+ namespace TestNamespace
236
+ {
237
+ class TestReference
238
+ {
239
+
240
+ }
241
+ class TestModel
242
+ {
243
+ public TestReference Prop1
244
+ {
245
+ get;
246
+ set;
247
+ }
248
+ }
249
+ }
250
+ `,
251
+ );
252
+ });
253
+
254
+ it("renders a class with enum members", async () => {
255
+ const { TestModel, TestEnum } = (await runner.compile(`
256
+ @test enum TestEnum {
257
+ Value1;
258
+ Value2;
259
+ }
260
+ @test model TestModel {
261
+ @test prop1: TestEnum;
262
+ }
263
+ `)) as { TestModel: Model; TestEnum: Enum };
264
+
265
+ const res = render(
266
+ <Output program={runner.program} namePolicy={cs.createCSharpNamePolicy()}>
267
+ <Namespace name="TestNamespace">
268
+ <SourceFile path="test.cs">
269
+ <EnumDeclaration type={TestEnum} />
270
+ <hbr />
271
+ <ClassDeclaration type={TestModel} />
272
+ </SourceFile>
273
+ </Namespace>
274
+ </Output>,
275
+ );
276
+
277
+ assertFileContents(
278
+ res,
279
+ d`
280
+ namespace TestNamespace
281
+ {
282
+ public enum TestEnum
283
+ {
284
+ Value1,
285
+ Value2
286
+ }
287
+ class TestModel
288
+ {
289
+ public TestEnum Prop1
290
+ {
291
+ get;
292
+ set;
293
+ }
294
+ }
295
+ }
296
+ `,
297
+ );
298
+ });
299
+
300
+ it("renders a class with string enums", async () => {
301
+ const { TestModel, TestEnum } = (await runner.compile(`
302
+ @test enum TestEnum {
303
+ Value1;
304
+ Value2;
305
+ }
306
+ @test model TestModel {
307
+ @test prop1: TestEnum;
308
+ }
309
+ `)) as { TestModel: Model; TestEnum: Enum };
310
+
311
+ const res = render(
312
+ <Output program={runner.program} namePolicy={cs.createCSharpNamePolicy()}>
313
+ <Namespace name="TestNamespace">
314
+ <SourceFile path="test.cs">
315
+ <EnumDeclaration type={TestEnum} />
316
+ <hbr />
317
+ <ClassDeclaration type={TestModel} />
318
+ </SourceFile>
319
+ </Namespace>
320
+ </Output>,
321
+ );
322
+
323
+ assertFileContents(
324
+ res,
325
+ d`
326
+ namespace TestNamespace
327
+ {
328
+ public enum TestEnum
329
+ {
330
+ Value1,
331
+ Value2
332
+ }
333
+ class TestModel
334
+ {
335
+ public TestEnum Prop1
336
+ {
337
+ get;
338
+ set;
339
+ }
340
+ }
341
+ }
342
+ `,
343
+ );
344
+ });
@@ -0,0 +1,296 @@
1
+ import { render } from "@alloy-js/core";
2
+ import { d } from "@alloy-js/core/testing";
3
+ import * as cs from "@alloy-js/csharp";
4
+ import { Namespace, SourceFile } from "@alloy-js/csharp";
5
+ import { Enum } from "@typespec/compiler";
6
+ import { BasicTestRunner } from "@typespec/compiler/testing";
7
+ import { beforeEach, it } from "vitest";
8
+ import { Output } from "../../../src/core/index.js";
9
+ import { EnumDeclaration } from "../../../src/csharp/index.js";
10
+ import { createEmitterFrameworkTestRunner } from "../test-host.js";
11
+ import { assertFileContents } from "../utils.js";
12
+
13
+ let runner: BasicTestRunner;
14
+
15
+ beforeEach(async () => {
16
+ runner = await createEmitterFrameworkTestRunner();
17
+ });
18
+
19
+ it("renders a basic enum declaration", async () => {
20
+ const { TestEnum } = (await runner.compile(`
21
+ @test enum TestEnum {
22
+ Value1;
23
+ Value2;
24
+ Value3;
25
+ }
26
+ `)) as { TestEnum: Enum };
27
+
28
+ const res = render(
29
+ <Output program={runner.program}>
30
+ <Namespace name="TestNamespace">
31
+ <SourceFile path="test.cs">
32
+ <EnumDeclaration type={TestEnum} />
33
+ </SourceFile>
34
+ </Namespace>
35
+ </Output>,
36
+ );
37
+
38
+ assertFileContents(
39
+ res,
40
+ d`
41
+ namespace TestNamespace
42
+ {
43
+ public enum TestEnum
44
+ {
45
+ Value1,
46
+ Value2,
47
+ Value3
48
+ }
49
+ }
50
+ `,
51
+ );
52
+ });
53
+
54
+ it("renders an empty enum declaration", async () => {
55
+ const { TestEnum } = (await runner.compile(`
56
+ @test enum TestEnum {}
57
+ `)) as { TestEnum: Enum };
58
+
59
+ const res = render(
60
+ <Output program={runner.program}>
61
+ <Namespace name="TestNamespace">
62
+ <SourceFile path="test.cs">
63
+ <EnumDeclaration type={TestEnum} />
64
+ </SourceFile>
65
+ </Namespace>
66
+ </Output>,
67
+ );
68
+
69
+ assertFileContents(
70
+ res,
71
+ d`
72
+ namespace TestNamespace
73
+ {
74
+ public enum TestEnum
75
+ {
76
+
77
+ }
78
+ }
79
+ `,
80
+ );
81
+ });
82
+
83
+ it("can override enum name", async () => {
84
+ const { TestEnum } = (await runner.compile(`
85
+ @test enum TestEnum {
86
+ Value1;
87
+ Value2;
88
+ }
89
+ `)) as { TestEnum: Enum };
90
+
91
+ const res = render(
92
+ <Output program={runner.program}>
93
+ <Namespace name="TestNamespace">
94
+ <SourceFile path="test.cs">
95
+ <EnumDeclaration type={TestEnum} name="CustomEnumName" />
96
+ </SourceFile>
97
+ </Namespace>
98
+ </Output>,
99
+ );
100
+
101
+ assertFileContents(
102
+ res,
103
+ d`
104
+ namespace TestNamespace
105
+ {
106
+ public enum CustomEnumName
107
+ {
108
+ Value1,
109
+ Value2
110
+ }
111
+ }
112
+ `,
113
+ );
114
+ });
115
+
116
+ it("renders an enum with access modifiers", async () => {
117
+ const { TestEnum } = (await runner.compile(`
118
+ @test enum TestEnum {
119
+ Value1;
120
+ Value2;
121
+ }
122
+ `)) as { TestEnum: Enum };
123
+
124
+ const res = render(
125
+ <Output program={runner.program}>
126
+ <Namespace name="TestNamespace">
127
+ <SourceFile path="test.cs">
128
+ <EnumDeclaration type={TestEnum} accessModifier="internal" />
129
+ </SourceFile>
130
+ </Namespace>
131
+ </Output>,
132
+ );
133
+
134
+ assertFileContents(
135
+ res,
136
+ d`
137
+ namespace TestNamespace
138
+ {
139
+ internal enum TestEnum
140
+ {
141
+ Value1,
142
+ Value2
143
+ }
144
+ }
145
+ `,
146
+ );
147
+ });
148
+
149
+ it("renders enum with C# naming conventions", async () => {
150
+ const { TestEnum } = (await runner.compile(`
151
+ @test enum TestEnum {
152
+ value_one;
153
+ value_two;
154
+ value_three;
155
+ }
156
+ `)) as { TestEnum: Enum };
157
+
158
+ const res = render(
159
+ <Output program={runner.program} namePolicy={cs.createCSharpNamePolicy()}>
160
+ <Namespace name="TestNamespace">
161
+ <SourceFile path="test.cs">
162
+ <EnumDeclaration type={TestEnum} />
163
+ </SourceFile>
164
+ </Namespace>
165
+ </Output>,
166
+ );
167
+
168
+ assertFileContents(
169
+ res,
170
+ d`
171
+ namespace TestNamespace
172
+ {
173
+ public enum TestEnum
174
+ {
175
+ ValueOne,
176
+ ValueTwo,
177
+ ValueThree
178
+ }
179
+ }
180
+ `,
181
+ );
182
+ });
183
+
184
+ it("renders enum with single value", async () => {
185
+ const { TestEnum } = (await runner.compile(`
186
+ @test enum TestEnum {
187
+ OnlyValue;
188
+ }
189
+ `)) as { TestEnum: Enum };
190
+
191
+ const res = render(
192
+ <Output program={runner.program}>
193
+ <Namespace name="TestNamespace">
194
+ <SourceFile path="test.cs">
195
+ <EnumDeclaration type={TestEnum} />
196
+ </SourceFile>
197
+ </Namespace>
198
+ </Output>,
199
+ );
200
+
201
+ assertFileContents(
202
+ res,
203
+ d`
204
+ namespace TestNamespace
205
+ {
206
+ public enum TestEnum
207
+ {
208
+ OnlyValue
209
+ }
210
+ }
211
+ `,
212
+ );
213
+ });
214
+
215
+ it("renders enum with numeric-like member names", async () => {
216
+ const { TestEnum } = (await runner.compile(`
217
+ @test enum TestEnum {
218
+ Value0;
219
+ Value1;
220
+ Value10;
221
+ Value100;
222
+ }
223
+ `)) as { TestEnum: Enum };
224
+
225
+ const res = render(
226
+ <Output program={runner.program}>
227
+ <Namespace name="TestNamespace">
228
+ <SourceFile path="test.cs">
229
+ <EnumDeclaration type={TestEnum} />
230
+ </SourceFile>
231
+ </Namespace>
232
+ </Output>,
233
+ );
234
+
235
+ assertFileContents(
236
+ res,
237
+ d`
238
+ namespace TestNamespace
239
+ {
240
+ public enum TestEnum
241
+ {
242
+ Value0,
243
+ Value1,
244
+ Value10,
245
+ Value100
246
+ }
247
+ }
248
+ `,
249
+ );
250
+ });
251
+
252
+ it("renders multiple enums in the same namespace", async () => {
253
+ const { TestEnum1, TestEnum2 } = (await runner.compile(`
254
+ @test enum TestEnum1 {
255
+ Value1;
256
+ Value2;
257
+ }
258
+ @test enum TestEnum2 {
259
+ OptionA;
260
+ OptionB;
261
+ OptionC;
262
+ }
263
+ `)) as { TestEnum1: Enum; TestEnum2: Enum };
264
+
265
+ const res = render(
266
+ <Output program={runner.program}>
267
+ <Namespace name="TestNamespace">
268
+ <SourceFile path="test.cs">
269
+ <EnumDeclaration type={TestEnum1} />
270
+ <hbr />
271
+ <EnumDeclaration type={TestEnum2} />
272
+ </SourceFile>
273
+ </Namespace>
274
+ </Output>,
275
+ );
276
+
277
+ assertFileContents(
278
+ res,
279
+ d`
280
+ namespace TestNamespace
281
+ {
282
+ public enum TestEnum1
283
+ {
284
+ Value1,
285
+ Value2
286
+ }
287
+ public enum TestEnum2
288
+ {
289
+ OptionA,
290
+ OptionB,
291
+ OptionC
292
+ }
293
+ }
294
+ `,
295
+ );
296
+ });
@@ -0,0 +1,42 @@
1
+ import { Program } from "@typespec/compiler";
2
+ import {
3
+ createTestHost,
4
+ createTestWrapper,
5
+ expectDiagnosticEmpty,
6
+ TypeSpecTestLibrary,
7
+ } from "@typespec/compiler/testing";
8
+ import { HttpTestLibrary } from "@typespec/http/testing";
9
+
10
+ export async function createTypespecCliTestHost(
11
+ options: { libraries: "Http"[] } = { libraries: [] },
12
+ ) {
13
+ const libraries: TypeSpecTestLibrary[] = [];
14
+ if (options.libraries.includes("Http")) {
15
+ libraries.push(HttpTestLibrary);
16
+ }
17
+ return createTestHost({
18
+ libraries,
19
+ });
20
+ }
21
+
22
+ export async function createEmitterFrameworkTestRunner(options: { autoUsings?: string[] } = {}) {
23
+ const host = await createTypespecCliTestHost();
24
+ return createTestWrapper(host, {
25
+ autoUsings: options.autoUsings,
26
+ });
27
+ }
28
+
29
+ export async function getProgram(
30
+ code: string,
31
+ options: { libraries: "Http"[] } = { libraries: [] },
32
+ ): Promise<Program> {
33
+ const host = await createTypespecCliTestHost(options);
34
+ const wrapper = createTestWrapper(host, {
35
+ compilerOptions: {
36
+ noEmit: true,
37
+ },
38
+ });
39
+ const [_, diagnostics] = await wrapper.compileAndDiagnose(code);
40
+ expectDiagnosticEmpty(diagnostics);
41
+ return wrapper.program;
42
+ }