@typespec/emitter-framework 0.9.0-dev.1 → 0.9.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 (192) hide show
  1. package/dist/src/core/components/output.d.ts +4 -4
  2. package/dist/src/core/components/output.d.ts.map +1 -1
  3. package/dist/src/core/components/output.js +3 -3
  4. package/dist/src/core/context/name-policy-context.d.ts +2 -2
  5. package/dist/src/core/context/name-policy-context.d.ts.map +1 -1
  6. package/dist/src/core/context/tsp-context.d.ts +2 -2
  7. package/dist/src/core/context/tsp-context.d.ts.map +1 -1
  8. package/dist/src/core/transport-name-policy.d.ts +1 -1
  9. package/dist/src/core/transport-name-policy.d.ts.map +1 -1
  10. package/dist/src/core/write-output.d.ts +2 -2
  11. package/dist/src/core/write-output.d.ts.map +1 -1
  12. package/dist/src/csharp/components/class/declaration.d.ts +13 -0
  13. package/dist/src/csharp/components/class/declaration.d.ts.map +1 -0
  14. package/dist/src/csharp/components/class/declaration.js +91 -0
  15. package/dist/src/csharp/components/class/declaration.test.d.ts +2 -0
  16. package/dist/src/csharp/components/class/declaration.test.d.ts.map +1 -0
  17. package/dist/src/csharp/components/class/declaration.test.js +377 -0
  18. package/dist/src/csharp/components/enum/declaration.d.ts +9 -0
  19. package/dist/src/csharp/components/enum/declaration.d.ts.map +1 -0
  20. package/dist/src/csharp/components/enum/declaration.js +55 -0
  21. package/dist/src/csharp/components/enum/declaration.test.d.ts +2 -0
  22. package/dist/src/csharp/components/enum/declaration.test.d.ts.map +1 -0
  23. package/dist/src/csharp/components/enum/declaration.test.js +309 -0
  24. package/dist/src/csharp/components/index.d.ts +5 -0
  25. package/dist/src/csharp/components/index.d.ts.map +1 -0
  26. package/dist/src/csharp/components/index.js +4 -0
  27. package/dist/src/csharp/components/property/property.d.ts +15 -0
  28. package/dist/src/csharp/components/property/property.d.ts.map +1 -0
  29. package/dist/src/csharp/components/property/property.js +85 -0
  30. package/dist/src/csharp/components/property/property.test.d.ts +2 -0
  31. package/dist/src/csharp/components/property/property.test.d.ts.map +1 -0
  32. package/dist/src/csharp/components/property/property.test.js +117 -0
  33. package/dist/src/csharp/components/type-expression.d.ts +11 -0
  34. package/dist/src/csharp/components/type-expression.d.ts.map +1 -0
  35. package/dist/src/csharp/components/type-expression.js +143 -0
  36. package/dist/src/csharp/components/type-expression.test.d.ts +2 -0
  37. package/dist/src/csharp/components/type-expression.test.d.ts.map +1 -0
  38. package/dist/src/csharp/components/type-expression.test.js +128 -0
  39. package/dist/src/csharp/components/utils/doc-comments.d.ts +14 -0
  40. package/dist/src/csharp/components/utils/doc-comments.d.ts.map +1 -0
  41. package/dist/src/csharp/components/utils/doc-comments.js +67 -0
  42. package/dist/src/csharp/components/utils/refkey.d.ts +23 -0
  43. package/dist/src/csharp/components/utils/refkey.d.ts.map +1 -0
  44. package/dist/src/csharp/components/utils/refkey.js +35 -0
  45. package/dist/src/csharp/index.d.ts +2 -0
  46. package/dist/src/csharp/index.d.ts.map +1 -0
  47. package/dist/src/csharp/index.js +1 -0
  48. package/dist/src/testing/index.d.ts +1 -1
  49. package/dist/src/testing/index.d.ts.map +1 -1
  50. package/dist/src/testing/scenario-test/code-block-expectation.d.ts +33 -0
  51. package/dist/src/testing/scenario-test/code-block-expectation.d.ts.map +1 -0
  52. package/dist/src/testing/scenario-test/code-block-expectation.js +69 -0
  53. package/dist/src/testing/scenario-test/code-block-expectation.test.d.ts +2 -0
  54. package/dist/src/testing/scenario-test/code-block-expectation.test.d.ts.map +1 -0
  55. package/dist/src/testing/scenario-test/code-block-expectation.test.js +80 -0
  56. package/dist/src/testing/scenario-test/harness.d.ts +3 -3
  57. package/dist/src/testing/scenario-test/harness.d.ts.map +1 -1
  58. package/dist/src/testing/scenario-test/harness.js +69 -158
  59. package/dist/src/testing/scenario-test/index.d.ts +0 -1
  60. package/dist/src/testing/scenario-test/index.d.ts.map +1 -1
  61. package/dist/src/testing/scenario-test/index.js +1 -2
  62. package/dist/src/testing/scenario-test/snippet-extractor.d.ts +1 -1
  63. package/dist/src/testing/scenario-test/snippet-extractor.js +1 -1
  64. package/dist/src/typescript/components/array-expression.d.ts +2 -3
  65. package/dist/src/typescript/components/array-expression.d.ts.map +1 -1
  66. package/dist/src/typescript/components/array-expression.js +2 -2
  67. package/dist/src/typescript/components/arrow-function.d.ts +2 -2
  68. package/dist/src/typescript/components/arrow-function.d.ts.map +1 -1
  69. package/dist/src/typescript/components/arrow-function.js +2 -1
  70. package/dist/src/typescript/components/class-method.d.ts +1 -1
  71. package/dist/src/typescript/components/class-method.d.ts.map +1 -1
  72. package/dist/src/typescript/components/enum-declaration.d.ts +6 -6
  73. package/dist/src/typescript/components/enum-declaration.d.ts.map +1 -1
  74. package/dist/src/typescript/components/enum-declaration.js +2 -2
  75. package/dist/src/typescript/components/function-declaration.d.ts +1 -1
  76. package/dist/src/typescript/components/function-declaration.d.ts.map +1 -1
  77. package/dist/src/typescript/components/function-expression.d.ts +2 -2
  78. package/dist/src/typescript/components/function-expression.d.ts.map +1 -1
  79. package/dist/src/typescript/components/function-expression.js +2 -1
  80. package/dist/src/typescript/components/function-type.d.ts +2 -2
  81. package/dist/src/typescript/components/function-type.d.ts.map +1 -1
  82. package/dist/src/typescript/components/function-type.js +2 -1
  83. package/dist/src/typescript/components/interface-declaration.d.ts +4 -4
  84. package/dist/src/typescript/components/interface-declaration.d.ts.map +1 -1
  85. package/dist/src/typescript/components/interface-declaration.js +3 -4
  86. package/dist/src/typescript/components/interface-member.d.ts +2 -2
  87. package/dist/src/typescript/components/interface-member.d.ts.map +1 -1
  88. package/dist/src/typescript/components/interface-method.d.ts +2 -2
  89. package/dist/src/typescript/components/interface-method.d.ts.map +1 -1
  90. package/dist/src/typescript/components/record-expression.d.ts +1 -1
  91. package/dist/src/typescript/components/record-expression.d.ts.map +1 -1
  92. package/dist/src/typescript/components/type-alias-declaration.d.ts +1 -1
  93. package/dist/src/typescript/components/type-alias-declaration.d.ts.map +1 -1
  94. package/dist/src/typescript/components/type-declaration.d.ts +1 -1
  95. package/dist/src/typescript/components/type-declaration.d.ts.map +1 -1
  96. package/dist/src/typescript/components/type-expression.d.ts +1 -1
  97. package/dist/src/typescript/components/type-expression.d.ts.map +1 -1
  98. package/dist/src/typescript/components/type-transform.d.ts +2 -2
  99. package/dist/src/typescript/components/type-transform.d.ts.map +1 -1
  100. package/dist/src/typescript/components/union-declaration.d.ts +2 -2
  101. package/dist/src/typescript/components/union-declaration.d.ts.map +1 -1
  102. package/dist/src/typescript/components/union-expression.d.ts +3 -4
  103. package/dist/src/typescript/components/union-expression.d.ts.map +1 -1
  104. package/dist/src/typescript/components/union-expression.js +3 -3
  105. package/dist/src/typescript/components/value-expression.d.ts +2 -2
  106. package/dist/src/typescript/components/value-expression.d.ts.map +1 -1
  107. package/dist/src/typescript/utils/operation.d.ts +2 -2
  108. package/dist/src/typescript/utils/operation.d.ts.map +1 -1
  109. package/dist/test/test-host.d.ts +2 -0
  110. package/dist/test/test-host.d.ts.map +1 -0
  111. package/dist/test/test-host.js +5 -0
  112. package/dist/test/testing/snippet-extractor-csharp.test.js +3 -3
  113. package/dist/test/testing/snippet-extractor-java.test.js +3 -3
  114. package/dist/test/testing/snippet-extractor-python.test.js +2 -2
  115. package/dist/test/testing/snippet-extractor-typescript.test.js +3 -3
  116. package/dist/test/typescript/components/member-expression.test.js +5 -5
  117. package/dist/test/typescript/test-host.d.ts +1 -1
  118. package/dist/test/typescript/test-host.d.ts.map +1 -1
  119. package/dist/test/utils.d.ts +2 -2
  120. package/dist/test/utils.d.ts.map +1 -1
  121. package/dist/test/vitest.setup.d.ts +2 -0
  122. package/dist/test/vitest.setup.d.ts.map +1 -0
  123. package/dist/test/vitest.setup.js +1 -0
  124. package/package.json +26 -8
  125. package/src/core/components/output.tsx +9 -5
  126. package/src/core/context/name-policy-context.ts +2 -2
  127. package/src/core/context/tsp-context.ts +2 -2
  128. package/src/core/transport-name-policy.ts +1 -1
  129. package/src/core/write-output.ts +2 -2
  130. package/src/csharp/components/class/declaration.test.tsx +339 -0
  131. package/src/csharp/components/class/declaration.tsx +86 -0
  132. package/src/csharp/components/enum/declaration.test.tsx +267 -0
  133. package/src/csharp/components/enum/declaration.tsx +56 -0
  134. package/src/csharp/components/index.ts +4 -0
  135. package/src/csharp/components/property/property.test.tsx +97 -0
  136. package/src/csharp/components/property/property.tsx +62 -0
  137. package/src/csharp/components/type-expression.test.tsx +133 -0
  138. package/src/csharp/components/type-expression.tsx +119 -0
  139. package/src/csharp/components/utils/doc-comments.tsx +58 -0
  140. package/src/csharp/components/utils/refkey.ts +36 -0
  141. package/src/csharp/index.ts +1 -0
  142. package/src/testing/index.ts +1 -1
  143. package/src/testing/scenario-test/code-block-expectation.test.ts +95 -0
  144. package/src/testing/scenario-test/code-block-expectation.ts +115 -0
  145. package/src/testing/scenario-test/harness.ts +92 -237
  146. package/src/testing/scenario-test/index.ts +0 -1
  147. package/src/testing/scenario-test/snippet-extractor.ts +1 -1
  148. package/src/typescript/components/array-expression.tsx +3 -3
  149. package/src/typescript/components/arrow-function.tsx +2 -2
  150. package/src/typescript/components/class-method.tsx +1 -1
  151. package/src/typescript/components/enum-declaration.tsx +6 -6
  152. package/src/typescript/components/function-declaration.tsx +1 -1
  153. package/src/typescript/components/function-expression.tsx +2 -2
  154. package/src/typescript/components/function-type.tsx +2 -2
  155. package/src/typescript/components/interface-declaration.tsx +11 -12
  156. package/src/typescript/components/interface-member.tsx +2 -2
  157. package/src/typescript/components/interface-method.tsx +2 -2
  158. package/src/typescript/components/record-expression.tsx +1 -1
  159. package/src/typescript/components/type-alias-declaration.tsx +1 -1
  160. package/src/typescript/components/type-declaration.tsx +1 -1
  161. package/src/typescript/components/type-expression.tsx +2 -2
  162. package/src/typescript/components/type-transform.tsx +3 -3
  163. package/src/typescript/components/union-declaration.tsx +2 -2
  164. package/src/typescript/components/union-expression.tsx +12 -7
  165. package/src/typescript/components/value-expression.tsx +2 -2
  166. package/src/typescript/utils/operation.ts +2 -2
  167. package/test/test-host.ts +4 -0
  168. package/test/testing/snippet-extractor-csharp.test.ts +4 -4
  169. package/test/testing/snippet-extractor-java.test.ts +4 -4
  170. package/test/testing/snippet-extractor-python.test.ts +3 -3
  171. package/test/testing/snippet-extractor-typescript.test.ts +4 -4
  172. package/test/typescript/components/arrow-function.test.tsx +2 -2
  173. package/test/typescript/components/enum-declaration.test.tsx +1 -1
  174. package/test/typescript/components/function-declaration.test.tsx +1 -1
  175. package/test/typescript/components/function-expression.test.tsx +2 -2
  176. package/test/typescript/components/function-type.test.tsx +2 -2
  177. package/test/typescript/components/interface-declaration.test.tsx +1 -1
  178. package/test/typescript/components/interface-method.test.tsx +2 -2
  179. package/test/typescript/components/member-expression.test.tsx +10 -10
  180. package/test/typescript/components/type-alias-declaration.test.tsx +1 -1
  181. package/test/typescript/components/type-transform.test.tsx +2 -2
  182. package/test/typescript/components/union-declaration.test.tsx +2 -2
  183. package/test/typescript/components/value-expression.test.tsx +6 -6
  184. package/test/typescript/test-host.ts +1 -1
  185. package/test/utils.ts +2 -2
  186. package/test/vitest.setup.ts +1 -0
  187. package/tsconfig.json +3 -1
  188. package/vitest.config.ts +2 -1
  189. package/dist/src/testing/scenario-test/test-host.d.ts +0 -8
  190. package/dist/src/testing/scenario-test/test-host.d.ts.map +0 -1
  191. package/dist/src/testing/scenario-test/test-host.js +0 -49
  192. package/src/testing/scenario-test/test-host.ts +0 -83
@@ -0,0 +1,56 @@
1
+ import { useTsp } from "#core/context/tsp-context.js";
2
+ import { type Children, For } from "@alloy-js/core";
3
+ import * as cs from "@alloy-js/csharp";
4
+ import type { Enum, Union } from "@typespec/compiler";
5
+ import { reportDiagnostic } from "../../../lib.js";
6
+ import { getDocComments } from "../utils/doc-comments.jsx";
7
+ import { declarationRefkeys, efRefkey } from "../utils/refkey.js";
8
+
9
+ export interface EnumDeclarationProps extends Omit<cs.EnumDeclarationProps, "name"> {
10
+ name?: string;
11
+ type: Union | Enum;
12
+ }
13
+
14
+ export function EnumDeclaration(props: EnumDeclarationProps): Children {
15
+ const { $ } = useTsp();
16
+ let type: Enum;
17
+ if ($.union.is(props.type)) {
18
+ if (!$.union.isValidEnum(props.type)) {
19
+ throw new Error("The provided union type cannot be represented as an enum");
20
+ }
21
+ type = $.enum.createFromUnion(props.type);
22
+ } else {
23
+ type = props.type;
24
+ }
25
+
26
+ if (!props.type.name) {
27
+ reportDiagnostic($.program, { code: "type-declaration-missing-name", target: props.type });
28
+ }
29
+ const refkeys = declarationRefkeys(props.refkey, props.type)[0]; // TODO: support multiple refkeys for declarations in alloy
30
+ const name = props.name ?? cs.useCSharpNamePolicy().getName(props.type.name!, "enum");
31
+ const members = Array.from(type.members.entries());
32
+
33
+ return (
34
+ <>
35
+ <cs.EnumDeclaration name={name} refkey={refkeys} {...props}>
36
+ <For each={members} joiner={",\n"}>
37
+ {([key, value]) => {
38
+ return (
39
+ <>
40
+ <cs.DocWhen doc={getDocComments($, value)} />
41
+ <cs.EnumMember
42
+ name={cs.useCSharpNamePolicy().getName(key, "enum-member")}
43
+ refkey={
44
+ $.union.is(props.type)
45
+ ? efRefkey(props.type.variants.get(key))
46
+ : efRefkey(value)
47
+ }
48
+ />
49
+ </>
50
+ );
51
+ }}
52
+ </For>
53
+ </cs.EnumDeclaration>
54
+ </>
55
+ );
56
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./class/declaration.js";
2
+ export * from "./enum/declaration.jsx";
3
+ export * from "./property/property.jsx";
4
+ export * from "./type-expression.jsx";
@@ -0,0 +1,97 @@
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";
4
+ import { t, type TesterInstance } from "@typespec/compiler/testing";
5
+ import { beforeEach, describe, expect, it } from "vitest";
6
+ import { Output } from "../../../core/components/output.jsx";
7
+ import { Property } from "./property.jsx";
8
+
9
+ let tester: TesterInstance;
10
+
11
+ beforeEach(async () => {
12
+ tester = await Tester.createInstance();
13
+ });
14
+
15
+ function Wrapper(props: { children: Children }) {
16
+ const policy = createCSharpNamePolicy();
17
+ return (
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>
24
+ </Output>
25
+ );
26
+ }
27
+
28
+ it("maps prop: string | null to nullable property", async () => {
29
+ const { prop1 } = await tester.compile(t.code`
30
+ model TestModel {
31
+ ${t.modelProperty("prop1")}: string | null;
32
+ }
33
+ `);
34
+
35
+ expect(
36
+ <Wrapper>
37
+ <Property type={prop1} />
38
+ </Wrapper>,
39
+ ).toRenderTo(`
40
+ namespace TestNamespace
41
+ {
42
+ class Test
43
+ {
44
+ public required string? Prop1 { get; set; }
45
+ }
46
+ }
47
+ `);
48
+ });
49
+
50
+ describe("jsonAttributes", () => {
51
+ it("adds [JsonNameAttribute]", async () => {
52
+ const { prop1 } = await tester.compile(t.code`
53
+ model TestModel {
54
+ ${t.modelProperty("prop1")}: string;
55
+ }
56
+ `);
57
+
58
+ expect(
59
+ <Wrapper>
60
+ <Property type={prop1} jsonAttributes />
61
+ </Wrapper>,
62
+ ).toRenderTo(`
63
+ namespace TestNamespace
64
+ {
65
+ class Test
66
+ {
67
+ [System.Text.Json.JsonPropertyName("prop1")]
68
+ public required string Prop1 { get; set; }
69
+ }
70
+ }
71
+ `);
72
+ });
73
+
74
+ it("adds [JsonNameAttribute] respecting encodedName", async () => {
75
+ const { prop1 } = await tester.compile(t.code`
76
+ model TestModel {
77
+ @encodedName("application/json", "prop_1")
78
+ ${t.modelProperty("prop1")}: string;
79
+ }
80
+ `);
81
+
82
+ expect(
83
+ <Wrapper>
84
+ <Property type={prop1} jsonAttributes />
85
+ </Wrapper>,
86
+ ).toRenderTo(`
87
+ namespace TestNamespace
88
+ {
89
+ class Test
90
+ {
91
+ [System.Text.Json.JsonPropertyName("prop_1")]
92
+ public required string Prop1 { get; set; }
93
+ }
94
+ }
95
+ `);
96
+ });
97
+ });
@@ -0,0 +1,62 @@
1
+ import { type Children } from "@alloy-js/core";
2
+ import * as cs from "@alloy-js/csharp";
3
+ import { Attribute } from "@alloy-js/csharp";
4
+ import { type ModelProperty, resolveEncodedName, type Type } from "@typespec/compiler";
5
+ import { useTsp } from "../../../core/index.js";
6
+ import { TypeExpression } from "../type-expression.jsx";
7
+ import { getDocComments } from "../utils/doc-comments.jsx";
8
+
9
+ export interface PropertyProps {
10
+ type: ModelProperty;
11
+ /** If set the property will add the json serialization attributes(using System.Text.Json). */
12
+ jsonAttributes?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Create a C# property declaration from a TypeSpec property type.
17
+ */
18
+ export function Property(props: PropertyProps): Children {
19
+ const result = preprocessPropertyType(props.type.type);
20
+ const { $ } = useTsp();
21
+
22
+ return (
23
+ <cs.Property
24
+ name={props.type.name}
25
+ type={<TypeExpression type={result.type} />}
26
+ public
27
+ required={!props.type.optional}
28
+ nullable={result.nullable}
29
+ doc={getDocComments($, props.type)}
30
+ attributes={props.jsonAttributes ? [<JsonNameAttribute type={props.type} />] : undefined}
31
+ get
32
+ set
33
+ />
34
+ );
35
+ }
36
+
37
+ export interface JsonNameAttributeProps {
38
+ type: ModelProperty;
39
+ }
40
+
41
+ function JsonNameAttribute(props: JsonNameAttributeProps): Children {
42
+ const { program } = useTsp();
43
+ const jsonName = resolveEncodedName(program, props.type, "application/json");
44
+ return <Attribute name="System.Text.Json.JsonPropertyName" args={[JSON.stringify(jsonName)]} />;
45
+ }
46
+
47
+ function preprocessPropertyType(type: Type): { type: Type; nullable: boolean } {
48
+ const { $ } = useTsp();
49
+
50
+ 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 };
56
+ } else {
57
+ return { type, nullable: false };
58
+ }
59
+ } else {
60
+ return { type, nullable: false };
61
+ }
62
+ }
@@ -0,0 +1,133 @@
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";
9
+ import { Output } from "../../core/index.js";
10
+ import { ClassDeclaration } from "./class/declaration.js";
11
+ import { TypeExpression } from "./type-expression.jsx";
12
+
13
+ let runner: BasicTestRunner;
14
+
15
+ beforeEach(async () => {
16
+ runner = await createEmitterFrameworkTestRunner();
17
+ });
18
+
19
+ function Wrapper(props: { children: Children }) {
20
+ return (
21
+ <Output program={runner.program}>
22
+ <Namespace name="TestNamespace">
23
+ <SourceFile path="test.ts">{props.children}</SourceFile>
24
+ </Namespace>
25
+ </Output>
26
+ );
27
+ }
28
+
29
+ async function compileType(ref: string) {
30
+ const { test } = await runner.compile(`
31
+ model Test {
32
+ @test test: ${ref};
33
+ }
34
+ `);
35
+
36
+ return (test as ModelProperty).type;
37
+ }
38
+
39
+ describe("map scalar to c# built-in types", () => {
40
+ it.each([
41
+ ["string", "string"],
42
+ ["int32", "int"],
43
+ ["int64", "long"],
44
+ ])("%s => %s", async (tspType, csType) => {
45
+ const type = await compileType(tspType);
46
+ const res = render(
47
+ <Wrapper>
48
+ <TypeExpression type={type} />
49
+ </Wrapper>,
50
+ );
51
+
52
+ assertFileContents(
53
+ res,
54
+ d`
55
+ namespace TestNamespace
56
+ {
57
+ ${csType}
58
+ }
59
+ `,
60
+ );
61
+ });
62
+ });
63
+
64
+ it("maps array to c# array", async () => {
65
+ const type = await compileType("int32[]");
66
+ const res = render(
67
+ <Wrapper>
68
+ <TypeExpression type={type} />
69
+ </Wrapper>,
70
+ );
71
+
72
+ assertFileContents(
73
+ res,
74
+ d`
75
+ namespace TestNamespace
76
+ {
77
+ int[]
78
+ }
79
+ `,
80
+ );
81
+ });
82
+
83
+ describe("Record map to IDictionary", () => {
84
+ it("for primitive types", async () => {
85
+ const type = await compileType("Record<int32>");
86
+ const res = render(
87
+ <Wrapper>
88
+ <TypeExpression type={type} />
89
+ </Wrapper>,
90
+ );
91
+
92
+ assertFileContents(
93
+ res,
94
+ d`
95
+ namespace TestNamespace
96
+ {
97
+ IDictionary<string, int>
98
+ }
99
+ `,
100
+ );
101
+ });
102
+
103
+ it("for models", async () => {
104
+ const { test, Pet } = (await runner.compile(`
105
+ model Test {
106
+ @test test: Record<Pet>;
107
+ }
108
+ @test model Pet {}
109
+ `)) as { test: ModelProperty; Pet: Model };
110
+
111
+ const res = render(
112
+ <Wrapper>
113
+ <ClassDeclaration type={Pet} />
114
+ <hbr />
115
+ <TypeExpression type={test.type} />
116
+ </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
+ );
132
+ });
133
+ });
@@ -0,0 +1,119 @@
1
+ import { type Children, code } from "@alloy-js/core";
2
+ import { Reference } from "@alloy-js/csharp";
3
+ import { getTypeName, type IntrinsicType, type Scalar, type Type } from "@typespec/compiler";
4
+ import type { Typekit } from "@typespec/compiler/typekit";
5
+ import { useTsp } from "../../core/index.js";
6
+ import { reportTypescriptDiagnostic } from "../../typescript/lib.js";
7
+ import { efRefkey } from "./utils/refkey.js";
8
+
9
+ export interface TypeExpressionProps {
10
+ type: Type;
11
+ }
12
+
13
+ export function TypeExpression(props: TypeExpressionProps): Children {
14
+ const { $ } = useTsp();
15
+ if (isDeclaration($, props.type)) {
16
+ return <Reference refkey={efRefkey(props.type)} />;
17
+ }
18
+ if ($.scalar.is(props.type)) {
19
+ return getScalarIntrinsicExpression($, props.type);
20
+ } else if ($.array.is(props.type)) {
21
+ return code`${(<TypeExpression type={props.type.indexer.value} />)}[]`;
22
+ } else if ($.record.is(props.type)) {
23
+ return code`IDictionary<string, ${(<TypeExpression type={props.type.indexer.value} />)}>`;
24
+ }
25
+
26
+ throw new Error(
27
+ `Unsupported type for TypeExpression: ${props.type.kind} (${getTypeName(props.type)})`,
28
+ );
29
+ }
30
+
31
+ const intrinsicNameToCSharpType = new Map<string, string | null>([
32
+ // Core types
33
+ ["unknown", "object"], // Matches C#'s `object`
34
+ ["string", "string"], // Matches C#'s `string`
35
+ ["boolean", "bool"], // Matches C#'s `bool`
36
+ ["null", "null"], // Matches C#'s `null`
37
+ ["void", "void"], // Matches C#'s `void`
38
+ ["never", null], // No direct equivalent in C#
39
+ ["bytes", "byte[]"], // Matches C#'s `byte[]`
40
+
41
+ // Numeric types
42
+ ["numeric", "decimal"], // Parent type for all numeric types, use most precise
43
+ ["integer", "int"], // Broad integer category, maps to `int`
44
+ ["float", "float"], // Broad float category, maps to `float`
45
+ ["decimal", "decimal"], // Broad decimal category, maps to `decimal`
46
+ ["decimal128", "decimal"], // C#'s decimal is 128-bit
47
+ ["int64", "long"], // 64-bit signed integer
48
+ ["int32", "int"], // 32-bit signed integer
49
+ ["int16", "short"], // 16-bit signed integer
50
+ ["int8", "sbyte"], // 8-bit signed integer
51
+ ["safeint", "int"], // Safe integer, use int as default
52
+ ["uint64", "ulong"], // 64-bit unsigned integer
53
+ ["uint32", "uint"], // 32-bit unsigned integer
54
+ ["uint16", "ushort"], // 16-bit unsigned integer
55
+ ["uint8", "byte"], // 8-bit unsigned integer
56
+ ["float32", "float"], // 32-bit floating point
57
+ ["float64", "double"], // 64-bit floating point
58
+
59
+ // Date and time types
60
+ ["plainDate", "DateOnly"], // Use .NET 6+ DateOnly for plain calendar dates
61
+ ["plainTime", "TimeOnly"], // Use .NET 6+ TimeOnly for plain clock times
62
+ ["utcDateTime", "DateTimeOffset"], // Use DateTimeOffset for UTC date-times
63
+ ["offsetDateTime", "DateTimeOffset"], // Use DateTimeOffset for timezone-specific date-times
64
+ ["duration", "TimeSpan"], // Duration as TimeSpan
65
+
66
+ // String types
67
+ ["url", "Uri"], // Matches C#'s `Uri`
68
+ ]);
69
+
70
+ export function getScalarIntrinsicExpression(
71
+ $: Typekit,
72
+ type: Scalar | IntrinsicType,
73
+ ): string | null {
74
+ let intrinsicName: string;
75
+
76
+ if ($.scalar.isUtcDateTime(type) || $.scalar.extendsUtcDateTime(type)) {
77
+ return "DateTimeOffset";
78
+ }
79
+ if ($.scalar.is(type)) {
80
+ intrinsicName = $.scalar.getStdBase(type)?.name ?? "";
81
+ } else {
82
+ intrinsicName = type.name;
83
+ }
84
+
85
+ const csType = intrinsicNameToCSharpType.get(intrinsicName);
86
+
87
+ if (!csType) {
88
+ reportTypescriptDiagnostic($.program, { code: "typescript-unsupported-scalar", target: type });
89
+ return "object"; // Fallback to object if unsupported
90
+ }
91
+
92
+ return csType;
93
+ }
94
+
95
+ function isDeclaration($: Typekit, type: Type): boolean {
96
+ switch (type.kind) {
97
+ case "Namespace":
98
+ case "Interface":
99
+ case "Enum":
100
+ case "Operation":
101
+ case "EnumMember":
102
+ return true;
103
+ case "UnionVariant":
104
+ return false;
105
+
106
+ case "Model":
107
+ if ($.array.is(type) || $.record.is(type)) {
108
+ return false;
109
+ }
110
+
111
+ return Boolean(type.name);
112
+ case "Union":
113
+ return Boolean(type.name);
114
+ default:
115
+ return false;
116
+ }
117
+ }
118
+
119
+ export { intrinsicNameToCSharpType };
@@ -0,0 +1,58 @@
1
+ import { type Children, List } from "@alloy-js/core";
2
+ import * as cs from "@alloy-js/csharp";
3
+ import { getReturnsDoc, type Type } from "@typespec/compiler";
4
+ import type { Typekit } from "@typespec/compiler/typekit";
5
+
6
+ /**
7
+ * Helper to render a doc string for a given TypeSpec type.
8
+ *
9
+ * This is not a JSX component as it needs to return undefined if there is no doc.
10
+ *
11
+ * @param $ The Typekit instance
12
+ * @param type The TypeSpec type to generate documentation for
13
+ * @returns A DocSummary component containing the rendered doc string, or undefined if no doc is available.
14
+ */
15
+ export function getDocComments($: Typekit, type: Type): Children {
16
+ const typeDoc = $.type.getDoc(type);
17
+ if (!typeDoc) {
18
+ return undefined;
19
+ }
20
+
21
+ const docElements: Children[] = [];
22
+
23
+ // Add main type documentation
24
+ docElements.push(
25
+ <cs.DocSummary>
26
+ <cs.DocFromMarkdown markdown={typeDoc} />
27
+ </cs.DocSummary>,
28
+ );
29
+
30
+ // Add operation-specific documentation if applicable
31
+ if ($.operation.is(type)) {
32
+ // Add parameter documentation
33
+ const paramDocs = [];
34
+ for (const param of type.parameters.properties.values()) {
35
+ const paramDoc = $.type.getDoc(param);
36
+ if (paramDoc) {
37
+ paramDocs.push(
38
+ <cs.DocParam name={param.name}>
39
+ <cs.DocFromMarkdown markdown={paramDoc} />
40
+ </cs.DocParam>,
41
+ );
42
+ }
43
+ }
44
+ docElements.push(...paramDocs);
45
+
46
+ // Add return documentation
47
+ const returnDoc = getReturnsDoc($.program, type);
48
+ if (returnDoc) {
49
+ docElements.push(
50
+ <cs.DocReturns>
51
+ <cs.DocFromMarkdown markdown={returnDoc} />
52
+ </cs.DocReturns>,
53
+ );
54
+ }
55
+ }
56
+
57
+ return <List doubleHardline>{docElements}</List>;
58
+ }
@@ -0,0 +1,36 @@
1
+ import { refkey as ayRefkey, type Refkey } from "@alloy-js/core";
2
+
3
+ const refKeyPrefix = Symbol.for("emitter-framework:csharp");
4
+
5
+ /**
6
+ * A wrapper around `refkey` that uses a custom symbol to avoid collisions with
7
+ * other libraries that use `refkey`.
8
+ *
9
+ * @remarks
10
+ *
11
+ * The underlying refkey function is called with the {@link refKeyPrefix} symbol as the first argument.
12
+ *
13
+ * @param args The parameters of the refkey.
14
+ * @returns A refkey object that can be used to identify the value.
15
+ */
16
+ export function efRefkey(...args: unknown[]): Refkey {
17
+ if (args.length === 0) {
18
+ return ayRefkey(); // Generates a unique refkey
19
+ }
20
+ return ayRefkey(refKeyPrefix, ...args);
21
+ }
22
+
23
+ /**
24
+ * Creates a refkey for a declaration by combining the provided refkey with an internal
25
+ * refkey generated from the provided arguments.
26
+ *
27
+ * @param refkey The refkey provided by the user to be passed as is.
28
+ * @param args The parameters of the refkey.
29
+ * @returns An array of refkeys that can be passed to an Alloy declaration.
30
+ */
31
+ export function declarationRefkeys(refkey?: Refkey | Refkey[], ...args: unknown[]): Refkey[] {
32
+ if (refkey) {
33
+ return [refkey, efRefkey(...args)].flat();
34
+ }
35
+ return [efRefkey(...args)];
36
+ }
@@ -0,0 +1 @@
1
+ export * from "./components/index.js";
@@ -1,5 +1,5 @@
1
1
  import { resolvePath } from "@typespec/compiler";
2
- import { createTestLibrary, TypeSpecTestLibrary } from "@typespec/compiler/testing";
2
+ import { createTestLibrary, type TypeSpecTestLibrary } from "@typespec/compiler/testing";
3
3
  import { fileURLToPath } from "url";
4
4
 
5
5
  export const EmitterFrameworkTestLibrary: TypeSpecTestLibrary = createTestLibrary({
@@ -0,0 +1,95 @@
1
+ import { d } from "@alloy-js/core/testing";
2
+ import { beforeAll, describe, expect, it } from "vitest";
3
+ import { getExcerptForQuery, parseCodeBlockHeading } from "./code-block-expectation.js";
4
+ import {
5
+ createSnippetExtractor,
6
+ createTypeScriptExtractorConfig,
7
+ type SnippetExtractor,
8
+ } from "./snippet-extractor.js";
9
+
10
+ describe("parseCodeBlockHeading", () => {
11
+ it("parse whole file expectation", () => {
12
+ expect(parseCodeBlockHeading("ts path/to/file.ts")).toEqual({
13
+ lang: "ts",
14
+ file: "path/to/file.ts",
15
+ });
16
+ });
17
+
18
+ it("throws error when no file is provided", () => {
19
+ expect(() => parseCodeBlockHeading("ts")).toThrow(
20
+ 'Invalid code block heading: "ts". Missing file path. Expected format: "<lang> <path>"',
21
+ );
22
+ });
23
+
24
+ it("parse parse with type and name", () => {
25
+ expect(parseCodeBlockHeading("ts path/to/file.ts interface foo")).toEqual({
26
+ lang: "ts",
27
+ file: "path/to/file.ts",
28
+ query: {
29
+ type: "interface",
30
+ name: "foo",
31
+ },
32
+ });
33
+ });
34
+
35
+ it("throws error when using type but no name is provided", () => {
36
+ expect(() => parseCodeBlockHeading("ts path/to/file.ts interface")).toThrow(
37
+ 'Invalid code block heading: "ts path/to/file.ts interface". Missing name when using type. Expected format: "<lang> <path> [type] [name]"',
38
+ );
39
+ });
40
+ });
41
+
42
+ describe("getExcerptForQuery", () => {
43
+ let snippetExtractor: SnippetExtractor;
44
+ beforeAll(async () => {
45
+ const tsExtractorConfig = await createTypeScriptExtractorConfig();
46
+ snippetExtractor = createSnippetExtractor(tsExtractorConfig);
47
+ });
48
+
49
+ it("gets a whole file", async () => {
50
+ const expectation = {
51
+ lang: "ts",
52
+ file: "file.ts",
53
+ };
54
+ const outputs = {
55
+ "file.ts": d`
56
+ interface bar {
57
+
58
+ }
59
+ interface foo {
60
+ bar: string;
61
+ }
62
+ `,
63
+ };
64
+ const excerpt = getExcerptForQuery(snippetExtractor, expectation, outputs);
65
+ expect(excerpt).toBe(outputs["file.ts"]);
66
+ });
67
+
68
+ it("gets an interface for typescript", async () => {
69
+ const expectation = {
70
+ lang: "ts",
71
+ file: "file.ts",
72
+ query: {
73
+ type: "interface",
74
+ name: "foo",
75
+ },
76
+ };
77
+ const outputs = {
78
+ "file.ts": d`
79
+ interface bar {
80
+
81
+ }
82
+
83
+ interface foo {
84
+ bar: string;
85
+ }
86
+ `,
87
+ };
88
+ const excerpt = getExcerptForQuery(snippetExtractor, expectation, outputs);
89
+ expect(excerpt).toBe(d`
90
+ interface foo {
91
+ bar: string;
92
+ }
93
+ `);
94
+ });
95
+ });