@typespec/emitter-framework 0.11.0-dev.5 → 0.11.0-dev.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"declaration.d.ts","sourceRoot":"","sources":["../../../../../src/csharp/components/class/declaration.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEvC,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAO5E,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,EAAE,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACnF,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC;IACxB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAYD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,QAAQ,CAuBvE"}
1
+ {"version":3,"file":"declaration.d.ts","sourceRoot":"","sources":["../../../../../src/csharp/components/class/declaration.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEvC,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAO5E,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,EAAE,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACnF,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC;IACxB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAYD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,QAAQ,CA6BvE"}
@@ -19,6 +19,13 @@ export function ClassDeclaration(props) {
19
19
  return [_$createComponent(cs.ClassDeclaration, _$mergeProps(props, {
20
20
  name: className,
21
21
  refkey: refkeys,
22
+ get baseType() {
23
+ return props.baseType ?? (props.type.kind === "Model" && props.type.baseModel ? _$createComponent(TypeExpression, {
24
+ get type() {
25
+ return props.type.baseModel;
26
+ }
27
+ }) : undefined);
28
+ },
22
29
  get doc() {
23
30
  return getDocComments($, props.type);
24
31
  },
@@ -1 +1 @@
1
- {"version":3,"file":"property.d.ts","sourceRoot":"","sources":["../../../../../src/csharp/components/property/property.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,EAAE,KAAK,aAAa,EAAiC,MAAM,oBAAoB,CAAC;AAKvF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,CAiBvD;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,aAAa,CAAC;CACrB"}
1
+ {"version":3,"file":"property.d.ts","sourceRoot":"","sources":["../../../../../src/csharp/components/property/property.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,EAAe,KAAK,aAAa,EAAiC,MAAM,oBAAoB,CAAC;AAMpG,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,CAkDvD;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,aAAa,CAAC;CACrB"}
@@ -1,10 +1,11 @@
1
1
  import { memo as _$memo, createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
2
2
  import * as cs from "@alloy-js/csharp";
3
3
  import { Attribute } from "@alloy-js/csharp";
4
- import { resolveEncodedName } from "@typespec/compiler";
4
+ import { getProperty, resolveEncodedName } from "@typespec/compiler";
5
5
  import { useTsp } from "../../../core/index.js";
6
6
  import { TypeExpression } from "../type-expression.js";
7
7
  import { getDocComments } from "../utils/doc-comments.js";
8
+ import { getNullableUnionInnerType } from "../utils/nullable-util.js";
8
9
  /**
9
10
  * Create a C# property declaration from a TypeSpec property type.
10
11
  */
@@ -13,6 +14,31 @@ export function Property(props) {
13
14
  const {
14
15
  $
15
16
  } = useTsp();
17
+ let overrideType = "";
18
+ let isVirtual = false;
19
+ if (props.type.model) {
20
+ if (props.type.model.baseModel) {
21
+ const base = props.type.model.baseModel;
22
+ const baseProperty = getProperty(base, props.type.name);
23
+ if (baseProperty) {
24
+ const baseResult = preprocessPropertyType(baseProperty);
25
+ if (baseResult.nullable === result.nullable && baseResult.type === result.type) {
26
+ overrideType = "override";
27
+ } else {
28
+ overrideType = "new";
29
+ }
30
+ }
31
+ }
32
+ if (overrideType === "" && props.type.model.derivedModels && props.type.model.derivedModels.length > 0) {
33
+ isVirtual = props.type.model.derivedModels.some(derived => {
34
+ const derivedProperty = derived.properties.get(props.type.name);
35
+ if (derivedProperty) {
36
+ const derivedResult = preprocessPropertyType(derivedProperty);
37
+ return derivedResult.nullable === result.nullable && derivedResult.type === result.type;
38
+ }
39
+ });
40
+ }
41
+ }
16
42
  return _$createComponent(cs.Property, {
17
43
  get name() {
18
44
  return props.type.name;
@@ -24,7 +50,10 @@ export function Property(props) {
24
50
  }
25
51
  });
26
52
  },
53
+ override: overrideType === "override",
54
+ "new": overrideType === "new",
27
55
  "public": true,
56
+ virtual: isVirtual,
28
57
  get required() {
29
58
  return !props.type.optional;
30
59
  },
@@ -59,34 +88,23 @@ function JsonNameAttribute(props) {
59
88
  }
60
89
  function preprocessPropertyType(prop) {
61
90
  const type = prop.type;
62
- if (prop.optional) {
63
- return {
64
- type,
65
- nullable: true
66
- };
67
- }
68
- const {
69
- $
70
- } = useTsp();
71
91
  if (type.kind === "Union") {
72
- const variants = type.variants;
73
- const nonNullVariant = [...variants.values()].find(v => v.type !== $.intrinsic.null);
74
- const nullVariant = [...variants.values()].find(v => v.type !== $.intrinsic.null);
75
- if (nonNullVariant && nullVariant && variants.size === 2) {
92
+ const innerType = getNullableUnionInnerType(type);
93
+ if (innerType) {
76
94
  return {
77
- type: nonNullVariant.type,
95
+ type: innerType,
78
96
  nullable: true
79
97
  };
80
98
  } else {
81
99
  return {
82
100
  type,
83
- nullable: false
101
+ nullable: prop.optional
84
102
  };
85
103
  }
86
104
  } else {
87
105
  return {
88
106
  type,
89
- nullable: false
107
+ nullable: prop.optional
90
108
  };
91
109
  }
92
110
  }
@@ -1,5 +1,6 @@
1
1
  import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
2
2
  import { Tester } from "#test/test-host.js";
3
+ import { List } from "@alloy-js/core";
3
4
  import { ClassDeclaration, createCSharpNamePolicy, Namespace, SourceFile } from "@alloy-js/csharp";
4
5
  import { t } from "@typespec/compiler/testing";
5
6
  import { beforeEach, describe, expect, it } from "vitest";
@@ -84,6 +85,30 @@ it("maps optional properties to nullable properties", async () => {
84
85
  }
85
86
  `);
86
87
  });
88
+ it("maps optional and nullable properties to nullable properties", async () => {
89
+ const {
90
+ prop1
91
+ } = await tester.compile(t.code`
92
+ model TestModel {
93
+ ${t.modelProperty("prop1")}?: string | null;
94
+ }
95
+ `);
96
+ expect(_$createComponent(Wrapper, {
97
+ get children() {
98
+ return _$createComponent(Property, {
99
+ type: prop1
100
+ });
101
+ }
102
+ })).toRenderTo(`
103
+ namespace TestNamespace
104
+ {
105
+ class Test
106
+ {
107
+ public string? Prop1 { get; set; }
108
+ }
109
+ }
110
+ `);
111
+ });
87
112
  describe("jsonAttributes", () => {
88
113
  it("adds [JsonNameAttribute]", async () => {
89
114
  const {
@@ -138,4 +163,80 @@ describe("jsonAttributes", () => {
138
163
  }
139
164
  `);
140
165
  });
166
+ it("inherit prop: override, new", async () => {
167
+ const r = await tester.compile(t.code`
168
+ model TestModel extends BaseModel {
169
+ ${t.modelProperty("prop1")}: string;
170
+ ${t.modelProperty("prop2")}: string | null;
171
+ }
172
+ model BaseModel {
173
+ prop1: string | null;
174
+ prop2: string | null;
175
+ }
176
+ `);
177
+ expect(_$createComponent(Wrapper, {
178
+ get children() {
179
+ return _$createComponent(List, {
180
+ get children() {
181
+ return [_$createComponent(Property, {
182
+ get type() {
183
+ return r.prop1;
184
+ }
185
+ }), _$createComponent(Property, {
186
+ get type() {
187
+ return r.prop2;
188
+ }
189
+ })];
190
+ }
191
+ });
192
+ }
193
+ })).toRenderTo(`
194
+ namespace TestNamespace
195
+ {
196
+ class Test
197
+ {
198
+ public new required string Prop1 { get; set; }
199
+ public override required string? Prop2 { get; set; }
200
+ }
201
+ }
202
+ `);
203
+ });
204
+ it("inherit prop: virtual", async () => {
205
+ const r = await tester.compile(t.code`
206
+ model TestModel extends BaseModel {
207
+ prop1: string;
208
+ prop2: string | null;
209
+ }
210
+ model BaseModel {
211
+ ${t.modelProperty("prop1")}: string | null;
212
+ ${t.modelProperty("prop2")}: string | null;
213
+ }
214
+ `);
215
+ expect(_$createComponent(Wrapper, {
216
+ get children() {
217
+ return _$createComponent(List, {
218
+ get children() {
219
+ return [_$createComponent(Property, {
220
+ get type() {
221
+ return r.prop1;
222
+ }
223
+ }), _$createComponent(Property, {
224
+ get type() {
225
+ return r.prop2;
226
+ }
227
+ })];
228
+ }
229
+ });
230
+ }
231
+ })).toRenderTo(`
232
+ namespace TestNamespace
233
+ {
234
+ class Test
235
+ {
236
+ public required string? Prop1 { get; set; }
237
+ public virtual required string? Prop2 { get; set; }
238
+ }
239
+ }
240
+ `);
241
+ });
141
242
  });
@@ -1 +1 @@
1
- {"version":3,"file":"type-expression.d.ts","sourceRoot":"","sources":["../../../../src/csharp/components/type-expression.tsx"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,IAAI,EAEV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAK1D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CAmCnE;AAwBD,QAAA,MAAM,yBAAyB,4BAqC7B,CAAC;AAEH,wBAAgB,4BAA4B,CAC1C,CAAC,EAAE,OAAO,EACV,IAAI,EAAE,MAAM,GAAG,aAAa,GAC3B,MAAM,GAAG,IAAI,CAoBf;AAyBD,OAAO,EAAE,yBAAyB,EAAE,CAAC"}
1
+ {"version":3,"file":"type-expression.d.ts","sourceRoot":"","sources":["../../../../src/csharp/components/type-expression.tsx"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAM1D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CAmCnE;AAED,QAAA,MAAM,yBAAyB,4BAqC7B,CAAC;AAEH,wBAAgB,4BAA4B,CAC1C,CAAC,EAAE,OAAO,EACV,IAAI,EAAE,MAAM,GAAG,aAAa,GAC3B,MAAM,GAAG,IAAI,CAoBf;AAyBD,OAAO,EAAE,yBAAyB,EAAE,CAAC"}
@@ -2,9 +2,10 @@ import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime
2
2
  import { Experimental_OverridableComponent } from "#core/index.js";
3
3
  import { code } from "@alloy-js/core";
4
4
  import { Reference } from "@alloy-js/csharp";
5
- import { getTypeName, isNullType, isVoidType } from "@typespec/compiler";
5
+ import { getTypeName, isVoidType } from "@typespec/compiler";
6
6
  import { useTsp } from "../../core/index.js";
7
7
  import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
8
+ import { getNullableUnionInnerType } from "./utils/nullable-util.js";
8
9
  import { efRefkey } from "./utils/refkey.js";
9
10
  export function TypeExpression(props) {
10
11
  if (props.type.kind === "Union") {
@@ -59,29 +60,6 @@ export function TypeExpression(props) {
59
60
  }
60
61
  throw new Error(`Unsupported type for TypeExpression: ${props.type.kind} (${getTypeName(props.type)})`);
61
62
  }
62
-
63
- /** Get the inner type if the union is a nullable, otherwise return undefined */
64
- function getNullableUnionInnerType(u) {
65
- const isNull = type => isNullType(type) || isVoidType(type);
66
- if (Array.from(u.variants.values()).some(v => isNull(v.type))) {
67
- const {
68
- $
69
- } = useTsp();
70
- const left = Array.from(u.variants.values()).filter(v => !isNull(v.type));
71
- if (left.length === 0) {
72
- // a union only has null or void?
73
- return $.intrinsic.void;
74
- } else if (left.length === 1) {
75
- return left[0].type;
76
- } else {
77
- return $.union.create({
78
- name: u.name,
79
- variants: left
80
- });
81
- }
82
- }
83
- return undefined;
84
- }
85
63
  const intrinsicNameToCSharpType = new Map([
86
64
  // Core types
87
65
  ["unknown", "object"],
@@ -0,0 +1,4 @@
1
+ import type { Type, Union } from "@typespec/compiler";
2
+ /** Get the inner type if the union is a nullable, otherwise return undefined */
3
+ export declare function getNullableUnionInnerType(u: Union): Type | undefined;
4
+ //# sourceMappingURL=nullable-util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nullable-util.d.ts","sourceRoot":"","sources":["../../../../../src/csharp/components/utils/nullable-util.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEtD,gFAAgF;AAChF,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,CAoBpE"}
@@ -0,0 +1,26 @@
1
+ import { useTsp } from "#core/index.js";
2
+ /** Get the inner type if the union is a nullable, otherwise return undefined */
3
+ export function getNullableUnionInnerType(u) {
4
+ const {
5
+ $
6
+ } = useTsp();
7
+ const isNull = type => type === $.intrinsic.null || type === $.intrinsic.void;
8
+ if (Array.from(u.variants.values()).some(v => isNull(v.type))) {
9
+ const {
10
+ $
11
+ } = useTsp();
12
+ const left = Array.from(u.variants.values()).filter(v => !isNull(v.type));
13
+ if (left.length === 0) {
14
+ // a union only has null or void?
15
+ return $.intrinsic.void;
16
+ } else if (left.length === 1) {
17
+ return left[0].type;
18
+ } else {
19
+ return $.union.create({
20
+ name: u.name,
21
+ variants: left
22
+ });
23
+ }
24
+ }
25
+ return undefined;
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typespec/emitter-framework",
3
- "version": "0.11.0-dev.5",
3
+ "version": "0.11.0-dev.7",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -41,6 +41,12 @@ export function ClassDeclaration(props: ClassDeclarationProps): Children {
41
41
  {...props}
42
42
  name={className}
43
43
  refkey={refkeys}
44
+ baseType={
45
+ props.baseType ??
46
+ (props.type.kind === "Model" && props.type.baseModel ? (
47
+ <TypeExpression type={props.type.baseModel} />
48
+ ) : undefined)
49
+ }
44
50
  doc={getDocComments($, props.type)}
45
51
  >
46
52
  {props.type.kind === "Model" && (
@@ -1,5 +1,5 @@
1
1
  import { Tester } from "#test/test-host.js";
2
- import { type Children } from "@alloy-js/core";
2
+ import { List, type Children } from "@alloy-js/core";
3
3
  import { ClassDeclaration, createCSharpNamePolicy, Namespace, SourceFile } from "@alloy-js/csharp";
4
4
  import { t, type TesterInstance } from "@typespec/compiler/testing";
5
5
  import { beforeEach, describe, expect, it } from "vitest";
@@ -69,6 +69,28 @@ it("maps optional properties to nullable properties", async () => {
69
69
  `);
70
70
  });
71
71
 
72
+ it("maps optional and nullable properties to nullable properties", async () => {
73
+ const { prop1 } = await tester.compile(t.code`
74
+ model TestModel {
75
+ ${t.modelProperty("prop1")}?: string | null;
76
+ }
77
+ `);
78
+
79
+ expect(
80
+ <Wrapper>
81
+ <Property type={prop1} />
82
+ </Wrapper>,
83
+ ).toRenderTo(`
84
+ namespace TestNamespace
85
+ {
86
+ class Test
87
+ {
88
+ public string? Prop1 { get; set; }
89
+ }
90
+ }
91
+ `);
92
+ });
93
+
72
94
  describe("jsonAttributes", () => {
73
95
  it("adds [JsonNameAttribute]", async () => {
74
96
  const { prop1 } = await tester.compile(t.code`
@@ -116,4 +138,66 @@ describe("jsonAttributes", () => {
116
138
  }
117
139
  `);
118
140
  });
141
+
142
+ it("inherit prop: override, new", async () => {
143
+ const r = await tester.compile(t.code`
144
+ model TestModel extends BaseModel {
145
+ ${t.modelProperty("prop1")}: string;
146
+ ${t.modelProperty("prop2")}: string | null;
147
+ }
148
+ model BaseModel {
149
+ prop1: string | null;
150
+ prop2: string | null;
151
+ }
152
+ `);
153
+
154
+ expect(
155
+ <Wrapper>
156
+ <List>
157
+ <Property type={r.prop1} />
158
+ <Property type={r.prop2} />
159
+ </List>
160
+ </Wrapper>,
161
+ ).toRenderTo(`
162
+ namespace TestNamespace
163
+ {
164
+ class Test
165
+ {
166
+ public new required string Prop1 { get; set; }
167
+ public override required string? Prop2 { get; set; }
168
+ }
169
+ }
170
+ `);
171
+ });
172
+
173
+ it("inherit prop: virtual", async () => {
174
+ const r = await tester.compile(t.code`
175
+ model TestModel extends BaseModel {
176
+ prop1: string;
177
+ prop2: string | null;
178
+ }
179
+ model BaseModel {
180
+ ${t.modelProperty("prop1")}: string | null;
181
+ ${t.modelProperty("prop2")}: string | null;
182
+ }
183
+ `);
184
+
185
+ expect(
186
+ <Wrapper>
187
+ <List>
188
+ <Property type={r.prop1} />
189
+ <Property type={r.prop2} />
190
+ </List>
191
+ </Wrapper>,
192
+ ).toRenderTo(`
193
+ namespace TestNamespace
194
+ {
195
+ class Test
196
+ {
197
+ public required string? Prop1 { get; set; }
198
+ public virtual required string? Prop2 { get; set; }
199
+ }
200
+ }
201
+ `);
202
+ });
119
203
  });
@@ -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;
@@ -19,11 +20,44 @@ export function Property(props: PropertyProps): Children {
19
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)}
@@ -47,22 +81,14 @@ function JsonNameAttribute(props: JsonNameAttributeProps): Children {
47
81
  function preprocessPropertyType(prop: ModelProperty): { type: Type; nullable: boolean } {
48
82
  const type = prop.type;
49
83
 
50
- if (prop.optional) {
51
- return { type, nullable: true };
52
- }
53
-
54
- const { $ } = useTsp();
55
-
56
84
  if (type.kind === "Union") {
57
- const variants = type.variants;
58
- const nonNullVariant = [...variants.values()].find((v) => v.type !== $.intrinsic.null);
59
- const nullVariant = [...variants.values()].find((v) => v.type !== $.intrinsic.null);
60
- if (nonNullVariant && nullVariant && variants.size === 2) {
61
- return { type: nonNullVariant.type, nullable: true };
85
+ const innerType = getNullableUnionInnerType(type);
86
+ if (innerType) {
87
+ return { type: innerType, nullable: true };
62
88
  } else {
63
- return { type, nullable: false };
89
+ return { type, nullable: prop.optional };
64
90
  }
65
91
  } else {
66
- return { type, nullable: false };
92
+ return { type, nullable: prop.optional };
67
93
  }
68
94
  }
@@ -3,16 +3,15 @@ import { code, type Children } from "@alloy-js/core";
3
3
  import { Reference } from "@alloy-js/csharp";
4
4
  import {
5
5
  getTypeName,
6
- isNullType,
7
6
  isVoidType,
8
7
  type IntrinsicType,
9
8
  type Scalar,
10
9
  type Type,
11
- type Union,
12
10
  } from "@typespec/compiler";
13
11
  import type { Typekit } from "@typespec/compiler/typekit";
14
12
  import { useTsp } from "../../core/index.js";
15
13
  import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
14
+ import { getNullableUnionInnerType } from "./utils/nullable-util.js";
16
15
  import { efRefkey } from "./utils/refkey.js";
17
16
 
18
17
  export interface TypeExpressionProps {
@@ -56,28 +55,6 @@ export function TypeExpression(props: TypeExpressionProps): Children {
56
55
  );
57
56
  }
58
57
 
59
- /** Get the inner type if the union is a nullable, otherwise return undefined */
60
- function getNullableUnionInnerType(u: Union): Type | undefined {
61
- const isNull = (type: Type) => isNullType(type) || isVoidType(type);
62
-
63
- if (Array.from(u.variants.values()).some((v) => isNull(v.type))) {
64
- const { $ } = useTsp();
65
- const left = Array.from(u.variants.values()).filter((v) => !isNull(v.type));
66
- if (left.length === 0) {
67
- // a union only has null or void?
68
- return $.intrinsic.void;
69
- } else if (left.length === 1) {
70
- return left[0].type;
71
- } else {
72
- return $.union.create({
73
- name: u.name,
74
- variants: left,
75
- });
76
- }
77
- }
78
- return undefined;
79
- }
80
-
81
58
  const intrinsicNameToCSharpType = new Map<string, string | null>([
82
59
  // Core types
83
60
  ["unknown", "object"], // Matches C#'s `object`
@@ -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
+ }