Skip to content

Commit 01f985c

Browse files
committed
feat(typedoc): document a type on the page of the one member using it
A type with members that only one member with a page of its own uses, as `treeshake?: boolean | TreeshakeOptions` uses `TreeshakeOptions`, is documented on that member's page, as `buildOptions.treeshake.annotations` and so on. The type's own page links there, and so do links to it and the type map; the page list leaves it out. The plugin did this before, and links into such options (`InputOptions.treeshake#annotations`) rely on it. Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 81582bd commit 01f985c

10 files changed

Lines changed: 230 additions & 54 deletions

File tree

‎packages/typedoc/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ The output directory receives, following TypeDoc's own layout:
4343

4444
- A page per module and namespace (`modules/plugins.md`), listing its exports.
4545
- A page per exported class, interface, enum, type alias, function and variable: `classes/Watcher.md`, `functions/build.md`, …
46-
- A page per member of the types in `docKitMemberPages`: `interfaces/BuildOptions.input.md`, …
46+
- A page per member of the types in `docKitMemberPages`: `interfaces/BuildOptions.input.md`, … A type only one of these members uses (`boolean | TreeshakeOptions`) is documented on its page, and the type's own page links there.
4747
- `type-map.json`, mapping type names to their pages, for doc-kit's `typeMap` to link `{Type}` annotations with.
48-
- `pages.json`, listing every page (full name, kind, URL and `@category`) for the site to build its navigation from.
48+
- `pages.json`, listing every page (full name, kind, URL and `@category`) for the site to build its navigation from, but those of types documented on a member's page.
4949

5050
`docKitUrlAdapter` adapts these URLs, to keep the URLs of a previous site working. Links between pages are relative `.md` links, which doc-kit resolves. Source links are relative to TypeDoc's `basePath` (or `displayBasePath`); set it to the root of your repository.
5151

‎packages/typedoc/src/__tests__/fixtures/index.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ export interface BuildOptions {
2727
* @experimental
2828
*/
2929
output?: Readonly<{ dir: string; format?: Format }>;
30+
/**
31+
* How to tree-shake, keeping
32+
* {@link TreeshakeOptions.annotations | annotated} calls or not.
33+
*/
34+
treeshake?: boolean | TreeshakeOptions;
35+
}
36+
37+
/**
38+
* Options of tree-shaking.
39+
*/
40+
export interface TreeshakeOptions {
41+
/** Whether to keep the calls annotated as pure. */
42+
annotations?: boolean;
3043
}
3144

3245
/**

‎packages/typedoc/src/__tests__/index.test.mjs.snapshot‎

Lines changed: 24 additions & 20 deletions
Large diffs are not rendered by default.

‎packages/typedoc/src/generate.mjs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,16 @@ export const generate = async (app, directory, project) => {
7474

7575
if (typeMapFile) {
7676
writes.push(
77-
writeJSON(join(directory, typeMapFile), typeMap(pages, basePath))
77+
writeJSON(join(directory, typeMapFile), typeMap(router, pages, basePath))
7878
);
7979
}
8080

8181
if (pageListFile) {
8282
writes.push(
83-
writeJSON(join(directory, pageListFile), pageList(pages, basePath))
83+
writeJSON(
84+
join(directory, pageListFile),
85+
pageList(router, pages, basePath)
86+
)
8487
);
8588
}
8689

‎packages/typedoc/src/render/entries.mjs‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ export const receiverOf = (app, owner) =>
1818
/**
1919
* The name an entry is documented by: `build` for an export, then
2020
* `new Watcher`, `Watcher.create`, `watcher.close`, or
21-
* `buildOptions.output.dir` for the members of a type.
21+
* `buildOptions.output.dir` for the members of a type, and
22+
* `buildOptions.treeshake.annotations` for those of a type documented on a
23+
* member's page.
2224
*
23-
* @param {import('typedoc').Application} app
25+
* @param {import('../utils/router.mjs').DocKitRouter} router
2426
* @param {import('typedoc').Reflection} reflection
2527
* @returns {string}
2628
*/
27-
export const entryName = (app, reflection) => {
29+
export const entryName = (router, reflection) => {
2830
// The members of an object type belong to what has that type
2931
const parent = reflection.parent?.kindOf(ReflectionKind.TypeLiteral)
3032
? reflection.parent.parent
@@ -45,8 +47,17 @@ export const entryName = (app, reflection) => {
4547
return `${parent.name}.${reflection.name}`;
4648
}
4749

50+
const documentedOn = router.inlined.get(parent);
51+
52+
if (documentedOn) {
53+
return `${entryName(router, documentedOn)}.${reflection.name}`;
54+
}
55+
4856
const isOwner = parent.parent?.kindOf(ReflectionKind.ExportContainer);
49-
const receiver = isOwner ? receiverOf(app, parent) : entryName(app, parent);
57+
58+
const receiver = isOwner
59+
? receiverOf(router.application, parent)
60+
: entryName(router, parent);
5061

5162
return `${receiver}.${reflection.name}`;
5263
};
@@ -116,12 +127,12 @@ export const callHeading = (name, signature) => {
116127
/**
117128
* The heading of an entry: its call, when given a signature, or its name.
118129
*
119-
* @param {import('typedoc').Application} app
130+
* @param {import('../utils/router.mjs').DocKitRouter} router
120131
* @param {import('typedoc').Reflection} reflection
121132
* @param {import('typedoc').SignatureReflection} [signature]
122133
*/
123-
export const entryHeading = (app, reflection, signature) => {
124-
const name = entryName(app, reflection);
134+
export const entryHeading = (router, reflection, signature) => {
135+
const name = entryName(router, reflection);
125136

126137
if (!signature) {
127138
return code(name);

‎packages/typedoc/src/render/members.mjs‎

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
membersOf,
1919
nestedObject,
2020
objectDeclaration,
21+
referencedType,
2122
signaturesOf,
2223
typeOf,
2324
} from '../utils/reflections.mjs';
@@ -55,7 +56,7 @@ export const renderSignature = (
5556

5657
return renderEntry(context, {
5758
depth,
58-
label: entryHeading(context.app, declaration, signature),
59+
label: entryHeading(context.router, declaration, signature),
5960
reflection: declaration,
6061
comment: signature.comment ?? declaration.comment,
6162
signature,
@@ -66,7 +67,25 @@ export const renderSignature = (
6667
};
6768

6869
/**
69-
* The entry of a property, with the properties of an object type nested.
70+
* The members documented under a property: those of its object type, or of
71+
* the type documented on its page (`TreeshakeOptions` on `treeshake`'s).
72+
*
73+
* @param {import('../types').Context} context
74+
* @param {import('typedoc').DeclarationReflection} member
75+
*/
76+
const nestedMembers = ({ router }, member) => {
77+
const type = typeOf(member);
78+
const named = referencedType(type);
79+
80+
if (named && router.inlined.get(named) === member) {
81+
return membersOf(named);
82+
}
83+
84+
return nestedObject(type)?.children ?? [];
85+
};
86+
87+
/**
88+
* The entry of a property, with the members of its type nested.
7089
*
7190
* @param {import('../types').Context} context
7291
* @param {import('typedoc').DeclarationReflection} member
@@ -76,14 +95,14 @@ export const renderSignature = (
7695
const renderProperty = (context, member, depth, extras = {}) => {
7796
const lines = renderEntry(context, {
7897
depth,
79-
label: entryHeading(context.app, member),
98+
label: entryHeading(context.router, member),
8099
reflection: member,
81100
comment: commentOf(member),
82101
items: [typeItem(context, member)],
83102
...extras,
84103
});
85104

86-
for (const child of nestedObject(typeOf(member))?.children ?? []) {
105+
for (const child of nestedMembers(context, member)) {
87106
lines.push(...renderMember(context, child, depth + 1));
88107
}
89108

‎packages/typedoc/src/render/pages.mjs‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { ReflectionKind } from 'typedoc';
44

55
import { splitSummary } from './comments.mjs';
6-
import { renderEntry } from './entries.mjs';
6+
import { entryName, renderEntry } from './entries.mjs';
77
import { typeItem } from './lists.mjs';
88
import {
99
renderEvents,
@@ -75,6 +75,26 @@ const pageEntry = (context, reflection, entry = {}) =>
7575
...entry,
7676
});
7777

78+
/**
79+
* A type documented on the page of the member using it: its description, and
80+
* a link there.
81+
*
82+
* @param {import('../types').Context} context
83+
* @param {import('typedoc').DeclarationReflection} declaration
84+
* @param {import('typedoc').DeclarationReflection} member
85+
*/
86+
const inlinedTypePage = (context, declaration, member) => {
87+
const name = code(entryName(context.router, member));
88+
const link = context.router.linkTo(context.page, member);
89+
90+
return pageEntry(context, declaration, {
91+
notes: [
92+
...importedFrom(context, declaration),
93+
`Documented with [${name}](${link}).`,
94+
],
95+
});
96+
};
97+
7898
/**
7999
* A module or namespace: a list of its exports, by group.
80100
*
@@ -180,6 +200,12 @@ const typePage = (context, declaration) => [
180200
* @returns {string[]}
181201
*/
182202
export const renderPage = (context, reflection) => {
203+
const member = context.router.inlined.get(reflection);
204+
205+
if (member) {
206+
return inlinedTypePage(context, reflection, member);
207+
}
208+
183209
if (reflection.kindOf(ReflectionKind.SomeModule)) {
184210
return containerPage(context, reflection);
185211
}

‎packages/typedoc/src/utils/manifest.mjs‎

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,39 @@ const inputPath = (basePath, url) =>
1414

1515
/**
1616
* Type names mapped to the pages documenting them, for doc-kit's `typeMap`
17-
* to link `{Type}` annotations with.
17+
* to link `{Type}` annotations with: their own, or the page of the member
18+
* documenting them.
1819
*
20+
* @param {import('./router.mjs').DocKitRouter} router
1921
* @param {Array<import('typedoc').PageDefinition>} pages
2022
* @param {string} basePath
2123
*/
22-
export const typeMap = (pages, basePath) =>
24+
export const typeMap = (router, pages, basePath) =>
2325
Object.fromEntries(
2426
pages
2527
.filter(({ model }) => model.kindOf(ReflectionKind.TypeReferenceTarget))
26-
.map(({ model, url }) => [model.name, inputPath(basePath, url)])
28+
.map(({ model }) => {
29+
const home = router.inlined.get(model) ?? model;
30+
31+
return [model.name, inputPath(basePath, router.getFullUrl(home))];
32+
})
2733
);
2834

2935
/**
30-
* Every page, for the site to build its navigation from.
36+
* Every page, for the site to build its navigation from, but those of the
37+
* types documented on a member's page, which link there.
3138
*
39+
* @param {import('./router.mjs').DocKitRouter} router
3240
* @param {Array<import('typedoc').PageDefinition>} pages
3341
* @param {string} basePath
3442
* @returns {import('../types').PageEntry[]}
3543
*/
36-
export const pageList = (pages, basePath) =>
37-
pages.map(({ model, url }) => ({
38-
name: model.getFriendlyFullName(),
39-
kind: ReflectionKind[model.kind],
40-
url: `/${inputPath(basePath, url)}`,
41-
category: categoryOf(model),
42-
}));
44+
export const pageList = (router, pages, basePath) =>
45+
pages
46+
.filter(({ model }) => !router.inlined.has(model))
47+
.map(({ model, url }) => ({
48+
name: model.getFriendlyFullName(),
49+
kind: ReflectionKind[model.kind],
50+
url: `/${inputPath(basePath, url)}`,
51+
category: categoryOf(model),
52+
}));

‎packages/typedoc/src/utils/reflections.mjs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ export const nestedObject = type => {
6060
return objects.length === 1 ? objects[0] : undefined;
6161
};
6262

63+
/**
64+
* The declaration of the project a type refers to, alone or as the one
65+
* reference of a union (`boolean | TreeshakeOptions`).
66+
*
67+
* @param {import('typedoc').SomeType | undefined} type
68+
*/
69+
export const referencedType = type => {
70+
const types = type?.type === 'union' ? type.types : [type];
71+
72+
const declarations = types
73+
.map(part => referencedDeclaration(unwrap(part)))
74+
.filter(Boolean);
75+
76+
return declarations.length === 1 ? declarations[0] : undefined;
77+
};
78+
6379
/**
6480
* The call signatures of a function, method, callable interface, or a
6581
* declaration whose type is one of them, named or not: a variable typed with

0 commit comments

Comments
 (0)