Skip to content

Commit 05ce00f

Browse files
authored
[Emitter Framework] Emit TypeSpec docs as JSDoc in TypeScript components (microsoft#7409)
Plumbing TypeSpec docs all the way through alloy to emit JSDocs based on TypeSpec docs
1 parent 4cf1273 commit 05ce00f

16 files changed

+538
-20
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@typespec/emitter-framework"
5+
- "@typespec/http-client-js"
6+
---
7+
8+
Emit TypeSpec comments as JSDoc in TypeScript components

packages/emitter-framework/src/typescript/components/enum-declaration.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,22 @@ export function EnumDeclaration(props: EnumDeclarationProps) {
2828
const refkeys = declarationRefkeys(props.refkey, props.type);
2929
const name = props.name ?? ts.useTSNamePolicy().getName(props.type.name!, "enum");
3030
const members = Array.from(type.members.entries());
31+
const doc = props.doc ?? $.type.getDoc(type);
3132

3233
return (
33-
<ts.EnumDeclaration name={name} refkey={refkeys} default={props.default} export={props.export}>
34+
<ts.EnumDeclaration
35+
doc={doc}
36+
name={name}
37+
refkey={refkeys}
38+
default={props.default}
39+
export={props.export}
40+
>
3441
<ay.For each={members} joiner={",\n"}>
3542
{([key, value]) => {
43+
const memberDoc = $.type.getDoc(value);
3644
return (
3745
<EnumMember
46+
doc={memberDoc}
3847
type={value}
3948
refkey={
4049
$.union.is(props.type) ? efRefkey(props.type.variants.get(key)) : efRefkey(value)
@@ -49,12 +58,14 @@ export function EnumDeclaration(props: EnumDeclarationProps) {
4958

5059
export interface EnumMemberProps {
5160
type: TspEnumMember;
61+
doc?: ay.Children;
5262
refkey?: ay.Refkey;
5363
}
5464

5565
export function EnumMember(props: EnumMemberProps) {
5666
return (
5767
<ts.EnumMember
68+
doc={props.doc}
5869
name={props.type.name}
5970
jsValue={props.type.value ?? props.type.name}
6071
refkey={props.refkey}

packages/emitter-framework/src/typescript/components/function-declaration.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as ts from "@alloy-js/typescript";
22
import { Model, Operation } from "@typespec/compiler";
3+
import { useTsp } from "../../core/index.js";
34
import { buildParameterDescriptors, getReturnType } from "../utils/operation.js";
45
import { declarationRefkeys } from "../utils/refkey.js";
56
import { TypeExpression } from "./type-expression.js";
@@ -21,6 +22,8 @@ export type FunctionDeclarationProps =
2122
* provided will take precedence.
2223
*/
2324
export function FunctionDeclaration(props: FunctionDeclarationProps) {
25+
const { $ } = useTsp();
26+
2427
if (!isTypedFunctionDeclarationProps(props)) {
2528
return <ts.FunctionDeclaration {...props} />;
2629
}
@@ -40,17 +43,19 @@ export function FunctionDeclaration(props: FunctionDeclarationProps) {
4043
params: props.parameters,
4144
mode: props.parametersMode,
4245
});
46+
const doc = props.doc ?? $.type.getDoc(props.type);
4347
return (
4448
<ts.FunctionDeclaration
49+
doc={doc}
4550
refkey={refkeys}
4651
name={name}
4752
async={props.async}
4853
default={props.default}
4954
export={props.export}
5055
kind={props.kind}
5156
returnType={returnType}
57+
parameters={allParameters}
5258
>
53-
<ts.FunctionDeclaration.Parameters parameters={allParameters} />
5459
{props.children}
5560
</ts.FunctionDeclaration>
5661
);

packages/emitter-framework/src/typescript/components/interface-declaration.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ export function InterfaceDeclaration(props: InterfaceDeclarationProps) {
4545
const refkeys = declarationRefkeys(props.refkey, props.type);
4646

4747
const extendsType = props.extends ?? getExtendsType($, props.type);
48+
const doc = props.doc ?? $.type.getDoc(props.type);
4849

4950
return (
5051
<ts.InterfaceDeclaration
52+
doc={doc}
5153
default={props.default}
5254
export={props.export}
5355
kind={props.kind}

packages/emitter-framework/src/typescript/components/interface-member.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Children } from "@alloy-js/core";
12
import * as ts from "@alloy-js/typescript";
23
import { isNeverType, ModelProperty, Operation } from "@typespec/compiler";
34
import { getHttpPart } from "@typespec/http";
@@ -7,13 +8,15 @@ import { TypeExpression } from "./type-expression.js";
78

89
export interface InterfaceMemberProps {
910
type: ModelProperty | Operation;
11+
doc?: Children;
1012
optional?: boolean;
1113
}
1214

1315
export function InterfaceMember(props: InterfaceMemberProps) {
1416
const { $ } = useTsp();
1517
const namer = ts.useTSNamePolicy();
1618
const name = namer.getName(props.type.name, "object-member-getter");
19+
const doc = props.doc ?? $.type.getDoc(props.type);
1720

1821
if ($.modelProperty.is(props.type)) {
1922
if (isNeverType(props.type.type)) {
@@ -28,6 +31,7 @@ export function InterfaceMember(props: InterfaceMemberProps) {
2831

2932
return (
3033
<ts.InterfaceMember
34+
doc={doc}
3135
name={name}
3236
optional={props.optional ?? props.type.optional}
3337
type={<TypeExpression type={unpackedType} />}

packages/emitter-framework/src/typescript/components/interface-method.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { splitProps } from "@alloy-js/core/jsx-runtime";
1+
import { Children, splitProps } from "@alloy-js/core";
22
import * as ts from "@alloy-js/typescript";
33
import { Operation } from "@typespec/compiler";
4+
import { useTsp } from "../../core/index.js";
45
import { buildParameterDescriptors, getReturnType } from "../utils/operation.js";
56
import { TypeExpression } from "./type-expression.jsx";
67

78
export interface InterfaceMethodPropsWithType extends Omit<ts.InterfaceMethodProps, "name"> {
89
type: Operation;
910
name?: string;
11+
doc?: Children;
1012
parametersMode?: "prepend" | "append" | "replace";
1113
}
1214

@@ -18,6 +20,7 @@ export type InterfaceMethodProps = InterfaceMethodPropsWithType | ts.InterfaceMe
1820
* provided will take precedence.
1921
*/
2022
export function InterfaceMethod(props: Readonly<InterfaceMethodProps>) {
23+
const { $ } = useTsp();
2124
const isTypeSpecTyped = "type" in props;
2225
if (!isTypeSpecTyped) {
2326
return <ts.InterfaceMethod {...props} />;
@@ -36,13 +39,16 @@ export function InterfaceMethod(props: Readonly<InterfaceMethodProps>) {
3639
mode: props.parametersMode,
3740
});
3841

42+
const doc = props.doc ?? $.type.getDoc(props.type);
43+
3944
return (
4045
<ts.InterfaceMethod
4146
{...forwardProps}
4247
{...updateProps}
4348
name={name}
4449
returnType={returnType}
4550
parameters={allParameters}
51+
doc={doc}
4652
/>
4753
);
4854
}

packages/emitter-framework/src/typescript/components/type-alias-declaration.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ export function TypeAliasDeclaration(props: TypeAliasDeclarationProps) {
3030
reportDiagnostic($.program, { code: "type-declaration-missing-name", target: props.type });
3131
}
3232

33+
const doc = props.doc ?? $.type.getDoc(props.type);
3334
const refkeys = declarationRefkeys(props.refkey, props.name);
3435

3536
const name = ts.useTSNamePolicy().getName(originalName, "type");
3637
return (
37-
<ts.TypeDeclaration {...props} name={name} refkey={refkeys}>
38+
<ts.TypeDeclaration doc={doc} {...props} name={name} refkey={refkeys}>
3839
<TypeExpression type={props.type} noReference />
3940
{props.children}
4041
</ts.TypeDeclaration>

packages/emitter-framework/src/typescript/components/type-declaration.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as ts from "@alloy-js/typescript";
22
import { Type } from "@typespec/compiler";
3+
import { useTsp } from "../../core/index.js";
34
import { declarationRefkeys } from "../utils/refkey.js";
45
import { EnumDeclaration } from "./enum-declaration.js";
56
import { InterfaceDeclaration } from "./interface-declaration.jsx";
@@ -14,9 +15,9 @@ export interface TypeDeclarationProps extends Omit<ts.TypeDeclarationProps, "nam
1415
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
1516

1617
export function TypeDeclaration(props: TypeDeclarationProps) {
18+
const { $ } = useTsp();
1719
if (!props.type) {
1820
const refkeys = declarationRefkeys(props.refkey, props.name);
19-
2021
return (
2122
<ts.TypeDeclaration
2223
{...(props as WithRequired<ts.TypeDeclarationProps, "name">)}
@@ -26,16 +27,17 @@ export function TypeDeclaration(props: TypeDeclarationProps) {
2627
}
2728

2829
const { type, ...restProps } = props;
30+
const doc = props.doc ?? $.type.getDoc(type);
2931
switch (type.kind) {
3032
case "Model":
31-
return <InterfaceDeclaration type={type} {...restProps} />;
33+
return <InterfaceDeclaration doc={doc} type={type} {...restProps} />;
3234
case "Union":
33-
return <UnionDeclaration type={type} {...restProps} />;
35+
return <UnionDeclaration doc={doc} type={type} {...restProps} />;
3436
case "Enum":
35-
return <EnumDeclaration type={type} {...restProps} />;
37+
return <EnumDeclaration doc={doc} type={type} {...restProps} />;
3638
case "Scalar":
37-
return <TypeAliasDeclaration type={type} {...restProps} />;
39+
return <TypeAliasDeclaration doc={doc} type={type} {...restProps} />;
3840
case "Operation":
39-
return <TypeAliasDeclaration type={type} {...restProps} />;
41+
return <TypeAliasDeclaration doc={doc} type={type} {...restProps} />;
4042
}
4143
}

packages/emitter-framework/src/typescript/components/union-declaration.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Children } from "@alloy-js/core";
12
import * as ts from "@alloy-js/typescript";
23
import { Enum, Union } from "@typespec/compiler";
34
import { useTsp } from "../../core/context/tsp-context.js";
@@ -7,6 +8,7 @@ import { UnionExpression } from "./union-expression.js";
78

89
export interface TypedUnionDeclarationProps extends Omit<ts.TypeDeclarationProps, "name"> {
910
type: Union | Enum;
11+
doc?: Children;
1012
name?: string;
1113
}
1214

@@ -29,8 +31,9 @@ export function UnionDeclaration(props: UnionDeclarationProps) {
2931

3032
const name = ts.useTSNamePolicy().getName(originalName!, "type");
3133

34+
const doc = props.doc ?? $.type.getDoc(type);
3235
return (
33-
<ts.TypeDeclaration {...props} name={name} refkey={refkeys}>
36+
<ts.TypeDeclaration doc={doc} {...props} name={name} refkey={refkeys}>
3437
<UnionExpression type={type}>{coreProps.children}</UnionExpression>
3538
</ts.TypeDeclaration>
3639
);

packages/emitter-framework/src/typescript/utils/operation.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,13 @@ export function buildParameterDescriptors(
4747
}
4848

4949
export function buildParameterDescriptor(modelProperty: ModelProperty): ts.ParameterDescriptor {
50+
const { $ } = useTsp();
5051
const namePolicy = ts.useTSNamePolicy();
5152
const paramName = namePolicy.getName(modelProperty.name, "parameter");
5253
const isOptional = modelProperty.optional || modelProperty.defaultValue !== undefined;
54+
const doc = $.type.getDoc(modelProperty);
5355
return {
56+
doc,
5457
name: paramName,
5558
refkey: efRefkey(modelProperty),
5659
optional: isOptional,

0 commit comments

Comments
 (0)