@typespec/emitter-framework 0.11.0-dev.0 → 0.11.0-dev.10

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 (212) hide show
  1. package/dist/src/core/components/index.d.ts +2 -0
  2. package/dist/src/core/components/index.d.ts.map +1 -1
  3. package/dist/src/core/components/index.js +4 -1
  4. package/dist/src/core/components/index.js.map +1 -0
  5. package/dist/src/core/components/output.js +2 -1
  6. package/dist/src/core/components/output.js.map +1 -0
  7. package/dist/src/core/components/overrides/component-overrides.d.ts +64 -0
  8. package/dist/src/core/components/overrides/component-overrides.d.ts.map +1 -0
  9. package/dist/src/core/components/overrides/component-overrides.js +41 -0
  10. package/dist/src/core/components/overrides/component-overrides.js.map +1 -0
  11. package/dist/src/core/components/overrides/config.d.ts +28 -0
  12. package/dist/src/core/components/overrides/config.d.ts.map +1 -0
  13. package/dist/src/core/components/overrides/config.js +55 -0
  14. package/dist/src/core/components/overrides/config.js.map +1 -0
  15. package/dist/src/core/components/overrides/context.d.ts +11 -0
  16. package/dist/src/core/components/overrides/context.d.ts.map +1 -0
  17. package/dist/src/core/components/overrides/context.js +9 -0
  18. package/dist/src/core/components/overrides/context.js.map +1 -0
  19. package/dist/src/core/context/index.js +2 -1
  20. package/dist/src/core/context/index.js.map +1 -0
  21. package/dist/src/core/context/name-policy-context.js +2 -1
  22. package/dist/src/core/context/name-policy-context.js.map +1 -0
  23. package/dist/src/core/context/tsp-context.js +2 -1
  24. package/dist/src/core/context/tsp-context.js.map +1 -0
  25. package/dist/src/core/index.js +2 -1
  26. package/dist/src/core/index.js.map +1 -0
  27. package/dist/src/core/transport-name-policy.js +2 -1
  28. package/dist/src/core/transport-name-policy.js.map +1 -0
  29. package/dist/src/core/write-output.d.ts.map +1 -1
  30. package/dist/src/core/write-output.js +4 -3
  31. package/dist/src/core/write-output.js.map +1 -0
  32. package/dist/src/csharp/components/class/declaration.d.ts +1 -1
  33. package/dist/src/csharp/components/class/declaration.d.ts.map +1 -1
  34. package/dist/src/csharp/components/class/declaration.js +14 -5
  35. package/dist/src/csharp/components/class/declaration.js.map +1 -0
  36. package/dist/src/csharp/components/class/declaration.test.js +125 -92
  37. package/dist/src/csharp/components/class/declaration.test.js.map +1 -0
  38. package/dist/src/csharp/components/enum/declaration.js +2 -1
  39. package/dist/src/csharp/components/enum/declaration.js.map +1 -0
  40. package/dist/src/csharp/components/enum/declaration.test.js +52 -88
  41. package/dist/src/csharp/components/enum/declaration.test.js.map +1 -0
  42. package/dist/src/csharp/components/index.js +2 -1
  43. package/dist/src/csharp/components/index.js.map +1 -0
  44. package/dist/src/csharp/components/property/property.d.ts.map +1 -1
  45. package/dist/src/csharp/components/property/property.js +40 -14
  46. package/dist/src/csharp/components/property/property.js.map +1 -0
  47. package/dist/src/csharp/components/property/property.test.js +129 -29
  48. package/dist/src/csharp/components/property/property.test.js.map +1 -0
  49. package/dist/src/csharp/components/type-expression.d.ts.map +1 -1
  50. package/dist/src/csharp/components/type-expression.js +34 -6
  51. package/dist/src/csharp/components/type-expression.js.map +1 -0
  52. package/dist/src/csharp/components/type-expression.test.js +87 -54
  53. package/dist/src/csharp/components/type-expression.test.js.map +1 -0
  54. package/dist/src/csharp/components/utils/doc-comments.js +2 -1
  55. package/dist/src/csharp/components/utils/doc-comments.js.map +1 -0
  56. package/dist/src/csharp/components/utils/nullable-util.d.ts +4 -0
  57. package/dist/src/csharp/components/utils/nullable-util.d.ts.map +1 -0
  58. package/dist/src/csharp/components/utils/nullable-util.js +27 -0
  59. package/dist/src/csharp/components/utils/nullable-util.js.map +1 -0
  60. package/dist/src/csharp/components/utils/refkey.js +2 -1
  61. package/dist/src/csharp/components/utils/refkey.js.map +1 -0
  62. package/dist/src/csharp/index.js +2 -1
  63. package/dist/src/csharp/index.js.map +1 -0
  64. package/dist/src/lib.js +2 -1
  65. package/dist/src/lib.js.map +1 -0
  66. package/dist/src/testing/index.js +2 -1
  67. package/dist/src/testing/index.js.map +1 -0
  68. package/dist/src/testing/scenario-test/code-block-expectation.js +2 -1
  69. package/dist/src/testing/scenario-test/code-block-expectation.js.map +1 -0
  70. package/dist/src/testing/scenario-test/code-block-expectation.test.js +2 -1
  71. package/dist/src/testing/scenario-test/code-block-expectation.test.js.map +1 -0
  72. package/dist/src/testing/scenario-test/harness.js +8 -2
  73. package/dist/src/testing/scenario-test/harness.js.map +1 -0
  74. package/dist/src/testing/scenario-test/index.js +2 -1
  75. package/dist/src/testing/scenario-test/index.js.map +1 -0
  76. package/dist/src/testing/scenario-test/snippet-extractor.js +2 -1
  77. package/dist/src/testing/scenario-test/snippet-extractor.js.map +1 -0
  78. package/dist/src/typescript/components/array-expression.js +2 -1
  79. package/dist/src/typescript/components/array-expression.js.map +1 -0
  80. package/dist/src/typescript/components/arrow-function.js +2 -1
  81. package/dist/src/typescript/components/arrow-function.js.map +1 -0
  82. package/dist/src/typescript/components/class-method.js +2 -1
  83. package/dist/src/typescript/components/class-method.js.map +1 -0
  84. package/dist/src/typescript/components/enum-declaration.js +2 -1
  85. package/dist/src/typescript/components/enum-declaration.js.map +1 -0
  86. package/dist/src/typescript/components/function-declaration.js +2 -1
  87. package/dist/src/typescript/components/function-declaration.js.map +1 -0
  88. package/dist/src/typescript/components/function-expression.js +2 -1
  89. package/dist/src/typescript/components/function-expression.js.map +1 -0
  90. package/dist/src/typescript/components/function-type.js +2 -1
  91. package/dist/src/typescript/components/function-type.js.map +1 -0
  92. package/dist/src/typescript/components/index.js +2 -1
  93. package/dist/src/typescript/components/index.js.map +1 -0
  94. package/dist/src/typescript/components/interface-declaration.js +2 -1
  95. package/dist/src/typescript/components/interface-declaration.js.map +1 -0
  96. package/dist/src/typescript/components/interface-member.d.ts.map +1 -1
  97. package/dist/src/typescript/components/interface-member.js +13 -20
  98. package/dist/src/typescript/components/interface-member.js.map +1 -0
  99. package/dist/src/typescript/components/interface-method.js +2 -1
  100. package/dist/src/typescript/components/interface-method.js.map +1 -0
  101. package/dist/src/typescript/components/record-expression.js +2 -1
  102. package/dist/src/typescript/components/record-expression.js.map +1 -0
  103. package/dist/src/typescript/components/static-serializers.js +2 -1
  104. package/dist/src/typescript/components/static-serializers.js.map +1 -0
  105. package/dist/src/typescript/components/type-alias-declaration.js +2 -1
  106. package/dist/src/typescript/components/type-alias-declaration.js.map +1 -0
  107. package/dist/src/typescript/components/type-declaration.js +2 -1
  108. package/dist/src/typescript/components/type-declaration.js.map +1 -0
  109. package/dist/src/typescript/components/type-expression.d.ts +0 -1
  110. package/dist/src/typescript/components/type-expression.d.ts.map +1 -1
  111. package/dist/src/typescript/components/type-expression.js +13 -12
  112. package/dist/src/typescript/components/type-expression.js.map +1 -0
  113. package/dist/src/typescript/components/type-transform.d.ts.map +1 -1
  114. package/dist/src/typescript/components/type-transform.js +5 -4
  115. package/dist/src/typescript/components/type-transform.js.map +1 -0
  116. package/dist/src/typescript/components/union/declaration.js +2 -1
  117. package/dist/src/typescript/components/union/declaration.js.map +1 -0
  118. package/dist/src/typescript/components/union/declaration.test.js +2 -1
  119. package/dist/src/typescript/components/union/declaration.test.js.map +1 -0
  120. package/dist/src/typescript/components/union/expression.js +2 -1
  121. package/dist/src/typescript/components/union/expression.js.map +1 -0
  122. package/dist/src/typescript/components/union/expression.test.js +2 -1
  123. package/dist/src/typescript/components/union/expression.test.js.map +1 -0
  124. package/dist/src/typescript/components/value-expression.js +2 -1
  125. package/dist/src/typescript/components/value-expression.js.map +1 -0
  126. package/dist/src/typescript/index.js +2 -1
  127. package/dist/src/typescript/index.js.map +1 -0
  128. package/dist/src/typescript/lib.js +2 -1
  129. package/dist/src/typescript/lib.js.map +1 -0
  130. package/dist/src/typescript/utils/index.js +2 -1
  131. package/dist/src/typescript/utils/index.js.map +1 -0
  132. package/dist/src/typescript/utils/operation.js +2 -1
  133. package/dist/src/typescript/utils/operation.js.map +1 -0
  134. package/dist/src/typescript/utils/refkey.js +2 -1
  135. package/dist/src/typescript/utils/refkey.js.map +1 -0
  136. package/dist/test/test-host.js +2 -1
  137. package/dist/test/test-host.js.map +1 -0
  138. package/dist/test/testing/snippet-extractor-csharp.test.js +2 -1
  139. package/dist/test/testing/snippet-extractor-csharp.test.js.map +1 -0
  140. package/dist/test/testing/snippet-extractor-java.test.js +2 -1
  141. package/dist/test/testing/snippet-extractor-java.test.js.map +1 -0
  142. package/dist/test/testing/snippet-extractor-python.test.js +2 -1
  143. package/dist/test/testing/snippet-extractor-python.test.js.map +1 -0
  144. package/dist/test/testing/snippet-extractor-typescript.test.js +2 -1
  145. package/dist/test/testing/snippet-extractor-typescript.test.js.map +1 -0
  146. package/dist/test/typescript/components/arrow-function.test.js +18 -22
  147. package/dist/test/typescript/components/arrow-function.test.js.map +1 -0
  148. package/dist/test/typescript/components/component-override.test.d.ts +2 -0
  149. package/dist/test/typescript/components/component-override.test.d.ts.map +1 -0
  150. package/dist/test/typescript/components/component-override.test.js +78 -0
  151. package/dist/test/typescript/components/component-override.test.js.map +1 -0
  152. package/dist/test/typescript/components/enum-declaration.test.js +2 -1
  153. package/dist/test/typescript/components/enum-declaration.test.js.map +1 -0
  154. package/dist/test/typescript/components/function-declaration.test.js +3 -2
  155. package/dist/test/typescript/components/function-declaration.test.js.map +1 -0
  156. package/dist/test/typescript/components/function-expression.test.js +18 -22
  157. package/dist/test/typescript/components/function-expression.test.js.map +1 -0
  158. package/dist/test/typescript/components/function-type.test.js +18 -22
  159. package/dist/test/typescript/components/function-type.test.js.map +1 -0
  160. package/dist/test/typescript/components/interface-declaration.test.js +3 -2
  161. package/dist/test/typescript/components/interface-declaration.test.js.map +1 -0
  162. package/dist/test/typescript/components/interface-method.test.js +37 -45
  163. package/dist/test/typescript/components/interface-method.test.js.map +1 -0
  164. package/dist/test/typescript/components/member-expression.test.js +2 -1
  165. package/dist/test/typescript/components/member-expression.test.js.map +1 -0
  166. package/dist/test/typescript/components/type-alias-declaration.test.js +8 -5
  167. package/dist/test/typescript/components/type-alias-declaration.test.js.map +1 -0
  168. package/dist/test/typescript/components/type-transform.test.js +29 -27
  169. package/dist/test/typescript/components/type-transform.test.js.map +1 -0
  170. package/dist/test/typescript/components/value-expression.test.js +3 -2
  171. package/dist/test/typescript/components/value-expression.test.js.map +1 -0
  172. package/dist/test/typescript/utils.js +2 -1
  173. package/dist/test/typescript/utils.js.map +1 -0
  174. package/dist/test/utils.d.ts +1 -0
  175. package/dist/test/utils.d.ts.map +1 -1
  176. package/dist/test/utils.js +13 -3
  177. package/dist/test/utils.js.map +1 -0
  178. package/dist/test/vitest.setup.js +2 -1
  179. package/dist/test/vitest.setup.js.map +1 -0
  180. package/package.json +9 -13
  181. package/src/core/components/index.tsx +2 -0
  182. package/src/core/components/overrides/component-overrides.tsx +134 -0
  183. package/src/core/components/overrides/config.ts +85 -0
  184. package/src/core/components/overrides/context.ts +14 -0
  185. package/src/core/write-output.ts +2 -2
  186. package/src/csharp/components/class/declaration.test.tsx +114 -88
  187. package/src/csharp/components/class/declaration.tsx +13 -3
  188. package/src/csharp/components/enum/declaration.test.tsx +47 -78
  189. package/src/csharp/components/property/property.test.tsx +108 -25
  190. package/src/csharp/components/property/property.tsx +43 -11
  191. package/src/csharp/components/type-expression.test.tsx +84 -69
  192. package/src/csharp/components/type-expression.tsx +31 -5
  193. package/src/csharp/components/utils/nullable-util.ts +25 -0
  194. package/src/testing/scenario-test/harness.ts +6 -1
  195. package/src/typescript/components/interface-member.tsx +8 -14
  196. package/src/typescript/components/type-expression.tsx +7 -8
  197. package/src/typescript/components/type-transform.tsx +3 -7
  198. package/test/typescript/components/arrow-function.test.tsx +23 -42
  199. package/test/typescript/components/component-override.test.tsx +71 -0
  200. package/test/typescript/components/function-declaration.test.tsx +2 -1
  201. package/test/typescript/components/function-expression.test.tsx +23 -42
  202. package/test/typescript/components/function-type.test.tsx +22 -40
  203. package/test/typescript/components/interface-declaration.test.tsx +1 -1
  204. package/test/typescript/components/interface-method.test.tsx +50 -92
  205. package/test/typescript/components/type-alias-declaration.test.tsx +8 -6
  206. package/test/typescript/components/type-transform.test.tsx +37 -37
  207. package/test/typescript/components/value-expression.test.tsx +1 -1
  208. package/test/utils.ts +10 -3
  209. package/dist/test/typescript/test-host.d.ts +0 -16
  210. package/dist/test/typescript/test-host.d.ts.map +0 -1
  211. package/dist/test/typescript/test-host.js +0 -40
  212. package/test/typescript/test-host.ts +0 -49
@@ -1,6 +1,6 @@
1
1
  import { Tester } from "#test/test-host.js";
2
- import { type Children } from "@alloy-js/core";
3
- import { ClassDeclaration, createCSharpNamePolicy, Namespace, SourceFile } from "@alloy-js/csharp";
2
+ import { List, type Children } from "@alloy-js/core";
3
+ import { ClassDeclaration, createCSharpNamePolicy, SourceFile } from "@alloy-js/csharp";
4
4
  import { t, type TesterInstance } from "@typespec/compiler/testing";
5
5
  import { beforeEach, describe, expect, it } from "vitest";
6
6
  import { Output } from "../../../core/components/output.jsx";
@@ -16,11 +16,9 @@ function Wrapper(props: { children: Children }) {
16
16
  const policy = createCSharpNamePolicy();
17
17
  return (
18
18
  <Output program={tester.program} namePolicy={policy}>
19
- <Namespace name="TestNamespace">
20
- <SourceFile path="test.cs">
21
- <ClassDeclaration name="Test">{props.children}</ClassDeclaration>
22
- </SourceFile>
23
- </Namespace>
19
+ <SourceFile path="test.cs">
20
+ <ClassDeclaration name="Test">{props.children}</ClassDeclaration>
21
+ </SourceFile>
24
22
  </Output>
25
23
  );
26
24
  }
@@ -37,12 +35,47 @@ it("maps prop: string | null to nullable property", async () => {
37
35
  <Property type={prop1} />
38
36
  </Wrapper>,
39
37
  ).toRenderTo(`
40
- namespace TestNamespace
38
+ class Test
41
39
  {
42
- class Test
43
- {
44
- public required string? Prop1 { get; set; }
45
- }
40
+ public required string? Prop1 { get; set; }
41
+ }
42
+ `);
43
+ });
44
+
45
+ it("maps optional properties to nullable properties", async () => {
46
+ const { prop1 } = await tester.compile(t.code`
47
+ model TestModel {
48
+ ${t.modelProperty("prop1")}?: string;
49
+ }
50
+ `);
51
+
52
+ expect(
53
+ <Wrapper>
54
+ <Property type={prop1} />
55
+ </Wrapper>,
56
+ ).toRenderTo(`
57
+ class Test
58
+ {
59
+ public string? Prop1 { get; set; }
60
+ }
61
+ `);
62
+ });
63
+
64
+ it("maps optional and nullable properties to nullable properties", async () => {
65
+ const { prop1 } = await tester.compile(t.code`
66
+ model TestModel {
67
+ ${t.modelProperty("prop1")}?: string | null;
68
+ }
69
+ `);
70
+
71
+ expect(
72
+ <Wrapper>
73
+ <Property type={prop1} />
74
+ </Wrapper>,
75
+ ).toRenderTo(`
76
+ class Test
77
+ {
78
+ public string? Prop1 { get; set; }
46
79
  }
47
80
  `);
48
81
  });
@@ -60,13 +93,10 @@ describe("jsonAttributes", () => {
60
93
  <Property type={prop1} jsonAttributes />
61
94
  </Wrapper>,
62
95
  ).toRenderTo(`
63
- namespace TestNamespace
96
+ class Test
64
97
  {
65
- class Test
66
- {
67
- [System.Text.Json.JsonPropertyName("prop1")]
68
- public required string Prop1 { get; set; }
69
- }
98
+ [System.Text.Json.JsonPropertyName("prop1")]
99
+ public required string Prop1 { get; set; }
70
100
  }
71
101
  `);
72
102
  });
@@ -84,14 +114,67 @@ describe("jsonAttributes", () => {
84
114
  <Property type={prop1} jsonAttributes />
85
115
  </Wrapper>,
86
116
  ).toRenderTo(`
87
- namespace TestNamespace
117
+ class Test
118
+ {
119
+ [System.Text.Json.JsonPropertyName("prop_1")]
120
+ public required string Prop1 { get; set; }
121
+ }
122
+ `);
123
+ });
124
+
125
+ it("inherit prop: override, new", async () => {
126
+ const r = await tester.compile(t.code`
127
+ model TestModel extends BaseModel {
128
+ ${t.modelProperty("prop1")}: string;
129
+ ${t.modelProperty("prop2")}: string | null;
130
+ }
131
+ model BaseModel {
132
+ prop1: string | null;
133
+ prop2: string | null;
134
+ }
135
+ `);
136
+
137
+ expect(
138
+ <Wrapper>
139
+ <List>
140
+ <Property type={r.prop1} />
141
+ <Property type={r.prop2} />
142
+ </List>
143
+ </Wrapper>,
144
+ ).toRenderTo(`
145
+ class Test
88
146
  {
89
- class Test
90
- {
91
- [System.Text.Json.JsonPropertyName("prop_1")]
92
- public required string Prop1 { get; set; }
93
- }
147
+ public new required string Prop1 { get; set; }
148
+ public override required string? Prop2 { get; set; }
94
149
  }
95
- `);
150
+ `);
151
+ });
152
+
153
+ it("inherit prop: virtual", async () => {
154
+ const r = await tester.compile(t.code`
155
+ model TestModel extends BaseModel {
156
+ prop1: string;
157
+ prop2: string | null;
158
+ }
159
+ model BaseModel {
160
+ ${t.modelProperty("prop1")}: string | null;
161
+ ${t.modelProperty("prop2")}: string | null;
162
+ }
163
+ `);
164
+
165
+ expect(
166
+ <Wrapper>
167
+ <List>
168
+ <Property type={r.prop1} />
169
+ <Property type={r.prop2} />
170
+ </List>
171
+ </Wrapper>,
172
+ ).toRenderTo(`
173
+ class Test
174
+ {
175
+ public required string? Prop1 { get; set; }
176
+ public virtual required string? Prop2 { get; set; }
177
+ }
178
+ `);
96
179
  });
97
180
  });
@@ -1,10 +1,11 @@
1
1
  import { type Children } from "@alloy-js/core";
2
2
  import * as cs from "@alloy-js/csharp";
3
3
  import { Attribute } from "@alloy-js/csharp";
4
- import { type ModelProperty, resolveEncodedName, type Type } from "@typespec/compiler";
4
+ import { getProperty, type ModelProperty, resolveEncodedName, type Type } from "@typespec/compiler";
5
5
  import { useTsp } from "../../../core/index.js";
6
6
  import { TypeExpression } from "../type-expression.jsx";
7
7
  import { getDocComments } from "../utils/doc-comments.jsx";
8
+ import { getNullableUnionInnerType } from "../utils/nullable-util.js";
8
9
 
9
10
  export interface PropertyProps {
10
11
  type: ModelProperty;
@@ -16,14 +17,47 @@ export interface PropertyProps {
16
17
  * Create a C# property declaration from a TypeSpec property type.
17
18
  */
18
19
  export function Property(props: PropertyProps): Children {
19
- const result = preprocessPropertyType(props.type.type);
20
+ const result = preprocessPropertyType(props.type);
20
21
  const { $ } = useTsp();
21
22
 
23
+ let overrideType: "" | "override" | "new" = "";
24
+ let isVirtual = false;
25
+ if (props.type.model) {
26
+ if (props.type.model.baseModel) {
27
+ const base = props.type.model.baseModel;
28
+ const baseProperty = getProperty(base, props.type.name);
29
+ if (baseProperty) {
30
+ const baseResult = preprocessPropertyType(baseProperty);
31
+ if (baseResult.nullable === result.nullable && baseResult.type === result.type) {
32
+ overrideType = "override";
33
+ } else {
34
+ overrideType = "new";
35
+ }
36
+ }
37
+ }
38
+ if (
39
+ overrideType === "" &&
40
+ props.type.model.derivedModels &&
41
+ props.type.model.derivedModels.length > 0
42
+ ) {
43
+ isVirtual = props.type.model.derivedModels.some((derived) => {
44
+ const derivedProperty = derived.properties.get(props.type.name);
45
+ if (derivedProperty) {
46
+ const derivedResult = preprocessPropertyType(derivedProperty);
47
+ return derivedResult.nullable === result.nullable && derivedResult.type === result.type;
48
+ }
49
+ });
50
+ }
51
+ }
52
+
22
53
  return (
23
54
  <cs.Property
24
55
  name={props.type.name}
25
56
  type={<TypeExpression type={result.type} />}
57
+ override={overrideType === "override"}
58
+ new={overrideType === "new"}
26
59
  public
60
+ virtual={isVirtual}
27
61
  required={!props.type.optional}
28
62
  nullable={result.nullable}
29
63
  doc={getDocComments($, props.type)}
@@ -44,19 +78,17 @@ function JsonNameAttribute(props: JsonNameAttributeProps): Children {
44
78
  return <Attribute name="System.Text.Json.JsonPropertyName" args={[JSON.stringify(jsonName)]} />;
45
79
  }
46
80
 
47
- function preprocessPropertyType(type: Type): { type: Type; nullable: boolean } {
48
- const { $ } = useTsp();
81
+ function preprocessPropertyType(prop: ModelProperty): { type: Type; nullable: boolean } {
82
+ const type = prop.type;
49
83
 
50
84
  if (type.kind === "Union") {
51
- const variants = type.variants;
52
- const nonNullVariant = [...variants.values()].find((v) => v.type !== $.intrinsic.null);
53
- const nullVariant = [...variants.values()].find((v) => v.type !== $.intrinsic.null);
54
- if (nonNullVariant && nullVariant && variants.size === 2) {
55
- return { type: nonNullVariant.type, nullable: true };
85
+ const innerType = getNullableUnionInnerType(type);
86
+ if (innerType) {
87
+ return { type: innerType, nullable: true };
56
88
  } else {
57
- return { type, nullable: false };
89
+ return { type, nullable: prop.optional };
58
90
  }
59
91
  } else {
60
- return { type, nullable: false };
92
+ return { type, nullable: prop.optional };
61
93
  }
62
94
  }
@@ -1,27 +1,23 @@
1
- import { type Children, render } from "@alloy-js/core";
2
- import { d } from "@alloy-js/core/testing";
3
- import { Namespace, SourceFile } from "@alloy-js/csharp";
4
- import type { Model, ModelProperty } from "@typespec/compiler";
5
- import type { BasicTestRunner } from "@typespec/compiler/testing";
6
- import { beforeEach, describe, it } from "vitest";
7
- import { createEmitterFrameworkTestRunner } from "../../../test/typescript/test-host.js";
8
- import { assertFileContents } from "../../../test/utils.js";
1
+ import { Tester } from "#test/test-host.js";
2
+ import { type Children } from "@alloy-js/core";
3
+ import { SourceFile } from "@alloy-js/csharp";
4
+ import type { ModelProperty } from "@typespec/compiler";
5
+ import { t, type TesterInstance } from "@typespec/compiler/testing";
6
+ import { beforeEach, describe, expect, it } from "vitest";
9
7
  import { Output } from "../../core/index.js";
10
8
  import { ClassDeclaration } from "./class/declaration.js";
11
9
  import { TypeExpression } from "./type-expression.jsx";
12
10
 
13
- let runner: BasicTestRunner;
11
+ let runner: TesterInstance;
14
12
 
15
13
  beforeEach(async () => {
16
- runner = await createEmitterFrameworkTestRunner();
14
+ runner = await Tester.createInstance();
17
15
  });
18
16
 
19
17
  function Wrapper(props: { children: Children }) {
20
18
  return (
21
19
  <Output program={runner.program}>
22
- <Namespace name="TestNamespace">
23
- <SourceFile path="test.ts">{props.children}</SourceFile>
24
- </Namespace>
20
+ <SourceFile path="test.ts">{props.children}</SourceFile>
25
21
  </Output>
26
22
  );
27
23
  }
@@ -43,91 +39,110 @@ describe("map scalar to c# built-in types", () => {
43
39
  ["int64", "long"],
44
40
  ])("%s => %s", async (tspType, csType) => {
45
41
  const type = await compileType(tspType);
46
- const res = render(
42
+ expect(
47
43
  <Wrapper>
48
44
  <TypeExpression type={type} />
49
45
  </Wrapper>,
50
- );
51
-
52
- assertFileContents(
53
- res,
54
- d`
55
- namespace TestNamespace
56
- {
57
- ${csType}
58
- }
59
- `,
60
- );
46
+ ).toRenderTo(`
47
+ ${csType}
48
+ `);
61
49
  });
62
50
  });
63
51
 
64
52
  it("maps array to c# array", async () => {
65
53
  const type = await compileType("int32[]");
66
- const res = render(
54
+ expect(
67
55
  <Wrapper>
68
56
  <TypeExpression type={type} />
69
57
  </Wrapper>,
70
- );
71
-
72
- assertFileContents(
73
- res,
74
- d`
75
- namespace TestNamespace
76
- {
77
- int[]
78
- }
79
- `,
80
- );
58
+ ).toRenderTo(`
59
+ int[]
60
+ `);
81
61
  });
82
62
 
83
63
  describe("Record map to IDictionary", () => {
84
64
  it("for primitive types", async () => {
85
65
  const type = await compileType("Record<int32>");
86
- const res = render(
66
+ expect(
87
67
  <Wrapper>
88
68
  <TypeExpression type={type} />
89
69
  </Wrapper>,
90
- );
91
-
92
- assertFileContents(
93
- res,
94
- d`
95
- namespace TestNamespace
96
- {
97
- IDictionary<string, int>
98
- }
99
- `,
100
- );
70
+ ).toRenderTo(`
71
+ IDictionary<string, int>
72
+ `);
101
73
  });
102
74
 
103
75
  it("for models", async () => {
104
- const { test, Pet } = (await runner.compile(`
76
+ const { test, Pet } = await runner.compile(t.code`
105
77
  model Test {
106
- @test test: Record<Pet>;
78
+ ${t.modelProperty("test")}: Record<Pet>;
107
79
  }
108
- @test model Pet {}
109
- `)) as { test: ModelProperty; Pet: Model };
80
+ @test model ${t.model("Pet")} {}
81
+ `);
110
82
 
111
- const res = render(
83
+ expect(
112
84
  <Wrapper>
113
85
  <ClassDeclaration type={Pet} />
114
86
  <hbr />
115
87
  <TypeExpression type={test.type} />
116
88
  </Wrapper>,
117
- );
118
-
119
- assertFileContents(
120
- res,
121
- d`
122
- namespace TestNamespace
123
- {
124
- class Pet
125
- {
126
-
127
- }
128
- IDictionary<string, Pet>
129
- }
130
- `,
131
- );
89
+ ).toRenderTo(`
90
+ class Pet
91
+ {
92
+
93
+ }
94
+ IDictionary<string, Pet>
95
+ `);
96
+ });
97
+ });
98
+
99
+ describe("Nullable union", () => {
100
+ it("nullable boolean", async () => {
101
+ const { Pet } = await runner.compile(t.code`
102
+ @test model ${t.model("Pet")} {
103
+ @test name: boolean | null;
104
+ }
105
+ `);
106
+
107
+ expect(
108
+ <Wrapper>
109
+ <ClassDeclaration type={Pet} />
110
+ </Wrapper>,
111
+ ).toRenderTo(`
112
+ class Pet
113
+ {
114
+ public required bool? name { get; set; }
115
+ }
116
+ `);
117
+ });
118
+ });
119
+
120
+ describe("Literal types", () => {
121
+ it("literal types (string, int, double, bool)", async () => {
122
+ const { Pet } = await runner.compile(t.code`
123
+ @test model ${t.model("Pet")} {
124
+ @test boolName: true;
125
+ @test intName: 42;
126
+ @test doubleName: 3.14;
127
+ @test stringName: "Hello";
128
+ }
129
+ `);
130
+
131
+ expect(
132
+ <Wrapper>
133
+ <ClassDeclaration type={Pet} />
134
+ </Wrapper>,
135
+ ).toRenderTo(`
136
+ class Pet
137
+ {
138
+ public required bool boolName { get; set; }
139
+
140
+ public required int intName { get; set; }
141
+
142
+ public required double doubleName { get; set; }
143
+
144
+ public required string stringName { get; set; }
145
+ }
146
+ `);
132
147
  });
133
148
  });
@@ -1,9 +1,17 @@
1
- import { type Children, code } from "@alloy-js/core";
1
+ import { Experimental_OverridableComponent } from "#core/index.js";
2
+ import { code, type Children } from "@alloy-js/core";
2
3
  import { Reference } from "@alloy-js/csharp";
3
- import { getTypeName, type IntrinsicType, type Scalar, type Type } from "@typespec/compiler";
4
+ import {
5
+ getTypeName,
6
+ isVoidType,
7
+ type IntrinsicType,
8
+ type Scalar,
9
+ type Type,
10
+ } from "@typespec/compiler";
4
11
  import type { Typekit } from "@typespec/compiler/typekit";
5
12
  import { useTsp } from "../../core/index.js";
6
13
  import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
14
+ import { getNullableUnionInnerType } from "./utils/nullable-util.js";
7
15
  import { efRefkey } from "./utils/refkey.js";
8
16
 
9
17
  export interface TypeExpressionProps {
@@ -11,9 +19,19 @@ export interface TypeExpressionProps {
11
19
  }
12
20
 
13
21
  export function TypeExpression(props: TypeExpressionProps): Children {
22
+ if (props.type.kind === "Union") {
23
+ const nullabletype = getNullableUnionInnerType(props.type);
24
+ if (nullabletype) {
25
+ return code`${(<TypeExpression type={nullabletype} />)}?`;
26
+ }
27
+ }
14
28
  const { $ } = useTsp();
15
29
  if (isDeclaration($, props.type)) {
16
- return <Reference refkey={efRefkey(props.type)} />;
30
+ return (
31
+ <Experimental_OverridableComponent reference type={props.type}>
32
+ <Reference refkey={efRefkey(props.type)} />
33
+ </Experimental_OverridableComponent>
34
+ );
17
35
  }
18
36
  if ($.scalar.is(props.type)) {
19
37
  return getScalarIntrinsicExpression($, props.type);
@@ -21,6 +39,15 @@ export function TypeExpression(props: TypeExpressionProps): Children {
21
39
  return code`${(<TypeExpression type={props.type.indexer.value} />)}[]`;
22
40
  } else if ($.record.is(props.type)) {
23
41
  return code`IDictionary<string, ${(<TypeExpression type={props.type.indexer.value} />)}>`;
42
+ } else if ($.literal.isString(props.type)) {
43
+ // c# doesn't have literal types, so we map them to their corresponding C# types in general
44
+ return code`string`;
45
+ } else if ($.literal.isNumeric(props.type)) {
46
+ return Number.isInteger(props.type.value) ? code`int` : code`double`;
47
+ } else if ($.literal.isBoolean(props.type)) {
48
+ return code`bool`;
49
+ } else if (isVoidType(props.type)) {
50
+ return code`void`;
24
51
  }
25
52
 
26
53
  throw new Error(
@@ -107,8 +134,7 @@ function isDeclaration($: Typekit, type: Type): boolean {
107
134
  if ($.array.is(type) || $.record.is(type)) {
108
135
  return false;
109
136
  }
110
-
111
- return Boolean(type.name);
137
+ return true;
112
138
  case "Union":
113
139
  return Boolean(type.name);
114
140
  default:
@@ -0,0 +1,25 @@
1
+ import { useTsp } from "#core/index.js";
2
+ import type { Type, Union } from "@typespec/compiler";
3
+
4
+ /** Get the inner type if the union is a nullable, otherwise return undefined */
5
+ export function getNullableUnionInnerType(u: Union): Type | undefined {
6
+ const { $ } = useTsp();
7
+ const isNull = (type: Type) => type === $.intrinsic.null || type === $.intrinsic.void;
8
+
9
+ if (Array.from(u.variants.values()).some((v) => isNull(v.type))) {
10
+ const { $ } = useTsp();
11
+ const left = Array.from(u.variants.values()).filter((v) => !isNull(v.type));
12
+ if (left.length === 0) {
13
+ // a union only has null or void?
14
+ return $.intrinsic.void;
15
+ } else if (left.length === 1) {
16
+ return left[0].type;
17
+ } else {
18
+ return $.union.create({
19
+ name: u.name,
20
+ variants: left,
21
+ });
22
+ }
23
+ }
24
+ return undefined;
25
+ }
@@ -198,7 +198,12 @@ function describeScenarios(
198
198
  );
199
199
 
200
200
  if (SCENARIOS_UPDATE) {
201
- testBlock.content = await languageConfiguration.format(result);
201
+ try {
202
+ testBlock.content = await languageConfiguration.format(result);
203
+ } catch {
204
+ // If formatting fails, we still want to update the content
205
+ testBlock.content = result;
206
+ }
202
207
  } else {
203
208
  const expected = await languageConfiguration.format(testBlock.content);
204
209
  const actual = await languageConfiguration.format(result);
@@ -1,7 +1,6 @@
1
1
  import { type Children } from "@alloy-js/core";
2
2
  import * as ts from "@alloy-js/typescript";
3
3
  import { isNeverType, type ModelProperty, type Operation } from "@typespec/compiler";
4
- import { getHttpPart } from "@typespec/http";
5
4
  import { useTsp } from "../../core/context/tsp-context.js";
6
5
  import { InterfaceMethod } from "./interface-method.jsx";
7
6
  import { TypeExpression } from "./type-expression.js";
@@ -23,20 +22,15 @@ export function InterfaceMember(props: InterfaceMemberProps) {
23
22
  return null;
24
23
  }
25
24
 
26
- let unpackedType = props.type.type;
27
- const part = getHttpPart($.program, props.type.type);
28
- if (part) {
29
- unpackedType = part.type;
30
- }
25
+ const unpackedType = props.type.type;
31
26
 
32
- return (
33
- <ts.InterfaceMember
34
- doc={doc}
35
- name={name}
36
- optional={props.optional ?? props.type.optional}
37
- type={<TypeExpression type={unpackedType} />}
38
- />
39
- );
27
+ const interfaceMemberProps = {
28
+ doc,
29
+ name,
30
+ optional: props.optional ?? props.type.optional,
31
+ type: <TypeExpression type={unpackedType} />,
32
+ };
33
+ return <ts.InterfaceMember {...interfaceMemberProps} />;
40
34
  }
41
35
 
42
36
  if ($.operation.is(props.type)) {
@@ -2,7 +2,7 @@ import { For } from "@alloy-js/core";
2
2
  import { Reference, ValueExpression } from "@alloy-js/typescript";
3
3
  import type { IntrinsicType, Model, Scalar, Type } from "@typespec/compiler";
4
4
  import type { Typekit } from "@typespec/compiler/typekit";
5
- import "@typespec/http/experimental/typekit";
5
+ import { Experimental_OverridableComponent } from "../../core/components/overrides/component-overrides.jsx";
6
6
  import { useTsp } from "../../core/context/tsp-context.js";
7
7
  import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
8
8
  import { efRefkey } from "../utils/refkey.js";
@@ -25,11 +25,15 @@ export interface TypeExpressionProps {
25
25
 
26
26
  export function TypeExpression(props: TypeExpressionProps) {
27
27
  const { $ } = useTsp();
28
- const type = $.httpPart.unpack(props.type);
28
+ const type = props.type;
29
29
  if (!props.noReference && isDeclaration($, type)) {
30
30
  // todo: probably need abstraction around deciding what's a declaration in the output
31
31
  // (it may not correspond to things which are declarations in TypeSpec?)
32
- return <Reference refkey={efRefkey(type)} />;
32
+ return (
33
+ <Experimental_OverridableComponent reference type={type}>
34
+ <Reference refkey={efRefkey(type)} />
35
+ </Experimental_OverridableComponent>
36
+ );
33
37
  //throw new Error("Reference not implemented");
34
38
  }
35
39
 
@@ -69,11 +73,6 @@ export function TypeExpression(props: TypeExpressionProps) {
69
73
  return <RecordExpression elementType={elementType} />;
70
74
  }
71
75
 
72
- if ($.httpPart.is(type)) {
73
- const partType = $.httpPart.unpack(type);
74
- return <TypeExpression type={partType} />;
75
- }
76
-
77
76
  return <InterfaceExpression type={type} />;
78
77
  case "Operation":
79
78
  return <FunctionType type={type} />;
@@ -227,7 +227,7 @@ export function ModelTransformExpression(props: ModelTransformExpressionProps) {
227
227
  {mapJoin(
228
228
  () => modelProperties,
229
229
  (_, property) => {
230
- const unpackedType = $.httpPart.unpack(property.type) ?? property.type;
230
+ const unpackedType = property.type;
231
231
  let targetPropertyName = property.name;
232
232
  let sourcePropertyName = namePolicy.getName(property.name, "interface-member");
233
233
 
@@ -368,9 +368,7 @@ export function TypeTransformCall(props: TypeTransformCallProps): Children {
368
368
  }
369
369
  const transformType = collapsedProperty?.type ?? props.type;
370
370
  if ($.model.is(transformType) && $.array.is(transformType)) {
371
- const unpackedElement =
372
- $.httpPart.unpack($.array.getElementType(transformType)) ??
373
- $.array.getElementType(transformType);
371
+ const unpackedElement = $.array.getElementType(transformType);
374
372
  return (
375
373
  <ts.FunctionCallExpression
376
374
  target={ArraySerializerRefkey}
@@ -380,9 +378,7 @@ export function TypeTransformCall(props: TypeTransformCallProps): Children {
380
378
  }
381
379
 
382
380
  if ($.model.is(transformType) && $.record.is(transformType)) {
383
- const unpackedElement =
384
- $.httpPart.unpack($.record.getElementType(transformType)) ??
385
- $.record.getElementType(transformType);
381
+ const unpackedElement = $.record.getElementType(transformType);
386
382
  return (
387
383
  <ts.FunctionCallExpression
388
384
  target={RecordSerializerRefkey}