Skip to content

chore(repo): typedoc - Render accessors to markdown in the same fashion as properties #6532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/old-dolls-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
75 changes: 73 additions & 2 deletions .typedoc/custom-theme.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,69 @@ class ClerkMarkdownThemeContext extends MarkdownThemeContext {
const customizedModel = model;
customizedModel.typeParameters = undefined;

const output = superPartials.memberWithGroups(customizedModel, options);
// Extract the Accessors group (if any) and prevent default rendering for it
const originalGroups = customizedModel.groups;
const accessorsGroup = originalGroups?.find(g => g.title === 'Accessors');
const groupsWithoutAccessors = originalGroups?.filter(g => g.title !== 'Accessors');

customizedModel.groups = groupsWithoutAccessors;
const nonAccessorOutput = superPartials.memberWithGroups(customizedModel, options);
customizedModel.groups = originalGroups;

/** @type {string[]} */
const md = [nonAccessorOutput];

if (accessorsGroup && accessorsGroup.children && accessorsGroup.children.length > 0) {
md.push('\n\n## Accessors\n');
// Table header
// This needs to be 'Property' instead of 'Accessor' so that clerk.com renders it correctly
md.push('| Property | Type | Description |');
md.push('| --- | --- | --- |');

for (const child of accessorsGroup.children) {
/** @type {import('typedoc').DeclarationReflection} */
// @ts-ignore - child is a DeclarationReflection for accessor members
const decl = child;
// Name and anchor id
const name = decl.name;
const id = name.toLowerCase().replace(/[^a-z0-9]/g, '');

// Resolve the accessor type from the getter signature
/** @type {any} */
const getterSig = /** @type {any} */ (decl).getSignature;
/** @type {any} */
const setterSig = /** @type {any} */ (decl).setSignature;
let typeStr = '';
if (getterSig?.type) {
typeStr = this.partials.someType(getterSig.type);
} else if (setterSig?.parameters?.[0]?.type) {
typeStr = this.partials.someType(setterSig.parameters[0].type);
} else if (decl.type) {
typeStr = this.partials.someType(decl.type);
}

return output;
// Prefer comment on the getter signature; fallback to declaration comment
const summary = getterSig?.comment?.summary ?? decl.comment?.summary ?? setterSig?.comment?.summary;
const description = Array.isArray(summary)
? summary.reduce((acc, curr) => acc + (curr.text || ''), '')
: '';

md.push(`| <a id="${id}"></a> \`${escapeChars(name)}\` | ${typeStr} | ${description} |`);
}
}

return md.join('\n');
},
Comment on lines +221 to +273
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use getCommentParts for descriptions and add a safe fallback for empty types

Two improvements:

  • Description: prefer the theme helper this.helpers.getCommentParts over manually reducing CommentDisplayPart[] so tags/formatting render correctly.
  • Type: if neither getter, setter nor decl.type yields a type, use a visible placeholder (e.g., —) to avoid an empty table cell.

Apply this diff:

-            let typeStr = '';
-            if (getterSig?.type) {
-              typeStr = this.partials.someType(getterSig.type);
-            } else if (setterSig?.parameters?.[0]?.type) {
-              typeStr = this.partials.someType(setterSig.parameters[0].type);
-            } else if (decl.type) {
-              typeStr = this.partials.someType(decl.type);
-            }
+            let typeStr = '';
+            if (getterSig?.type) {
+              typeStr = this.partials.someType(getterSig.type);
+            } else if (setterSig?.parameters?.[0]?.type) {
+              typeStr = this.partials.someType(setterSig.parameters[0].type);
+            } else if (decl.type) {
+              typeStr = this.partials.someType(decl.type);
+            }
+            if (!typeStr) {
+              typeStr = '—';
+            }
@@
-            // Prefer comment on the getter signature; fallback to declaration comment
-            const summary = getterSig?.comment?.summary ?? decl.comment?.summary ?? setterSig?.comment?.summary;
-            const description = Array.isArray(summary)
-              ? summary.reduce((acc, curr) => acc + (curr.text || ''), '')
-              : '';
+            // Prefer comment on the getter signature; fallback to declaration, then setter
+            const summaryParts =
+              getterSig?.comment?.summary ?? decl.comment?.summary ?? setterSig?.comment?.summary;
+            const description = summaryParts ? this.helpers.getCommentParts(summaryParts) : '';

Optional: to minimize potential ID collisions, consider prefixing the anchor with the parent name (e.g., ${decl.parent?.name}-${name}) before sanitizing.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Extract the Accessors group (if any) and prevent default rendering for it
const originalGroups = customizedModel.groups;
const accessorsGroup = originalGroups?.find(g => g.title === 'Accessors');
const groupsWithoutAccessors = originalGroups?.filter(g => g.title !== 'Accessors');
customizedModel.groups = groupsWithoutAccessors;
const nonAccessorOutput = superPartials.memberWithGroups(customizedModel, options);
customizedModel.groups = originalGroups;
/** @type {string[]} */
const md = [nonAccessorOutput];
if (accessorsGroup && accessorsGroup.children && accessorsGroup.children.length > 0) {
md.push('\n\n## Accessors\n');
// Table header
// This needs to be 'Property' instead of 'Accessor' so that clerk.com renders it correctly
md.push('| Property | Type | Description |');
md.push('| --- | --- | --- |');
for (const child of accessorsGroup.children) {
/** @type {import('typedoc').DeclarationReflection} */
// @ts-ignore - child is a DeclarationReflection for accessor members
const decl = child;
// Name and anchor id
const name = decl.name;
const id = name.toLowerCase().replace(/[^a-z0-9]/g, '');
// Resolve the accessor type from the getter signature
/** @type {any} */
const getterSig = /** @type {any} */ (decl).getSignature;
/** @type {any} */
const setterSig = /** @type {any} */ (decl).setSignature;
let typeStr = '';
if (getterSig?.type) {
typeStr = this.partials.someType(getterSig.type);
} else if (setterSig?.parameters?.[0]?.type) {
typeStr = this.partials.someType(setterSig.parameters[0].type);
} else if (decl.type) {
typeStr = this.partials.someType(decl.type);
}
return output;
// Prefer comment on the getter signature; fallback to declaration comment
const summary = getterSig?.comment?.summary ?? decl.comment?.summary ?? setterSig?.comment?.summary;
const description = Array.isArray(summary)
? summary.reduce((acc, curr) => acc + (curr.text || ''), '')
: '';
md.push(`| <a id="${id}"></a> \`${escapeChars(name)}\` | ${typeStr} | ${description} |`);
}
}
return md.join('\n');
},
for (const child of accessorsGroup.children) {
/** @type {import('typedoc').DeclarationReflection} */
// @ts-ignore - child is a DeclarationReflection for accessor members
const decl = child;
// Name and anchor id
const name = decl.name;
const id = name.toLowerCase().replace(/[^a-z0-9]/g, '');
// Resolve the accessor type from the getter signature
/** @type {any} */
const getterSig = /** @type {any} */ (decl).getSignature;
/** @type {any} */
const setterSig = /** @type {any} */ (decl).setSignature;
let typeStr = '';
if (getterSig?.type) {
typeStr = this.partials.someType(getterSig.type);
} else if (setterSig?.parameters?.[0]?.type) {
typeStr = this.partials.someType(setterSig.parameters?.[0].type);
} else if (decl.type) {
typeStr = this.partials.someType(decl.type);
}
if (!typeStr) {
typeStr = '—';
}
// Prefer comment on the getter signature; fallback to declaration, then setter
const summaryParts =
getterSig?.comment?.summary ?? decl.comment?.summary ?? setterSig?.comment?.summary;
const description = summaryParts ? this.helpers.getCommentParts(summaryParts) : '';
md.push(`| <a id="${id}"></a> \`${escapeChars(name)}\` | ${typeStr} | ${description} |`);
}
🤖 Prompt for AI Agents
In .typedoc/custom-theme.mjs around lines 221 to 273, the accessor table
currently builds descriptions by manually reducing CommentDisplayPart[] and
leaves the Type cell empty when no type is resolved; change the description
extraction to use this.helpers.getCommentParts (or
this.helpers.getCommentParts(summary) equivalent) so tags/formatting render
correctly, and ensure typeStr defaults to a visible placeholder (e.g., '—') when
getter/setter/decl.type are all absent; also, to reduce anchor collisions,
prefix the id with the parent name when present (e.g.,
`${decl.parent?.name}-${name}`) before sanitizing and lowercasing.

/**
* Suppress default per-accessor member rendering; table is rendered in memberWithGroups instead.
* @param {import('typedoc').DeclarationReflection} model
* @param {{ headingLevel: number, nested?: boolean }} options
*/
member: (model, options) => {
if (model.kind === ReflectionKind.Accessor) {
return '';
}
return superPartials.member(model, options);
},
/**
* This hides the "Type parameters" section and the declaration title from the output
Expand Down Expand Up @@ -412,6 +472,17 @@ ${tabs}
* Hide "Extends" and "Extended by" sections
*/
hierarchy: () => '',
/**
* @param {import('typedoc').DeclarationReflection} model
*/
accessor: model => {
// Fallback single-row rendering if used directly elsewhere
const name = model.name;
const typeStr = model.getSignature?.type ? this.partials.someType(model.getSignature.type) : '';
const summary = model.getSignature?.comment?.summary ?? model.comment?.summary;
const description = Array.isArray(summary) ? summary.reduce((acc, curr) => acc + (curr.text || ''), '') : '';
return '| ' + '`' + escapeChars(name) + '`' + ' | ' + typeStr + ' | ' + description + ' |';
},
};
Comment on lines +475 to 486
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Align fallback accessor partial with main table logic

For consistency, compute type from getter or setter (with fallback placeholder) and use getCommentParts for the description. This keeps standalone accessor rendering consistent with the table.

Apply this diff:

-      accessor: model => {
-        // Fallback single-row rendering if used directly elsewhere
-        const name = model.name;
-        const typeStr = model.getSignature?.type ? this.partials.someType(model.getSignature.type) : '';
-        const summary = model.getSignature?.comment?.summary ?? model.comment?.summary;
-        const description = Array.isArray(summary) ? summary.reduce((acc, curr) => acc + (curr.text || ''), '') : '';
-        return '| ' + '`' + escapeChars(name) + '`' + ' | ' + typeStr + ' | ' + description + ' |';
-      },
+      accessor: model => {
+        // Fallback single-row rendering if used directly elsewhere
+        const name = model.name;
+        let typeStr = '';
+        if (model.getSignature?.type) {
+          typeStr = this.partials.someType(model.getSignature.type);
+        } else if (model.setSignature?.parameters?.[0]?.type) {
+          typeStr = this.partials.someType(model.setSignature.parameters[0].type);
+        } else if (model.type) {
+          typeStr = this.partials.someType(model.type);
+        }
+        if (!typeStr) {
+          typeStr = '—';
+        }
+        const summaryParts =
+          model.getSignature?.comment?.summary ?? model.comment?.summary ?? model.setSignature?.comment?.summary;
+        const description = summaryParts ? this.helpers.getCommentParts(summaryParts) : '';
+        return '| ' + '`' + escapeChars(name) + '`' + ' | ' + typeStr + ' | ' + description + ' |';
+      },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* @param {import('typedoc').DeclarationReflection} model
*/
accessor: model => {
// Fallback single-row rendering if used directly elsewhere
const name = model.name;
const typeStr = model.getSignature?.type ? this.partials.someType(model.getSignature.type) : '';
const summary = model.getSignature?.comment?.summary ?? model.comment?.summary;
const description = Array.isArray(summary) ? summary.reduce((acc, curr) => acc + (curr.text || ''), '') : '';
return '| ' + '`' + escapeChars(name) + '`' + ' | ' + typeStr + ' | ' + description + ' |';
},
};
accessor: model => {
// Fallback single-row rendering if used directly elsewhere
const name = model.name;
let typeStr = '';
if (model.getSignature?.type) {
typeStr = this.partials.someType(model.getSignature.type);
} else if (model.setSignature?.parameters?.[0]?.type) {
typeStr = this.partials.someType(model.setSignature.parameters[0].type);
} else if (model.type) {
typeStr = this.partials.someType(model.type);
}
if (!typeStr) {
typeStr = '—';
}
const summaryParts =
model.getSignature?.comment?.summary ?? model.comment?.summary ?? model.setSignature?.comment?.summary;
const description = summaryParts ? this.helpers.getCommentParts(summaryParts) : '';
return '| ' + '`' + escapeChars(name) + '`' + ' | ' + typeStr + ' | ' + description + ' |';
},
🤖 Prompt for AI Agents
In .typedoc/custom-theme.mjs around lines 475 to 486, the fallback accessor
partial currently only reads type from getSignature and builds description
manually; update it to compute type from either getSignature or setSignature
(falling back to the same placeholder used by the main table) and replace the
manual summary/description assembly with a call to getCommentParts for the
appropriate signature so the standalone accessor rendering matches the main
table logic.

}
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue",
"test:typedoc": "pnpm typedoc:generate && cd ./.typedoc && vitest run",
"turbo:clean": "turbo daemon clean",
"typedoc:generate": "pnpm build:declarations && typedoc --tsconfig tsconfig.typedoc.json && rm -rf .typedoc/docs && mv .typedoc/temp-docs .typedoc/docs",
"typedoc:generate": "pnpm build:declarations && pnpm typedoc:generate:skip-build",
"typedoc:generate:skip-build": "typedoc --tsconfig tsconfig.typedoc.json && rm -rf .typedoc/docs && mv .typedoc/temp-docs .typedoc/docs",
"version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false",
"version-packages:canary": "./scripts/canary.mjs",
"version-packages:snapshot": "./scripts/snapshot.mjs",
Expand Down
Loading