@fgv/typedoc-compact-theme 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/chunked-rush-logs/typedoc-compact-theme.build.chunks.jsonl +1 -0
- package/.rush/temp/operation/build/all.log +1 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +28 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1083 -0
- package/lib/index.js.map +1 -0
- package/package.json +29 -0
- package/rush-logs/typedoc-compact-theme.build.cache.log +1 -0
- package/rush-logs/typedoc-compact-theme.build.log +1 -0
- package/src/index.ts +1327 -0
- package/tsconfig.json +18 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,1327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeDoc Compact Theme
|
|
3
|
+
*
|
|
4
|
+
* Renders API documentation in a compact HTML table format similar to api-documenter.
|
|
5
|
+
* Uses TypeDoc's project model directly, bypassing the theme partial system for
|
|
6
|
+
* complete control over output format.
|
|
7
|
+
*
|
|
8
|
+
* Properties table: Property | Modifiers | Type | Description (includes accessors)
|
|
9
|
+
* Methods table: Method | Modifiers | Description
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import {
|
|
15
|
+
Application,
|
|
16
|
+
Context,
|
|
17
|
+
Converter,
|
|
18
|
+
DeclarationReflection,
|
|
19
|
+
ParameterType,
|
|
20
|
+
ProjectReflection,
|
|
21
|
+
Reflection,
|
|
22
|
+
ReflectionKind,
|
|
23
|
+
SignatureReflection,
|
|
24
|
+
CommentDisplayPart
|
|
25
|
+
} from 'typedoc';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Options for the compact markdown renderer.
|
|
29
|
+
*/
|
|
30
|
+
interface ICompactMarkdownOptions {
|
|
31
|
+
/**
|
|
32
|
+
* If true, generate separate detail pages for inherited members.
|
|
33
|
+
* If false (default), inherited members link to the defining class's page.
|
|
34
|
+
*/
|
|
35
|
+
includeInheritedMemberPages: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Represents a member for rendering.
|
|
40
|
+
*/
|
|
41
|
+
interface IMemberInfo {
|
|
42
|
+
name: string;
|
|
43
|
+
kind: ReflectionKind;
|
|
44
|
+
modifiers: string[];
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
signature?: string;
|
|
48
|
+
anchor: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* URL info for a documented type.
|
|
53
|
+
*/
|
|
54
|
+
interface IUrlInfo {
|
|
55
|
+
url: string;
|
|
56
|
+
kind: ReflectionKind;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Renders a class or interface to markdown.
|
|
61
|
+
*/
|
|
62
|
+
class CompactMarkdownRenderer {
|
|
63
|
+
private _outputDir: string;
|
|
64
|
+
private _options: ICompactMarkdownOptions;
|
|
65
|
+
private _urlMap: Map<string, IUrlInfo> = new Map();
|
|
66
|
+
private _currentFilePath: string = '';
|
|
67
|
+
|
|
68
|
+
constructor(outputDir: string, options: ICompactMarkdownOptions) {
|
|
69
|
+
this._outputDir = outputDir;
|
|
70
|
+
this._options = options;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Render the entire project.
|
|
75
|
+
*/
|
|
76
|
+
public renderProject(project: ProjectReflection): void {
|
|
77
|
+
// Clear and recreate output directory to remove stale files
|
|
78
|
+
if (fs.existsSync(this._outputDir)) {
|
|
79
|
+
fs.rmSync(this._outputDir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
fs.mkdirSync(this._outputDir, { recursive: true });
|
|
82
|
+
|
|
83
|
+
// First pass: build URL map for all types
|
|
84
|
+
this._buildUrlMap(project, '');
|
|
85
|
+
|
|
86
|
+
// Render index
|
|
87
|
+
this._renderIndex(project);
|
|
88
|
+
|
|
89
|
+
// Recursively render all declarations
|
|
90
|
+
this._renderChildren(project, '');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build URL map for all documented types.
|
|
95
|
+
*/
|
|
96
|
+
private _buildUrlMap(parent: Reflection, basePath: string): void {
|
|
97
|
+
if (!(parent instanceof DeclarationReflection) && !(parent instanceof ProjectReflection)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const children = parent.children ?? [];
|
|
102
|
+
|
|
103
|
+
for (const child of children) {
|
|
104
|
+
const name = child.name;
|
|
105
|
+
let subdir = '';
|
|
106
|
+
let shouldIndex = true;
|
|
107
|
+
|
|
108
|
+
if (child.kind === ReflectionKind.Class) {
|
|
109
|
+
subdir = 'classes';
|
|
110
|
+
} else if (child.kind === ReflectionKind.Interface) {
|
|
111
|
+
subdir = 'interfaces';
|
|
112
|
+
} else if (child.kind === ReflectionKind.Enum) {
|
|
113
|
+
subdir = 'enums';
|
|
114
|
+
} else if (child.kind === ReflectionKind.TypeAlias) {
|
|
115
|
+
subdir = 'type-aliases';
|
|
116
|
+
} else if (child.kind === ReflectionKind.Function) {
|
|
117
|
+
subdir = 'functions';
|
|
118
|
+
} else if (child.kind === ReflectionKind.Variable) {
|
|
119
|
+
subdir = 'variables';
|
|
120
|
+
} else if (child.kind === ReflectionKind.Namespace || child.kind === ReflectionKind.Module) {
|
|
121
|
+
// Recurse into namespaces
|
|
122
|
+
const nsPath = path.join(basePath, this._sanitizeName(child.name));
|
|
123
|
+
this._buildUrlMap(child, nsPath);
|
|
124
|
+
shouldIndex = false;
|
|
125
|
+
} else {
|
|
126
|
+
shouldIndex = false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (shouldIndex && subdir) {
|
|
130
|
+
const url = path.join(basePath, subdir, `${this._sanitizeName(name)}.md`);
|
|
131
|
+
this._urlMap.set(name, { url, kind: child.kind });
|
|
132
|
+
|
|
133
|
+
// Also index with full path for namespaced types
|
|
134
|
+
if (basePath) {
|
|
135
|
+
const fullName = basePath.replace(/\//g, '.') + '.' + name;
|
|
136
|
+
this._urlMap.set(fullName, { url, kind: child.kind });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Recurse into children for nested types
|
|
141
|
+
this._buildUrlMap(child, basePath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private _renderChildren(parent: Reflection, basePath: string): void {
|
|
146
|
+
if (!(parent instanceof DeclarationReflection) && !(parent instanceof ProjectReflection)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const children = parent.children ?? [];
|
|
151
|
+
|
|
152
|
+
for (const child of children) {
|
|
153
|
+
if (child.kind === ReflectionKind.Class || child.kind === ReflectionKind.Interface) {
|
|
154
|
+
this._renderClassOrInterface(child, basePath);
|
|
155
|
+
} else if (child.kind === ReflectionKind.Namespace || child.kind === ReflectionKind.Module) {
|
|
156
|
+
// Create subdirectory for namespace and render its index
|
|
157
|
+
const nsPath = path.join(basePath, this._sanitizeName(child.name));
|
|
158
|
+
this._renderNamespaceIndex(child, nsPath);
|
|
159
|
+
this._renderChildren(child, nsPath);
|
|
160
|
+
} else if (child.kind === ReflectionKind.Enum) {
|
|
161
|
+
this._renderEnum(child, basePath);
|
|
162
|
+
} else if (child.kind === ReflectionKind.TypeAlias) {
|
|
163
|
+
this._renderTypeAlias(child, basePath);
|
|
164
|
+
} else if (child.kind === ReflectionKind.Function) {
|
|
165
|
+
this._renderFunction(child, basePath);
|
|
166
|
+
} else if (child.kind === ReflectionKind.Variable) {
|
|
167
|
+
this._renderVariable(child, basePath);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Recurse into children
|
|
171
|
+
this._renderChildren(child, basePath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private _renderIndex(project: ProjectReflection): void {
|
|
176
|
+
this._setCurrentFile('README.md');
|
|
177
|
+
|
|
178
|
+
const lines: string[] = [];
|
|
179
|
+
lines.push(`# ${project.name}`);
|
|
180
|
+
lines.push('');
|
|
181
|
+
|
|
182
|
+
if (project.comment?.summary) {
|
|
183
|
+
lines.push(this._getCommentText(project.comment.summary));
|
|
184
|
+
lines.push('');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Find namespaces/modules first
|
|
188
|
+
const namespaces = this._findByKind(project, ReflectionKind.Namespace);
|
|
189
|
+
const modules = this._findByKind(project, ReflectionKind.Module);
|
|
190
|
+
const allNamespaces = [...namespaces, ...modules];
|
|
191
|
+
|
|
192
|
+
if (allNamespaces.length > 0) {
|
|
193
|
+
lines.push('## Namespaces');
|
|
194
|
+
lines.push('');
|
|
195
|
+
lines.push(this._renderIndexTable(allNamespaces, (n) => `./${this._sanitizeName(n.name)}/README.md`));
|
|
196
|
+
lines.push('');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// List all top-level exports
|
|
200
|
+
const classes = this._findByKind(project, ReflectionKind.Class);
|
|
201
|
+
const interfaces = this._findByKind(project, ReflectionKind.Interface);
|
|
202
|
+
const enums = this._findByKind(project, ReflectionKind.Enum);
|
|
203
|
+
const typeAliases = this._findByKind(project, ReflectionKind.TypeAlias);
|
|
204
|
+
const functions = this._findByKind(project, ReflectionKind.Function);
|
|
205
|
+
const variables = this._findByKind(project, ReflectionKind.Variable);
|
|
206
|
+
|
|
207
|
+
if (classes.length > 0) {
|
|
208
|
+
lines.push('## Classes');
|
|
209
|
+
lines.push('');
|
|
210
|
+
lines.push(this._renderIndexTable(classes, (c) => `./classes/${this._sanitizeName(c.name)}.md`));
|
|
211
|
+
lines.push('');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (interfaces.length > 0) {
|
|
215
|
+
lines.push('## Interfaces');
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push(this._renderIndexTable(interfaces, (i) => `./interfaces/${this._sanitizeName(i.name)}.md`));
|
|
218
|
+
lines.push('');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (enums.length > 0) {
|
|
222
|
+
lines.push('## Enums');
|
|
223
|
+
lines.push('');
|
|
224
|
+
lines.push(this._renderIndexTable(enums, (e) => `./enums/${this._sanitizeName(e.name)}.md`));
|
|
225
|
+
lines.push('');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (typeAliases.length > 0) {
|
|
229
|
+
lines.push('## Type Aliases');
|
|
230
|
+
lines.push('');
|
|
231
|
+
lines.push(
|
|
232
|
+
this._renderIndexTable(typeAliases, (t) => `./type-aliases/${this._sanitizeName(t.name)}.md`)
|
|
233
|
+
);
|
|
234
|
+
lines.push('');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (functions.length > 0) {
|
|
238
|
+
lines.push('## Functions');
|
|
239
|
+
lines.push('');
|
|
240
|
+
lines.push(this._renderIndexTable(functions, (f) => `./functions/${this._sanitizeName(f.name)}.md`));
|
|
241
|
+
lines.push('');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (variables.length > 0) {
|
|
245
|
+
lines.push('## Variables');
|
|
246
|
+
lines.push('');
|
|
247
|
+
lines.push(this._renderIndexTable(variables, (v) => `./variables/${this._sanitizeName(v.name)}.md`));
|
|
248
|
+
lines.push('');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const indexPath = path.join(this._outputDir, 'README.md');
|
|
252
|
+
fs.writeFileSync(indexPath, lines.join('\n'));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Render an index table for a list of declarations.
|
|
257
|
+
*/
|
|
258
|
+
private _renderIndexTable(
|
|
259
|
+
items: DeclarationReflection[],
|
|
260
|
+
urlFn: (item: DeclarationReflection) => string
|
|
261
|
+
): string {
|
|
262
|
+
const lines: string[] = [];
|
|
263
|
+
|
|
264
|
+
lines.push('<table><thead><tr><th>');
|
|
265
|
+
lines.push('');
|
|
266
|
+
lines.push('Name');
|
|
267
|
+
lines.push('');
|
|
268
|
+
lines.push('</th><th>');
|
|
269
|
+
lines.push('');
|
|
270
|
+
lines.push('Description');
|
|
271
|
+
lines.push('');
|
|
272
|
+
lines.push('</th></tr></thead>');
|
|
273
|
+
lines.push('<tbody>');
|
|
274
|
+
|
|
275
|
+
for (const item of items) {
|
|
276
|
+
const url = urlFn(item);
|
|
277
|
+
const desc = this._getItemDescription(item);
|
|
278
|
+
|
|
279
|
+
lines.push('<tr><td>');
|
|
280
|
+
lines.push('');
|
|
281
|
+
lines.push(`[${item.name}](${url})`);
|
|
282
|
+
lines.push('');
|
|
283
|
+
lines.push('</td><td>');
|
|
284
|
+
lines.push('');
|
|
285
|
+
lines.push(desc);
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push('</td></tr>');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
lines.push('</tbody></table>');
|
|
291
|
+
return lines.join('\n');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get description for an item (first line of comment or signature comment).
|
|
296
|
+
*/
|
|
297
|
+
private _getItemDescription(item: DeclarationReflection): string {
|
|
298
|
+
// Try item's own comment
|
|
299
|
+
if (item.comment?.summary) {
|
|
300
|
+
return this._getFirstLine(this._getCommentText(item.comment.summary));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// For functions, try signature comment
|
|
304
|
+
const sig = item.signatures?.[0];
|
|
305
|
+
if (sig?.comment?.summary) {
|
|
306
|
+
return this._getFirstLine(this._getCommentText(sig.comment.summary));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// For accessors, try getter/setter comment
|
|
310
|
+
if (item.getSignature?.comment?.summary) {
|
|
311
|
+
return this._getFirstLine(this._getCommentText(item.getSignature.comment.summary));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return '';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get the first line/sentence of a description.
|
|
319
|
+
*/
|
|
320
|
+
private _getFirstLine(text: string): string {
|
|
321
|
+
// Split on period followed by space or newline
|
|
322
|
+
const firstSentence = text.split(/\.\s|\.\n/)[0];
|
|
323
|
+
if (firstSentence && firstSentence !== text) {
|
|
324
|
+
return firstSentence + '.';
|
|
325
|
+
}
|
|
326
|
+
// If no period, take first line
|
|
327
|
+
const firstLine = text.split('\n')[0];
|
|
328
|
+
return firstLine.trim();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Render an index page for a namespace/module.
|
|
333
|
+
*/
|
|
334
|
+
private _renderNamespaceIndex(ns: DeclarationReflection, nsPath: string): void {
|
|
335
|
+
const filePath = path.join(nsPath, 'README.md');
|
|
336
|
+
this._setCurrentFile(filePath);
|
|
337
|
+
|
|
338
|
+
const lines: string[] = [];
|
|
339
|
+
const kindName = ns.kind === ReflectionKind.Namespace ? 'Namespace' : 'Module';
|
|
340
|
+
|
|
341
|
+
// Breadcrumb - nsPath includes this namespace, file is at nsPath/README.md
|
|
342
|
+
// We need to go up one level more than pathParts length to reach root
|
|
343
|
+
const pathParts = nsPath.split('/').filter((p) => p);
|
|
344
|
+
const depth = pathParts.length;
|
|
345
|
+
const homePrefix = '../'.repeat(depth);
|
|
346
|
+
|
|
347
|
+
const breadcrumbParts: string[] = [`[Home](${homePrefix}README.md)`];
|
|
348
|
+
|
|
349
|
+
// Add parent namespaces (all but the last one)
|
|
350
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
351
|
+
const levelsUp = depth - (i + 1);
|
|
352
|
+
const nsPrefix = '../'.repeat(levelsUp);
|
|
353
|
+
breadcrumbParts.push(`[${pathParts[i]}](${nsPrefix}README.md)`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Current namespace (not linked)
|
|
357
|
+
breadcrumbParts.push(ns.name);
|
|
358
|
+
lines.push(breadcrumbParts.join(' > '));
|
|
359
|
+
lines.push('');
|
|
360
|
+
|
|
361
|
+
lines.push(`# ${kindName}: ${ns.name}`);
|
|
362
|
+
lines.push('');
|
|
363
|
+
|
|
364
|
+
if (ns.comment?.summary) {
|
|
365
|
+
lines.push(this._getCommentText(ns.comment.summary));
|
|
366
|
+
lines.push('');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Collect children by kind
|
|
370
|
+
const children = ns.children ?? [];
|
|
371
|
+
const childNamespaces = children.filter(
|
|
372
|
+
(c) => c.kind === ReflectionKind.Namespace || c.kind === ReflectionKind.Module
|
|
373
|
+
);
|
|
374
|
+
const classes = children.filter((c) => c.kind === ReflectionKind.Class);
|
|
375
|
+
const interfaces = children.filter((c) => c.kind === ReflectionKind.Interface);
|
|
376
|
+
const enums = children.filter((c) => c.kind === ReflectionKind.Enum);
|
|
377
|
+
const typeAliases = children.filter((c) => c.kind === ReflectionKind.TypeAlias);
|
|
378
|
+
const functions = children.filter((c) => c.kind === ReflectionKind.Function);
|
|
379
|
+
const variables = children.filter((c) => c.kind === ReflectionKind.Variable);
|
|
380
|
+
|
|
381
|
+
if (childNamespaces.length > 0) {
|
|
382
|
+
lines.push('## Namespaces');
|
|
383
|
+
lines.push('');
|
|
384
|
+
lines.push(this._renderIndexTable(childNamespaces, (n) => `./${this._sanitizeName(n.name)}/README.md`));
|
|
385
|
+
lines.push('');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (classes.length > 0) {
|
|
389
|
+
lines.push('## Classes');
|
|
390
|
+
lines.push('');
|
|
391
|
+
lines.push(this._renderIndexTable(classes, (c) => `./classes/${this._sanitizeName(c.name)}.md`));
|
|
392
|
+
lines.push('');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (interfaces.length > 0) {
|
|
396
|
+
lines.push('## Interfaces');
|
|
397
|
+
lines.push('');
|
|
398
|
+
lines.push(this._renderIndexTable(interfaces, (i) => `./interfaces/${this._sanitizeName(i.name)}.md`));
|
|
399
|
+
lines.push('');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (enums.length > 0) {
|
|
403
|
+
lines.push('## Enums');
|
|
404
|
+
lines.push('');
|
|
405
|
+
lines.push(this._renderIndexTable(enums, (e) => `./enums/${this._sanitizeName(e.name)}.md`));
|
|
406
|
+
lines.push('');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (typeAliases.length > 0) {
|
|
410
|
+
lines.push('## Type Aliases');
|
|
411
|
+
lines.push('');
|
|
412
|
+
lines.push(
|
|
413
|
+
this._renderIndexTable(typeAliases, (t) => `./type-aliases/${this._sanitizeName(t.name)}.md`)
|
|
414
|
+
);
|
|
415
|
+
lines.push('');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (functions.length > 0) {
|
|
419
|
+
lines.push('## Functions');
|
|
420
|
+
lines.push('');
|
|
421
|
+
lines.push(this._renderIndexTable(functions, (f) => `./functions/${this._sanitizeName(f.name)}.md`));
|
|
422
|
+
lines.push('');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (variables.length > 0) {
|
|
426
|
+
lines.push('## Variables');
|
|
427
|
+
lines.push('');
|
|
428
|
+
lines.push(this._renderIndexTable(variables, (v) => `./variables/${this._sanitizeName(v.name)}.md`));
|
|
429
|
+
lines.push('');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Ensure directory exists
|
|
433
|
+
const outDir = path.join(this._outputDir, nsPath);
|
|
434
|
+
if (!fs.existsSync(outDir)) {
|
|
435
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const outPath = path.join(this._outputDir, filePath);
|
|
439
|
+
fs.writeFileSync(outPath, lines.join('\n'));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private _renderClassOrInterface(decl: DeclarationReflection, basePath: string): void {
|
|
443
|
+
const lines: string[] = [];
|
|
444
|
+
const kindName = decl.kind === ReflectionKind.Class ? 'Class' : 'Interface';
|
|
445
|
+
|
|
446
|
+
// Set current file for relative URL computation
|
|
447
|
+
const subdir = decl.kind === ReflectionKind.Class ? 'classes' : 'interfaces';
|
|
448
|
+
const filePath = path.join(basePath, subdir, `${this._sanitizeName(decl.name)}.md`);
|
|
449
|
+
this._setCurrentFile(filePath);
|
|
450
|
+
|
|
451
|
+
// Breadcrumb
|
|
452
|
+
lines.push(this._generateBreadcrumb(basePath, decl.name, subdir));
|
|
453
|
+
lines.push('');
|
|
454
|
+
|
|
455
|
+
// Header
|
|
456
|
+
lines.push(`# ${kindName}: ${decl.name}`);
|
|
457
|
+
lines.push('');
|
|
458
|
+
|
|
459
|
+
// Description
|
|
460
|
+
if (decl.comment?.summary) {
|
|
461
|
+
lines.push(this._getCommentText(decl.comment.summary));
|
|
462
|
+
lines.push('');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Extends and Implements on single lines with links
|
|
466
|
+
if (decl.extendedTypes && decl.extendedTypes.length > 0) {
|
|
467
|
+
const extList = decl.extendedTypes.map((t) => this._linkTypeInline(t.toString())).join(', ');
|
|
468
|
+
lines.push(`**Extends:** ${extList}`);
|
|
469
|
+
lines.push('');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (decl.implementedTypes && decl.implementedTypes.length > 0) {
|
|
473
|
+
const implList = decl.implementedTypes.map((t) => this._linkTypeInline(t.toString())).join(', ');
|
|
474
|
+
lines.push(`**Implements:** ${implList}`);
|
|
475
|
+
lines.push('');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Collect members by category
|
|
479
|
+
const children = decl.children ?? [];
|
|
480
|
+
const constructors = children.filter((c) => c.kind === ReflectionKind.Constructor);
|
|
481
|
+
const properties = children.filter((c) => c.kind === ReflectionKind.Property);
|
|
482
|
+
const accessors = children.filter((c) => c.kind === ReflectionKind.Accessor);
|
|
483
|
+
const methods = children.filter((c) => c.kind === ReflectionKind.Method);
|
|
484
|
+
|
|
485
|
+
// Member detail pages base path
|
|
486
|
+
const memberBasePath = path.join(basePath, subdir);
|
|
487
|
+
|
|
488
|
+
// Render constructors
|
|
489
|
+
if (constructors.length > 0) {
|
|
490
|
+
lines.push('## Constructors');
|
|
491
|
+
lines.push('');
|
|
492
|
+
lines.push(this._renderConstructorsTable(constructors, decl.name, memberBasePath));
|
|
493
|
+
lines.push('');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Render properties + accessors combined
|
|
497
|
+
const allProperties = [...properties, ...accessors];
|
|
498
|
+
if (allProperties.length > 0) {
|
|
499
|
+
lines.push('## Properties');
|
|
500
|
+
lines.push('');
|
|
501
|
+
lines.push(this._renderPropertiesTable(allProperties, decl.name, memberBasePath));
|
|
502
|
+
lines.push('');
|
|
503
|
+
|
|
504
|
+
// Render property detail pages (optionally skip inherited members)
|
|
505
|
+
for (const prop of allProperties) {
|
|
506
|
+
if (this._options.includeInheritedMemberPages || !prop.inheritedFrom) {
|
|
507
|
+
this._renderPropertyDetailPage(prop, decl, memberBasePath);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Render methods
|
|
513
|
+
if (methods.length > 0) {
|
|
514
|
+
lines.push('## Methods');
|
|
515
|
+
lines.push('');
|
|
516
|
+
lines.push(this._renderMethodsTable(methods, decl.name, memberBasePath));
|
|
517
|
+
lines.push('');
|
|
518
|
+
|
|
519
|
+
// Render method detail pages (optionally skip inherited members)
|
|
520
|
+
for (const method of methods) {
|
|
521
|
+
if (this._options.includeInheritedMemberPages || !method.inheritedFrom) {
|
|
522
|
+
this._renderMethodDetailPage(method, decl, memberBasePath);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Write file
|
|
528
|
+
const outDir = path.join(this._outputDir, basePath, subdir);
|
|
529
|
+
if (!fs.existsSync(outDir)) {
|
|
530
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
531
|
+
}
|
|
532
|
+
const outPath = path.join(outDir, `${this._sanitizeName(decl.name)}.md`);
|
|
533
|
+
fs.writeFileSync(outPath, lines.join('\n'));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Render a detail page for a property or accessor.
|
|
538
|
+
*/
|
|
539
|
+
private _renderPropertyDetailPage(
|
|
540
|
+
prop: DeclarationReflection,
|
|
541
|
+
parent: DeclarationReflection,
|
|
542
|
+
basePath: string
|
|
543
|
+
): void {
|
|
544
|
+
const fileName = `${this._sanitizeName(parent.name)}.${this._sanitizeName(prop.name)}.md`;
|
|
545
|
+
const filePath = path.join(basePath, fileName);
|
|
546
|
+
this._setCurrentFile(filePath);
|
|
547
|
+
|
|
548
|
+
const lines: string[] = [];
|
|
549
|
+
const kindLabel = prop.kind === ReflectionKind.Accessor ? 'property' : 'property';
|
|
550
|
+
|
|
551
|
+
// Breadcrumb: extract namespace path from basePath (which is like "Namespace/classes")
|
|
552
|
+
const pathParts = basePath.split('/').filter((p) => p);
|
|
553
|
+
const subdir = pathParts.pop(); // Remove "classes" or "interfaces"
|
|
554
|
+
const namespacePath = pathParts.join('/');
|
|
555
|
+
|
|
556
|
+
// Generate breadcrumb with linked parent class
|
|
557
|
+
const breadcrumbParts: string[] = [];
|
|
558
|
+
const depth = pathParts.length + 1; // +1 for subdir
|
|
559
|
+
|
|
560
|
+
breadcrumbParts.push(`[Home](${'../'.repeat(depth)}README.md)`);
|
|
561
|
+
|
|
562
|
+
// Add namespace parts
|
|
563
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
564
|
+
const levelsUp = depth - (i + 1);
|
|
565
|
+
breadcrumbParts.push(`[${pathParts[i]}](${'../'.repeat(levelsUp)}README.md)`);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Add linked parent class
|
|
569
|
+
breadcrumbParts.push(`[${parent.name}](./${this._sanitizeName(parent.name)}.md)`);
|
|
570
|
+
|
|
571
|
+
// Add current property (unlinked)
|
|
572
|
+
breadcrumbParts.push(prop.name);
|
|
573
|
+
|
|
574
|
+
lines.push(breadcrumbParts.join(' > '));
|
|
575
|
+
lines.push('');
|
|
576
|
+
|
|
577
|
+
// Header
|
|
578
|
+
lines.push(`## ${parent.name}.${prop.name} ${kindLabel}`);
|
|
579
|
+
lines.push('');
|
|
580
|
+
|
|
581
|
+
// Description
|
|
582
|
+
let description = '';
|
|
583
|
+
if (prop.kind === ReflectionKind.Accessor) {
|
|
584
|
+
const getter = prop.getSignature;
|
|
585
|
+
const setter = prop.setSignature;
|
|
586
|
+
if (getter?.comment?.summary) {
|
|
587
|
+
description = this._getCommentText(getter.comment.summary);
|
|
588
|
+
} else if (setter?.comment?.summary) {
|
|
589
|
+
description = this._getCommentText(setter.comment.summary);
|
|
590
|
+
}
|
|
591
|
+
} else if (prop.comment?.summary) {
|
|
592
|
+
description = this._getCommentText(prop.comment.summary);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (description) {
|
|
596
|
+
lines.push(description);
|
|
597
|
+
lines.push('');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Signature
|
|
601
|
+
lines.push('**Signature:**');
|
|
602
|
+
lines.push('');
|
|
603
|
+
lines.push('```typescript');
|
|
604
|
+
lines.push(this._getPropertySignature(prop));
|
|
605
|
+
lines.push('```');
|
|
606
|
+
lines.push('');
|
|
607
|
+
|
|
608
|
+
// Write file
|
|
609
|
+
const outDir = path.join(this._outputDir, basePath);
|
|
610
|
+
if (!fs.existsSync(outDir)) {
|
|
611
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
612
|
+
}
|
|
613
|
+
const outPath = path.join(this._outputDir, filePath);
|
|
614
|
+
fs.writeFileSync(outPath, lines.join('\n'));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Render a detail page for a method.
|
|
619
|
+
*/
|
|
620
|
+
private _renderMethodDetailPage(
|
|
621
|
+
method: DeclarationReflection,
|
|
622
|
+
parent: DeclarationReflection,
|
|
623
|
+
basePath: string
|
|
624
|
+
): void {
|
|
625
|
+
const fileName = `${this._sanitizeName(parent.name)}.${this._sanitizeName(method.name)}.md`;
|
|
626
|
+
const filePath = path.join(basePath, fileName);
|
|
627
|
+
this._setCurrentFile(filePath);
|
|
628
|
+
|
|
629
|
+
const lines: string[] = [];
|
|
630
|
+
|
|
631
|
+
// Breadcrumb: extract namespace path from basePath (which is like "Namespace/classes")
|
|
632
|
+
const pathParts = basePath.split('/').filter((p) => p);
|
|
633
|
+
pathParts.pop(); // Remove "classes" or "interfaces"
|
|
634
|
+
|
|
635
|
+
// Generate breadcrumb with linked parent class
|
|
636
|
+
const breadcrumbParts: string[] = [];
|
|
637
|
+
const depth = pathParts.length + 1; // +1 for subdir
|
|
638
|
+
|
|
639
|
+
breadcrumbParts.push(`[Home](${'../'.repeat(depth)}README.md)`);
|
|
640
|
+
|
|
641
|
+
// Add namespace parts
|
|
642
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
643
|
+
const levelsUp = depth - (i + 1);
|
|
644
|
+
breadcrumbParts.push(`[${pathParts[i]}](${'../'.repeat(levelsUp)}README.md)`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Add linked parent class
|
|
648
|
+
breadcrumbParts.push(`[${parent.name}](./${this._sanitizeName(parent.name)}.md)`);
|
|
649
|
+
|
|
650
|
+
// Add current method (unlinked)
|
|
651
|
+
breadcrumbParts.push(method.name);
|
|
652
|
+
|
|
653
|
+
lines.push(breadcrumbParts.join(' > '));
|
|
654
|
+
lines.push('');
|
|
655
|
+
|
|
656
|
+
// Header
|
|
657
|
+
lines.push(`## ${parent.name}.${method.name}() method`);
|
|
658
|
+
lines.push('');
|
|
659
|
+
|
|
660
|
+
// Description from signature
|
|
661
|
+
const sig = method.signatures?.[0];
|
|
662
|
+
if (sig?.comment?.summary) {
|
|
663
|
+
lines.push(this._getCommentText(sig.comment.summary));
|
|
664
|
+
lines.push('');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Signature
|
|
668
|
+
lines.push('**Signature:**');
|
|
669
|
+
lines.push('');
|
|
670
|
+
lines.push('```typescript');
|
|
671
|
+
lines.push(this._getMethodSignature(method));
|
|
672
|
+
lines.push('```');
|
|
673
|
+
lines.push('');
|
|
674
|
+
|
|
675
|
+
// Parameters
|
|
676
|
+
if (sig?.parameters && sig.parameters.length > 0) {
|
|
677
|
+
lines.push('**Parameters:**');
|
|
678
|
+
lines.push('');
|
|
679
|
+
lines.push('<table><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead>');
|
|
680
|
+
lines.push('<tbody>');
|
|
681
|
+
for (const param of sig.parameters) {
|
|
682
|
+
const paramType = param.type?.toString() ?? 'unknown';
|
|
683
|
+
const paramDesc = param.comment?.summary ? this._getCommentText(param.comment.summary) : '';
|
|
684
|
+
lines.push(
|
|
685
|
+
`<tr><td>${param.name}</td><td>${this._escapeHtml(paramType)}</td><td>${paramDesc}</td></tr>`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
lines.push('</tbody></table>');
|
|
689
|
+
lines.push('');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Returns
|
|
693
|
+
if (sig?.type) {
|
|
694
|
+
lines.push('**Returns:**');
|
|
695
|
+
lines.push('');
|
|
696
|
+
lines.push(`${this._linkType(sig.type.toString())}`);
|
|
697
|
+
if (sig.comment?.blockTags) {
|
|
698
|
+
const returnsTag = sig.comment.blockTags.find((t) => t.tag === '@returns');
|
|
699
|
+
if (returnsTag?.content) {
|
|
700
|
+
lines.push('');
|
|
701
|
+
lines.push(this._getCommentText(returnsTag.content));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
lines.push('');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Write file
|
|
708
|
+
const outDir = path.join(this._outputDir, basePath);
|
|
709
|
+
if (!fs.existsSync(outDir)) {
|
|
710
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
711
|
+
}
|
|
712
|
+
const outPath = path.join(this._outputDir, filePath);
|
|
713
|
+
fs.writeFileSync(outPath, lines.join('\n'));
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Get signature string for a property.
|
|
718
|
+
*/
|
|
719
|
+
private _getPropertySignature(prop: DeclarationReflection): string {
|
|
720
|
+
const modifiers: string[] = [];
|
|
721
|
+
if (prop.flags?.isReadonly) modifiers.push('readonly');
|
|
722
|
+
if (prop.flags?.isStatic) modifiers.push('static');
|
|
723
|
+
|
|
724
|
+
let typeStr = '';
|
|
725
|
+
if (prop.kind === ReflectionKind.Accessor) {
|
|
726
|
+
const getter = prop.getSignature;
|
|
727
|
+
const setter = prop.setSignature;
|
|
728
|
+
if (getter?.type) {
|
|
729
|
+
typeStr = getter.type.toString();
|
|
730
|
+
} else if (setter?.parameters?.[0]?.type) {
|
|
731
|
+
typeStr = setter.parameters[0].type.toString();
|
|
732
|
+
}
|
|
733
|
+
if (getter && !setter) modifiers.push('readonly');
|
|
734
|
+
} else {
|
|
735
|
+
typeStr = prop.type?.toString() ?? 'unknown';
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const modStr = modifiers.length > 0 ? modifiers.join(' ') + ' ' : '';
|
|
739
|
+
return `${modStr}${prop.name}: ${typeStr};`;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Get signature string for a method.
|
|
744
|
+
*/
|
|
745
|
+
private _getMethodSignature(method: DeclarationReflection): string {
|
|
746
|
+
const sig = method.signatures?.[0];
|
|
747
|
+
if (!sig) return `${method.name}(): unknown;`;
|
|
748
|
+
|
|
749
|
+
const modifiers: string[] = [];
|
|
750
|
+
if (method.flags?.isStatic) modifiers.push('static');
|
|
751
|
+
|
|
752
|
+
const params =
|
|
753
|
+
sig.parameters
|
|
754
|
+
?.map((p) => {
|
|
755
|
+
const optional = p.flags?.isOptional ? '?' : '';
|
|
756
|
+
return `${p.name}${optional}: ${p.type?.toString() ?? 'unknown'}`;
|
|
757
|
+
})
|
|
758
|
+
.join(', ') ?? '';
|
|
759
|
+
|
|
760
|
+
const returnType = sig.type?.toString() ?? 'void';
|
|
761
|
+
const modStr = modifiers.length > 0 ? modifiers.join(' ') + ' ' : '';
|
|
762
|
+
|
|
763
|
+
return `${modStr}${method.name}(${params}): ${returnType};`;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private _renderEnum(decl: DeclarationReflection, basePath: string): void {
|
|
767
|
+
const lines: string[] = [];
|
|
768
|
+
|
|
769
|
+
// Breadcrumb
|
|
770
|
+
lines.push(this._generateBreadcrumb(basePath, decl.name, 'enums'));
|
|
771
|
+
lines.push('');
|
|
772
|
+
|
|
773
|
+
lines.push(`# Enum: ${decl.name}`);
|
|
774
|
+
lines.push('');
|
|
775
|
+
|
|
776
|
+
if (decl.comment?.summary) {
|
|
777
|
+
lines.push(this._getCommentText(decl.comment.summary));
|
|
778
|
+
lines.push('');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const members = decl.children ?? [];
|
|
782
|
+
if (members.length > 0) {
|
|
783
|
+
lines.push('## Members');
|
|
784
|
+
lines.push('');
|
|
785
|
+
lines.push('<table><thead><tr><th>');
|
|
786
|
+
lines.push('');
|
|
787
|
+
lines.push('Member');
|
|
788
|
+
lines.push('');
|
|
789
|
+
lines.push('</th><th>');
|
|
790
|
+
lines.push('');
|
|
791
|
+
lines.push('Value');
|
|
792
|
+
lines.push('');
|
|
793
|
+
lines.push('</th><th>');
|
|
794
|
+
lines.push('');
|
|
795
|
+
lines.push('Description');
|
|
796
|
+
lines.push('');
|
|
797
|
+
lines.push('</th></tr></thead>');
|
|
798
|
+
lines.push('<tbody>');
|
|
799
|
+
|
|
800
|
+
for (const member of members) {
|
|
801
|
+
const value = member.type?.toString() ?? '';
|
|
802
|
+
const desc = member.comment?.summary ? this._getCommentText(member.comment.summary) : '';
|
|
803
|
+
lines.push('<tr><td>');
|
|
804
|
+
lines.push('');
|
|
805
|
+
lines.push(`\`${member.name}\``);
|
|
806
|
+
lines.push('');
|
|
807
|
+
lines.push('</td><td>');
|
|
808
|
+
lines.push('');
|
|
809
|
+
lines.push(value);
|
|
810
|
+
lines.push('');
|
|
811
|
+
lines.push('</td><td>');
|
|
812
|
+
lines.push('');
|
|
813
|
+
lines.push(desc);
|
|
814
|
+
lines.push('');
|
|
815
|
+
lines.push('</td></tr>');
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
lines.push('</tbody></table>');
|
|
819
|
+
lines.push('');
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const outDir = path.join(this._outputDir, basePath, 'enums');
|
|
823
|
+
if (!fs.existsSync(outDir)) {
|
|
824
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
825
|
+
}
|
|
826
|
+
const outPath = path.join(outDir, `${this._sanitizeName(decl.name)}.md`);
|
|
827
|
+
fs.writeFileSync(outPath, lines.join('\n'));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
private _renderTypeAlias(decl: DeclarationReflection, basePath: string): void {
|
|
831
|
+
const lines: string[] = [];
|
|
832
|
+
|
|
833
|
+
// Breadcrumb
|
|
834
|
+
lines.push(this._generateBreadcrumb(basePath, decl.name, 'type-aliases'));
|
|
835
|
+
lines.push('');
|
|
836
|
+
|
|
837
|
+
lines.push(`# Type Alias: ${decl.name}`);
|
|
838
|
+
lines.push('');
|
|
839
|
+
|
|
840
|
+
if (decl.comment?.summary) {
|
|
841
|
+
lines.push(this._getCommentText(decl.comment.summary));
|
|
842
|
+
lines.push('');
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (decl.type) {
|
|
846
|
+
lines.push('## Type');
|
|
847
|
+
lines.push('');
|
|
848
|
+
lines.push('```typescript');
|
|
849
|
+
lines.push(`type ${decl.name} = ${decl.type.toString()}`);
|
|
850
|
+
lines.push('```');
|
|
851
|
+
lines.push('');
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const outDir = path.join(this._outputDir, basePath, 'type-aliases');
|
|
855
|
+
if (!fs.existsSync(outDir)) {
|
|
856
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
857
|
+
}
|
|
858
|
+
const outPath = path.join(outDir, `${this._sanitizeName(decl.name)}.md`);
|
|
859
|
+
fs.writeFileSync(outPath, lines.join('\n'));
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private _renderFunction(decl: DeclarationReflection, basePath: string): void {
|
|
863
|
+
const lines: string[] = [];
|
|
864
|
+
|
|
865
|
+
// Breadcrumb
|
|
866
|
+
lines.push(this._generateBreadcrumb(basePath, decl.name, 'functions'));
|
|
867
|
+
lines.push('');
|
|
868
|
+
|
|
869
|
+
lines.push(`# Function: ${decl.name}`);
|
|
870
|
+
lines.push('');
|
|
871
|
+
|
|
872
|
+
const sig = decl.signatures?.[0];
|
|
873
|
+
if (sig?.comment?.summary) {
|
|
874
|
+
lines.push(this._getCommentText(sig.comment.summary));
|
|
875
|
+
lines.push('');
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (sig) {
|
|
879
|
+
lines.push('## Signature');
|
|
880
|
+
lines.push('');
|
|
881
|
+
lines.push('```typescript');
|
|
882
|
+
lines.push(this._renderSignature(decl.name, sig));
|
|
883
|
+
lines.push('```');
|
|
884
|
+
lines.push('');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const outDir = path.join(this._outputDir, basePath, 'functions');
|
|
888
|
+
if (!fs.existsSync(outDir)) {
|
|
889
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
890
|
+
}
|
|
891
|
+
const outPath = path.join(outDir, `${this._sanitizeName(decl.name)}.md`);
|
|
892
|
+
fs.writeFileSync(outPath, lines.join('\n'));
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
private _renderVariable(decl: DeclarationReflection, basePath: string): void {
|
|
896
|
+
const lines: string[] = [];
|
|
897
|
+
|
|
898
|
+
// Breadcrumb
|
|
899
|
+
lines.push(this._generateBreadcrumb(basePath, decl.name, 'variables'));
|
|
900
|
+
lines.push('');
|
|
901
|
+
|
|
902
|
+
lines.push(`# Variable: ${decl.name}`);
|
|
903
|
+
lines.push('');
|
|
904
|
+
|
|
905
|
+
if (decl.comment?.summary) {
|
|
906
|
+
lines.push(this._getCommentText(decl.comment.summary));
|
|
907
|
+
lines.push('');
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (decl.type) {
|
|
911
|
+
lines.push('## Type');
|
|
912
|
+
lines.push('');
|
|
913
|
+
lines.push(`\`${decl.type.toString()}\``);
|
|
914
|
+
lines.push('');
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const outDir = path.join(this._outputDir, basePath, 'variables');
|
|
918
|
+
if (!fs.existsSync(outDir)) {
|
|
919
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
920
|
+
}
|
|
921
|
+
const outPath = path.join(outDir, `${this._sanitizeName(decl.name)}.md`);
|
|
922
|
+
fs.writeFileSync(outPath, lines.join('\n'));
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
private _renderConstructorsTable(
|
|
926
|
+
constructors: DeclarationReflection[],
|
|
927
|
+
_parentName: string,
|
|
928
|
+
_basePath: string
|
|
929
|
+
): string {
|
|
930
|
+
const lines: string[] = [];
|
|
931
|
+
|
|
932
|
+
lines.push('<table><thead><tr><th>');
|
|
933
|
+
lines.push('');
|
|
934
|
+
lines.push('Constructor');
|
|
935
|
+
lines.push('');
|
|
936
|
+
lines.push('</th><th>');
|
|
937
|
+
lines.push('');
|
|
938
|
+
lines.push('Modifiers');
|
|
939
|
+
lines.push('');
|
|
940
|
+
lines.push('</th><th>');
|
|
941
|
+
lines.push('');
|
|
942
|
+
lines.push('Description');
|
|
943
|
+
lines.push('');
|
|
944
|
+
lines.push('</th></tr></thead>');
|
|
945
|
+
lines.push('<tbody>');
|
|
946
|
+
|
|
947
|
+
for (const ctor of constructors) {
|
|
948
|
+
const sig = ctor.signatures?.[0];
|
|
949
|
+
const params = sig?.parameters?.map((p) => p.name).join(', ') ?? '';
|
|
950
|
+
const ctorSig = `constructor(${params})`;
|
|
951
|
+
|
|
952
|
+
const modifiers: string[] = [];
|
|
953
|
+
if (ctor.flags?.isProtected) modifiers.push('protected');
|
|
954
|
+
if (ctor.flags?.isPrivate) modifiers.push('private');
|
|
955
|
+
|
|
956
|
+
const desc = sig?.comment?.summary ? this._getCommentText(sig.comment.summary) : '';
|
|
957
|
+
|
|
958
|
+
lines.push('<tr><td>');
|
|
959
|
+
lines.push('');
|
|
960
|
+
lines.push(`\`${ctorSig}\``);
|
|
961
|
+
lines.push('');
|
|
962
|
+
lines.push('</td><td>');
|
|
963
|
+
lines.push('');
|
|
964
|
+
lines.push(modifiers.map((m) => `\`${m}\``).join(' '));
|
|
965
|
+
lines.push('');
|
|
966
|
+
lines.push('</td><td>');
|
|
967
|
+
lines.push('');
|
|
968
|
+
lines.push(desc);
|
|
969
|
+
lines.push('');
|
|
970
|
+
lines.push('</td></tr>');
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
lines.push('</tbody></table>');
|
|
974
|
+
return lines.join('\n');
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
private _renderPropertiesTable(
|
|
978
|
+
members: DeclarationReflection[],
|
|
979
|
+
parentName: string,
|
|
980
|
+
_basePath: string
|
|
981
|
+
): string {
|
|
982
|
+
const lines: string[] = [];
|
|
983
|
+
|
|
984
|
+
lines.push('<table><thead><tr><th>');
|
|
985
|
+
lines.push('');
|
|
986
|
+
lines.push('Property');
|
|
987
|
+
lines.push('');
|
|
988
|
+
lines.push('</th><th>');
|
|
989
|
+
lines.push('');
|
|
990
|
+
lines.push('Modifiers');
|
|
991
|
+
lines.push('');
|
|
992
|
+
lines.push('</th><th>');
|
|
993
|
+
lines.push('');
|
|
994
|
+
lines.push('Type');
|
|
995
|
+
lines.push('');
|
|
996
|
+
lines.push('</th><th>');
|
|
997
|
+
lines.push('');
|
|
998
|
+
lines.push('Description');
|
|
999
|
+
lines.push('');
|
|
1000
|
+
lines.push('</th></tr></thead>');
|
|
1001
|
+
lines.push('<tbody>');
|
|
1002
|
+
|
|
1003
|
+
for (const member of members) {
|
|
1004
|
+
if (member.kind === ReflectionKind.Accessor) {
|
|
1005
|
+
lines.push(this._renderAccessorRow(member, parentName));
|
|
1006
|
+
} else {
|
|
1007
|
+
lines.push(this._renderPropertyRow(member, parentName));
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
lines.push('</tbody></table>');
|
|
1012
|
+
return lines.join('\n');
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
private _renderPropertyRow(prop: DeclarationReflection, parentName: string): string {
|
|
1016
|
+
const typeStr = prop.type?.toString() ?? '';
|
|
1017
|
+
|
|
1018
|
+
const modifiers: string[] = [];
|
|
1019
|
+
if (prop.flags?.isReadonly) modifiers.push('readonly');
|
|
1020
|
+
if (prop.flags?.isProtected) modifiers.push('protected');
|
|
1021
|
+
if (prop.flags?.isPrivate) modifiers.push('private');
|
|
1022
|
+
if (prop.flags?.isStatic) modifiers.push('static');
|
|
1023
|
+
|
|
1024
|
+
const desc = prop.comment?.summary ? this._getFirstLine(this._getCommentText(prop.comment.summary)) : '';
|
|
1025
|
+
|
|
1026
|
+
// For inherited members, link to the defining class's detail page (unless includeInheritedMemberPages is true)
|
|
1027
|
+
const definingClass =
|
|
1028
|
+
!this._options.includeInheritedMemberPages && prop.inheritedFrom
|
|
1029
|
+
? this._sanitizeName(prop.inheritedFrom.reflection?.parent?.name ?? parentName)
|
|
1030
|
+
: parentName;
|
|
1031
|
+
const detailUrl = `./${this._sanitizeName(definingClass)}.${this._sanitizeName(prop.name)}.md`;
|
|
1032
|
+
|
|
1033
|
+
return this._formatTableRow(prop.name, modifiers, typeStr, desc, detailUrl);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
private _renderAccessorRow(accessor: DeclarationReflection, parentName: string): string {
|
|
1037
|
+
const getter = accessor.getSignature;
|
|
1038
|
+
const setter = accessor.setSignature;
|
|
1039
|
+
|
|
1040
|
+
let typeStr = '';
|
|
1041
|
+
if (getter?.type) {
|
|
1042
|
+
typeStr = getter.type.toString();
|
|
1043
|
+
} else if (setter?.parameters?.[0]?.type) {
|
|
1044
|
+
typeStr = setter.parameters[0].type.toString();
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const modifiers: string[] = [];
|
|
1048
|
+
if (getter && !setter) modifiers.push('readonly');
|
|
1049
|
+
if (accessor.flags?.isProtected) modifiers.push('protected');
|
|
1050
|
+
if (accessor.flags?.isPrivate) modifiers.push('private');
|
|
1051
|
+
if (accessor.flags?.isStatic) modifiers.push('static');
|
|
1052
|
+
|
|
1053
|
+
let desc = '';
|
|
1054
|
+
if (getter?.comment?.summary) {
|
|
1055
|
+
desc = this._getFirstLine(this._getCommentText(getter.comment.summary));
|
|
1056
|
+
} else if (setter?.comment?.summary) {
|
|
1057
|
+
desc = this._getFirstLine(this._getCommentText(setter.comment.summary));
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// For inherited members, link to the defining class's detail page (unless includeInheritedMemberPages is true)
|
|
1061
|
+
const definingClass =
|
|
1062
|
+
!this._options.includeInheritedMemberPages && accessor.inheritedFrom
|
|
1063
|
+
? this._sanitizeName(accessor.inheritedFrom.reflection?.parent?.name ?? parentName)
|
|
1064
|
+
: parentName;
|
|
1065
|
+
const detailUrl = `./${this._sanitizeName(definingClass)}.${this._sanitizeName(accessor.name)}.md`;
|
|
1066
|
+
|
|
1067
|
+
return this._formatTableRow(accessor.name, modifiers, typeStr, desc, detailUrl);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
private _formatTableRow(
|
|
1071
|
+
name: string,
|
|
1072
|
+
modifiers: string[],
|
|
1073
|
+
type: string,
|
|
1074
|
+
description: string,
|
|
1075
|
+
detailUrl?: string
|
|
1076
|
+
): string {
|
|
1077
|
+
const linkedType = this._linkType(type);
|
|
1078
|
+
const modifierStr = modifiers.map((m) => `\`${m}\``).join(' ');
|
|
1079
|
+
|
|
1080
|
+
// Create linked name if detail URL provided
|
|
1081
|
+
const nameDisplay = detailUrl ? `[${name}](${detailUrl})` : `\`${name}\``;
|
|
1082
|
+
|
|
1083
|
+
const lines: string[] = [];
|
|
1084
|
+
lines.push('<tr><td>');
|
|
1085
|
+
lines.push('');
|
|
1086
|
+
lines.push(nameDisplay);
|
|
1087
|
+
lines.push('');
|
|
1088
|
+
lines.push('</td><td>');
|
|
1089
|
+
lines.push('');
|
|
1090
|
+
lines.push(modifierStr);
|
|
1091
|
+
lines.push('');
|
|
1092
|
+
lines.push('</td><td>');
|
|
1093
|
+
lines.push('');
|
|
1094
|
+
lines.push(linkedType);
|
|
1095
|
+
lines.push('');
|
|
1096
|
+
lines.push('</td><td>');
|
|
1097
|
+
lines.push('');
|
|
1098
|
+
lines.push(description);
|
|
1099
|
+
lines.push('');
|
|
1100
|
+
lines.push('</td></tr>');
|
|
1101
|
+
|
|
1102
|
+
return lines.join('\n');
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
private _renderMethodsTable(
|
|
1106
|
+
methods: DeclarationReflection[],
|
|
1107
|
+
parentName: string,
|
|
1108
|
+
_basePath: string
|
|
1109
|
+
): string {
|
|
1110
|
+
const lines: string[] = [];
|
|
1111
|
+
|
|
1112
|
+
lines.push('<table><thead><tr><th>');
|
|
1113
|
+
lines.push('');
|
|
1114
|
+
lines.push('Method');
|
|
1115
|
+
lines.push('');
|
|
1116
|
+
lines.push('</th><th>');
|
|
1117
|
+
lines.push('');
|
|
1118
|
+
lines.push('Modifiers');
|
|
1119
|
+
lines.push('');
|
|
1120
|
+
lines.push('</th><th>');
|
|
1121
|
+
lines.push('');
|
|
1122
|
+
lines.push('Description');
|
|
1123
|
+
lines.push('');
|
|
1124
|
+
lines.push('</th></tr></thead>');
|
|
1125
|
+
lines.push('<tbody>');
|
|
1126
|
+
|
|
1127
|
+
for (const method of methods) {
|
|
1128
|
+
const sig = method.signatures?.[0];
|
|
1129
|
+
const params = sig?.parameters?.map((p) => p.name).join(', ') ?? '';
|
|
1130
|
+
const methodSig = `${method.name}(${params})`;
|
|
1131
|
+
|
|
1132
|
+
const modifiers: string[] = [];
|
|
1133
|
+
if (method.flags?.isProtected) modifiers.push('protected');
|
|
1134
|
+
if (method.flags?.isPrivate) modifiers.push('private');
|
|
1135
|
+
if (method.flags?.isStatic) modifiers.push('static');
|
|
1136
|
+
|
|
1137
|
+
const desc = sig?.comment?.summary ? this._getFirstLine(this._getCommentText(sig.comment.summary)) : '';
|
|
1138
|
+
|
|
1139
|
+
// For inherited members, link to the defining class's detail page (unless includeInheritedMemberPages is true)
|
|
1140
|
+
const definingClass =
|
|
1141
|
+
!this._options.includeInheritedMemberPages && method.inheritedFrom
|
|
1142
|
+
? this._sanitizeName(method.inheritedFrom.reflection?.parent?.name ?? parentName)
|
|
1143
|
+
: parentName;
|
|
1144
|
+
const detailUrl = `./${this._sanitizeName(definingClass)}.${this._sanitizeName(method.name)}.md`;
|
|
1145
|
+
|
|
1146
|
+
lines.push('<tr><td>');
|
|
1147
|
+
lines.push('');
|
|
1148
|
+
lines.push(`[${methodSig}](${detailUrl})`);
|
|
1149
|
+
lines.push('');
|
|
1150
|
+
lines.push('</td><td>');
|
|
1151
|
+
lines.push('');
|
|
1152
|
+
lines.push(modifiers.map((m) => `\`${m}\``).join(' '));
|
|
1153
|
+
lines.push('');
|
|
1154
|
+
lines.push('</td><td>');
|
|
1155
|
+
lines.push('');
|
|
1156
|
+
lines.push(desc);
|
|
1157
|
+
lines.push('');
|
|
1158
|
+
lines.push('</td></tr>');
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
lines.push('</tbody></table>');
|
|
1162
|
+
return lines.join('\n');
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
private _renderSignature(name: string, sig: SignatureReflection): string {
|
|
1166
|
+
const params =
|
|
1167
|
+
sig.parameters?.map((p) => `${p.name}: ${p.type?.toString() ?? 'unknown'}`).join(', ') ?? '';
|
|
1168
|
+
const returnType = sig.type?.toString() ?? 'void';
|
|
1169
|
+
return `function ${name}(${params}): ${returnType}`;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Find direct children of a specific kind (no recursion into namespaces).
|
|
1174
|
+
*/
|
|
1175
|
+
private _findByKind(parent: Reflection, kind: ReflectionKind): DeclarationReflection[] {
|
|
1176
|
+
if (!(parent instanceof DeclarationReflection) && !(parent instanceof ProjectReflection)) {
|
|
1177
|
+
return [];
|
|
1178
|
+
}
|
|
1179
|
+
return (parent.children ?? []).filter((child) => child.kind === kind);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
private _sanitizeName(name: string): string {
|
|
1183
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Generate breadcrumb navigation for a page.
|
|
1188
|
+
* @param basePath - The namespace path (e.g., "LibraryRuntime/Indexers")
|
|
1189
|
+
* @param currentName - The name of the current item
|
|
1190
|
+
* @param subdir - The subdirectory within the namespace (e.g., "classes", "interfaces")
|
|
1191
|
+
*/
|
|
1192
|
+
private _generateBreadcrumb(basePath: string, currentName: string, subdir?: string): string {
|
|
1193
|
+
const parts: string[] = [];
|
|
1194
|
+
|
|
1195
|
+
// Calculate how many levels deep we are
|
|
1196
|
+
const pathParts = basePath ? basePath.split('/').filter((p) => p) : [];
|
|
1197
|
+
const subdirDepth = subdir ? 1 : 0;
|
|
1198
|
+
const totalDepth = pathParts.length + subdirDepth;
|
|
1199
|
+
|
|
1200
|
+
// Home link - go up the appropriate number of directories
|
|
1201
|
+
const homePrefix = totalDepth > 0 ? '../'.repeat(totalDepth) : './';
|
|
1202
|
+
parts.push(`[Home](${homePrefix}README.md)`);
|
|
1203
|
+
|
|
1204
|
+
// Namespace path components
|
|
1205
|
+
let accumulatedPath = '';
|
|
1206
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
1207
|
+
const nsName = pathParts[i];
|
|
1208
|
+
accumulatedPath += (accumulatedPath ? '/' : '') + nsName;
|
|
1209
|
+
|
|
1210
|
+
// Calculate relative path from current location to this namespace
|
|
1211
|
+
const levelsUp = totalDepth - (i + 1);
|
|
1212
|
+
const nsPrefix = levelsUp > 0 ? '../'.repeat(levelsUp) : './';
|
|
1213
|
+
parts.push(`[${nsName}](${nsPrefix}README.md)`);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// Current item (not linked)
|
|
1217
|
+
parts.push(currentName);
|
|
1218
|
+
|
|
1219
|
+
return parts.join(' > ');
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
private _escapeHtml(str: string): string {
|
|
1223
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
private _getCommentText(summary: CommentDisplayPart[]): string {
|
|
1227
|
+
return summary.map((part) => part.text).join('');
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Compute relative path from current file to target file.
|
|
1232
|
+
*/
|
|
1233
|
+
private _relativePath(from: string, to: string): string {
|
|
1234
|
+
const fromDir = path.dirname(from);
|
|
1235
|
+
return path.relative(fromDir, to);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Convert a type string to a linked version where possible (for HTML table cells).
|
|
1240
|
+
* Handles generic types like `Map<K, V>` by linking individual type names.
|
|
1241
|
+
*/
|
|
1242
|
+
private _linkType(typeStr: string): string {
|
|
1243
|
+
if (!typeStr) return '';
|
|
1244
|
+
|
|
1245
|
+
// Parse type string and link known types
|
|
1246
|
+
// This regex matches type names (identifiers) that might be linkable
|
|
1247
|
+
const result = typeStr.replace(/\b([A-Z][a-zA-Z0-9_]*)\b/g, (match, typeName) => {
|
|
1248
|
+
const urlInfo = this._urlMap.get(typeName);
|
|
1249
|
+
if (urlInfo) {
|
|
1250
|
+
const relativePath = this._relativePath(this._currentFilePath, urlInfo.url);
|
|
1251
|
+
return `[${typeName}](${relativePath})`;
|
|
1252
|
+
}
|
|
1253
|
+
return match;
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
return this._escapeHtml(result);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* Convert a type string to a linked version for inline markdown (not in HTML tables).
|
|
1261
|
+
* Returns the type wrapped in backticks, with links if the type is documented.
|
|
1262
|
+
*/
|
|
1263
|
+
private _linkTypeInline(typeStr: string): string {
|
|
1264
|
+
if (!typeStr) return '';
|
|
1265
|
+
|
|
1266
|
+
// Check if the whole type name (without generics) is linkable
|
|
1267
|
+
const baseType = typeStr.split('<')[0].trim();
|
|
1268
|
+
const urlInfo = this._urlMap.get(baseType);
|
|
1269
|
+
|
|
1270
|
+
if (urlInfo) {
|
|
1271
|
+
const relativePath = this._relativePath(this._currentFilePath, urlInfo.url);
|
|
1272
|
+
return `[\`${typeStr}\`](${relativePath})`;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
return `\`${typeStr}\``;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* Set the current file being rendered (for computing relative URLs).
|
|
1280
|
+
*/
|
|
1281
|
+
private _setCurrentFile(filePath: string): void {
|
|
1282
|
+
this._currentFilePath = filePath;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Plugin load function - entry point for TypeDoc.
|
|
1288
|
+
*/
|
|
1289
|
+
export function load(app: Application): void {
|
|
1290
|
+
// Declare custom option for inherited member pages
|
|
1291
|
+
app.options.addDeclaration({
|
|
1292
|
+
name: 'includeInheritedMemberPages',
|
|
1293
|
+
help: '[Compact Theme] Generate separate detail pages for inherited members instead of linking to parent class',
|
|
1294
|
+
type: ParameterType.Boolean,
|
|
1295
|
+
defaultValue: false
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
// Read options after bootstrap
|
|
1299
|
+
let outputDir: string | undefined;
|
|
1300
|
+
let includeInheritedMemberPages: boolean = false;
|
|
1301
|
+
|
|
1302
|
+
app.on(Application.EVENT_BOOTSTRAP_END, () => {
|
|
1303
|
+
outputDir = app.options.getValue('out') as string;
|
|
1304
|
+
includeInheritedMemberPages = app.options.getValue('includeInheritedMemberPages') as boolean;
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
// Render our markdown after conversion completes, before the renderer runs
|
|
1308
|
+
app.converter.on(Converter.EVENT_RESOLVE_END, (context: Context) => {
|
|
1309
|
+
if (!outputDir) {
|
|
1310
|
+
console.error('[compact-markdown] Output directory not set');
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Render using our custom format
|
|
1315
|
+
const options: ICompactMarkdownOptions = {
|
|
1316
|
+
includeInheritedMemberPages
|
|
1317
|
+
};
|
|
1318
|
+
const renderer = new CompactMarkdownRenderer(outputDir, options);
|
|
1319
|
+
renderer.renderProject(context.project);
|
|
1320
|
+
|
|
1321
|
+
console.log(`[compact-markdown] Generated markdown documentation at ${outputDir}`);
|
|
1322
|
+
|
|
1323
|
+
// Exit before TypeDoc's HTML renderer runs
|
|
1324
|
+
// This is the simplest way to prevent default output without intermediate files
|
|
1325
|
+
process.exit(0);
|
|
1326
|
+
});
|
|
1327
|
+
}
|