UNPKG

@microsoft/api-documenter

Version:

Read JSON files from api-extractor, generate documentation pages

1,050 lines 52.1 kB
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MarkdownDocumenter = void 0;
const path = __importStar(require("node:path"));
const node_core_library_1 = require("@rushstack/node-core-library");
const tsdoc_1 = require("@microsoft/tsdoc");
const api_extractor_model_1 = require("@microsoft/api-extractor-model");
const CustomDocNodeKind_1 = require("../nodes/CustomDocNodeKind");
const DocHeading_1 = require("../nodes/DocHeading");
const DocTable_1 = require("../nodes/DocTable");
const DocEmphasisSpan_1 = require("../nodes/DocEmphasisSpan");
const DocTableRow_1 = require("../nodes/DocTableRow");
const DocTableCell_1 = require("../nodes/DocTableCell");
const DocNoteBox_1 = require("../nodes/DocNoteBox");
const Utilities_1 = require("../utils/Utilities");
const CustomMarkdownEmitter_1 = require("../markdown/CustomMarkdownEmitter");
const PluginLoader_1 = require("../plugin/PluginLoader");
const MarkdownDocumenterFeature_1 = require("../plugin/MarkdownDocumenterFeature");
const MarkdownDocumenterAccessor_1 = require("../plugin/MarkdownDocumenterAccessor");
/**
 * Renders API documentation in the Markdown file format.
 * For more info:  https://en.wikipedia.org/wiki/Markdown
 */
class MarkdownDocumenter {
    _apiModel;
    _documenterConfig;
    _tsdocConfiguration;
    _markdownEmitter;
    _outputFolder;
    _pluginLoader;
    constructor(options) {
        this._apiModel = options.apiModel;
        this._documenterConfig = options.documenterConfig;
        this._outputFolder = options.outputFolder;
        this._tsdocConfiguration = CustomDocNodeKind_1.CustomDocNodes.configuration;
        this._markdownEmitter = new CustomMarkdownEmitter_1.CustomMarkdownEmitter(this._apiModel);
        this._pluginLoader = new PluginLoader_1.PluginLoader();
    }
    generateFiles() {
        if (this._documenterConfig) {
            this._pluginLoader.load(this._documenterConfig, () => {
                return new MarkdownDocumenterFeature_1.MarkdownDocumenterFeatureContext({
                    apiModel: this._apiModel,
                    outputFolder: this._outputFolder,
                    documenter: new MarkdownDocumenterAccessor_1.MarkdownDocumenterAccessor({
                        getLinkForApiItem: (apiItem) => {
                            return this._getLinkFilenameForApiItem(apiItem);
                        }
                    })
                });
            });
        }
        console.log();
        this._deleteOldOutputFiles();
        this._writeApiItemPage(this._apiModel);
        if (this._pluginLoader.markdownDocumenterFeature) {
            this._pluginLoader.markdownDocumenterFeature.onFinished({});
        }
    }
    _writeApiItemPage(apiItem) {
        const configuration = this._tsdocConfiguration;
        const output = new tsdoc_1.DocSection({ configuration });
        this._writeBreadcrumb(output, apiItem);
        const scopedName = apiItem.getScopedNameWithinPackage();
        switch (apiItem.kind) {
            case api_extractor_model_1.ApiItemKind.Class:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} class` }));
                break;
            case api_extractor_model_1.ApiItemKind.Enum:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} enum` }));
                break;
            case api_extractor_model_1.ApiItemKind.Interface:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} interface` }));
                break;
            case api_extractor_model_1.ApiItemKind.Constructor:
            case api_extractor_model_1.ApiItemKind.ConstructSignature:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: scopedName }));
                break;
            case api_extractor_model_1.ApiItemKind.Method:
            case api_extractor_model_1.ApiItemKind.MethodSignature:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} method` }));
                break;
            case api_extractor_model_1.ApiItemKind.Function:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} function` }));
                break;
            case api_extractor_model_1.ApiItemKind.Model:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `API Reference` }));
                break;
            case api_extractor_model_1.ApiItemKind.Namespace:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} namespace` }));
                break;
            case api_extractor_model_1.ApiItemKind.Package:
                console.log(`Writing ${apiItem.displayName} package`);
                const unscopedPackageName = node_core_library_1.PackageName.getUnscopedName(apiItem.displayName);
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${unscopedPackageName} package` }));
                break;
            case api_extractor_model_1.ApiItemKind.Property:
            case api_extractor_model_1.ApiItemKind.PropertySignature:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} property` }));
                break;
            case api_extractor_model_1.ApiItemKind.TypeAlias:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} type` }));
                break;
            case api_extractor_model_1.ApiItemKind.Variable:
                output.appendNode(new DocHeading_1.DocHeading({ configuration, title: `${scopedName} variable` }));
                break;
            default:
                throw new Error('Unsupported API item kind: ' + apiItem.kind);
        }
        if (api_extractor_model_1.ApiReleaseTagMixin.isBaseClassOf(apiItem)) {
            if (apiItem.releaseTag === api_extractor_model_1.ReleaseTag.Alpha) {
                this._writeAlphaWarning(output);
            }
            else if (apiItem.releaseTag === api_extractor_model_1.ReleaseTag.Beta) {
                this._writeBetaWarning(output);
            }
        }
        const decoratorBlocks = [];
        if (apiItem instanceof api_extractor_model_1.ApiDocumentedItem) {
            const tsdocComment = apiItem.tsdocComment;
            if (tsdocComment) {
                decoratorBlocks.push(...tsdocComment.customBlocks.filter((block) => block.blockTag.tagNameWithUpperCase === tsdoc_1.StandardTags.decorator.tagNameWithUpperCase));
                if (tsdocComment.deprecatedBlock) {
                    output.appendNode(new DocNoteBox_1.DocNoteBox({ configuration }, [
                        new tsdoc_1.DocParagraph({ configuration }, [
                            new tsdoc_1.DocPlainText({
                                configuration,
                                text: 'Warning: This API is now obsolete. '
                            })
                        ]),
                        ...tsdocComment.deprecatedBlock.content.nodes
                    ]));
                }
                this._appendSection(output, tsdocComment.summarySection);
            }
        }
        if (apiItem instanceof api_extractor_model_1.ApiDeclaredItem) {
            if (apiItem.excerpt.text.length > 0) {
                output.appendNode(new tsdoc_1.DocParagraph({ configuration }, [
                    new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, bold: true }, [
                        new tsdoc_1.DocPlainText({ configuration, text: 'Signature:' })
                    ])
                ]));
                output.appendNode(new tsdoc_1.DocFencedCode({
                    configuration,
                    code: apiItem.getExcerptWithModifiers(),
                    language: 'typescript'
                }));
            }
            this._writeHeritageTypes(output, apiItem);
        }
        if (decoratorBlocks.length > 0) {
            output.appendNode(new tsdoc_1.DocParagraph({ configuration }, [
                new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, bold: true }, [
                    new tsdoc_1.DocPlainText({ configuration, text: 'Decorators:' })
                ])
            ]));
            for (const decoratorBlock of decoratorBlocks) {
                output.appendNodes(decoratorBlock.content.nodes);
            }
        }
        let appendRemarks = true;
        switch (apiItem.kind) {
            case api_extractor_model_1.ApiItemKind.Class:
            case api_extractor_model_1.ApiItemKind.Interface:
            case api_extractor_model_1.ApiItemKind.Namespace:
            case api_extractor_model_1.ApiItemKind.Package:
                this._writeRemarksSection(output, apiItem);
                appendRemarks = false;
                break;
        }
        switch (apiItem.kind) {
            case api_extractor_model_1.ApiItemKind.Class:
                this._writeClassTables(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Enum:
                this._writeEnumTables(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Interface:
                this._writeInterfaceTables(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Constructor:
            case api_extractor_model_1.ApiItemKind.ConstructSignature:
            case api_extractor_model_1.ApiItemKind.Method:
            case api_extractor_model_1.ApiItemKind.MethodSignature:
            case api_extractor_model_1.ApiItemKind.Function:
                this._writeParameterTables(output, apiItem);
                this._writeThrowsSection(output, apiItem);
                this._writeDefaultValueSection(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Namespace:
                this._writePackageOrNamespaceTables(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Model:
                this._writeModelTable(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Package:
                this._writePackageOrNamespaceTables(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Property:
            case api_extractor_model_1.ApiItemKind.PropertySignature:
                this._writeDefaultValueSection(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.TypeAlias:
                this._writeDefaultValueSection(output, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Variable:
                this._writeDefaultValueSection(output, apiItem);
                break;
            default:
                throw new Error('Unsupported API item kind: ' + apiItem.kind);
        }
        if (appendRemarks) {
            this._writeRemarksSection(output, apiItem);
        }
        const filename = path.join(this._outputFolder, this._getFilenameForApiItem(apiItem));
        const stringBuilder = new tsdoc_1.StringBuilder();
        stringBuilder.append('<!-- Do not edit this file. It is automatically generated by API Documenter. -->\n\n');
        this._markdownEmitter.emit(stringBuilder, output, {
            contextApiItem: apiItem,
            onGetFilenameForApiItem: (apiItemForFilename) => {
                return this._getLinkFilenameForApiItem(apiItemForFilename);
            }
        });
        let pageContent = stringBuilder.toString();
        if (this._pluginLoader.markdownDocumenterFeature) {
            // Allow the plugin to customize the pageContent
            const eventArgs = {
                apiItem: apiItem,
                outputFilename: filename,
                pageContent: pageContent
            };
            this._pluginLoader.markdownDocumenterFeature.onBeforeWritePage(eventArgs);
            pageContent = eventArgs.pageContent;
        }
        node_core_library_1.FileSystem.writeFile(filename, pageContent, {
            convertLineEndings: this._documenterConfig ? this._documenterConfig.newlineKind : node_core_library_1.NewlineKind.CrLf
        });
    }
    _writeHeritageTypes(output, apiItem) {
        const configuration = this._tsdocConfiguration;
        if (apiItem instanceof api_extractor_model_1.ApiClass) {
            if (apiItem.extendsType) {
                const extendsParagraph = new tsdoc_1.DocParagraph({ configuration }, [
                    new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, bold: true }, [
                        new tsdoc_1.DocPlainText({ configuration, text: 'Extends: ' })
                    ])
                ]);
                this._appendExcerptWithHyperlinks(extendsParagraph, apiItem.extendsType.excerpt);
                output.appendNode(extendsParagraph);
            }
            if (apiItem.implementsTypes.length > 0) {
                const implementsParagraph = new tsdoc_1.DocParagraph({ configuration }, [
                    new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, bold: true }, [
                        new tsdoc_1.DocPlainText({ configuration, text: 'Implements: ' })
                    ])
                ]);
                let needsComma = false;
                for (const implementsType of apiItem.implementsTypes) {
                    if (needsComma) {
                        implementsParagraph.appendNode(new tsdoc_1.DocPlainText({ configuration, text: ', ' }));
                    }
                    this._appendExcerptWithHyperlinks(implementsParagraph, implementsType.excerpt);
                    needsComma = true;
                }
                output.appendNode(implementsParagraph);
            }
        }
        if (apiItem instanceof api_extractor_model_1.ApiInterface) {
            if (apiItem.extendsTypes.length > 0) {
                const extendsParagraph = new tsdoc_1.DocParagraph({ configuration }, [
                    new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, bold: true }, [
                        new tsdoc_1.DocPlainText({ configuration, text: 'Extends: ' })
                    ])
                ]);
                let needsComma = false;
                for (const extendsType of apiItem.extendsTypes) {
                    if (needsComma) {
                        extendsParagraph.appendNode(new tsdoc_1.DocPlainText({ configuration, text: ', ' }));
                    }
                    this._appendExcerptWithHyperlinks(extendsParagraph, extendsType.excerpt);
                    needsComma = true;
                }
                output.appendNode(extendsParagraph);
            }
        }
        if (apiItem instanceof api_extractor_model_1.ApiTypeAlias) {
            const refs = apiItem.excerptTokens.filter((token) => token.kind === api_extractor_model_1.ExcerptTokenKind.Reference &&
                token.canonicalReference &&
                this._apiModel.resolveDeclarationReference(token.canonicalReference, undefined).resolvedApiItem);
            if (refs.length > 0) {
                const referencesParagraph = new tsdoc_1.DocParagraph({ configuration }, [
                    new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, bold: true }, [
                        new tsdoc_1.DocPlainText({ configuration, text: 'References: ' })
                    ])
                ]);
                let needsComma = false;
                const visited = new Set();
                for (const ref of refs) {
                    if (visited.has(ref.text)) {
                        continue;
                    }
                    visited.add(ref.text);
                    if (needsComma) {
                        referencesParagraph.appendNode(new tsdoc_1.DocPlainText({ configuration, text: ', ' }));
                    }
                    this._appendExcerptTokenWithHyperlinks(referencesParagraph, ref);
                    needsComma = true;
                }
                output.appendNode(referencesParagraph);
            }
        }
    }
    _writeRemarksSection(output, apiItem) {
        const configuration = this._tsdocConfiguration;
        if (apiItem instanceof api_extractor_model_1.ApiDocumentedItem) {
            const tsdocComment = apiItem.tsdocComment;
            if (tsdocComment) {
                // Write the @remarks block
                if (tsdocComment.remarksBlock) {
                    output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Remarks' }));
                    this._appendSection(output, tsdocComment.remarksBlock.content);
                }
                // Write the @example blocks
                const exampleBlocks = tsdocComment.customBlocks.filter((x) => x.blockTag.tagNameWithUpperCase === tsdoc_1.StandardTags.example.tagNameWithUpperCase);
                let exampleNumber = 1;
                for (const exampleBlock of exampleBlocks) {
                    const heading = exampleBlocks.length > 1 ? `Example ${exampleNumber}` : 'Example';
                    output.appendNode(new DocHeading_1.DocHeading({ configuration, title: heading }));
                    this._appendSection(output, exampleBlock.content);
                    ++exampleNumber;
                }
            }
        }
    }
    _writeThrowsSection(output, apiItem) {
        const configuration = this._tsdocConfiguration;
        if (apiItem instanceof api_extractor_model_1.ApiDocumentedItem) {
            const tsdocComment = apiItem.tsdocComment;
            if (tsdocComment) {
                // Write the @throws blocks
                const throwsBlocks = tsdocComment.customBlocks.filter((x) => x.blockTag.tagNameWithUpperCase === tsdoc_1.StandardTags.throws.tagNameWithUpperCase);
                if (throwsBlocks.length > 0) {
                    const heading = 'Exceptions';
                    output.appendNode(new DocHeading_1.DocHeading({ configuration, title: heading }));
                    for (const throwsBlock of throwsBlocks) {
                        this._appendSection(output, throwsBlock.content);
                    }
                }
            }
        }
    }
    _writeDefaultValueSection(output, apiItem) {
        const configuration = this._tsdocConfiguration;
        if (apiItem instanceof api_extractor_model_1.ApiDocumentedItem) {
            const tsdocComment = apiItem.tsdocComment;
            if (tsdocComment) {
                // Write the @defaultValue blocks
                const defaultValueBlocks = tsdocComment.customBlocks.filter((x) => x.blockTag.tagNameWithUpperCase === tsdoc_1.StandardTags.defaultValue.tagNameWithUpperCase);
                if (defaultValueBlocks.length > 0) {
                    const heading = 'Default Value';
                    output.appendNode(new DocHeading_1.DocHeading({ configuration, title: heading }));
                    for (const defaultValueBlock of defaultValueBlocks) {
                        this._appendSection(output, defaultValueBlock.content);
                    }
                }
            }
        }
    }
    /**
     * GENERATE PAGE: MODEL
     */
    _writeModelTable(output, apiModel) {
        const configuration = this._tsdocConfiguration;
        const packagesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Package', 'Description']
        });
        for (const apiMember of apiModel.members) {
            const row = new DocTableRow_1.DocTableRow({ configuration }, [
                this._createTitleCell(apiMember),
                this._createDescriptionCell(apiMember)
            ]);
            switch (apiMember.kind) {
                case api_extractor_model_1.ApiItemKind.Package:
                    packagesTable.addRow(row);
                    this._writeApiItemPage(apiMember);
                    break;
            }
        }
        if (packagesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Packages' }));
            output.appendNode(packagesTable);
        }
    }
    /**
     * GENERATE PAGE: PACKAGE or NAMESPACE
     */
    _writePackageOrNamespaceTables(output, apiContainer) {
        const configuration = this._tsdocConfiguration;
        const abstractClassesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Abstract Class', 'Description']
        });
        const classesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Class', 'Description']
        });
        const enumerationsTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Enumeration', 'Description']
        });
        const functionsTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Function', 'Description']
        });
        const interfacesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Interface', 'Description']
        });
        const namespacesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Namespace', 'Description']
        });
        const variablesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Variable', 'Description']
        });
        const typeAliasesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Type Alias', 'Description']
        });
        const apiMembers = apiContainer.kind === api_extractor_model_1.ApiItemKind.Package
            ? apiContainer.entryPoints[0].members
            : apiContainer.members;
        for (const apiMember of apiMembers) {
            const row = new DocTableRow_1.DocTableRow({ configuration }, [
                this._createTitleCell(apiMember),
                this._createDescriptionCell(apiMember)
            ]);
            switch (apiMember.kind) {
                case api_extractor_model_1.ApiItemKind.Class:
                    if (api_extractor_model_1.ApiAbstractMixin.isBaseClassOf(apiMember) && apiMember.isAbstract) {
                        abstractClassesTable.addRow(row);
                    }
                    else {
                        classesTable.addRow(row);
                    }
                    this._writeApiItemPage(apiMember);
                    break;
                case api_extractor_model_1.ApiItemKind.Enum:
                    enumerationsTable.addRow(row);
                    this._writeApiItemPage(apiMember);
                    break;
                case api_extractor_model_1.ApiItemKind.Interface:
                    interfacesTable.addRow(row);
                    this._writeApiItemPage(apiMember);
                    break;
                case api_extractor_model_1.ApiItemKind.Namespace:
                    namespacesTable.addRow(row);
                    this._writeApiItemPage(apiMember);
                    break;
                case api_extractor_model_1.ApiItemKind.Function:
                    functionsTable.addRow(row);
                    this._writeApiItemPage(apiMember);
                    break;
                case api_extractor_model_1.ApiItemKind.TypeAlias:
                    typeAliasesTable.addRow(row);
                    this._writeApiItemPage(apiMember);
                    break;
                case api_extractor_model_1.ApiItemKind.Variable:
                    variablesTable.addRow(row);
                    this._writeApiItemPage(apiMember);
                    break;
            }
        }
        if (classesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Classes' }));
            output.appendNode(classesTable);
        }
        if (abstractClassesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Abstract Classes' }));
            output.appendNode(abstractClassesTable);
        }
        if (enumerationsTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Enumerations' }));
            output.appendNode(enumerationsTable);
        }
        if (functionsTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Functions' }));
            output.appendNode(functionsTable);
        }
        if (interfacesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Interfaces' }));
            output.appendNode(interfacesTable);
        }
        if (namespacesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Namespaces' }));
            output.appendNode(namespacesTable);
        }
        if (variablesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Variables' }));
            output.appendNode(variablesTable);
        }
        if (typeAliasesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Type Aliases' }));
            output.appendNode(typeAliasesTable);
        }
    }
    /**
     * GENERATE PAGE: CLASS
     */
    _writeClassTables(output, apiClass) {
        const configuration = this._tsdocConfiguration;
        const eventsTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Property', 'Modifiers', 'Type', 'Description']
        });
        const constructorsTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Constructor', 'Modifiers', 'Description']
        });
        const propertiesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Property', 'Modifiers', 'Type', 'Description']
        });
        const methodsTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Method', 'Modifiers', 'Description']
        });
        const apiMembers = this._getMembersAndWriteIncompleteWarning(apiClass, output);
        for (const apiMember of apiMembers) {
            const isInherited = apiMember.parent !== apiClass;
            switch (apiMember.kind) {
                case api_extractor_model_1.ApiItemKind.Constructor: {
                    constructorsTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                        this._createTitleCell(apiMember),
                        this._createModifiersCell(apiMember),
                        this._createDescriptionCell(apiMember, isInherited)
                    ]));
                    this._writeApiItemPage(apiMember);
                    break;
                }
                case api_extractor_model_1.ApiItemKind.Method: {
                    methodsTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                        this._createTitleCell(apiMember),
                        this._createModifiersCell(apiMember),
                        this._createDescriptionCell(apiMember, isInherited)
                    ]));
                    this._writeApiItemPage(apiMember);
                    break;
                }
                case api_extractor_model_1.ApiItemKind.Property: {
                    if (apiMember.isEventProperty) {
                        eventsTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                            this._createTitleCell(apiMember),
                            this._createModifiersCell(apiMember),
                            this._createPropertyTypeCell(apiMember),
                            this._createDescriptionCell(apiMember, isInherited)
                        ]));
                    }
                    else {
                        propertiesTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                            this._createTitleCell(apiMember),
                            this._createModifiersCell(apiMember),
                            this._createPropertyTypeCell(apiMember),
                            this._createDescriptionCell(apiMember, isInherited)
                        ]));
                    }
                    this._writeApiItemPage(apiMember);
                    break;
                }
            }
        }
        if (eventsTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Events' }));
            output.appendNode(eventsTable);
        }
        if (constructorsTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Constructors' }));
            output.appendNode(constructorsTable);
        }
        if (propertiesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Properties' }));
            output.appendNode(propertiesTable);
        }
        if (methodsTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Methods' }));
            output.appendNode(methodsTable);
        }
    }
    /**
     * GENERATE PAGE: ENUM
     */
    _writeEnumTables(output, apiEnum) {
        const configuration = this._tsdocConfiguration;
        const enumMembersTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Member', 'Value', 'Description']
        });
        for (const apiEnumMember of apiEnum.members) {
            enumMembersTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                new DocTableCell_1.DocTableCell({ configuration }, [
                    new tsdoc_1.DocParagraph({ configuration }, [
                        new tsdoc_1.DocPlainText({ configuration, text: Utilities_1.Utilities.getConciseSignature(apiEnumMember) })
                    ])
                ]),
                this._createInitializerCell(apiEnumMember),
                this._createDescriptionCell(apiEnumMember)
            ]));
        }
        if (enumMembersTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Enumeration Members' }));
            output.appendNode(enumMembersTable);
        }
    }
    /**
     * GENERATE PAGE: INTERFACE
     */
    _writeInterfaceTables(output, apiInterface) {
        const configuration = this._tsdocConfiguration;
        const eventsTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Property', 'Modifiers', 'Type', 'Description']
        });
        const propertiesTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Property', 'Modifiers', 'Type', 'Description']
        });
        const methodsTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Method', 'Description']
        });
        const apiMembers = this._getMembersAndWriteIncompleteWarning(apiInterface, output);
        for (const apiMember of apiMembers) {
            const isInherited = apiMember.parent !== apiInterface;
            switch (apiMember.kind) {
                case api_extractor_model_1.ApiItemKind.ConstructSignature:
                case api_extractor_model_1.ApiItemKind.MethodSignature: {
                    methodsTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                        this._createTitleCell(apiMember),
                        this._createDescriptionCell(apiMember, isInherited)
                    ]));
                    this._writeApiItemPage(apiMember);
                    break;
                }
                case api_extractor_model_1.ApiItemKind.PropertySignature: {
                    if (apiMember.isEventProperty) {
                        eventsTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                            this._createTitleCell(apiMember),
                            this._createModifiersCell(apiMember),
                            this._createPropertyTypeCell(apiMember),
                            this._createDescriptionCell(apiMember, isInherited)
                        ]));
                    }
                    else {
                        propertiesTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                            this._createTitleCell(apiMember),
                            this._createModifiersCell(apiMember),
                            this._createPropertyTypeCell(apiMember),
                            this._createDescriptionCell(apiMember, isInherited)
                        ]));
                    }
                    this._writeApiItemPage(apiMember);
                    break;
                }
            }
        }
        if (eventsTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Events' }));
            output.appendNode(eventsTable);
        }
        if (propertiesTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Properties' }));
            output.appendNode(propertiesTable);
        }
        if (methodsTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Methods' }));
            output.appendNode(methodsTable);
        }
    }
    /**
     * GENERATE PAGE: FUNCTION-LIKE
     */
    _writeParameterTables(output, apiParameterListMixin) {
        const configuration = this._tsdocConfiguration;
        const parametersTable = new DocTable_1.DocTable({
            configuration,
            headerTitles: ['Parameter', 'Type', 'Description']
        });
        for (const apiParameter of apiParameterListMixin.parameters) {
            const parameterDescription = new tsdoc_1.DocSection({ configuration });
            if (apiParameter.isOptional) {
                parameterDescription.appendNodesInParagraph([
                    new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, italic: true }, [
                        new tsdoc_1.DocPlainText({ configuration, text: '(Optional)' })
                    ]),
                    new tsdoc_1.DocPlainText({ configuration, text: ' ' })
                ]);
            }
            if (apiParameter.tsdocParamBlock) {
                this._appendAndMergeSection(parameterDescription, apiParameter.tsdocParamBlock.content);
            }
            parametersTable.addRow(new DocTableRow_1.DocTableRow({ configuration }, [
                new DocTableCell_1.DocTableCell({ configuration }, [
                    new tsdoc_1.DocParagraph({ configuration }, [
                        new tsdoc_1.DocPlainText({ configuration, text: apiParameter.name })
                    ])
                ]),
                new DocTableCell_1.DocTableCell({ configuration }, [
                    this._createParagraphForTypeExcerpt(apiParameter.parameterTypeExcerpt)
                ]),
                new DocTableCell_1.DocTableCell({ configuration }, parameterDescription.nodes)
            ]));
        }
        if (parametersTable.rows.length > 0) {
            output.appendNode(new DocHeading_1.DocHeading({ configuration, title: 'Parameters' }));
            output.appendNode(parametersTable);
        }
        if (api_extractor_model_1.ApiReturnTypeMixin.isBaseClassOf(apiParameterListMixin)) {
            const returnTypeExcerpt = apiParameterListMixin.returnTypeExcerpt;
            output.appendNode(new tsdoc_1.DocParagraph({ configuration }, [
                new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, bold: true }, [
                    new tsdoc_1.DocPlainText({ configuration, text: 'Returns:' })
                ])
            ]));
            output.appendNode(this._createParagraphForTypeExcerpt(returnTypeExcerpt));
            if (apiParameterListMixin instanceof api_extractor_model_1.ApiDocumentedItem) {
                if (apiParameterListMixin.tsdocComment && apiParameterListMixin.tsdocComment.returnsBlock) {
                    this._appendSection(output, apiParameterListMixin.tsdocComment.returnsBlock.content);
                }
            }
        }
    }
    _createParagraphForTypeExcerpt(excerpt) {
        const configuration = this._tsdocConfiguration;
        const paragraph = new tsdoc_1.DocParagraph({ configuration });
        if (!excerpt.text.trim()) {
            paragraph.appendNode(new tsdoc_1.DocPlainText({ configuration, text: '(not declared)' }));
        }
        else {
            this._appendExcerptWithHyperlinks(paragraph, excerpt);
        }
        return paragraph;
    }
    _appendExcerptWithHyperlinks(docNodeContainer, excerpt) {
        for (const token of excerpt.spannedTokens) {
            this._appendExcerptTokenWithHyperlinks(docNodeContainer, token);
        }
    }
    _appendExcerptTokenWithHyperlinks(docNodeContainer, token) {
        const configuration = this._tsdocConfiguration;
        // Markdown doesn't provide a standardized syntax for hyperlinks inside code spans, so we will render
        // the type expression as DocPlainText.  Instead of creating multiple DocParagraphs, we can simply
        // discard any newlines and let the renderer do normal word-wrapping.
        const unwrappedTokenText = token.text.replace(/[\r\n]+/g, ' ');
        // If it's hyperlinkable, then append a DocLinkTag
        if (token.kind === api_extractor_model_1.ExcerptTokenKind.Reference && token.canonicalReference) {
            const apiItemResult = this._apiModel.resolveDeclarationReference(token.canonicalReference, undefined);
            if (apiItemResult.resolvedApiItem) {
                docNodeContainer.appendNode(new tsdoc_1.DocLinkTag({
                    configuration,
                    tagName: '@link',
                    linkText: unwrappedTokenText,
                    urlDestination: this._getLinkFilenameForApiItem(apiItemResult.resolvedApiItem)
                }));
                return;
            }
        }
        // Otherwise append non-hyperlinked text
        docNodeContainer.appendNode(new tsdoc_1.DocPlainText({ configuration, text: unwrappedTokenText }));
    }
    _createTitleCell(apiItem) {
        const configuration = this._tsdocConfiguration;
        let linkText = Utilities_1.Utilities.getConciseSignature(apiItem);
        if (api_extractor_model_1.ApiOptionalMixin.isBaseClassOf(apiItem) && apiItem.isOptional) {
            linkText += '?';
        }
        return new DocTableCell_1.DocTableCell({ configuration }, [
            new tsdoc_1.DocParagraph({ configuration }, [
                new tsdoc_1.DocLinkTag({
                    configuration,
                    tagName: '@link',
                    linkText: linkText,
                    urlDestination: this._getLinkFilenameForApiItem(apiItem)
                })
            ])
        ]);
    }
    /**
     * This generates a DocTableCell for an ApiItem including the summary section and "(BETA)" annotation.
     *
     * @remarks
     * We mostly assume that the input is an ApiDocumentedItem, but it's easier to perform this as a runtime
     * check than to have each caller perform a type cast.
     */
    _createDescriptionCell(apiItem, isInherited = false) {
        const configuration = this._tsdocConfiguration;
        const section = new tsdoc_1.DocSection({ configuration });
        if (api_extractor_model_1.ApiReleaseTagMixin.isBaseClassOf(apiItem)) {
            if (apiItem.releaseTag === api_extractor_model_1.ReleaseTag.Alpha || apiItem.releaseTag === api_extractor_model_1.ReleaseTag.Beta) {
                section.appendNodesInParagraph([
                    new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, bold: true, italic: true }, [
                        new tsdoc_1.DocPlainText({
                            configuration,
                            text: `(${apiItem.releaseTag === api_extractor_model_1.ReleaseTag.Alpha ? 'ALPHA' : 'BETA'})`
                        })
                    ]),
                    new tsdoc_1.DocPlainText({ configuration, text: ' ' })
                ]);
            }
        }
        if (api_extractor_model_1.ApiOptionalMixin.isBaseClassOf(apiItem) && apiItem.isOptional) {
            section.appendNodesInParagraph([
                new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, italic: true }, [
                    new tsdoc_1.DocPlainText({ configuration, text: '(Optional)' })
                ]),
                new tsdoc_1.DocPlainText({ configuration, text: ' ' })
            ]);
        }
        if (apiItem instanceof api_extractor_model_1.ApiDocumentedItem) {
            if (apiItem.tsdocComment !== undefined) {
                this._appendAndMergeSection(section, apiItem.tsdocComment.summarySection);
            }
        }
        if (isInherited && apiItem.parent) {
            section.appendNode(new tsdoc_1.DocParagraph({ configuration }, [
                new tsdoc_1.DocPlainText({ configuration, text: '(Inherited from ' }),
                new tsdoc_1.DocLinkTag({
                    configuration,
                    tagName: '@link',
                    linkText: apiItem.parent.displayName,
                    urlDestination: this._getLinkFilenameForApiItem(apiItem.parent)
                }),
                new tsdoc_1.DocPlainText({ configuration, text: ')' })
            ]));
        }
        return new DocTableCell_1.DocTableCell({ configuration }, section.nodes);
    }
    _createModifiersCell(apiItem) {
        const configuration = this._tsdocConfiguration;
        const section = new tsdoc_1.DocSection({ configuration });
        // Output modifiers in syntactically correct order: first access modifier (here: `protected`), then
        // `static` or `abstract` (no member can be both, so the order between the two of them does not matter),
        // last `readonly`. If `override` was supported, it would go directly before `readonly`.
        if (api_extractor_model_1.ApiProtectedMixin.isBaseClassOf(apiItem)) {
            if (apiItem.isProtected) {
                section.appendNode(new tsdoc_1.DocParagraph({ configuration }, [new tsdoc_1.DocCodeSpan({ configuration, code: 'protected' })]));
            }
        }
        if (api_extractor_model_1.ApiStaticMixin.isBaseClassOf(apiItem)) {
            if (apiItem.isStatic) {
                section.appendNode(new tsdoc_1.DocParagraph({ configuration }, [new tsdoc_1.DocCodeSpan({ configuration, code: 'static' })]));
            }
        }
        if (api_extractor_model_1.ApiAbstractMixin.isBaseClassOf(apiItem)) {
            if (apiItem.isAbstract) {
                section.appendNode(new tsdoc_1.DocParagraph({ configuration }, [new tsdoc_1.DocCodeSpan({ configuration, code: 'abstract' })]));
            }
        }
        if (api_extractor_model_1.ApiReadonlyMixin.isBaseClassOf(apiItem)) {
            if (apiItem.isReadonly) {
                section.appendNode(new tsdoc_1.DocParagraph({ configuration }, [new tsdoc_1.DocCodeSpan({ configuration, code: 'readonly' })]));
            }
        }
        return new DocTableCell_1.DocTableCell({ configuration }, section.nodes);
    }
    _createPropertyTypeCell(apiItem) {
        const configuration = this._tsdocConfiguration;
        const section = new tsdoc_1.DocSection({ configuration });
        if (apiItem instanceof api_extractor_model_1.ApiPropertyItem) {
            section.appendNode(this._createParagraphForTypeExcerpt(apiItem.propertyTypeExcerpt));
        }
        return new DocTableCell_1.DocTableCell({ configuration }, section.nodes);
    }
    _createInitializerCell(apiItem) {
        const configuration = this._tsdocConfiguration;
        const section = new tsdoc_1.DocSection({ configuration });
        if (api_extractor_model_1.ApiInitializerMixin.isBaseClassOf(apiItem)) {
            if (apiItem.initializerExcerpt) {
                section.appendNodeInParagraph(new tsdoc_1.DocCodeSpan({ configuration, code: apiItem.initializerExcerpt.text }));
            }
        }
        return new DocTableCell_1.DocTableCell({ configuration }, section.nodes);
    }
    _writeBreadcrumb(output, apiItem) {
        const configuration = this._tsdocConfiguration;
        output.appendNodeInParagraph(new tsdoc_1.DocLinkTag({
            configuration,
            tagName: '@link',
            linkText: 'Home',
            urlDestination: this._getLinkFilenameForApiItem(this._apiModel)
        }));
        for (const hierarchyItem of apiItem.getHierarchy()) {
            switch (hierarchyItem.kind) {
                case api_extractor_model_1.ApiItemKind.Model:
                case api_extractor_model_1.ApiItemKind.EntryPoint:
                    // We don't show the model as part of the breadcrumb because it is the root-level container.
                    // We don't show the entry point because today API Extractor doesn't support multiple entry points;
                    // this may change in the future.
                    break;
                default:
                    output.appendNodesInParagraph([
                        new tsdoc_1.DocPlainText({
                            configuration,
                            text: ' > '
                        }),
                        new tsdoc_1.DocLinkTag({
                            configuration,
                            tagName: '@link',
                            linkText: hierarchyItem.displayName,
                            urlDestination: this._getLinkFilenameForApiItem(hierarchyItem)
                        })
                    ]);
            }
        }
    }
    _writeAlphaWarning(output) {
        const configuration = this._tsdocConfiguration;
        const betaWarning = 'This API is provided as an alpha preview for developers and may change' +
            ' based on feedback that we receive.  Do not use this API in a production environment.';
        output.appendNode(new DocNoteBox_1.DocNoteBox({ configuration }, [
            new tsdoc_1.DocParagraph({ configuration }, [new tsdoc_1.DocPlainText({ configuration, text: betaWarning })])
        ]));
    }
    _writeBetaWarning(output) {
        const configuration = this._tsdocConfiguration;
        const betaWarning = 'This API is provided as a beta preview for developers and may change' +
            ' based on feedback that we receive.  Do not use this API in a production environment.';
        output.appendNode(new DocNoteBox_1.DocNoteBox({ configuration }, [
            new tsdoc_1.DocParagraph({ configuration }, [new tsdoc_1.DocPlainText({ configuration, text: betaWarning })])
        ]));
    }
    _appendSection(output, docSection) {
        for (const node of docSection.nodes) {
            output.appendNode(node);
        }
    }
    _appendAndMergeSection(output, docSection) {
        let firstNode = true;
        for (const node of docSection.nodes) {
            if (firstNode) {
                if (node.kind === tsdoc_1.DocNodeKind.Paragraph) {
                    output.appendNodesInParagraph(node.getChildNodes());
                    firstNode = false;
                    continue;
                }
            }
            firstNode = false;
            output.appendNode(node);
        }
    }
    _getMembersAndWriteIncompleteWarning(apiClassOrInterface, output) {
        const configuration = this._tsdocConfiguration;
        const showInheritedMembers = !!this._documenterConfig?.configFile.showInheritedMembers;
        if (!showInheritedMembers) {
            return apiClassOrInterface.members;
        }
        const result = apiClassOrInterface.findMembersWithInheritance();
        // If the result is potentially incomplete, write a short warning communicating this.
        if (result.maybeIncompleteResult) {
            output.appendNode(new tsdoc_1.DocParagraph({ configuration }, [
                new DocEmphasisSpan_1.DocEmphasisSpan({ configuration, italic: true }, [
                    new tsdoc_1.DocPlainText({
                        configuration,
                        text: '(Some inherited members may not be shown because they are not represented in the documentation.)'
                    })
                ])
            ]));
        }
        // Log the messages for diagnostic purposes.
        for (const message of result.messages) {
            console.log(`Diagnostic message for findMembersWithInheritance: ${message.text}`);
        }
        return result.items;
    }
    _getFilenameForApiItem(apiItem) {
        if (apiItem.kind === api_extractor_model_1.ApiItemKind.Model) {
            return 'index.md';
        }
        let baseName = '';
        for (const hierarchyItem of apiItem.getHierarchy()) {
            // For overloaded methods, add a suffix such as "MyClass.myMethod_2".
            let qualifiedName = Utilities_1.Utilities.getSafeFilenameForName(hierarchyItem.displayName);
            if (api_extractor_model_1.ApiParameterListMixin.isBaseClassOf(hierarchyItem)) {
                if (hierarchyItem.overloadIndex > 1) {
                    // Subtract one for compatibility with earlier releases of API Documenter.
                    // (This will get revamped when we fix GitHub issue #1308)
                    qualifiedName += `_${hierarchyItem.overloadIndex - 1}`;
                }
            }
            switch (hierarchyItem.kind) {
                case api_extractor_model_1.ApiItemKind.Model:
                case api_extractor_model_1.ApiItemKind.EntryPoint:
                case api_extractor_model_1.ApiItemKind.EnumMember:
                    break;
                case api_extractor_model_1.ApiItemKind.Package:
                    baseName = Utilities_1.Utilities.getSafeFilenameForName(node_core_library_1.PackageName.getUnscopedName(hierarchyItem.displayName));
                    break;
                default:
                    baseName += '.' + qualifiedName;
            }
        }
        return baseName + '.md';
    }
    _getLinkFilenameForApiItem(apiItem) {
        return './' + this._getFilenameForApiItem(apiItem);
    }
    _deleteOldOutputFiles() {
        console.log('Deleting old output from ' + this._outputFolder);
        node_core_library_1.FileSystem.ensureEmptyFolder(this._outputFolder);
    }
}
exports.MarkdownDocumenter = MarkdownDocumenter;
//# sourceMappingURL=MarkdownDocumenter.js.map