@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.
- package/dist/src/csharp/components/class/declaration.d.ts.map +1 -1
- package/dist/src/csharp/components/class/declaration.js +7 -0
- package/dist/src/csharp/components/property/property.d.ts.map +1 -1
- package/dist/src/csharp/components/property/property.js +35 -17
- package/dist/src/csharp/components/property/property.test.js +101 -0
- package/dist/src/csharp/components/type-expression.d.ts.map +1 -1
- package/dist/src/csharp/components/type-expression.js +2 -24
- package/dist/src/csharp/components/utils/nullable-util.d.ts +4 -0
- package/dist/src/csharp/components/utils/nullable-util.d.ts.map +1 -0
- package/dist/src/csharp/components/utils/nullable-util.js +26 -0
- package/package.json +1 -1
- package/src/csharp/components/class/declaration.tsx +6 -0
- package/src/csharp/components/property/property.test.tsx +85 -1
- package/src/csharp/components/property/property.tsx +40 -14
- package/src/csharp/components/type-expression.tsx +1 -24
- package/src/csharp/components/utils/nullable-util.ts +25 -0
|
@@ -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,
|
|
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,
|
|
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
|
|
73
|
-
|
|
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:
|
|
95
|
+
type: innerType,
|
|
78
96
|
nullable: true
|
|
79
97
|
};
|
|
80
98
|
} else {
|
|
81
99
|
return {
|
|
82
100
|
type,
|
|
83
|
-
nullable:
|
|
101
|
+
nullable: prop.optional
|
|
84
102
|
};
|
|
85
103
|
}
|
|
86
104
|
} else {
|
|
87
105
|
return {
|
|
88
106
|
type,
|
|
89
|
-
nullable:
|
|
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,
|
|
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,
|
|
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 @@
|
|
|
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
|
@@ -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
|
|
58
|
-
|
|
59
|
-
|
|
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:
|
|
89
|
+
return { type, nullable: prop.optional };
|
|
64
90
|
}
|
|
65
91
|
} else {
|
|
66
|
-
return { type, nullable:
|
|
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
|
+
}
|