custom_markdown_documenter.ts (47592B)
1 /** 2 * @license 3 * Copyright 2022 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the 8 // MIT license. See LICENSE in the project root for license information. 9 10 // Taken from 11 // https://github.com/microsoft/rushstack/blob/main/apps/api-documenter/src/documenters/MarkdownDocumenter.ts 12 // This file has been edited to morph into Docusaurus's expected inputs. 13 14 import * as path from 'node:path'; 15 16 import type {DocumenterConfig} from '@microsoft/api-documenter/lib/documenters/DocumenterConfig.js'; 17 import {CustomMarkdownEmitter as ApiFormatterMarkdownEmitter} from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter.js'; 18 import {CustomDocNodes} from '@microsoft/api-documenter/lib/nodes/CustomDocNodeKind.js'; 19 import {DocEmphasisSpan} from '@microsoft/api-documenter/lib/nodes/DocEmphasisSpan.js'; 20 import {DocHeading} from '@microsoft/api-documenter/lib/nodes/DocHeading.js'; 21 import {DocNoteBox} from '@microsoft/api-documenter/lib/nodes/DocNoteBox.js'; 22 import {DocTable} from '@microsoft/api-documenter/lib/nodes/DocTable.js'; 23 import {DocTableCell} from '@microsoft/api-documenter/lib/nodes/DocTableCell.js'; 24 import {DocTableRow} from '@microsoft/api-documenter/lib/nodes/DocTableRow.js'; 25 import {MarkdownDocumenterAccessor} from '@microsoft/api-documenter/lib/plugin/MarkdownDocumenterAccessor.js'; 26 import { 27 type IMarkdownDocumenterFeatureOnBeforeWritePageArgs, 28 MarkdownDocumenterFeatureContext, 29 } from '@microsoft/api-documenter/lib/plugin/MarkdownDocumenterFeature.js'; 30 import {PluginLoader} from '@microsoft/api-documenter/lib/plugin/PluginLoader.js'; 31 import {Utilities} from '@microsoft/api-documenter/lib/utils/Utilities.js'; 32 import { 33 ApiClass, 34 ApiDeclaredItem, 35 ApiDocumentedItem, 36 type ApiEnum, 37 ApiInitializerMixin, 38 ApiInterface, 39 type ApiItem, 40 ApiItemKind, 41 type ApiModel, 42 type ApiNamespace, 43 ApiOptionalMixin, 44 type ApiPackage, 45 ApiParameterListMixin, 46 ApiPropertyItem, 47 ApiProtectedMixin, 48 ApiReadonlyMixin, 49 ApiReleaseTagMixin, 50 ApiReturnTypeMixin, 51 ApiStaticMixin, 52 ApiTypeAlias, 53 type Excerpt, 54 type ExcerptToken, 55 ExcerptTokenKind, 56 type IResolveDeclarationReferenceResult, 57 ReleaseTag, 58 } from '@microsoft/api-extractor-model'; 59 import { 60 type DocBlock, 61 DocCodeSpan, 62 type DocComment, 63 DocFencedCode, 64 DocLinkTag, 65 type DocNodeContainer, 66 DocNodeKind, 67 DocParagraph, 68 DocPlainText, 69 DocSection, 70 StandardTags, 71 StringBuilder, 72 DocHtmlStartTag, 73 DocHtmlEndTag, 74 DocHtmlAttribute, 75 type TSDocConfiguration, 76 } from '@microsoft/tsdoc'; 77 import { 78 FileSystem, 79 NewlineKind, 80 PackageName, 81 } from '@rushstack/node-core-library'; 82 83 export interface IMarkdownDocumenterOptions { 84 apiModel: ApiModel; 85 documenterConfig: DocumenterConfig | undefined; 86 outputFolder: string; 87 } 88 89 export class CustomMarkdownEmitter extends ApiFormatterMarkdownEmitter { 90 protected override getEscapedText(text: string): string { 91 const textWithBackslashes = text 92 .replace(/\\/g, '\\\\') // first replace the escape character 93 .replace(/[*#[\]_|`~]/g, x => { 94 return '\\' + x; 95 }) // then escape any special characters 96 .replace(/---/g, '\\-\\-\\-') // hyphens only if it's 3 or more 97 .replace(/&/g, '&') 98 .replace(/</g, '<') 99 .replace(/>/g, '>') 100 .replace(/\{/g, '{') 101 .replace(/\}/g, '}'); 102 return textWithBackslashes; 103 } 104 } 105 106 /** 107 * Renders API documentation in the Markdown file format. 108 * For more info: https://en.wikipedia.org/wiki/Markdown 109 */ 110 export class MarkdownDocumenter { 111 private readonly _apiModel: ApiModel; 112 private readonly _documenterConfig: DocumenterConfig | undefined; 113 private readonly _tsdocConfiguration: TSDocConfiguration; 114 private readonly _markdownEmitter: CustomMarkdownEmitter; 115 private readonly _outputFolder: string; 116 private readonly _pluginLoader: PluginLoader; 117 118 public constructor(options: IMarkdownDocumenterOptions) { 119 this._apiModel = options.apiModel; 120 this._documenterConfig = options.documenterConfig; 121 this._outputFolder = options.outputFolder; 122 this._tsdocConfiguration = CustomDocNodes.configuration; 123 this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel); 124 125 this._pluginLoader = new PluginLoader(); 126 } 127 128 public generateFiles(): void { 129 if (this._documenterConfig) { 130 this._pluginLoader.load(this._documenterConfig, () => { 131 return new MarkdownDocumenterFeatureContext({ 132 apiModel: this._apiModel, 133 outputFolder: this._outputFolder, 134 documenter: new MarkdownDocumenterAccessor({ 135 getLinkForApiItem: (apiItem: ApiItem) => { 136 return this._getLinkFilenameForApiItem(apiItem); 137 }, 138 }), 139 }); 140 }); 141 } 142 143 this._deleteOldOutputFiles(); 144 145 this._writeApiItemPage(this._apiModel.members[0]!); 146 147 if (this._pluginLoader.markdownDocumenterFeature) { 148 this._pluginLoader.markdownDocumenterFeature.onFinished({}); 149 } 150 } 151 152 private _getBaseApiItemPage(apiItem: ApiItem): DocSection { 153 const configuration = this._tsdocConfiguration; 154 const output = new DocSection({ 155 configuration, 156 }); 157 158 const scopedName = apiItem.getScopedNameWithinPackage(); 159 switch (apiItem.kind) { 160 case ApiItemKind.Class: 161 output.appendNode( 162 new DocHeading({configuration, title: `${scopedName} class`}), 163 ); 164 break; 165 case ApiItemKind.Enum: 166 output.appendNode( 167 new DocHeading({configuration, title: `${scopedName} enum`}), 168 ); 169 break; 170 case ApiItemKind.Interface: 171 output.appendNode( 172 new DocHeading({ 173 configuration, 174 title: `${scopedName} interface`, 175 }), 176 ); 177 break; 178 case ApiItemKind.Constructor: 179 case ApiItemKind.ConstructSignature: 180 output.appendNode(new DocHeading({configuration, title: scopedName})); 181 break; 182 case ApiItemKind.Method: 183 case ApiItemKind.MethodSignature: 184 output.appendNode( 185 new DocHeading({configuration, title: `${scopedName} method`}), 186 ); 187 break; 188 case ApiItemKind.Function: 189 output.appendNode( 190 new DocHeading({configuration, title: `${scopedName} function`}), 191 ); 192 break; 193 case ApiItemKind.Model: 194 output.appendNode( 195 new DocHeading({configuration, title: `API Reference`}), 196 ); 197 break; 198 case ApiItemKind.Namespace: 199 output.appendNode( 200 new DocHeading({ 201 configuration, 202 title: `${scopedName} namespace`, 203 }), 204 ); 205 break; 206 case ApiItemKind.Package: 207 console.log(`Writing ${apiItem.displayName} package`); 208 output.appendNode( 209 new DocHeading({ 210 configuration, 211 title: `API Reference`, 212 }), 213 ); 214 break; 215 case ApiItemKind.Property: 216 case ApiItemKind.PropertySignature: 217 output.appendNode( 218 new DocHeading({configuration, title: `${scopedName} property`}), 219 ); 220 break; 221 case ApiItemKind.TypeAlias: 222 output.appendNode( 223 new DocHeading({configuration, title: `${scopedName} type`}), 224 ); 225 break; 226 case ApiItemKind.Variable: 227 output.appendNode( 228 new DocHeading({configuration, title: `${scopedName} variable`}), 229 ); 230 break; 231 default: 232 throw new Error('Unsupported API item kind: ' + apiItem.kind); 233 } 234 235 return output; 236 } 237 238 private _getApiItemPage(apiItem: ApiItem): DocSection { 239 const configuration = this._tsdocConfiguration; 240 const output = new DocSection({ 241 configuration, 242 }); 243 244 if ( 245 apiItem instanceof ApiDeclaredItem && 246 apiItem.excerpt.text.length > 0 && 247 ApiParameterListMixin.isBaseClassOf(apiItem) && 248 ApiReturnTypeMixin.isBaseClassOf(apiItem) && 249 apiItem.getMergedSiblings().length > 1 250 ) { 251 const name = apiItem.displayName; 252 const overloadIndex = apiItem.overloadIndex - 1; 253 const overloadId = 254 overloadIndex === 0 ? name : `${name}-${overloadIndex}`; 255 256 // TODO: See if we don't need to create all of the on our own. 257 const overLoadHeader = `${apiItem.displayName}(): ${apiItem.returnTypeExcerpt.text}`; 258 output.appendNode( 259 new DocParagraph({configuration}, [ 260 new DocHtmlStartTag({ 261 configuration, 262 name: 'h2', 263 htmlAttributes: [ 264 new DocHtmlAttribute({ 265 configuration, 266 name: 'id', 267 value: `"${overloadId}"`, 268 }), 269 ], 270 }), 271 new DocPlainText({ 272 configuration, 273 text: overLoadHeader, 274 }), 275 new DocHtmlEndTag({ 276 configuration, 277 name: 'h2', 278 }), 279 ]), 280 ); 281 } 282 283 if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) { 284 if (apiItem.releaseTag === ReleaseTag.Beta) { 285 this._writeBetaWarning(output); 286 } 287 } 288 289 const decoratorBlocks: DocBlock[] = []; 290 291 if (apiItem instanceof ApiDocumentedItem) { 292 const tsdocComment: DocComment | undefined = apiItem.tsdocComment; 293 294 if (tsdocComment) { 295 decoratorBlocks.push( 296 ...tsdocComment.customBlocks.filter(block => { 297 return ( 298 block.blockTag.tagNameWithUpperCase === 299 StandardTags.decorator.tagNameWithUpperCase 300 ); 301 }), 302 ); 303 304 if (tsdocComment.deprecatedBlock) { 305 output.appendNode( 306 new DocNoteBox({configuration: this._tsdocConfiguration}, [ 307 new DocParagraph({configuration: this._tsdocConfiguration}, [ 308 new DocPlainText({ 309 configuration, 310 text: 'Warning: This API is now obsolete. ', 311 }), 312 ]), 313 ...tsdocComment.deprecatedBlock.content.nodes, 314 ]), 315 ); 316 } 317 318 this._appendSection(output, tsdocComment.summarySection); 319 } 320 } 321 322 if (apiItem instanceof ApiDeclaredItem && apiItem.excerpt.text.length > 0) { 323 let code: string | undefined; 324 switch (apiItem.parent?.kind) { 325 case ApiItemKind.Class: 326 code = `class ${ 327 apiItem.parent.displayName 328 } {${apiItem.getExcerptWithModifiers()}}`; 329 break; 330 case ApiItemKind.Interface: 331 code = `interface ${ 332 apiItem.parent.displayName 333 } {${apiItem.getExcerptWithModifiers()}}`; 334 break; 335 default: 336 code = apiItem.getExcerptWithModifiers(); 337 } 338 if (code) { 339 output.appendNode( 340 new DocHeading({ 341 configuration, 342 title: 'Signature', 343 level: 3, 344 }), 345 ); 346 output.appendNode( 347 new DocFencedCode({ 348 configuration, 349 code: code, 350 language: 'typescript', 351 }), 352 ); 353 } 354 355 this._writeHeritageTypes(output, apiItem); 356 } 357 358 if (decoratorBlocks.length > 0) { 359 output.appendNode( 360 new DocHeading({configuration, title: 'Decorators:', level: 4}), 361 ); 362 for (const decoratorBlock of decoratorBlocks) { 363 output.appendNodes(decoratorBlock.content.nodes); 364 } 365 } 366 367 let appendRemarks = true; 368 switch (apiItem.kind) { 369 case ApiItemKind.Class: 370 case ApiItemKind.Interface: 371 case ApiItemKind.Namespace: 372 case ApiItemKind.Package: 373 this._writeRemarksSection(output, apiItem); 374 appendRemarks = false; 375 break; 376 } 377 378 switch (apiItem.kind) { 379 case ApiItemKind.Class: 380 this._writeClassTables(output, apiItem as ApiClass); 381 break; 382 case ApiItemKind.Enum: 383 this._writeEnumTables(output, apiItem as ApiEnum); 384 break; 385 case ApiItemKind.Interface: 386 this._writeInterfaceTables(output, apiItem as ApiInterface); 387 break; 388 case ApiItemKind.Constructor: 389 case ApiItemKind.ConstructSignature: 390 case ApiItemKind.Method: 391 case ApiItemKind.MethodSignature: 392 case ApiItemKind.Function: 393 this._writeParameterTables(output, apiItem as ApiParameterListMixin); 394 this._writeThrowsSection(output, apiItem); 395 break; 396 case ApiItemKind.Namespace: 397 this._writePackageOrNamespaceTables(output, apiItem as ApiNamespace); 398 break; 399 case ApiItemKind.Model: 400 this._writeModelTable(output, apiItem as ApiModel); 401 break; 402 case ApiItemKind.Package: 403 this._writePackageOrNamespaceTables(output, apiItem as ApiPackage); 404 break; 405 case ApiItemKind.Property: 406 case ApiItemKind.PropertySignature: 407 break; 408 case ApiItemKind.TypeAlias: 409 break; 410 case ApiItemKind.Variable: 411 break; 412 default: 413 throw new Error('Unsupported API item kind: ' + apiItem.kind); 414 } 415 416 this._writeDefaultValueSection(output, apiItem); 417 418 if (appendRemarks) { 419 this._writeRemarksSection(output, apiItem); 420 } 421 422 return output; 423 } 424 425 private _writeApiItemPage(apiItem: ApiItem) { 426 const output = this._getBaseApiItemPage(apiItem); 427 if (ApiParameterListMixin.isBaseClassOf(apiItem)) { 428 if (apiItem.overloadIndex > 1) { 429 return; 430 } 431 432 for (const item of apiItem.getMergedSiblings()) { 433 const itemOutput = this._getApiItemPage(item); 434 output.appendNodes(itemOutput.nodes); 435 } 436 } else { 437 const itemOutput = this._getApiItemPage(apiItem); 438 output.appendNodes(itemOutput.nodes); 439 } 440 441 const filename = path.join( 442 this._outputFolder, 443 this._getFilenameForApiItem(apiItem), 444 ); 445 const stringBuilder = new StringBuilder(); 446 this._markdownEmitter.emit(stringBuilder, output, { 447 contextApiItem: apiItem, 448 onGetFilenameForApiItem: (apiItemForFilename: ApiItem) => { 449 return this._getLinkFilenameForApiItem(apiItemForFilename); 450 }, 451 }); 452 453 let pageContent = stringBuilder.toString(); 454 455 if (this._pluginLoader.markdownDocumenterFeature) { 456 // Allow the plugin to customize the pageContent 457 const eventArgs: IMarkdownDocumenterFeatureOnBeforeWritePageArgs = { 458 apiItem: apiItem, 459 outputFilename: filename, 460 pageContent: pageContent, 461 }; 462 this._pluginLoader.markdownDocumenterFeature.onBeforeWritePage(eventArgs); 463 pageContent = eventArgs.pageContent; 464 } 465 466 pageContent = 467 `---\nsidebar_label: ${this._getSidebarLabelForApiItem(apiItem)}\n---` + 468 pageContent; 469 pageContent = pageContent.replace('##', '#'); 470 pageContent = pageContent.replace(/<!-- -->/g, ''); 471 pageContent = pageContent.replace(/\\\*\\\*/g, '**'); 472 pageContent = pageContent.replace(/<b>|<\/b>/g, '**'); 473 FileSystem.writeFile(filename, pageContent, { 474 convertLineEndings: this._documenterConfig 475 ? this._documenterConfig.newlineKind 476 : NewlineKind.CrLf, 477 }); 478 } 479 480 private _writeHeritageTypes( 481 output: DocSection, 482 apiItem: ApiDeclaredItem, 483 ): void { 484 const configuration = this._tsdocConfiguration; 485 486 if (apiItem instanceof ApiClass) { 487 if (apiItem.extendsType) { 488 const extendsParagraph = new DocParagraph({configuration}, [ 489 new DocEmphasisSpan({configuration, bold: true}, [ 490 new DocPlainText({configuration, text: 'Extends: '}), 491 ]), 492 ]); 493 this._appendExcerptWithHyperlinks( 494 extendsParagraph, 495 apiItem.extendsType.excerpt, 496 ); 497 output.appendNode(extendsParagraph); 498 } 499 if (apiItem.implementsTypes.length > 0) { 500 const extendsParagraph = new DocParagraph({configuration}, [ 501 new DocEmphasisSpan({configuration, bold: true}, [ 502 new DocPlainText({configuration, text: 'Implements: '}), 503 ]), 504 ]); 505 let needsComma = false; 506 for (const implementsType of apiItem.implementsTypes) { 507 if (needsComma) { 508 extendsParagraph.appendNode( 509 new DocPlainText({configuration, text: ', '}), 510 ); 511 } 512 this._appendExcerptWithHyperlinks( 513 extendsParagraph, 514 implementsType.excerpt, 515 ); 516 needsComma = true; 517 } 518 output.appendNode(extendsParagraph); 519 } 520 } 521 522 if (apiItem instanceof ApiInterface) { 523 if (apiItem.extendsTypes.length > 0) { 524 const extendsParagraph = new DocParagraph({configuration}, [ 525 new DocEmphasisSpan({configuration, bold: true}, [ 526 new DocPlainText({configuration, text: 'Extends: '}), 527 ]), 528 ]); 529 let needsComma = false; 530 for (const extendsType of apiItem.extendsTypes) { 531 if (needsComma) { 532 extendsParagraph.appendNode( 533 new DocPlainText({configuration, text: ', '}), 534 ); 535 } 536 this._appendExcerptWithHyperlinks( 537 extendsParagraph, 538 extendsType.excerpt, 539 ); 540 needsComma = true; 541 } 542 output.appendNode(extendsParagraph); 543 } 544 } 545 546 if (apiItem instanceof ApiTypeAlias) { 547 const refs: ExcerptToken[] = apiItem.excerptTokens.filter(token => { 548 return ( 549 token.kind === ExcerptTokenKind.Reference && 550 token.canonicalReference && 551 this._apiModel.resolveDeclarationReference( 552 token.canonicalReference, 553 undefined, 554 ).resolvedApiItem 555 ); 556 }); 557 if (refs.length > 0) { 558 const referencesParagraph = new DocParagraph({configuration}, [ 559 new DocEmphasisSpan({configuration, bold: true}, [ 560 new DocPlainText({configuration, text: 'References: '}), 561 ]), 562 ]); 563 let needsComma = false; 564 const visited = new Set<string>(); 565 for (const ref of refs) { 566 if (visited.has(ref.text)) { 567 continue; 568 } 569 visited.add(ref.text); 570 571 if (needsComma) { 572 referencesParagraph.appendNode( 573 new DocPlainText({configuration, text: ', '}), 574 ); 575 } 576 577 this._appendExcerptTokenWithHyperlinks(referencesParagraph, ref); 578 needsComma = true; 579 } 580 output.appendNode(referencesParagraph); 581 } 582 } 583 } 584 585 private _writeDefaultValueSection(output: DocSection, apiItem: ApiItem) { 586 if (apiItem instanceof ApiDocumentedItem) { 587 const block = apiItem.tsdocComment?.customBlocks.find(block => { 588 return ( 589 block.blockTag.tagNameWithUpperCase === 590 StandardTags.defaultValue.tagNameWithUpperCase 591 ); 592 }); 593 if (block) { 594 output.appendNode( 595 new DocHeading({ 596 configuration: this._tsdocConfiguration, 597 title: 'Default value:', 598 level: 4, 599 }), 600 ); 601 this._appendSection(output, block.content); 602 } 603 } 604 } 605 606 private _writeRemarksSection(output: DocSection, apiItem: ApiItem): void { 607 const configuration = this._tsdocConfiguration; 608 609 if (apiItem instanceof ApiDocumentedItem) { 610 const tsdocComment: DocComment | undefined = apiItem.tsdocComment; 611 612 if (tsdocComment) { 613 // Write the @remarks block 614 if (tsdocComment.remarksBlock) { 615 output.appendNode( 616 new DocHeading({ 617 configuration, 618 title: 'Remarks', 619 }), 620 ); 621 this._appendSection(output, tsdocComment.remarksBlock.content); 622 } 623 624 // Write the @example blocks 625 const exampleBlocks: DocBlock[] = tsdocComment.customBlocks.filter( 626 x => { 627 return ( 628 x.blockTag.tagNameWithUpperCase === 629 StandardTags.example.tagNameWithUpperCase 630 ); 631 }, 632 ); 633 634 let exampleNumber = 1; 635 for (const exampleBlock of exampleBlocks) { 636 const heading: string = 637 exampleBlocks.length > 1 ? `Example ${exampleNumber}` : 'Example'; 638 639 output.appendNode( 640 new DocHeading({ 641 configuration, 642 title: heading, 643 }), 644 ); 645 646 this._appendSection(output, exampleBlock.content); 647 648 ++exampleNumber; 649 } 650 } 651 } 652 } 653 654 private _writeThrowsSection(output: DocSection, apiItem: ApiItem): void { 655 if (apiItem instanceof ApiDocumentedItem) { 656 const tsdocComment: DocComment | undefined = apiItem.tsdocComment; 657 658 if (tsdocComment) { 659 // Write the @throws blocks 660 const throwsBlocks: DocBlock[] = tsdocComment.customBlocks.filter(x => { 661 return ( 662 x.blockTag.tagNameWithUpperCase === 663 StandardTags.throws.tagNameWithUpperCase 664 ); 665 }); 666 667 if (throwsBlocks.length > 0) { 668 const heading = 'Exceptions'; 669 output.appendNode( 670 new DocHeading({ 671 configuration: this._tsdocConfiguration, 672 title: heading, 673 }), 674 ); 675 676 for (const throwsBlock of throwsBlocks) { 677 this._appendSection(output, throwsBlock.content); 678 } 679 } 680 } 681 } 682 } 683 684 /** 685 * GENERATE PAGE: MODEL 686 */ 687 private _writeModelTable(output: DocSection, apiModel: ApiModel): void { 688 const configuration = this._tsdocConfiguration; 689 690 const packagesTable = new DocTable({ 691 configuration, 692 headerTitles: ['Package', 'Description'], 693 }); 694 695 for (const apiMember of apiModel.members) { 696 const row = new DocTableRow({configuration}, [ 697 this._createTitleCell(apiMember), 698 this._createDescriptionCell(apiMember), 699 ]); 700 701 switch (apiMember.kind) { 702 case ApiItemKind.Package: 703 packagesTable.addRow(row); 704 this._writeApiItemPage(apiMember); 705 break; 706 } 707 } 708 709 if (packagesTable.rows.length > 0) { 710 output.appendNode( 711 new DocHeading({ 712 configuration, 713 title: 'Packages', 714 }), 715 ); 716 output.appendNode(packagesTable); 717 } 718 } 719 720 /** 721 * GENERATE PAGE: PACKAGE or NAMESPACE 722 */ 723 private _writePackageOrNamespaceTables( 724 output: DocSection, 725 apiContainer: ApiPackage | ApiNamespace, 726 ): void { 727 const configuration = this._tsdocConfiguration; 728 729 const classesTable = new DocTable({ 730 configuration, 731 headerTitles: ['Class', 'Description'], 732 }); 733 734 const enumerationsTable = new DocTable({ 735 configuration, 736 headerTitles: ['Enumeration', 'Description'], 737 }); 738 739 const functionsTable = new DocTable({ 740 configuration, 741 headerTitles: ['Function', 'Description'], 742 }); 743 744 const interfacesTable = new DocTable({ 745 configuration, 746 headerTitles: ['Interface', 'Description'], 747 }); 748 749 const namespacesTable = new DocTable({ 750 configuration, 751 headerTitles: ['Namespace', 'Description'], 752 }); 753 754 const variablesTable = new DocTable({ 755 configuration, 756 headerTitles: ['Variable', 'Description'], 757 }); 758 759 const typeAliasesTable = new DocTable({ 760 configuration, 761 headerTitles: ['Type Alias', 'Description'], 762 }); 763 764 const apiMembers: readonly ApiItem[] = 765 apiContainer.kind === ApiItemKind.Package 766 ? (apiContainer as ApiPackage).entryPoints[0]!.members 767 : (apiContainer as ApiNamespace).members; 768 769 for (const apiMember of apiMembers) { 770 const row = new DocTableRow({configuration}, [ 771 this._createTitleCell(apiMember), 772 this._createDescriptionCell(apiMember), 773 ]); 774 775 switch (apiMember.kind) { 776 case ApiItemKind.Class: 777 classesTable.addRow(row); 778 this._writeApiItemPage(apiMember); 779 break; 780 781 case ApiItemKind.Enum: 782 enumerationsTable.addRow(row); 783 this._writeApiItemPage(apiMember); 784 break; 785 786 case ApiItemKind.Interface: 787 interfacesTable.addRow(row); 788 this._writeApiItemPage(apiMember); 789 break; 790 791 case ApiItemKind.Namespace: 792 namespacesTable.addRow(row); 793 this._writeApiItemPage(apiMember); 794 break; 795 796 case ApiItemKind.Function: 797 functionsTable.addRow(row); 798 this._writeApiItemPage(apiMember); 799 break; 800 801 case ApiItemKind.TypeAlias: 802 typeAliasesTable.addRow(row); 803 this._writeApiItemPage(apiMember); 804 break; 805 806 case ApiItemKind.Variable: 807 variablesTable.addRow(row); 808 this._writeApiItemPage(apiMember); 809 break; 810 } 811 } 812 813 if (classesTable.rows.length > 0) { 814 output.appendNode( 815 new DocHeading({ 816 configuration, 817 title: 'Classes', 818 }), 819 ); 820 output.appendNode(classesTable); 821 } 822 823 if (enumerationsTable.rows.length > 0) { 824 output.appendNode( 825 new DocHeading({ 826 configuration, 827 title: 'Enumerations', 828 }), 829 ); 830 output.appendNode(enumerationsTable); 831 } 832 if (functionsTable.rows.length > 0) { 833 output.appendNode( 834 new DocHeading({ 835 configuration, 836 title: 'Functions', 837 }), 838 ); 839 output.appendNode(functionsTable); 840 } 841 842 if (interfacesTable.rows.length > 0) { 843 output.appendNode( 844 new DocHeading({ 845 configuration, 846 title: 'Interfaces', 847 }), 848 ); 849 output.appendNode(interfacesTable); 850 } 851 852 if (namespacesTable.rows.length > 0) { 853 output.appendNode( 854 new DocHeading({ 855 configuration, 856 title: 'Namespaces', 857 }), 858 ); 859 output.appendNode(namespacesTable); 860 } 861 862 if (variablesTable.rows.length > 0) { 863 output.appendNode( 864 new DocHeading({ 865 configuration, 866 title: 'Variables', 867 }), 868 ); 869 output.appendNode(variablesTable); 870 } 871 872 if (typeAliasesTable.rows.length > 0) { 873 output.appendNode( 874 new DocHeading({ 875 configuration, 876 title: 'Type Aliases', 877 }), 878 ); 879 output.appendNode(typeAliasesTable); 880 } 881 } 882 883 /** 884 * GENERATE PAGE: CLASS 885 */ 886 private _writeClassTables(output: DocSection, apiClass: ApiClass): void { 887 const configuration = this._tsdocConfiguration; 888 889 const eventsTable = new DocTable({ 890 configuration, 891 headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], 892 }); 893 894 const constructorsTable = new DocTable({ 895 configuration, 896 headerTitles: ['Constructor', 'Modifiers', 'Description'], 897 }); 898 899 const propertiesTable = new DocTable({ 900 configuration, 901 headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], 902 }); 903 904 const methodsTable = new DocTable({ 905 configuration, 906 headerTitles: ['Method', 'Modifiers', 'Description'], 907 }); 908 909 for (const apiMember of apiClass.members) { 910 switch (apiMember.kind) { 911 case ApiItemKind.Constructor: { 912 constructorsTable.addRow( 913 new DocTableRow({configuration}, [ 914 this._createTitleCell(apiMember), 915 this._createModifiersCell(apiMember), 916 this._createDescriptionCell(apiMember), 917 ]), 918 ); 919 920 this._writeApiItemPage(apiMember); 921 break; 922 } 923 case ApiItemKind.Method: { 924 methodsTable.addRow( 925 new DocTableRow({configuration}, [ 926 this._createTitleCell(apiMember), 927 this._createModifiersCell(apiMember), 928 this._createDescriptionCell(apiMember), 929 ]), 930 ); 931 932 this._writeApiItemPage(apiMember); 933 break; 934 } 935 case ApiItemKind.Property: { 936 if ((apiMember as ApiPropertyItem).isEventProperty) { 937 eventsTable.addRow( 938 new DocTableRow({configuration}, [ 939 this._createTitleCell(apiMember, true), 940 this._createModifiersCell(apiMember), 941 this._createPropertyTypeCell(apiMember), 942 this._createDescriptionCell(apiMember), 943 ]), 944 ); 945 } else { 946 propertiesTable.addRow( 947 new DocTableRow({configuration}, [ 948 this._createTitleCell(apiMember, true), 949 this._createModifiersCell(apiMember), 950 this._createPropertyTypeCell(apiMember), 951 this._createDescriptionCell(apiMember), 952 ]), 953 ); 954 } 955 break; 956 } 957 } 958 } 959 960 if (eventsTable.rows.length > 0) { 961 output.appendNode( 962 new DocHeading({ 963 configuration, 964 title: 'Events', 965 }), 966 ); 967 output.appendNode(eventsTable); 968 } 969 970 if (constructorsTable.rows.length > 0) { 971 output.appendNode( 972 new DocHeading({ 973 configuration, 974 title: 'Constructors', 975 }), 976 ); 977 output.appendNode(constructorsTable); 978 } 979 980 if (propertiesTable.rows.length > 0) { 981 output.appendNode( 982 new DocHeading({ 983 configuration, 984 title: 'Properties', 985 }), 986 ); 987 output.appendNode(propertiesTable); 988 } 989 990 if (methodsTable.rows.length > 0) { 991 output.appendNode( 992 new DocHeading({ 993 configuration, 994 title: 'Methods', 995 }), 996 ); 997 output.appendNode(methodsTable); 998 } 999 } 1000 1001 /** 1002 * GENERATE PAGE: ENUM 1003 */ 1004 private _writeEnumTables(output: DocSection, apiEnum: ApiEnum): void { 1005 const configuration = this._tsdocConfiguration; 1006 1007 const enumMembersTable = new DocTable({ 1008 configuration, 1009 headerTitles: ['Member', 'Value', 'Description'], 1010 }); 1011 1012 for (const apiEnumMember of apiEnum.members) { 1013 enumMembersTable.addRow( 1014 new DocTableRow({configuration}, [ 1015 new DocTableCell({configuration}, [ 1016 new DocParagraph({configuration}, [ 1017 new DocPlainText({ 1018 configuration, 1019 text: Utilities.getConciseSignature(apiEnumMember), 1020 }), 1021 ]), 1022 ]), 1023 this._createInitializerCell(apiEnumMember), 1024 this._createDescriptionCell(apiEnumMember), 1025 ]), 1026 ); 1027 } 1028 1029 if (enumMembersTable.rows.length > 0) { 1030 output.appendNode( 1031 new DocHeading({ 1032 configuration, 1033 title: 'Enumeration Members', 1034 }), 1035 ); 1036 output.appendNode(enumMembersTable); 1037 } 1038 } 1039 1040 /** 1041 * GENERATE PAGE: INTERFACE 1042 */ 1043 private _writeInterfaceTables( 1044 output: DocSection, 1045 apiClass: ApiInterface, 1046 ): void { 1047 const configuration = this._tsdocConfiguration; 1048 1049 const eventsTable = new DocTable({ 1050 configuration, 1051 headerTitles: ['Property', 'Modifiers', 'Type', 'Description'], 1052 }); 1053 1054 const propertiesTable = new DocTable({ 1055 configuration, 1056 headerTitles: ['Property', 'Modifiers', 'Type', 'Description', 'Default'], 1057 }); 1058 1059 const methodsTable = new DocTable({ 1060 configuration, 1061 headerTitles: ['Method', 'Description'], 1062 }); 1063 1064 for (const apiMember of apiClass.members) { 1065 switch (apiMember.kind) { 1066 case ApiItemKind.ConstructSignature: 1067 case ApiItemKind.MethodSignature: { 1068 methodsTable.addRow( 1069 new DocTableRow({configuration}, [ 1070 this._createTitleCell(apiMember), 1071 this._createDescriptionCell(apiMember), 1072 ]), 1073 ); 1074 1075 this._writeApiItemPage(apiMember); 1076 break; 1077 } 1078 case ApiItemKind.PropertySignature: { 1079 if ((apiMember as ApiPropertyItem).isEventProperty) { 1080 eventsTable.addRow( 1081 new DocTableRow({configuration}, [ 1082 this._createTitleCell(apiMember, true), 1083 this._createModifiersCell(apiMember), 1084 this._createPropertyTypeCell(apiMember), 1085 this._createDescriptionCell(apiMember), 1086 ]), 1087 ); 1088 } else { 1089 propertiesTable.addRow( 1090 new DocTableRow({configuration}, [ 1091 this._createTitleCell(apiMember, true), 1092 this._createModifiersCell(apiMember), 1093 this._createPropertyTypeCell(apiMember), 1094 this._createDescriptionCell(apiMember), 1095 this._createDefaultCell(apiMember), 1096 ]), 1097 ); 1098 } 1099 break; 1100 } 1101 } 1102 } 1103 1104 if (eventsTable.rows.length > 0) { 1105 output.appendNode( 1106 new DocHeading({ 1107 configuration, 1108 title: 'Events', 1109 }), 1110 ); 1111 output.appendNode(eventsTable); 1112 } 1113 1114 if (propertiesTable.rows.length > 0) { 1115 output.appendNode( 1116 new DocHeading({ 1117 configuration, 1118 title: 'Properties', 1119 }), 1120 ); 1121 output.appendNode(propertiesTable); 1122 } 1123 1124 if (methodsTable.rows.length > 0) { 1125 output.appendNode( 1126 new DocHeading({ 1127 configuration, 1128 title: 'Methods', 1129 }), 1130 ); 1131 output.appendNode(methodsTable); 1132 } 1133 } 1134 1135 /** 1136 * GENERATE PAGE: FUNCTION-LIKE 1137 */ 1138 private _writeParameterTables( 1139 output: DocSection, 1140 apiParameterListMixin: ApiParameterListMixin, 1141 ): void { 1142 const configuration = this._tsdocConfiguration; 1143 1144 const parametersTable = new DocTable({ 1145 configuration, 1146 headerTitles: ['Parameter', 'Type', 'Description'], 1147 }); 1148 for (const apiParameter of apiParameterListMixin.parameters) { 1149 const parameterDescription = new DocSection({configuration}); 1150 1151 if (apiParameter.isOptional) { 1152 parameterDescription.appendNodesInParagraph([ 1153 new DocEmphasisSpan({configuration, italic: true}, [ 1154 new DocPlainText({configuration, text: '(Optional)'}), 1155 ]), 1156 new DocPlainText({configuration, text: ' '}), 1157 ]); 1158 } 1159 1160 if (apiParameter.tsdocParamBlock) { 1161 this._appendAndMergeSection( 1162 parameterDescription, 1163 apiParameter.tsdocParamBlock.content, 1164 ); 1165 } 1166 1167 parametersTable.addRow( 1168 new DocTableRow({configuration}, [ 1169 new DocTableCell({configuration}, [ 1170 new DocParagraph({configuration}, [ 1171 new DocPlainText({configuration, text: apiParameter.name}), 1172 ]), 1173 ]), 1174 new DocTableCell({configuration}, [ 1175 this._createParagraphForTypeExcerpt( 1176 apiParameter.parameterTypeExcerpt, 1177 ), 1178 ]), 1179 new DocTableCell({configuration}, parameterDescription.nodes), 1180 ]), 1181 ); 1182 } 1183 1184 if (parametersTable.rows.length > 0) { 1185 output.appendNode( 1186 new DocHeading({ 1187 configuration, 1188 title: 'Parameters', 1189 }), 1190 ); 1191 output.appendNode(parametersTable); 1192 } 1193 1194 if (ApiReturnTypeMixin.isBaseClassOf(apiParameterListMixin)) { 1195 const returnTypeExcerpt: Excerpt = 1196 apiParameterListMixin.returnTypeExcerpt; 1197 output.appendNode( 1198 new DocParagraph({configuration}, [ 1199 new DocEmphasisSpan({configuration, bold: true}, [ 1200 new DocPlainText({configuration, text: 'Returns:'}), 1201 ]), 1202 ]), 1203 ); 1204 1205 output.appendNode(this._createParagraphForTypeExcerpt(returnTypeExcerpt)); 1206 1207 if (apiParameterListMixin instanceof ApiDocumentedItem) { 1208 if ( 1209 apiParameterListMixin.tsdocComment && 1210 apiParameterListMixin.tsdocComment.returnsBlock 1211 ) { 1212 this._appendSection( 1213 output, 1214 apiParameterListMixin.tsdocComment.returnsBlock.content, 1215 ); 1216 } 1217 } 1218 } 1219 } 1220 1221 private _createParagraphForTypeExcerpt(excerpt: Excerpt): DocParagraph { 1222 const configuration = this._tsdocConfiguration; 1223 1224 const paragraph = new DocParagraph({configuration}); 1225 if (!excerpt.text.trim()) { 1226 paragraph.appendNode( 1227 new DocPlainText({configuration, text: '(not declared)'}), 1228 ); 1229 } else { 1230 this._appendExcerptWithHyperlinks(paragraph, excerpt); 1231 } 1232 1233 return paragraph; 1234 } 1235 1236 private _appendExcerptWithHyperlinks( 1237 docNodeContainer: DocNodeContainer, 1238 excerpt: Excerpt, 1239 ): void { 1240 for (const token of excerpt.spannedTokens) { 1241 this._appendExcerptTokenWithHyperlinks(docNodeContainer, token); 1242 } 1243 } 1244 1245 private _appendExcerptTokenWithHyperlinks( 1246 docNodeContainer: DocNodeContainer, 1247 token: ExcerptToken, 1248 ): void { 1249 const configuration = this._tsdocConfiguration; 1250 1251 // Markdown doesn't provide a standardized syntax for hyperlinks inside code 1252 // spans, so we will render the type expression as DocPlainText. Instead of 1253 // creating multiple DocParagraphs, we can simply discard any newlines and 1254 // let the renderer do normal word-wrapping. 1255 const unwrappedTokenText = token.text.replace(/[\r\n]+/g, ' '); 1256 1257 // If it's hyperlinkable, then append a DocLinkTag 1258 if (token.kind === ExcerptTokenKind.Reference && token.canonicalReference) { 1259 const apiItemResult: IResolveDeclarationReferenceResult = 1260 this._apiModel.resolveDeclarationReference( 1261 token.canonicalReference, 1262 undefined, 1263 ); 1264 1265 if (apiItemResult.resolvedApiItem) { 1266 docNodeContainer.appendNode( 1267 new DocLinkTag({ 1268 configuration, 1269 tagName: StandardTags.link.tagName, 1270 linkText: unwrappedTokenText, 1271 urlDestination: this._getLinkFilenameForApiItem( 1272 apiItemResult.resolvedApiItem, 1273 ), 1274 }), 1275 ); 1276 return; 1277 } 1278 } 1279 1280 // Otherwise append non-hyperlinked text 1281 docNodeContainer.appendNode( 1282 new DocPlainText({configuration, text: unwrappedTokenText}), 1283 ); 1284 } 1285 1286 private _createTitleCell(apiItem: ApiItem, plain = false): DocTableCell { 1287 const configuration = this._tsdocConfiguration; 1288 1289 const text = Utilities.getConciseSignature(apiItem); 1290 1291 return new DocTableCell({configuration}, [ 1292 new DocParagraph({configuration}, [ 1293 new DocHtmlStartTag({ 1294 configuration, 1295 name: 'span', 1296 htmlAttributes: [ 1297 new DocHtmlAttribute({ 1298 configuration, 1299 name: 'id', 1300 value: `"${Utilities.getSafeFilenameForName(apiItem.displayName)}"`, 1301 }), 1302 ], 1303 }), 1304 plain 1305 ? new DocPlainText({configuration, text}) 1306 : new DocLinkTag({ 1307 configuration, 1308 tagName: '@link', 1309 linkText: text, 1310 urlDestination: this._getLinkFilenameForApiItem(apiItem), 1311 }), 1312 new DocHtmlEndTag({ 1313 configuration, 1314 name: 'span', 1315 }), 1316 ]), 1317 ]); 1318 } 1319 1320 /** 1321 * This generates a DocTableCell for an ApiItem including the summary section 1322 * and "(BETA)" annotation. 1323 * 1324 * @remarks 1325 * We mostly assume that the input is an ApiDocumentedItem, but it's easier to 1326 * perform this as a runtime check than to have each caller perform a type 1327 * cast. 1328 */ 1329 private _createDescriptionCell(apiItem: ApiItem): DocTableCell { 1330 const configuration = this._tsdocConfiguration; 1331 1332 const section = new DocSection({configuration}); 1333 1334 if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) { 1335 if (apiItem.releaseTag === ReleaseTag.Beta) { 1336 section.appendNodesInParagraph([ 1337 new DocEmphasisSpan({configuration, bold: true, italic: true}, [ 1338 new DocPlainText({configuration, text: '(BETA)'}), 1339 ]), 1340 new DocPlainText({configuration, text: ' '}), 1341 ]); 1342 } 1343 } 1344 1345 if (apiItem instanceof ApiDocumentedItem) { 1346 const isExperimental = 1347 apiItem.tsdocComment?.modifierTagSet.isExperimental(); 1348 if (isExperimental) { 1349 section.appendNodesInParagraph([ 1350 new DocEmphasisSpan({configuration, bold: true, italic: true}, [ 1351 new DocPlainText({configuration, text: '(Experimental)'}), 1352 ]), 1353 new DocPlainText({configuration, text: ' '}), 1354 ]); 1355 } 1356 } 1357 1358 if (apiItem instanceof ApiDocumentedItem) { 1359 if (apiItem.tsdocComment !== undefined) { 1360 this._appendAndMergeSection( 1361 section, 1362 apiItem.tsdocComment.summarySection, 1363 ); 1364 1365 if (apiItem.tsdocComment.deprecatedBlock) { 1366 section.appendNode( 1367 new DocParagraph({configuration}, [ 1368 new DocEmphasisSpan({configuration, bold: true}, [ 1369 new DocPlainText({configuration, text: 'Deprecated: '}), 1370 ]), 1371 ]), 1372 ); 1373 1374 section.appendNodes( 1375 apiItem.tsdocComment.deprecatedBlock.content.getChildNodes(), 1376 ); 1377 } 1378 } 1379 } 1380 1381 if (apiItem instanceof ApiDocumentedItem) { 1382 const remarks = apiItem.tsdocComment?.remarksBlock; 1383 if (remarks) { 1384 section.appendNode( 1385 new DocParagraph({configuration}, [ 1386 new DocEmphasisSpan({configuration, bold: true}, [ 1387 new DocPlainText({configuration, text: 'Remarks: '}), 1388 ]), 1389 ]), 1390 ); 1391 1392 section.appendNodes(remarks.content.getChildNodes()); 1393 } 1394 } 1395 1396 return new DocTableCell({configuration}, section.nodes); 1397 } 1398 1399 private _createDefaultCell(apiItem: ApiItem): DocTableCell { 1400 const configuration = this._tsdocConfiguration; 1401 1402 if (apiItem instanceof ApiDocumentedItem) { 1403 const block = apiItem.tsdocComment?.customBlocks.find(block => { 1404 return ( 1405 block.blockTag.tagNameWithUpperCase === 1406 StandardTags.defaultValue.tagNameWithUpperCase 1407 ); 1408 }); 1409 if (block !== undefined) { 1410 return new DocTableCell({configuration}, block.content.getChildNodes()); 1411 } 1412 } 1413 1414 return new DocTableCell({configuration}, []); 1415 } 1416 1417 private _createModifiersCell(apiItem: ApiItem): DocTableCell { 1418 const configuration = this._tsdocConfiguration; 1419 1420 const section = new DocSection({configuration}); 1421 1422 const codes = []; 1423 1424 if (ApiProtectedMixin.isBaseClassOf(apiItem)) { 1425 if (apiItem.isProtected) { 1426 codes.push('protected'); 1427 } 1428 } 1429 1430 if (ApiReadonlyMixin.isBaseClassOf(apiItem)) { 1431 if (apiItem.isReadonly) { 1432 codes.push('readonly'); 1433 } 1434 } 1435 1436 if (ApiStaticMixin.isBaseClassOf(apiItem)) { 1437 if (apiItem.isStatic) { 1438 codes.push('static'); 1439 } 1440 } 1441 1442 if (ApiOptionalMixin.isBaseClassOf(apiItem)) { 1443 if (apiItem.isOptional) { 1444 codes.push('optional'); 1445 } 1446 } 1447 1448 if (apiItem instanceof ApiDocumentedItem) { 1449 if (apiItem.tsdocComment?.deprecatedBlock) { 1450 codes.push('deprecated'); 1451 } 1452 } 1453 if (codes.length) { 1454 section.appendNode( 1455 new DocParagraph({configuration}, [ 1456 new DocCodeSpan({configuration, code: codes.join(', ')}), 1457 ]), 1458 ); 1459 } 1460 1461 return new DocTableCell({configuration}, section.nodes); 1462 } 1463 1464 private _createPropertyTypeCell(apiItem: ApiItem): DocTableCell { 1465 const configuration = this._tsdocConfiguration; 1466 1467 const section = new DocSection({configuration}); 1468 1469 if (apiItem instanceof ApiPropertyItem) { 1470 section.appendNode( 1471 this._createParagraphForTypeExcerpt(apiItem.propertyTypeExcerpt), 1472 ); 1473 } 1474 1475 return new DocTableCell({configuration}, section.nodes); 1476 } 1477 1478 private _createInitializerCell(apiItem: ApiItem): DocTableCell { 1479 const configuration = this._tsdocConfiguration; 1480 1481 const section = new DocSection({configuration}); 1482 1483 if (ApiInitializerMixin.isBaseClassOf(apiItem)) { 1484 if (apiItem.initializerExcerpt) { 1485 section.appendNodeInParagraph( 1486 new DocCodeSpan({ 1487 configuration, 1488 code: apiItem.initializerExcerpt.text, 1489 }), 1490 ); 1491 } 1492 } 1493 1494 return new DocTableCell({configuration}, section.nodes); 1495 } 1496 1497 private _writeBetaWarning(output: DocSection): void { 1498 const configuration = this._tsdocConfiguration; 1499 const betaWarning = 1500 'This API is provided as a preview for developers and may change' + 1501 ' based on feedback that we receive. Do not use this API in a production environment.'; 1502 output.appendNode( 1503 new DocNoteBox({configuration}, [ 1504 new DocParagraph({configuration}, [ 1505 new DocPlainText({configuration, text: betaWarning}), 1506 ]), 1507 ]), 1508 ); 1509 } 1510 1511 private _appendSection(output: DocSection, docSection: DocSection): void { 1512 for (const node of docSection.nodes) { 1513 output.appendNode(node); 1514 } 1515 } 1516 1517 private _appendAndMergeSection( 1518 output: DocSection, 1519 docSection: DocSection, 1520 ): void { 1521 let firstNode = true; 1522 for (const node of docSection.nodes) { 1523 if (firstNode) { 1524 if (node.kind === DocNodeKind.Paragraph) { 1525 output.appendNodesInParagraph(node.getChildNodes()); 1526 firstNode = false; 1527 continue; 1528 } 1529 } 1530 firstNode = false; 1531 1532 output.appendNode(node); 1533 } 1534 } 1535 1536 private _getSidebarLabelForApiItem(apiItem: ApiItem): string { 1537 if (apiItem.kind === ApiItemKind.Package) { 1538 return 'API'; 1539 } 1540 1541 let baseName = ''; 1542 for (const hierarchyItem of apiItem.getHierarchy()) { 1543 // For overloaded methods, add a suffix such as "MyClass.myMethod_2". 1544 let qualifiedName = hierarchyItem.displayName; 1545 if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { 1546 if (hierarchyItem.overloadIndex > 1) { 1547 // Subtract one for compatibility with earlier releases of API Documenter. 1548 qualifiedName += `_${hierarchyItem.overloadIndex - 1}`; 1549 } 1550 } 1551 1552 switch (hierarchyItem.kind) { 1553 case ApiItemKind.Model: 1554 case ApiItemKind.EntryPoint: 1555 case ApiItemKind.EnumMember: 1556 case ApiItemKind.Package: 1557 break; 1558 default: 1559 baseName += qualifiedName + '.'; 1560 } 1561 } 1562 return baseName.slice(0, baseName.length - 1); 1563 } 1564 1565 private _getFilenameForApiItem(apiItem: ApiItem, link = false): string { 1566 if (apiItem.kind === ApiItemKind.Package) { 1567 return 'index.md'; 1568 } 1569 1570 let baseName = ''; 1571 let suffix = ''; 1572 for (const hierarchyItem of apiItem.getHierarchy()) { 1573 // For overloaded methods, add a suffix such as "MyClass.myMethod_2". 1574 const qualifiedName = Utilities.getSafeFilenameForName( 1575 hierarchyItem.displayName, 1576 ); 1577 1578 switch (hierarchyItem.kind) { 1579 case ApiItemKind.Model: 1580 case ApiItemKind.EntryPoint: 1581 case ApiItemKind.EnumMember: 1582 // Properties don't have separate pages 1583 case ApiItemKind.Property: 1584 case ApiItemKind.PropertySignature: 1585 break; 1586 case ApiItemKind.Package: 1587 baseName = Utilities.getSafeFilenameForName( 1588 PackageName.getUnscopedName(hierarchyItem.displayName), 1589 ); 1590 break; 1591 default: 1592 baseName += '.' + qualifiedName; 1593 } 1594 1595 if (link) { 1596 switch (hierarchyItem.kind) { 1597 case ApiItemKind.Property: 1598 case ApiItemKind.PropertySignature: 1599 suffix = 1600 '#' + 1601 Utilities.getSafeFilenameForName( 1602 PackageName.getUnscopedName(hierarchyItem.displayName), 1603 ); 1604 break; 1605 } 1606 } 1607 } 1608 1609 return `${baseName}.md${suffix}`; 1610 } 1611 1612 private _getLinkFilenameForApiItem(apiItem: ApiItem): string { 1613 return './' + this._getFilenameForApiItem(apiItem, true); 1614 } 1615 1616 private _deleteOldOutputFiles(): void { 1617 console.log('Deleting old output from ' + this._outputFolder); 1618 FileSystem.ensureEmptyFolder(this._outputFolder); 1619 } 1620 }