Repository: alloy-framework/alloy
Pull request #72 (2025-03-27 21:38:18)

Add JSDoc support to TS Declarations
Add support for TS Declaration Docs

github-actions[bot]: <!--chronus-github-change-commenter-->
All changed packages have been documented.
 - :white_check_mark: `@alloy-js/typescript`

<details>
<summary>Show changes</summary>

### `@alloy-js/typescript` - _feature_ [✏️](https://github.com/joheredi/alloy/edit/feature/ts-declaration-doc/.chronus/changes/feature-ts-declaration-doc-2025-2-27-21-33-51.md?pr=/alloy-framework/alloy/pull/72)
> Add TS Declaration JSDoc
</details>

pkg-pr-new[bot]: 
[Open in Stackblitz](https://pkg.pr.new/template/988f0c88-11cb-47ae-9834-da64a9d3f4b9)


<details><summary><b>@alloy-js/babel-plugin</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/babel-plugin@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/babel-plugin-jsx-dom-expressions</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/babel-plugin-jsx-dom-expressions@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/babel-preset</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/babel-preset@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/core</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/core@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/create</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/create@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/csharp</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/csharp@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/java</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/java@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/json</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/json@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/prettier-plugin-alloy</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/prettier-plugin-alloy@72
```

</p></details>
      
    

<details><summary><b>@alloy-js/typescript</b></summary><p>

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/typescript@72
```

</p></details>
      
    

_commit: <a href="https://github.com/alloy-framework/alloy/runs/39553757605"><code>178acbd</code></a>_


Patch:
diff --git a/.chronus/changes/feature-ts-declaration-doc-2025-2-27-21-33-51.md b/.chronus/changes/feature-ts-declaration-doc-2025-2-27-21-33-51.md
new file mode 100644
index 0000000..84bd456
--- /dev/null
+++ b/.chronus/changes/feature-ts-declaration-doc-2025-2-27-21-33-51.md
@@ -0,0 +1,7 @@
+---
+changeKind: feature
+packages:
+  - "@alloy-js/typescript"
+---
+
+Adding support for JSDocs on declarations via the `docs` prop.
diff --git a/.chronus/changes/feature-ts-declaration-doc-2025-3-4-10-32-42.md b/.chronus/changes/feature-ts-declaration-doc-2025-3-4-10-32-42.md
new file mode 100644
index 0000000..e0eb36d
--- /dev/null
+++ b/.chronus/changes/feature-ts-declaration-doc-2025-3-4-10-32-42.md
@@ -0,0 +1,7 @@
+---
+changeKind: feature
+packages:
+  - "@alloy-js/typescript"
+---
+
+Add `JSDocParam` component which renders a single JSDoc comment for a parameter.
\ No newline at end of file
diff --git a/.chronus/changes/feature-ts-declaration-doc-2025-3-4-10-33-12.md b/.chronus/changes/feature-ts-declaration-doc-2025-3-4-10-33-12.md
new file mode 100644
index 0000000..6213aee
--- /dev/null
+++ b/.chronus/changes/feature-ts-declaration-doc-2025-3-4-10-33-12.md
@@ -0,0 +1,7 @@
+---
+changeKind: feature
+packages:
+  - "@alloy-js/typescript"
+---
+
+Add `JSDocParams` which takes an array of `ParameterDescription` to render JSDocParam comments for each.
\ No newline at end of file
diff --git a/packages/typescript/src/components/CallSignature.tsx b/packages/typescript/src/components/CallSignature.tsx
index 9f1a335..bac652e 100644
--- a/packages/typescript/src/components/CallSignature.tsx
+++ b/packages/typescript/src/components/CallSignature.tsx
@@ -1,9 +1,9 @@
 import { Children } from "@alloy-js/core/jsx-runtime";
 import {
-  FunctionDeclaration,
   ParameterDescriptor,
   TypeParameterDescriptor,
-} from "./FunctionDeclaration.jsx";
+} from "../parameter-descriptor.js";
+import { FunctionDeclaration } from "./FunctionDeclaration.jsx";
 
 export interface CallSignatureProps {
   /**
diff --git a/packages/typescript/src/components/ClassDeclaration.tsx b/packages/typescript/src/components/ClassDeclaration.tsx
index f035772..ce67bdf 100644
--- a/packages/typescript/src/components/ClassDeclaration.tsx
+++ b/packages/typescript/src/components/ClassDeclaration.tsx
@@ -7,12 +7,17 @@ import {
   OutputSymbolFlags,
   Refkey,
   Scope,
+  Show,
+  splitProps,
 } from "@alloy-js/core";
 import { useTSNamePolicy } from "../name-policy.js";
 import { createTSSymbol, TSOutputSymbol } from "../symbols/ts-output-symbol.js";
 import { getCallSignatureProps } from "../utils.js";
 import { CallSignature, CallSignatureProps } from "./CallSignature.jsx";
 import { BaseDeclarationProps, Declaration } from "./Declaration.jsx";
+import { JSDoc } from "./JSDoc.jsx";
+import { JSDocParams } from "./JSDocParam.jsx";
+import { Prose } from "./Prose.jsx";
 
 export interface ClassDeclarationProps extends BaseDeclarationProps {
   extends?: Children;
@@ -56,10 +61,16 @@ export function ClassDeclaration(props: ClassDeclarationProps) {
   const flags = OutputSymbolFlags.MemberContainer;
 
   return (
-    <Declaration {...props} flags={flags} nameKind="class">
-      class <Name />
-      {extendsPart} <Block>{props.children}</Block>
-    </Declaration>
+    <>
+      <Show when={Boolean(props.doc)}>
+        <JSDoc children={props.doc} />
+        <hbr />
+      </Show>
+      <Declaration {...props} flags={flags} nameKind="class">
+        class <Name />
+        {extendsPart} <Block>{props.children}</Block>
+      </Declaration>
+    </>
   );
 }
 
@@ -72,6 +83,7 @@ export interface ClassMemberProps {
   jsPrivate?: boolean;
   static?: boolean;
   children?: Children;
+  doc?: Children;
 }
 
 export function ClassMember(props: ClassMemberProps) {
@@ -98,13 +110,19 @@ export function ClassMember(props: ClassMemberProps) {
   }
 
   return (
-    <MemberDeclaration symbol={sym} name={name} refkey={props.refkey}>
-      {props.public && "public "}
-      {props.private && "private "}
-      {props.protected && "protected "}
-      {props.static && "static "}
-      {props.children}
-    </MemberDeclaration>
+    <>
+      <Show when={Boolean(props.doc)}>
+        <JSDoc children={props.doc} />
+        <hbr />
+      </Show>
+      <MemberDeclaration symbol={sym} name={name} refkey={props.refkey}>
+        {props.public && "public "}
+        {props.private && "private "}
+        {props.protected && "protected "}
+        {props.static && "static "}
+        {props.children}
+      </MemberDeclaration>
+    </>
   );
 }
 
@@ -133,14 +151,26 @@ export interface ClassMethodProps extends ClassMemberProps, CallSignatureProps {
 
 export function ClassMethod(props: ClassMethodProps) {
   const callProps = getCallSignatureProps(props);
+  const [_, rest] = splitProps(props, ["doc"]);
 
   return (
-    <ClassMember {...props}>
-      {props.async && "async "}
-      <MemberName />
-      <Scope name={props.name} kind="function">
-        <CallSignature {...callProps} /> <Block>{props.children}</Block>
-      </Scope>
-    </ClassMember>
+    <>
+      <Show when={Boolean(props.doc)}>
+        <JSDoc>
+          {props.doc && <Prose children={props.doc} />}
+          {Array.isArray(rest.parameters) && (
+            <JSDocParams parameters={rest.parameters} />
+          )}
+        </JSDoc>
+        <hbr />
+      </Show>
+      <ClassMember {...rest}>
+        {props.async && "async "}
+        <MemberName />
+        <Scope name={props.name} kind="function">
+          <CallSignature {...callProps} /> <Block>{props.children}</Block>
+        </Scope>
+      </ClassMember>
+    </>
   );
 }
diff --git a/packages/typescript/src/components/Declaration.tsx b/packages/typescript/src/components/Declaration.tsx
index 312a774..8759e75 100644
--- a/packages/typescript/src/components/Declaration.tsx
+++ b/packages/typescript/src/components/Declaration.tsx
@@ -55,6 +55,11 @@ export interface BaseDeclarationProps {
    * Arbitrary metadata about this declaration.
    */
   metadata?: Record<string, unknown>;
+
+  /**
+   * Documentation for this declaration
+   */
+  doc?: Children;
 }
 
 export interface DeclarationProps extends BaseDeclarationProps {
diff --git a/packages/typescript/src/components/EnumDeclaration.tsx b/packages/typescript/src/components/EnumDeclaration.tsx
index 49974b4..98fb5c2 100644
--- a/packages/typescript/src/components/EnumDeclaration.tsx
+++ b/packages/typescript/src/components/EnumDeclaration.tsx
@@ -6,12 +6,14 @@ import {
   MemberScope,
   Name,
   OutputSymbolFlags,
+  Show,
   useBinder,
 } from "@alloy-js/core";
 import { useTSNamePolicy } from "../name-policy.js";
 import { createTSSymbol, useTSScope } from "../symbols/index.js";
 import { BaseDeclarationProps } from "./Declaration.js";
 import { EnumMember } from "./EnumMember.jsx";
+import { JSDoc } from "./JSDoc.jsx";
 export interface EnumDeclarationProps extends BaseDeclarationProps {
   /**
    * A JS object representing the enum member names and values.
@@ -39,22 +41,28 @@ export function EnumDeclaration(props: EnumDeclarationProps) {
 
   const valueEntries = computed(() => Object.entries(props.jsValue ?? {}));
   return (
-    <CoreDeclaration symbol={sym}>
-      {props.export ? "export " : ""}
-      {props.default ? "default " : ""}enum <Name />{" "}
-      <MemberScope owner={sym}>
-        <Block>
-          <For each={valueEntries} comma hardline enderPunctuation>
-            {([name, value]) => <EnumMember name={name} jsValue={value} />}
-          </For>
-          {props.children && (
-            <>
-              {valueEntries.value.length > 0 && <hbr />}
-              {props.children}
-            </>
-          )}
-        </Block>
-      </MemberScope>
-    </CoreDeclaration>
+    <>
+      <Show when={Boolean(props.doc)}>
+        <JSDoc children={props.doc} />
+        <hbr />
+      </Show>
+      <CoreDeclaration symbol={sym}>
+        {props.export ? "export " : ""}
+        {props.default ? "default " : ""}enum <Name />{" "}
+        <MemberScope owner={sym}>
+          <Block>
+            <For each={valueEntries} comma hardline enderPunctuation>
+              {([name, value]) => <EnumMember name={name} jsValue={value} />}
+            </For>
+            {props.children && (
+              <>
+                {valueEntries.value.length > 0 && <hbr />}
+                {props.children}
+              </>
+            )}
+          </Block>
+        </MemberScope>
+      </CoreDeclaration>
+    </>
   );
 }
diff --git a/packages/typescript/src/components/EnumMember.tsx b/packages/typescript/src/components/EnumMember.tsx
index 48213af..0184c1f 100644
--- a/packages/typescript/src/components/EnumMember.tsx
+++ b/packages/typescript/src/components/EnumMember.tsx
@@ -1,6 +1,7 @@
-import { Children, OutputSymbolFlags, Refkey } from "@alloy-js/core";
+import { Children, OutputSymbolFlags, Refkey, Show } from "@alloy-js/core";
 import { useTSNamePolicy } from "../name-policy.js";
 import { createTSSymbol, TSOutputSymbol } from "../symbols/ts-output-symbol.js";
+import { JSDoc } from "./JSDoc.jsx";
 import { ValueExpression } from "./ValueExpression.jsx";
 
 export interface EnumMemberProps {
@@ -32,6 +33,11 @@ export interface EnumMemberProps {
    * Arbitrary symbol metadata for the enum member.
    */
   metadata?: Record<string, unknown>;
+
+  /**
+   * Documentation for the enum member.
+   */
+  doc?: Children;
 }
 
 /**
@@ -51,11 +57,18 @@ export function EnumMember(props: EnumMemberProps) {
   }
   const nameCode = sym ? sym.name : name;
   const valueCode =
-    props.jsValue ? <ValueExpression jsValue={props.jsValue} /> : props.value;
+    props.jsValue !== undefined ?
+      <ValueExpression jsValue={props.jsValue} />
+    : props.value;
 
   return (
     <>
-      {nameCode} = {valueCode}
+      <Show when={Boolean(props.doc)}>
+        <JSDoc children={props.doc} />
+        <hbr />
+      </Show>
+      {nameCode}
+      <Show when={valueCode !== undefined}> = {valueCode}</Show>
     </>
   );
 }
diff --git a/packages/typescript/src/components/FunctionDeclaration.tsx b/packages/typescript/src/components/FunctionDeclaration.tsx
index df2a379..7130d6f 100644
--- a/packages/typescript/src/components/FunctionDeclaration.tsx
+++ b/packages/typescript/src/components/FunctionDeclaration.tsx
@@ -7,13 +7,16 @@ import {
   For,
   Indent,
   Name,
-  Refkey,
   Scope,
   Show,
   taggedComponent,
 } from "@alloy-js/core";
 import { Children, onCleanup } from "@alloy-js/core/jsx-runtime";
 import { useTSNamePolicy } from "../name-policy.js";
+import {
+  ParameterDescriptor,
+  TypeParameterDescriptor,
+} from "../parameter-descriptor.js";
 import {
   createTSSymbol,
   TSOutputSymbol,
@@ -22,71 +25,9 @@ import {
 import { getCallSignatureProps } from "../utils.js";
 import { CallSignature, CallSignatureProps } from "./CallSignature.jsx";
 import { BaseDeclarationProps, Declaration } from "./Declaration.js";
-
-/**
- * Information for a TypeScript function parameter.
- */
-export interface ParameterDescriptor {
-  /**
-   * The name of the parameter.
-   */
-  name: string;
-
-  /**
-   * The type of the parameter.
-   */
-  type?: Children;
-
-  /**
-   * The default value of the parameter.
-   */
-  default?: Children;
-
-  /**
-   * The refkey for this parameter.
-   */
-  refkey?: Refkey | Refkey[];
-
-  /**
-   * Whether the parameter is optional.
-   */
-  optional?: boolean;
-
-  /**
-   * Arbitrary metadata for the parameter symbol.
-   */
-  metadata?: Record<string, unknown>;
-}
-
-/**
- * Information for a TypeScript generic type parameter.
- */
-export interface TypeParameterDescriptor {
-  /**
-   * The name of the type parameter.
-   */
-  name: string;
-
-  /**
-   * The extends constraint for the type parameter.
-   */
-  extends?: Children;
-
-  /**
-   * The default type of the type parameter.
-   */
-  default?: Children;
-
-  /**
-   * A refkey or array of refkeys for this type parameter.
-   */
-  refkey?: Refkey | Refkey[];
-
-  /**
-   * Arbitrary metadata for the type parameter symbol.
-   */
-  metadata?: Record<string, unknown>;
-}
+import { JSDoc } from "./JSDoc.jsx";
+import { JSDocParams } from "./JSDocParam.jsx";
+import { Prose } from "./Prose.jsx";
 
 export interface FunctionDeclarationProps
   extends BaseDeclarationProps,
@@ -137,13 +78,24 @@ export function FunctionDeclaration(props: FunctionDeclarationProps) {
   });
 
   return (
-    <Declaration {...props} nameKind="function">
-      {asyncKwd}function <Name />
-      <Scope name={props.name} kind="function">
-        <CallSignature {...callSignatureProps} returnType={returnType} />{" "}
-        {sBody}
-      </Scope>
-    </Declaration>
+    <>
+      <Show when={Boolean(props.doc)}>
+        <JSDoc>
+          {props.doc && <Prose children={props.doc} />}
+          {Array.isArray(props.parameters) && (
+            <JSDocParams parameters={props.parameters} />
+          )}
+        </JSDoc>
+        <hbr />
+      </Show>
+      <Declaration {...props} nameKind="function">
+        {asyncKwd}function <Name />
+        <Scope name={props.name} kind="function">
+          <CallSignature {...callSignatureProps} returnType={returnType} />{" "}
+          {sBody}
+        </Scope>
+      </Declaration>
+    </>
   );
 }
 
diff --git a/packages/typescript/src/components/Interface.tsx b/packages/typescript/src/components/Interface.tsx
index c713f1d..23b640f 100644
--- a/packages/typescript/src/components/Interface.tsx
+++ b/packages/typescript/src/components/Interface.tsx
@@ -1,6 +1,7 @@
-import { Block, Children, Name } from "@alloy-js/core";
+import { Block, Children, Name, Show } from "@alloy-js/core";
 import { useTSNamePolicy } from "../name-policy.js";
 import { BaseDeclarationProps, Declaration } from "./Declaration.js";
+import { JSDoc } from "./JSDoc.jsx";
 
 export interface InterfaceDeclarationProps extends BaseDeclarationProps {
   extends?: Children;
@@ -19,10 +20,17 @@ export function InterfaceDeclaration(props: InterfaceDeclarationProps) {
   const extendsPart = props.extends ? <> extends {props.extends}</> : "";
 
   return (
-    <Declaration {...props} nameKind="interface">
-      interface <Name />
-      {extendsPart} <InterfaceExpression>{props.children}</InterfaceExpression>
-    </Declaration>
+    <>
+      <Show when={Boolean(props.doc)}>
+        <JSDoc children={props.doc} />
+        <hbr />
+      </Show>
+      <Declaration {...props} nameKind="interface">
+        interface <Name />
+        {extendsPart}{" "}
+        <InterfaceExpression>{props.children}</InterfaceExpression>
+      </Declaration>
+    </>
   );
 }
 
@@ -41,6 +49,7 @@ export interface InterfaceMemberProps {
   children?: Children;
   optional?: boolean;
   readonly?: boolean;
+  doc?: Children;
 }
 
 /**
@@ -60,6 +69,10 @@ export function InterfaceMember(props: InterfaceMemberProps) {
   } else {
     return (
       <>
+        <Show when={Boolean(props.doc)}>
+          <JSDoc children={props.doc} />
+          <hbr />
+        </Show>
         {readonly}
         {namer.getName(props.name!, "interface-member")}
         {optionality}: {type}
diff --git a/packages/typescript/src/components/JSDocParam.tsx b/packages/typescript/src/components/JSDocParam.tsx
new file mode 100644
index 0000000..98fb017
--- /dev/null
+++ b/packages/typescript/src/components/JSDocParam.tsx
@@ -0,0 +1,116 @@
+import { For, Show } from "@alloy-js/core";
+import { Children } from "@alloy-js/core/jsx-runtime";
+import { ParameterDescriptor } from "../parameter-descriptor.js";
+import { Prose } from "./Prose.jsx";
+
+export interface JSDocParamsProps {
+  parameters: ParameterDescriptor[] | string[];
+}
+
+/**
+ * A component that creates a JSDoc block with `@param` tags for each parameter.
+ */
+export function JSDocParams(props: JSDocParamsProps) {
+  const parameters = normalizeParametersForDoc(props.parameters);
+  return (
+    <For each={parameters}>
+      {(param) => (
+        <JSDocParam
+          name={param.name}
+          type={param.type}
+          optional={param.optional}
+          hyphen
+        >
+          {param.doc}
+        </JSDocParam>
+      )}
+    </For>
+  );
+}
+
+function normalizeParametersForDoc(
+  parameters: ParameterDescriptor[] | string[],
+): ParameterDescriptor[] {
+  if (parameters.some((p) => typeof p === "string")) {
+    return [];
+  }
+
+  return parameters as ParameterDescriptor[];
+}
+
+export interface JSDocParamProps {
+  name: Children;
+  type?: Children;
+  children?: Children;
+  hyphen?: boolean;
+  optional?: boolean;
+  defaultValue?: Children;
+}
+
+/**
+ * Create a JSDoc parameter set off with `@param`.
+ */
+export function JSDocParam(props: JSDocParamProps) {
+  return (
+    <>
+      {"@param "}
+      <JSDocParamType type={props.type} />
+      <JSDocParamName
+        name={props.name}
+        optional={props.optional}
+        defaultValue={props.defaultValue}
+      />
+      <JSDocParamDescription hyphen={props.hyphen} children={props.children} />
+    </>
+  );
+}
+
+interface JSDocParamTypeProps {
+  type?: Children;
+}
+
+function JSDocParamType(props: JSDocParamTypeProps) {
+  return (
+    <>
+      <Show when={Boolean(props.type)}>
+        {"{"}
+        {props.type}
+        {"} "}
+      </Show>
+    </>
+  );
+}
+
+interface JSDocParamNameProps {
+  name: Children;
+  optional?: boolean;
+  defaultValue?: Children;
+}
+
+function JSDocParamName(props: JSDocParamNameProps) {
+  return (
+    <>
+      <Show when={props.optional}>{"["}</Show>
+      {props.name}
+      <Show when={Boolean(props.defaultValue)}>={props.defaultValue}</Show>
+      <Show when={props.optional}>{"]"}</Show>
+    </>
+  );
+}
+
+interface JSDocParamDescriptionProps {
+  children?: Children;
+  hyphen?: boolean;
+}
+
+function JSDocParamDescription(props: JSDocParamDescriptionProps) {
+  return (
+    <Show when={Boolean(props.children)}>
+      <Show when={props.hyphen} children={" - "} />
+      <Show when={!props.hyphen} children={" "} />
+      <align width={2}>
+        <Prose>{props.children}</Prose>
+      </align>
+    </Show>
+  );
+}
diff --git a/packages/typescript/src/components/TypeDeclaration.tsx b/packages/typescript/src/components/TypeDeclaration.tsx
index 0eefa8c..0dd8eef 100644
--- a/packages/typescript/src/components/TypeDeclaration.tsx
+++ b/packages/typescript/src/components/TypeDeclaration.tsx
@@ -1,12 +1,19 @@
-import { Name } from "@alloy-js/core";
+import { Name, Show } from "@alloy-js/core";
 import { BaseDeclarationProps, Declaration } from "./Declaration.js";
+import { JSDoc } from "./JSDoc.jsx";
 
 export interface TypeDeclarationProps extends BaseDeclarationProps {}
 
 export function TypeDeclaration(props: TypeDeclarationProps) {
   return (
-    <Declaration {...props} nameKind="type">
-      type <Name /> = {props.children};
-    </Declaration>
+    <>
+      <Show when={Boolean(props.doc)}>
+        <JSDoc children={props.doc} />
+        <hbr />
+      </Show>
+      <Declaration {...props} nameKind="type">
+        type <Name /> = {props.children};
+      </Declaration>
+    </>
   );
 }
diff --git a/packages/typescript/src/components/VarDeclaration.tsx b/packages/typescript/src/components/VarDeclaration.tsx
index fe4bd97..fdcb4ce 100644
--- a/packages/typescript/src/components/VarDeclaration.tsx
+++ b/packages/typescript/src/components/VarDeclaration.tsx
@@ -4,10 +4,12 @@ import {
   Declaration as CoreDeclaration,
   createAssignmentContext,
   Name,
+  Show,
 } from "@alloy-js/core";
 import { useTSNamePolicy } from "../name-policy.js";
 import { createTSSymbol } from "../symbols/index.js";
 import { BaseDeclarationProps } from "./Declaration.js";
+import { JSDoc } from "./JSDoc.jsx";
 
 export interface VarDeclarationProps extends BaseDeclarationProps {
   const?: boolean;
@@ -35,14 +37,20 @@ export function VarDeclaration(props: VarDeclarationProps) {
   const assignmentContext = createAssignmentContext(sym);
 
   return (
-    <CoreDeclaration symbol={sym}>
-      {props.export ? "export " : ""}
-      {props.default ? "default " : ""}
-      {keyword} <Name />
-      {type} ={" "}
-      <AssignmentContext.Provider value={assignmentContext}>
-        {props.initializer ?? props.children}
-      </AssignmentContext.Provider>
-    </CoreDeclaration>
+    <>
+      <Show when={Boolean(props.doc)}>
+        <JSDoc children={props.doc} />
+        <hbr />
+      </Show>
+      <CoreDeclaration symbol={sym}>
+        {props.export ? "export " : ""}
+        {props.default ? "default " : ""}
+        {keyword} <Name />
+        {type} ={" "}
+        <AssignmentContext.Provider value={assignmentContext}>
+          {props.initializer ?? props.children}
+        </AssignmentContext.Provider>
+      </CoreDeclaration>
+    </>
   );
 }
diff --git a/packages/typescript/src/components/index.ts b/packages/typescript/src/components/index.ts
index 4446427..c213981 100644
--- a/packages/typescript/src/components/index.ts
+++ b/packages/typescript/src/components/index.ts
@@ -1,3 +1,4 @@
+export * from "../parameter-descriptor.js";
 export * from "./ArrayExpression.js";
 export * from "./BarrelFile.js";
 export * from "./BlockScope.js";
diff --git a/packages/typescript/src/components/stc/index.ts b/packages/typescript/src/components/stc/index.ts
index 69b1aed..928f889 100644
--- a/packages/typescript/src/components/stc/index.ts
+++ b/packages/typescript/src/components/stc/index.ts
@@ -23,12 +23,12 @@ export const InterfaceMember = stc(base.InterfaceMember);
 export const JSDoc = stc(base.JSDoc);
 export const JSDocExample = stc(base.JSDocExample);
 export const JSDocComment = stc(base.JSDocComment);
-export const JSDocParagraph = stc(base.Prose);
 export const MemberChainExpression = stc(base.MemberChainExpression);
 export const ObjectExpression = stc(base.ObjectExpression);
 export const ObjectProperty = stc(base.ObjectProperty);
 export const PackageDirectory = stc(base.PackageDirectory);
 export const PackageJsonFile = stc(base.PackageJsonFile);
+export const Prose = stc(base.Prose);
 export const Reference = stc(base.Reference);
 export const SourceFile = stc(base.SourceFile);
 export const SwitchStatement = stc(base.SwitchStatement);
diff --git a/packages/typescript/src/parameter-descriptor.ts b/packages/typescript/src/parameter-descriptor.ts
new file mode 100644
index 0000000..1e4baf1
--- /dev/null
+++ b/packages/typescript/src/parameter-descriptor.ts
@@ -0,0 +1,71 @@
+import { Children, Refkey } from "@alloy-js/core";
+
+/**
+ * Information for a TypeScript function parameter.
+ */
+export interface ParameterDescriptor {
+  /**
+   * The name of the parameter.
+   */
+  name: string;
+
+  /**
+   * The type of the parameter.
+   */
+  type?: Children;
+
+  /**
+   * The default value of the parameter.
+   */
+  default?: Children;
+
+  /**
+   * The refkey for this parameter.
+   */
+  refkey?: Refkey | Refkey[];
+
+  /**
+   * Whether the parameter is optional.
+   */
+  optional?: boolean;
+
+  /**
+   * Arbitrary metadata for the parameter symbol.
+   */
+  metadata?: Record<string, unknown>;
+
+  /**
+   * Documentation for the parameter.
+   */
+  doc?: Children;
+}
+
+/**
+ * Information for a TypeScript generic type parameter.
+ */
+export interface TypeParameterDescriptor {
+  /**
+   * The name of the type parameter.
+   */
+  name: string;
+
+  /**
+   * The extends constraint for the type parameter.
+   */
+  extends?: Children;
+
+  /**
+   * The default type of the type parameter.
+   */
+  default?: Children;
+
+  /**
+   * A refkey or array of refkeys for this type parameter.
+   */
+  refkey?: Refkey | Refkey[];
+
+  /**
+   * Arbitrary metadata for the type parameter symbol.
+   */
+  metadata?: Record<string, unknown>;
+}


Test patch:
diff --git a/packages/typescript/test/class.test.tsx b/packages/typescript/test/class.test.tsx
index ec8febe..2dc9469 100644
--- a/packages/typescript/test/class.test.tsx
+++ b/packages/typescript/test/class.test.tsx
@@ -194,3 +194,68 @@ describe("instance methods", () => {
     `);
   });
 });
+
+it("renders a class with docs for the class and its members", () => {
+  const res = toSourceText(
+    <ts.ClassDeclaration name="Foo" doc="This is a class documentation">
+      <ts.ClassField name="bar" doc="This is a field documentation">
+        123
+      </ts.ClassField>
+      ;<hbr />
+      <ts.ClassMethod name="baz" doc="This is a method documentation">
+        return 123;
+      </ts.ClassMethod>
+    </ts.ClassDeclaration>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * This is a class documentation
+     **/
+    class Foo {
+      /**
+       * This is a field documentation
+       **/
+      bar = 123;
+      /**
+       * This is a method documentation
+       **/
+      baz() {
+        return 123;
+      }
+    }
+  `);
+});
+
+it("renders a method with parameter docs", () => {
+  const res = toSourceText(
+    <ts.ClassDeclaration name="Foo">
+      <ts.ClassMethod
+        name="bar"
+        doc="Method documentation"
+        returnType="void"
+        parameters={[
+          { name: "a", type: "number", doc: "Parameter a doc" },
+          {
+            name: "b",
+            type: "string",
+            doc: "Line 1 for b. This is a long description that should continue in the next line.",
+          },
+        ]}
+      />
+    </ts.ClassDeclaration>,
+    { printWidth: 40 },
+  );
+  expect(res).toEqual(d`
+    class Foo {
+      /**
+       * Method documentation
+       *
+       * @param {number} a - Parameter a doc
+       * @param {string} b - Line 1 for b.
+       *   This is a long description that
+       *   should continue in the next line.
+       **/
+      bar(a: number, b: string): void {}
+    }
+  `);
+});
diff --git a/packages/typescript/test/enum-jsdoc.test.tsx b/packages/typescript/test/enum-jsdoc.test.tsx
new file mode 100644
index 0000000..9abb917
--- /dev/null
+++ b/packages/typescript/test/enum-jsdoc.test.tsx
@@ -0,0 +1,74 @@
+import { d } from "@alloy-js/core/testing";
+import { expect, it } from "vitest";
+import * as ts from "../src/index.js";
+import { toSourceText } from "./utils.js";
+
+it("renders an enum declaration with documentation", () => {
+  const res = toSourceText(
+    <ts.EnumDeclaration
+      name="Color"
+      doc="Color enumeration for the application"
+    >
+      <ts.CommaList>
+        <ts.EnumMember name="Red" />
+        <ts.EnumMember name="Green" />
+        <ts.EnumMember name="Blue" />
+      </ts.CommaList>
+    </ts.EnumDeclaration>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * Color enumeration for the application
+     **/
+    enum Color {
+      Red,
+      Green,
+      Blue,
+    }
+  `);
+});
+
+it("renders an enum declaration with documented members", () => {
+  const res = toSourceText(
+    <ts.EnumDeclaration
+      name="Direction"
+      doc="Represents cardinal directions. Used for navigation purposes"
+    >
+      <ts.CommaList>
+        <ts.EnumMember name="North" jsValue={0} doc={["Bound to the north"]} />
+        <ts.EnumMember
+          name="East"
+          jsValue={1}
+          doc={["Bound to the East", "A second doc paragraph"]}
+        />
+        <ts.EnumMember name="South" jsValue={2} doc="Opposite to North" />
+        <ts.EnumMember name="West" jsValue={3} doc="Going west!" />
+      </ts.CommaList>
+    </ts.EnumDeclaration>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * Represents cardinal directions. Used for navigation purposes
+     **/
+    enum Direction {
+      /**
+       * Bound to the north
+       **/
+      North = 0,
+      /**
+       * Bound to the East
+       *
+       * A second doc paragraph
+       **/
+      East = 1,
+      /**
+       * Opposite to North
+       **/
+      South = 2,
+      /**
+       * Going west!
+       **/
+      West = 3,
+    }
+  `);
+});
diff --git a/packages/typescript/test/enum.test.tsx b/packages/typescript/test/enum.test.tsx
index a591435..df040d8 100644
--- a/packages/typescript/test/enum.test.tsx
+++ b/packages/typescript/test/enum.test.tsx
@@ -4,6 +4,46 @@ import { describe, expect, it } from "vitest";
 
 import * as ts from "../src/index.js";
 
+it("renders an enum with only named members, no value", () => {
+  expect(
+    <Output>
+      <ts.SourceFile path="foo.ts">
+        <ts.EnumDeclaration name="MyEnum">
+          <ts.CommaList>
+            <ts.EnumMember name="foo" />
+            <ts.EnumMember name="bar" />
+          </ts.CommaList>
+        </ts.EnumDeclaration>
+      </ts.SourceFile>
+    </Output>,
+  ).toRenderTo(`
+    enum MyEnum {
+      foo,
+      bar,
+    }
+  `);
+});
+
+it("renders enum member with falsy value", () => {
+  expect(
+    <Output>
+      <ts.SourceFile path="foo.ts">
+        <ts.EnumDeclaration name="MyEnum">
+          <ts.CommaList>
+            <ts.EnumMember name="foo" jsValue={0} />
+            <ts.EnumMember name="bar" jsValue={""} />
+          </ts.CommaList>
+        </ts.EnumDeclaration>
+      </ts.SourceFile>
+    </Output>,
+  ).toRenderTo(`
+    enum MyEnum {
+      foo = 0,
+      bar = "",
+    }
+  `);
+});
+
 it("renders an enum with enum children", () => {
   expect(
     <Output>
diff --git a/packages/typescript/test/function-declaration-jsdoc.test.tsx b/packages/typescript/test/function-declaration-jsdoc.test.tsx
new file mode 100644
index 0000000..59c038f
--- /dev/null
+++ b/packages/typescript/test/function-declaration-jsdoc.test.tsx
@@ -0,0 +1,89 @@
+import { refkey, StatementList } from "@alloy-js/core";
+import { d } from "@alloy-js/core/testing";
+import { expect, it } from "vitest";
+import * as ts from "../src/index.js";
+import { toSourceText } from "./utils.js";
+
+it("renders a function declaration with documentation", () => {
+  const res = toSourceText(
+    <ts.FunctionDeclaration
+      name="greet"
+      doc="A function that greets a person"
+      parameters={[{ name: "name", type: "string" }]}
+      returnType="string"
+    />,
+  );
+  expect(res).toEqual(d`
+    /**
+     * A function that greets a person
+     *
+     * @param {string} name
+     **/
+    function greet(name: string): string {}
+  `);
+});
+
+it("With documented parameters", () => {
+  const res = toSourceText(
+    <ts.FunctionDeclaration
+      export
+      name="calculateTotal"
+      doc="Calculates the total price including tax"
+      parameters={[
+        { name: "price", type: "number" },
+        { name: "taxRate", type: "number", optional: true },
+      ]}
+      returnType="number"
+    />,
+  );
+  expect(res).toEqual(d`
+    /**
+     * Calculates the total price including tax
+     *
+     * @param {number} price
+     * @param {number} [taxRate]
+     **/
+    export function calculateTotal(price: number, taxRate?: number): number {}
+  `);
+});
+
+it("renders a function with a reference", () => {
+  const userRef = refkey();
+
+  const res = toSourceText(
+    <>
+      <ts.InterfaceDeclaration name="User" refkey={userRef}>
+        <StatementList>
+          <ts.InterfaceMember name="id" type="number" />
+          <ts.InterfaceMember name="name" type="string" />
+        </StatementList>
+      </ts.InterfaceDeclaration>
+      <hbr />
+      <hbr />
+      <ts.FunctionDeclaration
+        name="createUser"
+        doc="Creates a new user"
+        parameters={[
+          {
+            name: "user",
+            type: userRef,
+            doc: "The user object to create",
+          },
+        ]}
+        returnType="void"
+      />
+    </>,
+  );
+  expect(res).toEqual(d`
+    interface User {
+      id: number;
+      name: string;
+    }
+
+    /**
+     * Creates a new user
+     *
+     * @param {User} user - The user object to create
+     **/
+    function createUser(user: User): void {}`);
+});
diff --git a/packages/typescript/test/function-declaration.test.tsx b/packages/typescript/test/function-declaration.test.tsx
index 67e2c16..999f0d9 100644
--- a/packages/typescript/test/function-declaration.test.tsx
+++ b/packages/typescript/test/function-declaration.test.tsx
@@ -2,11 +2,8 @@ import { Props, refkey } from "@alloy-js/core";
 import { d } from "@alloy-js/core/testing";
 import { describe, expect, it } from "vitest";
 import { StatementList } from "../../core/src/components/StatementList.jsx";
-import {
-  FunctionDeclaration,
-  ParameterDescriptor,
-  VarDeclaration,
-} from "../src/index.js";
+import { FunctionDeclaration, VarDeclaration } from "../src/index.js";
+import { ParameterDescriptor } from "../src/parameter-descriptor.js";
 import { toSourceText } from "./utils.js";
 
 it("works", () => {
diff --git a/packages/typescript/test/interface-jsdoc.test.tsx b/packages/typescript/test/interface-jsdoc.test.tsx
new file mode 100644
index 0000000..2ac597b
--- /dev/null
+++ b/packages/typescript/test/interface-jsdoc.test.tsx
@@ -0,0 +1,164 @@
+import { StatementList } from "@alloy-js/core";
+import { d } from "@alloy-js/core/testing";
+import { expect, it } from "vitest";
+import * as ts from "../src/index.js";
+import { toSourceText } from "./utils.js";
+
+it("renders an interface declaration with documentation", () => {
+  const res = toSourceText(
+    <ts.InterfaceDeclaration
+      name="Person"
+      doc="Interface representing a person"
+    >
+      <StatementList>
+        <ts.InterfaceMember name="name" type="string" />
+        <ts.InterfaceMember name="age" type="number" />
+      </StatementList>
+    </ts.InterfaceDeclaration>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * Interface representing a person
+     **/
+    interface Person {
+      name: string;
+      age: number;
+    }
+  `);
+});
+
+it("renders an interface declaration with docs from a string array with single element", () => {
+  const res = toSourceText(
+    <ts.InterfaceDeclaration
+      name="Person"
+      doc={["Interface representing a person"]}
+    >
+      <StatementList>
+        <ts.InterfaceMember name="name" type="string" />
+        <ts.InterfaceMember name="age" type="number" />
+      </StatementList>
+    </ts.InterfaceDeclaration>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * Interface representing a person
+     **/
+    interface Person {
+      name: string;
+      age: number;
+    }
+  `);
+});
+
+it("renders an interface declaration with docs from a string array", () => {
+  const res = toSourceText(
+    <ts.InterfaceDeclaration
+      name="Person"
+      doc={[
+        "Interface representing a person",
+        "This should be another paragraph",
+      ]}
+    >
+      <StatementList>
+        <ts.InterfaceMember name="name" type="string" />
+        <ts.InterfaceMember name="age" type="number" />
+      </StatementList>
+    </ts.InterfaceDeclaration>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * Interface representing a person
+     *
+     * This should be another paragraph
+     **/
+    interface Person {
+      name: string;
+      age: number;
+    }
+  `);
+});
+
+it("renders an interface declaration with documented properties", () => {
+  const res = toSourceText(
+    <ts.InterfaceDeclaration
+      export
+      name="APIResponse"
+      doc="Standard API response format"
+    >
+      <StatementList>
+        <ts.InterfaceMember
+          name="success"
+          type="boolean"
+          doc="Wether or not the request completed successfully"
+        />
+        <ts.InterfaceMember
+          name="data"
+          type="unknown"
+          doc="the response payload"
+        />
+        <ts.InterfaceMember name="error" optional type="string" />
+      </StatementList>
+    </ts.InterfaceDeclaration>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * Standard API response format
+     **/
+    export interface APIResponse {
+      /**
+       * Wether or not the request completed successfully
+       **/
+      success: boolean;
+      /**
+       * the response payload
+       **/
+      data: unknown;
+      error?: string;
+    }
+  `);
+});
+
+it("renders an interface declaration with array documented properties ", () => {
+  const res = toSourceText(
+    <ts.InterfaceDeclaration
+      export
+      name="APIResponse"
+      doc="Standard API response format"
+    >
+      <StatementList>
+        <ts.InterfaceMember
+          name="success"
+          type="boolean"
+          doc={[
+            "Wether or not the request completed successfully",
+            "This is another paragraph",
+          ]}
+        />
+        <ts.InterfaceMember
+          name="data"
+          type="unknown"
+          doc="the response payload"
+        />
+        <ts.InterfaceMember name="error" optional type="string" />
+      </StatementList>
+    </ts.InterfaceDeclaration>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * Standard API response format
+     **/
+    export interface APIResponse {
+      /**
+       * Wether or not the request completed successfully
+       *
+       * This is another paragraph
+       **/
+      success: boolean;
+      /**
+       * the response payload
+       **/
+      data: unknown;
+      error?: string;
+    }
+  `);
+});
diff --git a/packages/typescript/test/jsdoc.test.tsx b/packages/typescript/test/jsdoc.test.tsx
index b18a14b..a15c3b3 100644
--- a/packages/typescript/test/jsdoc.test.tsx
+++ b/packages/typescript/test/jsdoc.test.tsx
@@ -31,7 +31,7 @@ describe("JSDoc", () => {
   });
 });
 
-describe("JSDocParagraph", () => {
+describe("Prose", () => {
   it("formats properly", () => {
     const template = (
       <JSDoc>
diff --git a/packages/typescript/test/jsdocparam.test.tsx b/packages/typescript/test/jsdocparam.test.tsx
new file mode 100644
index 0000000..1a753e2
--- /dev/null
+++ b/packages/typescript/test/jsdocparam.test.tsx
@@ -0,0 +1,188 @@
+import { d } from "@alloy-js/core/testing";
+import { expect, it } from "vitest";
+import { JSDocParam } from "../src/components/JSDocParam.jsx";
+import { JSDoc } from "../src/index.js";
+
+it("name only", () => {
+  const template = (
+    <JSDoc>
+      <JSDocParam name="somebody" />
+    </JSDoc>
+  );
+
+  expect(template).toRenderTo(
+    d`
+          /**
+           * @param somebody
+           **/
+        `,
+  );
+});
+
+it("name and type", () => {
+  const template = (
+    <JSDoc>
+      <JSDocParam name="somebody" type="string" />
+    </JSDoc>
+  );
+
+  expect(template).toRenderTo(
+    d`
+          /**
+           * @param {string} somebody
+           **/
+        `,
+  );
+});
+
+it("name, type and description", () => {
+  const template = (
+    <JSDoc>
+      <JSDocParam name="somebody" type="string">
+        Somebody's name.
+      </JSDocParam>
+    </JSDoc>
+  );
+
+  expect(template).toRenderTo(
+    d`
+          /**
+           * @param {string} somebody Somebody's name.
+           **/
+        `,
+  );
+});
+
+it("name, type and description with hyphen", () => {
+  const template = (
+    <JSDoc>
+      <JSDocParam name="somebody" type="string" hyphen>
+        Somebody's name.
+      </JSDocParam>
+    </JSDoc>
+  );
+
+  expect(template).toRenderTo(
+    d`
+          /**
+           * @param {string} somebody - Somebody's name.
+           **/
+        `,
+  );
+});
+
+it("name, type, description, hyphen and optional", () => {
+  const template = (
+    <JSDoc>
+      <JSDocParam name="somebody" type="string" optional hyphen>
+        Somebody's name.
+      </JSDocParam>
+    </JSDoc>
+  );
+
+  expect(template).toRenderTo(
+    d`
+        /**
+         * @param {string} [somebody] - Somebody's name.
+         **/
+        `,
+  );
+});
+
+it("name, type, description, hyphen and optional with default value", () => {
+  const template = (
+    <JSDoc>
+      <JSDocParam
+        name="somebody"
+        type="string"
+        optional
+        defaultValue="John Doe"
+        hyphen
+      >
+        Somebody's name.
+      </JSDocParam>
+    </JSDoc>
+  );
+
+  expect(template).toRenderTo(
+    d`
+            /**
+             * @param {string} [somebody=John Doe] - Somebody's name.
+             **/
+            `,
+  );
+});
+
+it("name, type, description, hyphen and optional with default value with a very lon description", () => {
+  const template = (
+    <JSDoc>
+      <JSDocParam
+        name="somebody"
+        type="string"
+        optional
+        defaultValue="John Doe"
+        hyphen
+      >
+        Somebody's name. This can be any string representing a person, whether
+        it's a first name, full name, nickname, or even a codename (e.g., "Agent
+        X"). It's used primarily for display purposes, logging, or greeting
+        messages and is not required to be unique or validated unless specified
+        by the caller.
+      </JSDocParam>
+      <JSDocParam name="somebody2" type="string" hyphen>
+        Somebody's name. This can be any string representing a person, whether
+        it's a first name, full name, nickname, or even a codename (e.g., "Agent
+        X"). It's used primarily for display purposes, logging, or greeting
+        messages and is not required to be unique or validated unless specified
+        by the caller.
+      </JSDocParam>
+    </JSDoc>
+  );
+
+  expect(template).toRenderTo(
+    d`
+          /**
+           * @param {string} [somebody=John Doe] - Somebody's name. This can be any string
+           *   representing a person, whether it's a first name, full name, nickname, or
+           *   even a codename (e.g., "Agent X"). It's used primarily for display
+           *   purposes, logging, or greeting messages and is not required to be unique or
+           *   validated unless specified by the caller.
+           *
+           * @param {string} somebody2 - Somebody's name. This can be any string
+           *   representing a person, whether it's a first name, full name, nickname, or
+           *   even a codename (e.g., "Agent X"). It's used primarily for display
+           *   purposes, logging, or greeting messages and is not required to be unique or
+           *   validated unless specified by the caller.
+           **/
+          `,
+    { printWidth: 80 },
+  );
+});
+
+it("name, type, description, hyphen and optional with default value with a description containing a linebreak", () => {
+  const template = (
+    <JSDoc>
+      <JSDocParam
+        name="somebody"
+        type="string"
+        optional
+        defaultValue="John Doe"
+        hyphen
+      >
+        Somebody's name. This is one line
+        <hbr />
+        This is another line
+      </JSDocParam>
+    </JSDoc>
+  );
+
+  expect(template).toRenderTo(
+    d`
+          /**
+           * @param {string} [somebody=John Doe] - Somebody's name. This is one line
+           *   This is another line
+           **/
+          `,
+    { printWidth: 80 },
+  );
+});
diff --git a/packages/typescript/test/name-conflict.test.tsx b/packages/typescript/test/name-conflict.test.tsx
index 6f7120e..a348df8 100644
--- a/packages/typescript/test/name-conflict.test.tsx
+++ b/packages/typescript/test/name-conflict.test.tsx
@@ -4,11 +4,11 @@ import { camelCase } from "change-case";
 import { expect, it } from "vitest";
 import {
   FunctionDeclaration,
-  ParameterDescriptor,
   SourceFile,
   TSOutputSymbol,
   TSSymbolFlags,
 } from "../src/index.js";
+import { ParameterDescriptor } from "../src/parameter-descriptor.js";
 it("handles custom name conflict resolver based on metadata", () => {
   function resolver(name: string, symbols: TSOutputSymbol[]) {
     const goodNamedSymbols = symbols.filter(
diff --git a/packages/typescript/test/var-declaration-jsdoc.test.tsx b/packages/typescript/test/var-declaration-jsdoc.test.tsx
new file mode 100644
index 0000000..b8d4df0
--- /dev/null
+++ b/packages/typescript/test/var-declaration-jsdoc.test.tsx
@@ -0,0 +1,70 @@
+import { d } from "@alloy-js/core/testing";
+import { expect, it } from "vitest";
+import * as ts from "../src/index.js";
+import { toSourceText } from "./utils.js";
+
+it("renders a variable declaration with documentation", () => {
+  const res = toSourceText(
+    <>
+      <ts.VarDeclaration
+        name="myVar"
+        const
+        doc="This is a variable documentation"
+      >
+        123
+      </ts.VarDeclaration>
+      ;
+    </>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * This is a variable documentation
+     **/
+    const myVar = 123;
+  `);
+});
+
+it("renders a let declaration with documentation", () => {
+  const res = toSourceText(
+    <>
+      <ts.VarDeclaration
+        let
+        name="counter"
+        doc="A counter variable that can be changed"
+        type="number"
+      >
+        0
+      </ts.VarDeclaration>
+      ;
+    </>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * A counter variable that can be changed
+     **/
+    let counter: number = 0;
+  `);
+});
+
+it("renders an exported variable declaration with documentation", () => {
+  const res = toSourceText(
+    <>
+      <ts.VarDeclaration
+        export
+        name="API_URL"
+        const
+        doc="The base URL for API calls"
+        type="string"
+      >
+        "https://api.example.com"
+      </ts.VarDeclaration>
+      ;
+    </>,
+  );
+  expect(res).toEqual(d`
+    /**
+     * The base URL for API calls
+     **/
+    export const API_URL: string = "https://api.example.com";
+  `);
+});