Repository: alloy-framework/alloy
Pull request #201 (2025-06-17 21:25:02)

Doc comments

github-actions[bot]: <!--chronus-github-change-commenter-->
:x: There is undocummented changes. Run `chronus add` to add a changeset or [click here](https://github.com/timotheeguerin/alloy/new/csharp-doc-comments?filename=.chronus/changes/csharp-doc-comments-2025-5-17-21-25-24.md&value=---%0A%23%20Change%20versionKind%20to%20one%20of%3A%20breaking%2C%20feature%2C%20fix%2C%20internal%0AchangeKind%3A%20fix%0Apackages%3A%0A%20%20-%20%22%40alloy-js%2Fcore%22%0A%20%20-%20%22%40alloy-js%2Fcsharp%22%0A---%0A%0ADoc%20comments).

**The following packages have changes but are not documented.**
 - :x:`@alloy-js/core`
 - :x:`@alloy-js/csharp`

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


</details>

pkg-pr-new[bot]: 
[Open in StackBlitz](https://pkg.pr.new/template/e4f68457-7619-4373-a783-84b792e70c17)


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

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

</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@201
```

</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@201
```

</p></details>
      
    

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

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/cli@201
```

</p></details>
      
    

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

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

</p></details>
      
    

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

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

</p></details>
      
    

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

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

</p></details>
      
    

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

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

</p></details>
      
    

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

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

</p></details>
      
    

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

```
npm i https://pkg.pr.new/alloy-framework/alloy/@alloy-js/markdown@201
```

</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@201
```

</p></details>
      
    

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

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

</p></details>
      
    

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

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

</p></details>
      
    

_commit: <a href="https://github.com/alloy-framework/alloy/runs/44294405869"><code>4ee205c</code></a>_


Patch:
diff --git a/.chronus/changes/csharp-doc-comments-2025-5-17-21-25-24.md b/.chronus/changes/csharp-doc-comments-2025-5-17-21-25-24.md
new file mode 100644
index 0000000..6987b00
--- /dev/null
+++ b/.chronus/changes/csharp-doc-comments-2025-5-17-21-25-24.md
@@ -0,0 +1,8 @@
+---
+# Change versionKind to one of: breaking, feature, fix, internal
+changeKind: fix
+packages:
+  - "@alloy-js/csharp"
+---
+
+Add support for csharp doc comments with various `<Doc*>` components
diff --git a/.chronus/changes/csharp-doc-comments-2025-5-17-21-27-50.md b/.chronus/changes/csharp-doc-comments-2025-5-17-21-27-50.md
new file mode 100644
index 0000000..f566aae
--- /dev/null
+++ b/.chronus/changes/csharp-doc-comments-2025-5-17-21-27-50.md
@@ -0,0 +1,6 @@
+---
+# Change versionKind to one of: breaking, feature, fix, internal
+changeKind: internal
+packages:
+  - "@alloy-js/core"
+---
diff --git a/cspell.yaml b/cspell.yaml
index 09cad34..d44e1d3 100644
--- a/cspell.yaml
+++ b/cspell.yaml
@@ -5,5 +5,6 @@ dictionaries:
   - node
   - typescript
 words:
+  - cref
 useGitignore: true
 enableGlobDot: true
diff --git a/packages/csharp/src/components/ClassDeclaration.tsx b/packages/csharp/src/components/ClassDeclaration.tsx
index 0d034c3..53a2f9c 100644
--- a/packages/csharp/src/components/ClassDeclaration.tsx
+++ b/packages/csharp/src/components/ClassDeclaration.tsx
@@ -10,6 +10,7 @@ import { CSharpOutputSymbol } from "../symbols/csharp-output-symbol.js";
 import { CSharpMemberScope, useCSharpScope } from "../symbols/scopes.js";
 import { Name } from "./Name.jsx";
 import { ParameterProps, Parameters } from "./Parameters.jsx";
+import { DocWhen } from "./doc/comment.jsx";
 
 export interface ClassModifiers {
   readonly abstract?: boolean;
@@ -31,6 +32,8 @@ export interface ClassDeclarationProps
     AccessModifiers,
     ClassModifiers {
   name: string;
+  /** Doc comment */
+  doc?: core.Children;
   refkey?: core.Refkey;
   typeParameters?: Record<string, core.Refkey>;
 }
@@ -105,6 +108,7 @@ export function ClassDeclaration(props: ClassDeclarationProps) {
   ]);
   return (
     <core.Declaration symbol={thisClassSymbol}>
+      <DocWhen doc={props.doc} />
       {modifiers}class <Name />
       {typeParams}
       {!props.children && ";"}
diff --git a/packages/csharp/src/components/ClassMethod.tsx b/packages/csharp/src/components/ClassMethod.tsx
index 423a9a5..da9fe42 100644
--- a/packages/csharp/src/components/ClassMethod.tsx
+++ b/packages/csharp/src/components/ClassMethod.tsx
@@ -17,6 +17,7 @@ import { useCSharpNamePolicy } from "../name-policy.js";
 import { CSharpOutputSymbol } from "../symbols/csharp-output-symbol.js";
 import { CSharpMemberScope, useCSharpScope } from "../symbols/scopes.js";
 import { ParameterProps, Parameters } from "./Parameters.jsx";
+import { DocWhen } from "./doc/comment.jsx";
 
 /** Method modifiers. Can only be one. */
 export interface ClassMethodModifiers {
@@ -47,6 +48,9 @@ export interface ClassMethodProps
    * If true, the method will be declared as an async method.
    */
   async?: boolean;
+
+  /** Doc comment */
+  doc?: Children;
 }
 
 // a C# class method
@@ -80,6 +84,7 @@ export function ClassMethod(props: ClassMethodProps) {
   return (
     <MemberDeclaration symbol={methodSymbol}>
       <Scope value={methodScope}>
+        <DocWhen doc={props.doc} />
         {modifiers}
         {returns} {name}({params})
         {props.abstract ? ";" : <Block newline>{props.children}</Block>}
diff --git a/packages/csharp/src/components/doc/comment.tsx b/packages/csharp/src/components/doc/comment.tsx
new file mode 100644
index 0000000..839ce56
--- /dev/null
+++ b/packages/csharp/src/components/doc/comment.tsx
@@ -0,0 +1,122 @@
+import { Children, code, List, Prose, Show } from "@alloy-js/core";
+
+export interface DocCommentProps {
+  children: Children;
+}
+
+export function DocComment(props: DocCommentProps) {
+  return (
+    <>
+      {"/// "}
+      <align string="/// ">
+        <List>{props.children}</List>
+      </align>
+    </>
+  );
+}
+
+export interface DocWhenProps {
+  doc: Children | undefined;
+}
+
+/** Conditionally render the given doc in a <DocComment /> component and tail with a line */
+export function DocWhen(props: DocWhenProps) {
+  return (
+    <Show when={Boolean(props.doc)}>
+      <DocComment children={props.doc} />
+      <hbr />
+    </Show>
+  );
+}
+
+export interface DocCommentTagProps {
+  children: Children;
+}
+
+export function makeDocCommentTag(name: string) {
+  return function DocCommentTag(props: DocCommentProps) {
+    return (
+      <Prose>
+        {code`
+        <${name}>
+        ${props.children}
+        </${name}>`}
+      </Prose>
+    );
+  };
+}
+export function makeInlineDocCommentTag(name: string) {
+  return function DocCommentTag(props: DocCommentProps) {
+    return `<${name}>${props.children}</${name}>`;
+  };
+}
+
+export const DocSummary = makeDocCommentTag("summary");
+export const DocCode = makeDocCommentTag("code");
+export const DocC = makeDocCommentTag("c");
+export const DocExample = makeDocCommentTag("example");
+export const DocException = makeDocCommentTag("exception");
+
+export interface DocIncludeProps {
+  /**  is the file name of an external XML file. The file name is interpreted relative to the file that contains the include tag. */
+  file: string;
+  /** is an XPath expression that selects some of the XML in the external XML file. */
+  path?: string;
+}
+export const DocInclude = (props: DocIncludeProps) => {
+  return `<include file="${props.file}" path="${props.path}" />`;
+};
+
+export interface DocParamProps {
+  name: string;
+  children: Children;
+}
+export const DocParam = (props: DocParamProps) =>
+  code`<param name="${props.name}">${props.children}</param>`;
+
+export interface DocTypeParamProps {
+  name: string;
+  children: Children;
+}
+export const DocTypeParam = (props: DocTypeParamProps) =>
+  code`<typeparam name="${props.name}">${props.children}</typeparam>`;
+
+export const DocReturns = makeDocCommentTag("returns");
+export const DocRemarks = makeDocCommentTag("remarks");
+export const DocValue = makeDocCommentTag("value");
+export const DocPermission = makeDocCommentTag("permission");
+export const DocResponse = makeDocCommentTag("response");
+export const DocCompletionList = makeDocCommentTag("completionlist");
+export const DocList = makeDocCommentTag("list");
+export const DocItem = makeDocCommentTag("item");
+export const DocTerm = makeDocCommentTag("term");
+export const DocDescription = makeDocCommentTag("description");
+export const DocPara = makeDocCommentTag("para");
+
+export interface DocSeeProps {
+  cref: string;
+  langword?: string;
+  children?: Children;
+}
+export const DocSee = (props: DocSeeProps) =>
+  `<see cref="${props.cref}"${props.langword ? ` langword="${props.langword}"` : ""}${props.children ? `>${props.children}</see>` : " />"}`;
+
+export interface DocSeeAlsoProps {
+  cref: string;
+  langword?: string;
+  children?: Children;
+}
+export const DocSeeAlso = (props: DocSeeAlsoProps) =>
+  `<seealso cref="${props.cref}"${props.langword ? ` langword="${props.langword}"` : ""}${props.children ? `>${props.children}</seealso>` : " />"}`;
+
+export interface DocParamRefProps {
+  name: string;
+}
+export const DocParamRef = (props: DocParamRefProps) =>
+  `<paramref name="${props.name}" />`;
+
+export interface DocTypeParamRefProps {
+  name: string;
+}
+export const DocTypeParamRef = (props: DocTypeParamRefProps) =>
+  `<typeparamref name="${props.name}" />`;
diff --git a/packages/csharp/src/components/interface/declaration.tsx b/packages/csharp/src/components/interface/declaration.tsx
index 8dd83cb..07e026c 100644
--- a/packages/csharp/src/components/interface/declaration.tsx
+++ b/packages/csharp/src/components/interface/declaration.tsx
@@ -8,6 +8,7 @@ import {
 import { useCSharpNamePolicy } from "../../name-policy.js";
 import { CSharpOutputSymbol } from "../../symbols/csharp-output-symbol.js";
 import { CSharpMemberScope } from "../../symbols/scopes.js";
+import { DocWhen } from "../doc/comment.jsx";
 import { Name } from "../Name.jsx";
 
 export interface InterfaceModifiers {
@@ -22,6 +23,9 @@ export interface InterfaceDeclarationProps
     AccessModifiers,
     InterfaceModifiers {
   name: string;
+
+  /** Doc comment */
+  doc?: core.Children;
   refkey?: core.Refkey;
   typeParameters?: Record<string, core.Refkey>;
 }
@@ -92,6 +96,7 @@ export function InterfaceDeclaration(props: InterfaceDeclarationProps) {
   ]);
   return (
     <core.Declaration symbol={thisInterfaceSymbol}>
+      <DocWhen doc={props.doc} />
       {modifiers}interface <Name />
       {typeParams}
       {props.children ?
diff --git a/packages/csharp/src/components/interface/method.tsx b/packages/csharp/src/components/interface/method.tsx
index 9069f92..e562946 100644
--- a/packages/csharp/src/components/interface/method.tsx
+++ b/packages/csharp/src/components/interface/method.tsx
@@ -16,6 +16,7 @@ import { useCSharpNamePolicy } from "../../name-policy.js";
 import { CSharpOutputSymbol } from "../../symbols/csharp-output-symbol.js";
 import { CSharpMemberScope, useCSharpScope } from "../../symbols/scopes.js";
 import { ParameterProps, Parameters } from "../Parameters.jsx";
+import { DocWhen } from "../doc/comment.jsx";
 
 /** Method modifiers. Can only be one. */
 export interface InterfaceMethodModifiers {
@@ -33,6 +34,9 @@ export interface InterfaceMethodProps
   children?: Children;
   parameters?: Array<ParameterProps>;
   returns?: Children;
+
+  /** Doc comment */
+  doc?: Children;
 }
 
 // a C# interface method
@@ -66,6 +70,7 @@ export function InterfaceMethod(props: InterfaceMethodProps) {
   return (
     <MemberDeclaration symbol={methodSymbol}>
       <Scope value={methodScope}>
+        <DocWhen doc={props.doc} />
         {modifiers}
         {props.returns ?? "void"} {name}({params})
         {props.children ?
diff --git a/packages/csharp/src/components/interface/property.tsx b/packages/csharp/src/components/interface/property.tsx
index e6c4a2d..983e8ba 100644
--- a/packages/csharp/src/components/interface/property.tsx
+++ b/packages/csharp/src/components/interface/property.tsx
@@ -16,6 +16,7 @@ import {
 import { useCSharpNamePolicy } from "../../name-policy.js";
 import { CSharpOutputSymbol } from "../../symbols/csharp-output-symbol.js";
 import { CSharpMemberScope, useCSharpScope } from "../../symbols/scopes.js";
+import { DocWhen } from "../doc/comment.jsx";
 
 /** Method modifiers. Can only be one. */
 export interface InterfacePropertyModifiers {
@@ -41,6 +42,9 @@ export interface InterfacePropertyProps
 
   /** If property should have a setter */
   set?: boolean;
+
+  /** Doc comment */
+  doc?: Children;
 }
 
 // a C# interface property
@@ -71,6 +75,7 @@ export function InterfaceProperty(props: InterfacePropertyProps) {
   return (
     <MemberDeclaration symbol={propertySymbol}>
       <Scope value={propertyScope}>
+        <DocWhen doc={props.doc} />
         {modifiers}
         {props.type} {name}{" "}
         <Block newline inline>


Test patch:
diff --git a/packages/core/testing/vitest.d.ts b/packages/core/testing/vitest.d.ts
index 5589827..74fb4fc 100644
--- a/packages/core/testing/vitest.d.ts
+++ b/packages/core/testing/vitest.d.ts
@@ -10,6 +10,5 @@ interface CustomMatchers<R = unknown> {
 }
 
 declare module "vitest" {
-  interface Assertion<T = any> extends CustomMatchers<T> {}
-  interface AsymmetricMatchersContaining extends CustomMatchers {}
+  interface Matchers<T = any> extends CustomMatchers<T> {}
 }
diff --git a/packages/csharp/src/components/doc/comment.test.tsx b/packages/csharp/src/components/doc/comment.test.tsx
new file mode 100644
index 0000000..db4ae30
--- /dev/null
+++ b/packages/csharp/src/components/doc/comment.test.tsx
@@ -0,0 +1,336 @@
+import { expect, it } from "vitest";
+import {
+  DocC,
+  DocCode,
+  DocComment,
+  DocCompletionList,
+  DocDescription,
+  DocExample,
+  DocException,
+  DocInclude,
+  DocItem,
+  DocList,
+  DocPara,
+  DocParam,
+  DocParamRef,
+  DocPermission,
+  DocRemarks,
+  DocResponse,
+  DocReturns,
+  DocSee,
+  DocSeeAlso,
+  DocSummary,
+  DocTerm,
+  DocTypeParam,
+  DocTypeParamRef,
+  DocValue,
+} from "./comment.jsx";
+
+it("define summary", () => {
+  expect(
+    <DocComment>
+      <DocSummary>This is a sample doc comment</DocSummary>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <summary>
+    /// This is a sample doc comment
+    /// </summary>
+  `);
+});
+
+it("define code", () => {
+  expect(
+    <DocComment>
+      <DocCode>code sample</DocCode>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <code>
+    /// code sample
+    /// </code>
+  `);
+});
+
+it("define c", () => {
+  expect(
+    <DocComment>
+      <DocC>inline code</DocC>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <c>
+    /// inline code
+    /// </c>
+  `);
+});
+
+it("define example", () => {
+  expect(
+    <DocComment>
+      <DocExample>example usage</DocExample>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <example>
+    /// example usage
+    /// </example>
+  `);
+});
+
+it("define exception", () => {
+  expect(
+    <DocComment>
+      <DocException>exception info</DocException>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <exception>
+    /// exception info
+    /// </exception>
+  `);
+});
+
+it("define include", () => {
+  expect(
+    <DocComment>
+      <>{DocInclude({ file: "external.xml", path: "/doc/summary" })}</>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <include file="external.xml" path="/doc/summary" />
+  `);
+});
+
+it("define param", () => {
+  expect(
+    <DocComment>
+      <DocParam name="x">parameter x</DocParam>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <param name="x">parameter x</param>
+  `);
+});
+
+it("define typeparam", () => {
+  expect(
+    <DocComment>
+      <DocTypeParam name="T">type parameter T</DocTypeParam>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <typeparam name="T">type parameter T</typeparam>
+  `);
+});
+
+it("define returns", () => {
+  expect(
+    <DocComment>
+      <DocReturns>return value</DocReturns>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <returns>
+    /// return value
+    /// </returns>
+  `);
+});
+
+it("define remarks", () => {
+  expect(
+    <DocComment>
+      <DocRemarks>remarks here</DocRemarks>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <remarks>
+    /// remarks here
+    /// </remarks>
+  `);
+});
+
+it("define value", () => {
+  expect(
+    <DocComment>
+      <DocValue>property value</DocValue>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <value>
+    /// property value
+    /// </value>
+  `);
+});
+
+it("define permission", () => {
+  expect(
+    <DocComment>
+      <DocPermission>permission info</DocPermission>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <permission>
+    /// permission info
+    /// </permission>
+  `);
+});
+
+it("define response", () => {
+  expect(
+    <DocComment>
+      <DocResponse>response info</DocResponse>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <response>
+    /// response info
+    /// </response>
+  `);
+});
+
+it("define completionlist", () => {
+  expect(
+    <DocComment>
+      <DocCompletionList>completion list</DocCompletionList>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <completionlist>
+    /// completion list
+    /// </completionlist>
+  `);
+});
+
+it("define list", () => {
+  expect(
+    <DocComment>
+      <DocList>list content</DocList>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <list>
+    /// list content
+    /// </list>
+  `);
+});
+
+it("define item", () => {
+  expect(
+    <DocComment>
+      <DocItem>item content</DocItem>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <item>
+    /// item content
+    /// </item>
+  `);
+});
+
+it("define term", () => {
+  expect(
+    <DocComment>
+      <DocTerm>term content</DocTerm>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <term>
+    /// term content
+    /// </term>
+  `);
+});
+
+it("define description", () => {
+  expect(
+    <DocComment>
+      <DocDescription>description content</DocDescription>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <description>
+    /// description content
+    /// </description>
+  `);
+});
+
+it("define para", () => {
+  expect(
+    <DocComment>
+      <DocPara>paragraph content</DocPara>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <para>
+    /// paragraph content
+    /// </para>
+  `);
+});
+
+it("define see", () => {
+  expect(
+    <DocComment>
+      <>{DocSee({ cref: "T:MyType" })}</>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <see cref="T:MyType" />
+  `);
+});
+
+it("define seealso", () => {
+  expect(
+    <DocComment>
+      <>{DocSeeAlso({ cref: "T:OtherType" })}</>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <seealso cref="T:OtherType" />
+  `);
+});
+
+it("define paramref", () => {
+  expect(
+    <DocComment>
+      <>{DocParamRef({ name: "x" })}</>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <paramref name="x" />
+  `);
+});
+
+it("define typeparamref", () => {
+  expect(
+    <DocComment>
+      <>{DocTypeParamRef({ name: "T" })}</>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <typeparamref name="T" />
+  `);
+});
+
+it("wrap long summary", () => {
+  expect(
+    <DocComment>
+      <DocSummary>
+        This is a very long sample doc comment that exceeds the typical line
+        length limit and should be wrapped appropriately in the generated
+        documentation.
+      </DocSummary>
+    </DocComment>,
+  ).toRenderTo(`
+    /// <summary>
+    /// This is a very long sample doc comment that exceeds the typical line length limit
+    /// and should be wrapped appropriately in the generated documentation.
+    /// </summary>
+  `);
+});
+
+it("combine multiple tags", () => {
+  expect(
+    <DocComment>
+      <DocSummary>
+        This operator determines whether two Points have the same location.
+      </DocSummary>
+      <DocParam name="p1">The first Point to be compared.</DocParam>
+      <DocParam name="p2">The second Point to be compared.</DocParam>
+      <DocReturns>
+        True if the Points do not have the same location and the exact same
+        type; otherwise, false.
+      </DocReturns>
+      <DocSeeAlso cref="Equals" />
+      <DocSeeAlso cref="operator==" />
+    </DocComment>,
+  ).toRenderTo(`
+    /// <summary>
+    /// This operator determines whether two Points have the same location.
+    /// </summary>
+    /// <param name="p1">The first Point to be compared.</param>
+    /// <param name="p2">The second Point to be compared.</param>
+    /// <returns>
+    /// True if the Points do not have the same location and the exact same type; otherwise,
+    /// false.
+    /// </returns>
+    /// <seealso cref="Equals" />
+    /// <seealso cref="operator==" />
+  `);
+});
diff --git a/packages/csharp/src/components/interface/declaration.test.tsx b/packages/csharp/src/components/interface/declaration.test.tsx
index d31c4ef..d368f32 100644
--- a/packages/csharp/src/components/interface/declaration.test.tsx
+++ b/packages/csharp/src/components/interface/declaration.test.tsx
@@ -43,3 +43,14 @@ describe("modifiers", () => {
     `);
   });
 });
+
+it("specify doc comment", () => {
+  expect(
+    <TestNamespace>
+      <InterfaceDeclaration name="TestInterface" doc="This is a test" />
+    </TestNamespace>,
+  ).toRenderTo(`
+    /// This is a test
+    interface TestInterface;
+  `);
+});
diff --git a/packages/csharp/src/components/interface/method.test.tsx b/packages/csharp/src/components/interface/method.test.tsx
index 017acc5..c5ec44d 100644
--- a/packages/csharp/src/components/interface/method.test.tsx
+++ b/packages/csharp/src/components/interface/method.test.tsx
@@ -102,3 +102,19 @@ it("defines params and return type", () => {
     }
   `);
 });
+
+it("specify doc comment", () => {
+  expect(
+    <TestNamespace>
+      <InterfaceDeclaration name="Test">
+        <InterfaceMethod name="Method" doc="This is a test" />
+      </InterfaceDeclaration>
+    </TestNamespace>,
+  ).toRenderTo(`
+    interface Test
+    {
+      /// This is a test
+      void Method();
+    }
+  `);
+});
diff --git a/packages/csharp/src/components/interface/property.test.tsx b/packages/csharp/src/components/interface/property.test.tsx
index 0c553f6..6a2c5d4 100644
--- a/packages/csharp/src/components/interface/property.test.tsx
+++ b/packages/csharp/src/components/interface/property.test.tsx
@@ -120,3 +120,25 @@ it("has getter and setter", () => {
     }
   `);
 });
+
+it("specify doc comment", () => {
+  expect(
+    <TestNamespace>
+      <InterfaceDeclaration name="Test">
+        <InterfaceProperty
+          name="Method"
+          type="string"
+          get
+          set
+          doc="This is a test"
+        />
+      </InterfaceDeclaration>
+    </TestNamespace>,
+  ).toRenderTo(`
+    interface Test
+    {
+      /// This is a test
+      string Method { get; set; }
+    }
+  `);
+});
diff --git a/packages/csharp/test/class-method.test.tsx b/packages/csharp/test/class-method.test.tsx
index 65dde22..d118293 100644
--- a/packages/csharp/test/class-method.test.tsx
+++ b/packages/csharp/test/class-method.test.tsx
@@ -129,3 +129,19 @@ it("defines params and return type", () => {
     }
   `);
 });
+
+it("specify doc comment", () => {
+  expect(
+    <TestNamespace>
+      <ClassDeclaration name="Test">
+        <ClassMethod name="Method" doc="This is a test" />
+      </ClassDeclaration>
+    </TestNamespace>,
+  ).toRenderTo(`
+    class Test
+    {
+      /// This is a test
+      void Method() {}
+    }
+  `);
+});
diff --git a/packages/csharp/test/class.test.tsx b/packages/csharp/test/class.test.tsx
index 5ec8f85..31b940e 100644
--- a/packages/csharp/test/class.test.tsx
+++ b/packages/csharp/test/class.test.tsx
@@ -299,3 +299,14 @@ it("declares class with constructor params and assigns values to fields", () =>
     }
   `);
 });
+
+it("specify doc comment", () => {
+  expect(
+    <utils.TestNamespace>
+      <ClassDeclaration name="Test" doc="This is a test" />
+    </utils.TestNamespace>,
+  ).toRenderTo(`
+    /// This is a test
+    class Test;
+  `);
+});
diff --git a/packages/csharp/test/vitest.setup.ts b/packages/csharp/test/vitest.setup.ts
new file mode 100644
index 0000000..96cd441
--- /dev/null
+++ b/packages/csharp/test/vitest.setup.ts
@@ -0,0 +1,1 @@
+import "@alloy-js/core/testing";
diff --git a/packages/csharp/vitest.config.ts b/packages/csharp/vitest.config.ts
index ba661c0..a984519 100644
--- a/packages/csharp/vitest.config.ts
+++ b/packages/csharp/vitest.config.ts
@@ -6,5 +6,8 @@ export default defineConfig({
     jsx: "preserve",
     sourcemap: "both",
   },
+  test: {
+    setupFiles: ["./test/vitest.setup.ts"],
+  },
   plugins: [alloyPlugin()],
 });