@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/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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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
+ }