@typespec/emitter-framework 0.11.0-dev.1 → 0.11.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/csharp/components/class/declaration.d.ts +1 -1
- package/dist/src/csharp/components/class/declaration.d.ts.map +1 -1
- package/dist/src/csharp/components/class/declaration.js +4 -3
- package/dist/src/csharp/components/type-expression.d.ts.map +1 -1
- package/dist/src/csharp/components/type-expression.js +42 -2
- package/dist/src/csharp/components/type-expression.test.js +60 -0
- package/package.json +1 -1
- package/src/csharp/components/class/declaration.tsx +7 -3
- package/src/csharp/components/type-expression.test.tsx +64 -0
- package/src/csharp/components/type-expression.tsx +48 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Children } from "@alloy-js/core";
|
|
2
2
|
import * as cs from "@alloy-js/csharp";
|
|
3
|
-
import type
|
|
3
|
+
import { type Interface, type Model } from "@typespec/compiler";
|
|
4
4
|
export interface ClassDeclarationProps extends Omit<cs.ClassDeclarationProps, "name"> {
|
|
5
5
|
/** Set an alternative name for the class. Otherwise default to the type name. */
|
|
6
6
|
name?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"declaration.d.ts","sourceRoot":"","sources":["../../../../../src/csharp/components/class/declaration.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
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"}
|
|
@@ -2,6 +2,7 @@ import { memo as _$memo, mergeProps as _$mergeProps, createComponent as _$create
|
|
|
2
2
|
import { For } from "@alloy-js/core";
|
|
3
3
|
import * as cs from "@alloy-js/csharp";
|
|
4
4
|
import { Method } from "@alloy-js/csharp";
|
|
5
|
+
import { isVoidType } from "@typespec/compiler";
|
|
5
6
|
import { useTsp } from "../../../core/index.js";
|
|
6
7
|
import { Property } from "../property/property.js";
|
|
7
8
|
import { TypeExpression } from "../type-expression.js";
|
|
@@ -38,10 +39,10 @@ export function ClassDeclaration(props) {
|
|
|
38
39
|
}))];
|
|
39
40
|
}
|
|
40
41
|
function ClassProperties(props) {
|
|
42
|
+
// Ignore 'void' type properties which is not valid in csharp
|
|
43
|
+
const properties = Array.from(props.type.properties.entries()).filter(([_, p]) => !isVoidType(p.type));
|
|
41
44
|
return _$createComponent(For, {
|
|
42
|
-
|
|
43
|
-
return props.type.properties.entries();
|
|
44
|
-
},
|
|
45
|
+
each: properties,
|
|
45
46
|
hardline: true,
|
|
46
47
|
children: ([name, property]) => _$createComponent(Property, {
|
|
47
48
|
type: property,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-expression.d.ts","sourceRoot":"","sources":["../../../../src/csharp/components/type-expression.tsx"],"names":[],"mappings":"AACA,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,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"}
|
|
@@ -2,11 +2,19 @@ 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 } from "@typespec/compiler";
|
|
5
|
+
import { getTypeName, isNullType, isVoidType } from "@typespec/compiler";
|
|
6
6
|
import { useTsp } from "../../core/index.js";
|
|
7
7
|
import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
|
|
8
8
|
import { efRefkey } from "./utils/refkey.js";
|
|
9
9
|
export function TypeExpression(props) {
|
|
10
|
+
if (props.type.kind === "Union") {
|
|
11
|
+
const nullabletype = getNullableUnionInnerType(props.type);
|
|
12
|
+
if (nullabletype) {
|
|
13
|
+
return code`${_$createComponent(TypeExpression, {
|
|
14
|
+
type: nullabletype
|
|
15
|
+
})}?`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
10
18
|
const {
|
|
11
19
|
$
|
|
12
20
|
} = useTsp();
|
|
@@ -39,9 +47,41 @@ export function TypeExpression(props) {
|
|
|
39
47
|
return props.type.indexer.value;
|
|
40
48
|
}
|
|
41
49
|
})}>`;
|
|
50
|
+
} else if ($.literal.isString(props.type)) {
|
|
51
|
+
// c# doesn't have literal types, so we map them to their corresponding C# types in general
|
|
52
|
+
return code`string`;
|
|
53
|
+
} else if ($.literal.isNumeric(props.type)) {
|
|
54
|
+
return Number.isInteger(props.type.value) ? code`int` : code`double`;
|
|
55
|
+
} else if ($.literal.isBoolean(props.type)) {
|
|
56
|
+
return code`bool`;
|
|
57
|
+
} else if (isVoidType(props.type)) {
|
|
58
|
+
return code`void`;
|
|
42
59
|
}
|
|
43
60
|
throw new Error(`Unsupported type for TypeExpression: ${props.type.kind} (${getTypeName(props.type)})`);
|
|
44
61
|
}
|
|
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
|
+
}
|
|
45
85
|
const intrinsicNameToCSharpType = new Map([
|
|
46
86
|
// Core types
|
|
47
87
|
["unknown", "object"],
|
|
@@ -142,7 +182,7 @@ function isDeclaration($, type) {
|
|
|
142
182
|
if ($.array.is(type) || $.record.is(type)) {
|
|
143
183
|
return false;
|
|
144
184
|
}
|
|
145
|
-
return
|
|
185
|
+
return true;
|
|
146
186
|
case "Union":
|
|
147
187
|
return Boolean(type.name);
|
|
148
188
|
default:
|
|
@@ -125,4 +125,64 @@ describe("Record map to IDictionary", () => {
|
|
|
125
125
|
}
|
|
126
126
|
`);
|
|
127
127
|
});
|
|
128
|
+
});
|
|
129
|
+
describe("Nullable union", () => {
|
|
130
|
+
it("nullable boolean", async () => {
|
|
131
|
+
const {
|
|
132
|
+
Pet
|
|
133
|
+
} = await runner.compile(`
|
|
134
|
+
@test model Pet {
|
|
135
|
+
@test name: boolean | null;
|
|
136
|
+
}
|
|
137
|
+
`);
|
|
138
|
+
const res = render(_$createComponent(Wrapper, {
|
|
139
|
+
get children() {
|
|
140
|
+
return _$createComponent(ClassDeclaration, {
|
|
141
|
+
type: Pet
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}));
|
|
145
|
+
assertFileContents(res, d`
|
|
146
|
+
namespace TestNamespace
|
|
147
|
+
{
|
|
148
|
+
class Pet
|
|
149
|
+
{
|
|
150
|
+
public required bool? name { get; set; }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
`);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe("Literal types", () => {
|
|
157
|
+
it("literal types (string, int, double, bool)", async () => {
|
|
158
|
+
const {
|
|
159
|
+
Pet
|
|
160
|
+
} = await runner.compile(`
|
|
161
|
+
@test model Pet {
|
|
162
|
+
@test boolName: true;
|
|
163
|
+
@test intName: 42;
|
|
164
|
+
@test doubleName: 3.14;
|
|
165
|
+
@test stringName: "Hello";
|
|
166
|
+
}
|
|
167
|
+
`);
|
|
168
|
+
const res = render(_$createComponent(Wrapper, {
|
|
169
|
+
get children() {
|
|
170
|
+
return _$createComponent(ClassDeclaration, {
|
|
171
|
+
type: Pet
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
assertFileContents(res, d`
|
|
176
|
+
namespace TestNamespace
|
|
177
|
+
{
|
|
178
|
+
class Pet
|
|
179
|
+
{
|
|
180
|
+
public required bool boolName { get; set; }
|
|
181
|
+
public required int intName { get; set; }
|
|
182
|
+
public required double doubleName { get; set; }
|
|
183
|
+
public required string stringName { get; set; }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
`);
|
|
187
|
+
});
|
|
128
188
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type Children
|
|
1
|
+
import { For, type Children } from "@alloy-js/core";
|
|
2
2
|
import * as cs from "@alloy-js/csharp";
|
|
3
3
|
import { Method } from "@alloy-js/csharp";
|
|
4
|
-
import type
|
|
4
|
+
import { isVoidType, type Interface, type Model } from "@typespec/compiler";
|
|
5
5
|
import { useTsp } from "../../../core/index.js";
|
|
6
6
|
import { Property } from "../property/property.jsx";
|
|
7
7
|
import { TypeExpression } from "../type-expression.jsx";
|
|
@@ -53,8 +53,12 @@ export function ClassDeclaration(props: ClassDeclarationProps): Children {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function ClassProperties(props: ClassPropertiesProps): Children {
|
|
56
|
+
// Ignore 'void' type properties which is not valid in csharp
|
|
57
|
+
const properties = Array.from(props.type.properties.entries()).filter(
|
|
58
|
+
([_, p]) => !isVoidType(p.type),
|
|
59
|
+
);
|
|
56
60
|
return (
|
|
57
|
-
<For each={
|
|
61
|
+
<For each={properties} hardline>
|
|
58
62
|
{([name, property]) => <Property type={property} jsonAttributes={props.jsonAttributes} />}
|
|
59
63
|
</For>
|
|
60
64
|
);
|
|
@@ -131,3 +131,67 @@ describe("Record map to IDictionary", () => {
|
|
|
131
131
|
);
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
|
+
|
|
135
|
+
describe("Nullable union", () => {
|
|
136
|
+
it("nullable boolean", async () => {
|
|
137
|
+
const { Pet } = (await runner.compile(`
|
|
138
|
+
@test model Pet {
|
|
139
|
+
@test name: boolean | null;
|
|
140
|
+
}
|
|
141
|
+
`)) as { Pet: Model };
|
|
142
|
+
|
|
143
|
+
const res = render(
|
|
144
|
+
<Wrapper>
|
|
145
|
+
<ClassDeclaration type={Pet} />
|
|
146
|
+
</Wrapper>,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
assertFileContents(
|
|
150
|
+
res,
|
|
151
|
+
d`
|
|
152
|
+
namespace TestNamespace
|
|
153
|
+
{
|
|
154
|
+
class Pet
|
|
155
|
+
{
|
|
156
|
+
public required bool? name { get; set; }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
`,
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("Literal types", () => {
|
|
165
|
+
it("literal types (string, int, double, bool)", async () => {
|
|
166
|
+
const { Pet } = (await runner.compile(`
|
|
167
|
+
@test model Pet {
|
|
168
|
+
@test boolName: true;
|
|
169
|
+
@test intName: 42;
|
|
170
|
+
@test doubleName: 3.14;
|
|
171
|
+
@test stringName: "Hello";
|
|
172
|
+
}
|
|
173
|
+
`)) as { Pet: Model };
|
|
174
|
+
|
|
175
|
+
const res = render(
|
|
176
|
+
<Wrapper>
|
|
177
|
+
<ClassDeclaration type={Pet} />
|
|
178
|
+
</Wrapper>,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
assertFileContents(
|
|
182
|
+
res,
|
|
183
|
+
d`
|
|
184
|
+
namespace TestNamespace
|
|
185
|
+
{
|
|
186
|
+
class Pet
|
|
187
|
+
{
|
|
188
|
+
public required bool boolName { get; set; }
|
|
189
|
+
public required int intName { get; set; }
|
|
190
|
+
public required double doubleName { get; set; }
|
|
191
|
+
public required string stringName { get; set; }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`,
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { Experimental_OverridableComponent } from "#core/index.js";
|
|
2
|
-
import { type Children
|
|
2
|
+
import { code, type Children } from "@alloy-js/core";
|
|
3
3
|
import { Reference } from "@alloy-js/csharp";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getTypeName,
|
|
6
|
+
isNullType,
|
|
7
|
+
isVoidType,
|
|
8
|
+
type IntrinsicType,
|
|
9
|
+
type Scalar,
|
|
10
|
+
type Type,
|
|
11
|
+
type Union,
|
|
12
|
+
} from "@typespec/compiler";
|
|
5
13
|
import type { Typekit } from "@typespec/compiler/typekit";
|
|
6
14
|
import { useTsp } from "../../core/index.js";
|
|
7
15
|
import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
|
|
@@ -12,6 +20,12 @@ export interface TypeExpressionProps {
|
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
export function TypeExpression(props: TypeExpressionProps): Children {
|
|
23
|
+
if (props.type.kind === "Union") {
|
|
24
|
+
const nullabletype = getNullableUnionInnerType(props.type);
|
|
25
|
+
if (nullabletype) {
|
|
26
|
+
return code`${(<TypeExpression type={nullabletype} />)}?`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
15
29
|
const { $ } = useTsp();
|
|
16
30
|
if (isDeclaration($, props.type)) {
|
|
17
31
|
return (
|
|
@@ -26,6 +40,15 @@ export function TypeExpression(props: TypeExpressionProps): Children {
|
|
|
26
40
|
return code`${(<TypeExpression type={props.type.indexer.value} />)}[]`;
|
|
27
41
|
} else if ($.record.is(props.type)) {
|
|
28
42
|
return code`IDictionary<string, ${(<TypeExpression type={props.type.indexer.value} />)}>`;
|
|
43
|
+
} else if ($.literal.isString(props.type)) {
|
|
44
|
+
// c# doesn't have literal types, so we map them to their corresponding C# types in general
|
|
45
|
+
return code`string`;
|
|
46
|
+
} else if ($.literal.isNumeric(props.type)) {
|
|
47
|
+
return Number.isInteger(props.type.value) ? code`int` : code`double`;
|
|
48
|
+
} else if ($.literal.isBoolean(props.type)) {
|
|
49
|
+
return code`bool`;
|
|
50
|
+
} else if (isVoidType(props.type)) {
|
|
51
|
+
return code`void`;
|
|
29
52
|
}
|
|
30
53
|
|
|
31
54
|
throw new Error(
|
|
@@ -33,6 +56,28 @@ export function TypeExpression(props: TypeExpressionProps): Children {
|
|
|
33
56
|
);
|
|
34
57
|
}
|
|
35
58
|
|
|
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
|
+
|
|
36
81
|
const intrinsicNameToCSharpType = new Map<string, string | null>([
|
|
37
82
|
// Core types
|
|
38
83
|
["unknown", "object"], // Matches C#'s `object`
|
|
@@ -112,8 +157,7 @@ function isDeclaration($: Typekit, type: Type): boolean {
|
|
|
112
157
|
if ($.array.is(type) || $.record.is(type)) {
|
|
113
158
|
return false;
|
|
114
159
|
}
|
|
115
|
-
|
|
116
|
-
return Boolean(type.name);
|
|
160
|
+
return true;
|
|
117
161
|
case "Union":
|
|
118
162
|
return Boolean(type.name);
|
|
119
163
|
default:
|