@typespec/emitter-framework 0.8.0-dev.2 → 0.8.0-dev.3

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 (46) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/src/typescript/components/enum-declaration.d.ts +1 -0
  3. package/dist/src/typescript/components/enum-declaration.d.ts.map +1 -1
  4. package/dist/src/typescript/components/enum-declaration.js +7 -0
  5. package/dist/src/typescript/components/function-declaration.d.ts.map +1 -1
  6. package/dist/src/typescript/components/function-declaration.js +8 -3
  7. package/dist/src/typescript/components/interface-declaration.d.ts.map +1 -1
  8. package/dist/src/typescript/components/interface-declaration.js +3 -0
  9. package/dist/src/typescript/components/interface-member.d.ts +3 -1
  10. package/dist/src/typescript/components/interface-member.d.ts.map +1 -1
  11. package/dist/src/typescript/components/interface-member.js +2 -0
  12. package/dist/src/typescript/components/interface-method.d.ts +3 -1
  13. package/dist/src/typescript/components/interface-method.d.ts.map +1 -1
  14. package/dist/src/typescript/components/interface-method.js +9 -2
  15. package/dist/src/typescript/components/type-alias-declaration.d.ts.map +1 -1
  16. package/dist/src/typescript/components/type-alias-declaration.js +4 -1
  17. package/dist/src/typescript/components/type-declaration.d.ts.map +1 -1
  18. package/dist/src/typescript/components/type-declaration.js +10 -0
  19. package/dist/src/typescript/components/union-declaration.d.ts +3 -1
  20. package/dist/src/typescript/components/union-declaration.d.ts.map +1 -1
  21. package/dist/src/typescript/components/union-declaration.js +4 -1
  22. package/dist/src/typescript/components/union-expression.d.ts.map +1 -1
  23. package/dist/src/typescript/components/union-expression.js +103 -10
  24. package/dist/src/typescript/utils/operation.d.ts.map +1 -1
  25. package/dist/src/typescript/utils/operation.js +5 -0
  26. package/dist/test/typescript/components/enum-declaration.test.js +79 -0
  27. package/dist/test/typescript/components/function-declaration.test.js +81 -0
  28. package/dist/test/typescript/components/interface-declaration.test.js +232 -9
  29. package/dist/test/typescript/components/type-alias-declaration.test.js +75 -0
  30. package/dist/test/typescript/components/union-declaration.test.js +358 -106
  31. package/package.json +1 -1
  32. package/src/typescript/components/enum-declaration.tsx +12 -1
  33. package/src/typescript/components/function-declaration.tsx +6 -1
  34. package/src/typescript/components/interface-declaration.tsx +3 -1
  35. package/src/typescript/components/interface-member.tsx +4 -0
  36. package/src/typescript/components/interface-method.tsx +7 -1
  37. package/src/typescript/components/type-alias-declaration.tsx +2 -1
  38. package/src/typescript/components/type-declaration.tsx +8 -6
  39. package/src/typescript/components/union-declaration.tsx +4 -1
  40. package/src/typescript/components/union-expression.tsx +100 -7
  41. package/src/typescript/utils/operation.ts +3 -0
  42. package/test/typescript/components/enum-declaration.test.tsx +72 -0
  43. package/test/typescript/components/function-declaration.test.tsx +78 -0
  44. package/test/typescript/components/interface-declaration.test.tsx +223 -9
  45. package/test/typescript/components/type-alias-declaration.test.tsx +72 -0
  46. package/test/typescript/components/union-declaration.test.tsx +330 -102
@@ -1,3 +1,4 @@
1
+ import { Children } from "@alloy-js/core";
1
2
  import * as ts from "@alloy-js/typescript";
2
3
  import { isNeverType, ModelProperty, Operation } from "@typespec/compiler";
3
4
  import { getHttpPart } from "@typespec/http";
@@ -7,6 +8,7 @@ import { TypeExpression } from "./type-expression.js";
7
8
 
8
9
  export interface InterfaceMemberProps {
9
10
  type: ModelProperty | Operation;
11
+ doc?: Children;
10
12
  optional?: boolean;
11
13
  }
12
14
 
@@ -14,6 +16,7 @@ export function InterfaceMember(props: InterfaceMemberProps) {
14
16
  const { $ } = useTsp();
15
17
  const namer = ts.useTSNamePolicy();
16
18
  const name = namer.getName(props.type.name, "object-member-getter");
19
+ const doc = props.doc ?? $.type.getDoc(props.type);
17
20
 
18
21
  if ($.modelProperty.is(props.type)) {
19
22
  if (isNeverType(props.type.type)) {
@@ -28,6 +31,7 @@ export function InterfaceMember(props: InterfaceMemberProps) {
28
31
 
29
32
  return (
30
33
  <ts.InterfaceMember
34
+ doc={doc}
31
35
  name={name}
32
36
  optional={props.optional ?? props.type.optional}
33
37
  type={<TypeExpression type={unpackedType} />}
@@ -1,12 +1,14 @@
1
- import { splitProps } from "@alloy-js/core/jsx-runtime";
1
+ import { Children, splitProps } from "@alloy-js/core";
2
2
  import * as ts from "@alloy-js/typescript";
3
3
  import { Operation } from "@typespec/compiler";
4
+ import { useTsp } from "../../core/index.js";
4
5
  import { buildParameterDescriptors, getReturnType } from "../utils/operation.js";
5
6
  import { TypeExpression } from "./type-expression.jsx";
6
7
 
7
8
  export interface InterfaceMethodPropsWithType extends Omit<ts.InterfaceMethodProps, "name"> {
8
9
  type: Operation;
9
10
  name?: string;
11
+ doc?: Children;
10
12
  parametersMode?: "prepend" | "append" | "replace";
11
13
  }
12
14
 
@@ -18,6 +20,7 @@ export type InterfaceMethodProps = InterfaceMethodPropsWithType | ts.InterfaceMe
18
20
  * provided will take precedence.
19
21
  */
20
22
  export function InterfaceMethod(props: Readonly<InterfaceMethodProps>) {
23
+ const { $ } = useTsp();
21
24
  const isTypeSpecTyped = "type" in props;
22
25
  if (!isTypeSpecTyped) {
23
26
  return <ts.InterfaceMethod {...props} />;
@@ -36,6 +39,8 @@ export function InterfaceMethod(props: Readonly<InterfaceMethodProps>) {
36
39
  mode: props.parametersMode,
37
40
  });
38
41
 
42
+ const doc = props.doc ?? $.type.getDoc(props.type);
43
+
39
44
  return (
40
45
  <ts.InterfaceMethod
41
46
  {...forwardProps}
@@ -43,6 +48,7 @@ export function InterfaceMethod(props: Readonly<InterfaceMethodProps>) {
43
48
  name={name}
44
49
  returnType={returnType}
45
50
  parameters={allParameters}
51
+ doc={doc}
46
52
  />
47
53
  );
48
54
  }
@@ -30,11 +30,12 @@ export function TypeAliasDeclaration(props: TypeAliasDeclarationProps) {
30
30
  reportDiagnostic($.program, { code: "type-declaration-missing-name", target: props.type });
31
31
  }
32
32
 
33
+ const doc = props.doc ?? $.type.getDoc(props.type);
33
34
  const refkeys = declarationRefkeys(props.refkey, props.name);
34
35
 
35
36
  const name = ts.useTSNamePolicy().getName(originalName, "type");
36
37
  return (
37
- <ts.TypeDeclaration {...props} name={name} refkey={refkeys}>
38
+ <ts.TypeDeclaration doc={doc} {...props} name={name} refkey={refkeys}>
38
39
  <TypeExpression type={props.type} noReference />
39
40
  {props.children}
40
41
  </ts.TypeDeclaration>
@@ -1,5 +1,6 @@
1
1
  import * as ts from "@alloy-js/typescript";
2
2
  import { Type } from "@typespec/compiler";
3
+ import { useTsp } from "../../core/index.js";
3
4
  import { declarationRefkeys } from "../utils/refkey.js";
4
5
  import { EnumDeclaration } from "./enum-declaration.js";
5
6
  import { InterfaceDeclaration } from "./interface-declaration.jsx";
@@ -14,9 +15,9 @@ export interface TypeDeclarationProps extends Omit<ts.TypeDeclarationProps, "nam
14
15
  export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
15
16
 
16
17
  export function TypeDeclaration(props: TypeDeclarationProps) {
18
+ const { $ } = useTsp();
17
19
  if (!props.type) {
18
20
  const refkeys = declarationRefkeys(props.refkey, props.name);
19
-
20
21
  return (
21
22
  <ts.TypeDeclaration
22
23
  {...(props as WithRequired<ts.TypeDeclarationProps, "name">)}
@@ -26,16 +27,17 @@ export function TypeDeclaration(props: TypeDeclarationProps) {
26
27
  }
27
28
 
28
29
  const { type, ...restProps } = props;
30
+ const doc = props.doc ?? $.type.getDoc(type);
29
31
  switch (type.kind) {
30
32
  case "Model":
31
- return <InterfaceDeclaration type={type} {...restProps} />;
33
+ return <InterfaceDeclaration doc={doc} type={type} {...restProps} />;
32
34
  case "Union":
33
- return <UnionDeclaration type={type} {...restProps} />;
35
+ return <UnionDeclaration doc={doc} type={type} {...restProps} />;
34
36
  case "Enum":
35
- return <EnumDeclaration type={type} {...restProps} />;
37
+ return <EnumDeclaration doc={doc} type={type} {...restProps} />;
36
38
  case "Scalar":
37
- return <TypeAliasDeclaration type={type} {...restProps} />;
39
+ return <TypeAliasDeclaration doc={doc} type={type} {...restProps} />;
38
40
  case "Operation":
39
- return <TypeAliasDeclaration type={type} {...restProps} />;
41
+ return <TypeAliasDeclaration doc={doc} type={type} {...restProps} />;
40
42
  }
41
43
  }
@@ -1,3 +1,4 @@
1
+ import { Children } from "@alloy-js/core";
1
2
  import * as ts from "@alloy-js/typescript";
2
3
  import { Enum, Union } from "@typespec/compiler";
3
4
  import { useTsp } from "../../core/context/tsp-context.js";
@@ -7,6 +8,7 @@ import { UnionExpression } from "./union-expression.js";
7
8
 
8
9
  export interface TypedUnionDeclarationProps extends Omit<ts.TypeDeclarationProps, "name"> {
9
10
  type: Union | Enum;
11
+ doc?: Children;
10
12
  name?: string;
11
13
  }
12
14
 
@@ -29,8 +31,9 @@ export function UnionDeclaration(props: UnionDeclarationProps) {
29
31
 
30
32
  const name = ts.useTSNamePolicy().getName(originalName!, "type");
31
33
 
34
+ const doc = props.doc ?? $.type.getDoc(type);
32
35
  return (
33
- <ts.TypeDeclaration {...props} name={name} refkey={refkeys}>
36
+ <ts.TypeDeclaration doc={doc} {...props} name={name} refkey={refkeys}>
34
37
  <UnionExpression type={type}>{coreProps.children}</UnionExpression>
35
38
  </ts.TypeDeclaration>
36
39
  );
@@ -1,8 +1,9 @@
1
1
  import * as ay from "@alloy-js/core";
2
2
  import { Children } from "@alloy-js/core";
3
3
  import * as ts from "@alloy-js/typescript";
4
- import { Enum, EnumMember, Union, UnionVariant } from "@typespec/compiler";
4
+ import { compilerAssert, Enum, EnumMember, Union, UnionVariant } from "@typespec/compiler";
5
5
  import { useTsp } from "../../core/context/tsp-context.js";
6
+ import { efRefkey } from "../utils/refkey.js";
6
7
  import { TypeExpression } from "./type-expression.jsx";
7
8
 
8
9
  export interface UnionExpressionProps {
@@ -19,11 +20,30 @@ export function UnionExpression({ type, children }: UnionExpressionProps) {
19
20
 
20
21
  const variants = (
21
22
  <ay.For joiner={" | "} each={items}>
22
- {(_, value) => {
23
- if ($.enumMember.is(value)) {
24
- return <ts.ValueExpression jsValue={value.value ?? value.name} />;
25
- } else {
26
- return <TypeExpression type={value.type} />;
23
+ {(_, type) => {
24
+ if ($.enumMember.is(type)) {
25
+ return <ts.ValueExpression jsValue={type.value ?? type.name} />;
26
+ }
27
+
28
+ const discriminatedUnion = $.union.getDiscriminatedUnion(type.union);
29
+ switch (discriminatedUnion?.options.envelope) {
30
+ case "object":
31
+ return (
32
+ <ObjectEnvelope
33
+ discriminatorPropertyName={discriminatedUnion.options.discriminatorPropertyName}
34
+ envelopePropertyName={discriminatedUnion.options.envelopePropertyName}
35
+ type={type}
36
+ />
37
+ );
38
+ case "none":
39
+ return (
40
+ <NoneEnvelope
41
+ discriminatorPropertyName={discriminatedUnion.options.discriminatorPropertyName}
42
+ type={type}
43
+ />
44
+ );
45
+ default:
46
+ return <TypeExpression type={type.type} />;
27
47
  }
28
48
  }}
29
49
  </ay.For>
@@ -32,10 +52,83 @@ export function UnionExpression({ type, children }: UnionExpressionProps) {
32
52
  if (children || (Array.isArray(children) && children.length)) {
33
53
  return (
34
54
  <>
35
- {variants} {` | ${children}`}
55
+ {variants} {`| ${children}`}
36
56
  </>
37
57
  );
38
58
  }
39
59
 
40
60
  return variants;
41
61
  }
62
+
63
+ interface ObjectEnvelopeProps {
64
+ type: UnionVariant;
65
+ discriminatorPropertyName: string;
66
+ envelopePropertyName: string;
67
+ }
68
+
69
+ /**
70
+ * Renders a discriminated union with "object" envelope style
71
+ * where model properties are nested inside an envelope
72
+ */
73
+ function ObjectEnvelope(props: ObjectEnvelopeProps) {
74
+ const { $ } = useTsp();
75
+
76
+ const envelope = $.model.create({
77
+ properties: {
78
+ [props.discriminatorPropertyName]: $.modelProperty.create({
79
+ name: props.discriminatorPropertyName,
80
+ type: $.literal.createString(props.type.name as string),
81
+ }),
82
+ [props.envelopePropertyName]: $.modelProperty.create({
83
+ name: props.envelopePropertyName,
84
+ type: props.type.type,
85
+ }),
86
+ },
87
+ });
88
+
89
+ return <TypeExpression type={envelope} />;
90
+ }
91
+
92
+ interface NoneEnvelopeProps {
93
+ type: UnionVariant;
94
+ discriminatorPropertyName: string;
95
+ }
96
+
97
+ /**
98
+ * Renders a discriminated union with "none" envelope style
99
+ * where discriminator property sits alongside model properties
100
+ */
101
+ function NoneEnvelope(props: NoneEnvelopeProps) {
102
+ const { $ } = useTsp();
103
+
104
+ compilerAssert(
105
+ $.model.is(props.type.type),
106
+ "Expected all union variants to be models when using a discriminated union with no envelope",
107
+ );
108
+
109
+ // Render anonymous models as a set of properties + the discriminator
110
+ if ($.model.isExpresion(props.type.type)) {
111
+ const model = $.model.create({
112
+ properties: {
113
+ [props.discriminatorPropertyName]: $.modelProperty.create({
114
+ name: props.discriminatorPropertyName,
115
+ type: $.literal.createString(props.type.name as string),
116
+ }),
117
+ ...Object.fromEntries(props.type.type.properties),
118
+ },
119
+ });
120
+ return <TypeExpression type={model} />;
121
+ }
122
+
123
+ return (
124
+ <ay.List joiner={" & "}>
125
+ <ts.ObjectExpression>
126
+ <ts.ObjectProperty
127
+ name={props.discriminatorPropertyName}
128
+ value={<ts.ValueExpression jsValue={props.type.name} />}
129
+ />
130
+ </ts.ObjectExpression>
131
+ <>{efRefkey(props.type.type)}</>
132
+ </ay.List>
133
+ );
134
+ }
@@ -47,10 +47,13 @@ export function buildParameterDescriptors(
47
47
  }
48
48
 
49
49
  export function buildParameterDescriptor(modelProperty: ModelProperty): ts.ParameterDescriptor {
50
+ const { $ } = useTsp();
50
51
  const namePolicy = ts.useTSNamePolicy();
51
52
  const paramName = namePolicy.getName(modelProperty.name, "parameter");
52
53
  const isOptional = modelProperty.optional || modelProperty.defaultValue !== undefined;
54
+ const doc = $.type.getDoc(modelProperty);
53
55
  return {
56
+ doc,
54
57
  name: paramName,
55
58
  refkey: efRefkey(modelProperty),
56
59
  optional: isOptional,
@@ -34,6 +34,78 @@ describe("Typescript Enum Declaration", () => {
34
34
  `);
35
35
  });
36
36
 
37
+ it("adds JSDoc from TypeSpec", async () => {
38
+ const code = `
39
+ /**
40
+ * This is a test enum
41
+ */
42
+ enum Foo {
43
+ @doc("This is one")
44
+ one: 1,
45
+ two: 2,
46
+ three: 3
47
+ }
48
+ `;
49
+ const output = await getEmitOutput(code, (program) => {
50
+ const Foo = program.resolveTypeReference("Foo")[0]! as Enum;
51
+ return (
52
+ <TspContext.Provider value={{ program }}>
53
+ <EnumDeclaration type={Foo} />
54
+ </TspContext.Provider>
55
+ );
56
+ });
57
+
58
+ expect(output).toBe(d`
59
+ /**
60
+ * This is a test enum
61
+ */
62
+ enum Foo {
63
+ /**
64
+ * This is one
65
+ */
66
+ one = 1,
67
+ two = 2,
68
+ three = 3
69
+ }
70
+ `);
71
+ });
72
+
73
+ it("explicit doc take precedence", async () => {
74
+ const code = `
75
+ /**
76
+ * This is a test enum
77
+ */
78
+ enum Foo {
79
+ @doc("This is one")
80
+ one: 1,
81
+ two: 2,
82
+ three: 3
83
+ }
84
+ `;
85
+ const output = await getEmitOutput(code, (program) => {
86
+ const Foo = program.resolveTypeReference("Foo")[0]! as Enum;
87
+ return (
88
+ <TspContext.Provider value={{ program }}>
89
+ <EnumDeclaration type={Foo} doc={["This is an explicit doc"]} />
90
+ </TspContext.Provider>
91
+ );
92
+ });
93
+
94
+ expect(output).toBe(d`
95
+ /**
96
+ * This is an explicit doc
97
+ */
98
+ enum Foo {
99
+ /**
100
+ * This is one
101
+ */
102
+ one = 1,
103
+ two = 2,
104
+ three = 3
105
+ }
106
+ `);
107
+ });
108
+
37
109
  it("takes a union type parameter", async () => {
38
110
  const code = `
39
111
  union Foo {
@@ -36,6 +36,84 @@ describe("Typescript Function Declaration", () => {
36
36
  expect(actualContent).toBe(expectedContent);
37
37
  });
38
38
 
39
+ it("creates a function with JSDoc", async () => {
40
+ const program = await getProgram(`
41
+ namespace DemoService;
42
+ /**
43
+ * This is a test function
44
+ */
45
+ op getName(
46
+ @doc("This is the id")
47
+ id: string, name: string): string;
48
+ `);
49
+
50
+ const [namespace] = program.resolveTypeReference("DemoService");
51
+ const operation = Array.from((namespace as Namespace).operations.values())[0];
52
+
53
+ const res = render(
54
+ <Output program={program}>
55
+ <SourceFile path="test.ts">
56
+ <FunctionDeclaration type={operation} />
57
+ </SourceFile>
58
+ </Output>,
59
+ );
60
+
61
+ const testFile = res.contents.find((file) => file.path === "test.ts");
62
+ assert(testFile, "test.ts file not rendered");
63
+ const actualContent = await format(testFile.contents as string, { parser: "typescript" });
64
+ const expectedContent = await format(
65
+ `
66
+ /**
67
+ * This is a test function
68
+ *
69
+ * @param {string} id - This is the id
70
+ * @param {string} name
71
+ */
72
+ function getName(id: string, name: string): string{}`,
73
+ {
74
+ parser: "typescript",
75
+ },
76
+ );
77
+ expect(actualContent).toBe(expectedContent);
78
+ });
79
+
80
+ it("creates a function with overridden JSDoc", async () => {
81
+ const program = await getProgram(`
82
+ namespace DemoService;
83
+ /**
84
+ * This is a test function
85
+ */
86
+ op getName(id: string): string;`);
87
+
88
+ const [namespace] = program.resolveTypeReference("DemoService");
89
+ const operation = Array.from((namespace as Namespace).operations.values())[0];
90
+
91
+ const res = render(
92
+ <Output program={program}>
93
+ <SourceFile path="test.ts">
94
+ <FunctionDeclaration doc={["This is a custom description"]} type={operation} />
95
+ </SourceFile>
96
+ </Output>,
97
+ );
98
+
99
+ const testFile = res.contents.find((file) => file.path === "test.ts");
100
+ assert(testFile, "test.ts file not rendered");
101
+ const actualContent = await format(testFile.contents as string, { parser: "typescript" });
102
+ const expectedContent = await format(
103
+ `
104
+ /**
105
+ * This is a custom description
106
+ *
107
+ * @param {string} id
108
+ */
109
+ function getName(id: string): string{}`,
110
+ {
111
+ parser: "typescript",
112
+ },
113
+ );
114
+ expect(actualContent).toBe(expectedContent);
115
+ });
116
+
39
117
  it("creates an async function", async () => {
40
118
  const program = await getProgram(`
41
119
  namespace DemoService;