@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.
@@ -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 { Interface, Model } from "@typespec/compiler";
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,EAAE,KAAK,QAAQ,EAAO,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAO3D,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,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
- get each() {
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,EAAE,KAAK,QAAQ,EAAQ,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAe,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC7F,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,CAoBnE;AAED,QAAA,MAAM,yBAAyB,4BAqC7B,CAAC;AAEH,wBAAgB,4BAA4B,CAC1C,CAAC,EAAE,OAAO,EACV,IAAI,EAAE,MAAM,GAAG,aAAa,GAC3B,MAAM,GAAG,IAAI,CAoBf;AA0BD,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,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 Boolean(type.name);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@typespec/emitter-framework",
3
- "version": "0.11.0-dev.1",
3
+ "version": "0.11.0-dev.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -1,7 +1,7 @@
1
- import { type Children, For } from "@alloy-js/core";
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 { Interface, Model } from "@typespec/compiler";
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={props.type.properties.entries()} hardline>
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, code } from "@alloy-js/core";
2
+ import { code, type Children } from "@alloy-js/core";
3
3
  import { Reference } from "@alloy-js/csharp";
4
- import { getTypeName, type IntrinsicType, type Scalar, type Type } from "@typespec/compiler";
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: