UNPKG

@microsoft/tsdoc

Version:

A parser for the TypeScript doc comment syntax

371 lines 16.2 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. Object.defineProperty(exports, "__esModule", { value: true }); exports.TSDocEmitter = void 0; const DocNode_1 = require("../nodes/DocNode"); const DocNodeTransforms_1 = require("../transforms/DocNodeTransforms"); const StandardTags_1 = require("../details/StandardTags"); var LineState; (function (LineState) { LineState[LineState["Closed"] = 0] = "Closed"; LineState[LineState["StartOfLine"] = 1] = "StartOfLine"; LineState[LineState["MiddleOfLine"] = 2] = "MiddleOfLine"; })(LineState || (LineState = {})); /** * Renders a DocNode tree as a code comment. */ class TSDocEmitter { constructor() { this.eol = '\n'; // Whether to emit the /** */ framing this._emitCommentFraming = true; // This state machine is used by the writer functions to generate the /** */ framing around the emitted lines this._lineState = LineState.Closed; // State for _ensureLineSkipped() this._previousLineHadContent = false; // Normally a paragraph is precede by a blank line (unless it's the first thing written). // But sometimes we want the paragraph to be attached to the previous element, e.g. when it's part of // an "@param" block. Setting _hangingParagraph=true accomplishes that. this._hangingParagraph = false; } renderComment(output, docComment) { this._emitCommentFraming = true; this._renderCompleteObject(output, docComment); } renderHtmlTag(output, htmlTag) { this._emitCommentFraming = false; this._renderCompleteObject(output, htmlTag); } renderDeclarationReference(output, declarationReference) { this._emitCommentFraming = false; this._renderCompleteObject(output, declarationReference); } _renderCompleteObject(output, docNode) { this._output = output; this._lineState = LineState.Closed; this._previousLineHadContent = false; this._hangingParagraph = false; this._renderNode(docNode); this._writeEnd(); } _renderNode(docNode) { if (docNode === undefined) { return; } switch (docNode.kind) { case DocNode_1.DocNodeKind.Block: const docBlock = docNode; this._ensureLineSkipped(); this._renderNode(docBlock.blockTag); if (docBlock.blockTag.tagNameWithUpperCase === StandardTags_1.StandardTags.returns.tagNameWithUpperCase || docBlock.blockTag.tagNameWithUpperCase === StandardTags_1.StandardTags.defaultValue.tagNameWithUpperCase) { this._writeContent(' '); this._hangingParagraph = true; } this._renderNode(docBlock.content); break; case DocNode_1.DocNodeKind.BlockTag: const docBlockTag = docNode; if (this._lineState === LineState.MiddleOfLine) { this._writeContent(' '); } this._writeContent(docBlockTag.tagName); break; case DocNode_1.DocNodeKind.CodeSpan: const docCodeSpan = docNode; this._writeContent('`'); this._writeContent(docCodeSpan.code); this._writeContent('`'); break; case DocNode_1.DocNodeKind.Comment: const docComment = docNode; this._renderNodes([ docComment.summarySection, docComment.remarksBlock, docComment.privateRemarks, docComment.deprecatedBlock, docComment.params, docComment.typeParams, docComment.returnsBlock, ...docComment.customBlocks, ...docComment.seeBlocks, docComment.inheritDocTag ]); if (docComment.modifierTagSet.nodes.length > 0) { this._ensureLineSkipped(); this._renderNodes(docComment.modifierTagSet.nodes); } break; case DocNode_1.DocNodeKind.DeclarationReference: const docDeclarationReference = docNode; this._writeContent(docDeclarationReference.packageName); this._writeContent(docDeclarationReference.importPath); if (docDeclarationReference.packageName !== undefined || docDeclarationReference.importPath !== undefined) { this._writeContent('#'); } this._renderNodes(docDeclarationReference.memberReferences); break; case DocNode_1.DocNodeKind.ErrorText: const docErrorText = docNode; this._writeContent(docErrorText.text); break; case DocNode_1.DocNodeKind.EscapedText: const docEscapedText = docNode; this._writeContent(docEscapedText.encodedText); break; case DocNode_1.DocNodeKind.FencedCode: const docFencedCode = docNode; this._ensureAtStartOfLine(); this._writeContent('```'); this._writeContent(docFencedCode.language); this._writeNewline(); this._writeContent(docFencedCode.code); this._writeContent('```'); this._writeNewline(); this._writeNewline(); break; case DocNode_1.DocNodeKind.HtmlAttribute: const docHtmlAttribute = docNode; this._writeContent(docHtmlAttribute.name); this._writeContent(docHtmlAttribute.spacingAfterName); this._writeContent('='); this._writeContent(docHtmlAttribute.spacingAfterEquals); this._writeContent(docHtmlAttribute.value); this._writeContent(docHtmlAttribute.spacingAfterValue); break; case DocNode_1.DocNodeKind.HtmlEndTag: const docHtmlEndTag = docNode; this._writeContent('</'); this._writeContent(docHtmlEndTag.name); this._writeContent('>'); break; case DocNode_1.DocNodeKind.HtmlStartTag: const docHtmlStartTag = docNode; this._writeContent('<'); this._writeContent(docHtmlStartTag.name); this._writeContent(docHtmlStartTag.spacingAfterName); let needsSpace = docHtmlStartTag.spacingAfterName === undefined || docHtmlStartTag.spacingAfterName.length === 0; for (const attribute of docHtmlStartTag.htmlAttributes) { if (needsSpace) { this._writeContent(' '); } this._renderNode(attribute); needsSpace = attribute.spacingAfterValue === undefined || attribute.spacingAfterValue.length === 0; } this._writeContent(docHtmlStartTag.selfClosingTag ? '/>' : '>'); break; case DocNode_1.DocNodeKind.InheritDocTag: const docInheritDocTag = docNode; this._renderInlineTag(docInheritDocTag, () => { if (docInheritDocTag.declarationReference) { this._writeContent(' '); this._renderNode(docInheritDocTag.declarationReference); } }); break; case DocNode_1.DocNodeKind.InlineTag: const docInlineTag = docNode; this._renderInlineTag(docInlineTag, () => { if (docInlineTag.tagContent.length > 0) { this._writeContent(' '); this._writeContent(docInlineTag.tagContent); } }); break; case DocNode_1.DocNodeKind.LinkTag: const docLinkTag = docNode; this._renderInlineTag(docLinkTag, () => { if (docLinkTag.urlDestination !== undefined || docLinkTag.codeDestination !== undefined) { if (docLinkTag.urlDestination !== undefined) { this._writeContent(' '); this._writeContent(docLinkTag.urlDestination); } else if (docLinkTag.codeDestination !== undefined) { this._writeContent(' '); this._renderNode(docLinkTag.codeDestination); } } if (docLinkTag.linkText !== undefined) { this._writeContent(' '); this._writeContent('|'); this._writeContent(' '); this._writeContent(docLinkTag.linkText); } }); break; case DocNode_1.DocNodeKind.MemberIdentifier: const docMemberIdentifier = docNode; if (docMemberIdentifier.hasQuotes) { this._writeContent('"'); this._writeContent(docMemberIdentifier.identifier); // todo: encoding this._writeContent('"'); } else { this._writeContent(docMemberIdentifier.identifier); } break; case DocNode_1.DocNodeKind.MemberReference: const docMemberReference = docNode; if (docMemberReference.hasDot) { this._writeContent('.'); } if (docMemberReference.selector) { this._writeContent('('); } if (docMemberReference.memberSymbol) { this._renderNode(docMemberReference.memberSymbol); } else { this._renderNode(docMemberReference.memberIdentifier); } if (docMemberReference.selector) { this._writeContent(':'); this._renderNode(docMemberReference.selector); this._writeContent(')'); } break; case DocNode_1.DocNodeKind.MemberSelector: const docMemberSelector = docNode; this._writeContent(docMemberSelector.selector); break; case DocNode_1.DocNodeKind.MemberSymbol: const docMemberSymbol = docNode; this._writeContent('['); this._renderNode(docMemberSymbol.symbolReference); this._writeContent(']'); break; case DocNode_1.DocNodeKind.Section: const docSection = docNode; this._renderNodes(docSection.nodes); break; case DocNode_1.DocNodeKind.Paragraph: const trimmedParagraph = DocNodeTransforms_1.DocNodeTransforms.trimSpacesInParagraph(docNode); if (trimmedParagraph.nodes.length > 0) { if (this._hangingParagraph) { // If it's a hanging paragraph, then don't skip a line this._hangingParagraph = false; } else { this._ensureLineSkipped(); } this._renderNodes(trimmedParagraph.nodes); this._writeNewline(); } break; case DocNode_1.DocNodeKind.ParamBlock: const docParamBlock = docNode; this._ensureLineSkipped(); this._renderNode(docParamBlock.blockTag); this._writeContent(' '); this._writeContent(docParamBlock.parameterName); this._writeContent(' - '); this._hangingParagraph = true; this._renderNode(docParamBlock.content); this._hangingParagraph = false; break; case DocNode_1.DocNodeKind.ParamCollection: const docParamCollection = docNode; this._renderNodes(docParamCollection.blocks); break; case DocNode_1.DocNodeKind.PlainText: const docPlainText = docNode; this._writeContent(docPlainText.text); break; } } _renderInlineTag(docInlineTagBase, writeInlineTagContent) { this._writeContent('{'); this._writeContent(docInlineTagBase.tagName); writeInlineTagContent(); this._writeContent('}'); } _renderNodes(docNodes) { for (const docNode of docNodes) { this._renderNode(docNode); } } // Calls _writeNewline() only if we're not already at the start of a new line _ensureAtStartOfLine() { if (this._lineState === LineState.MiddleOfLine) { this._writeNewline(); } } // Calls _writeNewline() if needed to ensure that we have skipped at least one line _ensureLineSkipped() { this._ensureAtStartOfLine(); if (this._previousLineHadContent) { this._writeNewline(); } } // Writes literal text content. If it contains newlines, they will automatically be converted to // _writeNewline() calls, to ensure that "*" is written at the start of each line. _writeContent(content) { if (content === undefined || content.length === 0) { return; } const splitLines = content.split(/\r?\n/g); if (splitLines.length > 1) { let firstLine = true; for (const line of splitLines) { if (firstLine) { firstLine = false; } else { this._writeNewline(); } this._writeContent(line); } return; } if (this._lineState === LineState.Closed) { if (this._emitCommentFraming) { this._output.append('/**' + this.eol + ' *'); } this._lineState = LineState.StartOfLine; } if (this._lineState === LineState.StartOfLine) { if (this._emitCommentFraming) { this._output.append(' '); } } this._output.append(content); this._lineState = LineState.MiddleOfLine; this._previousLineHadContent = true; } // Starts a new line, and inserts "/**" or "*" as appropriate. _writeNewline() { if (this._lineState === LineState.Closed) { if (this._emitCommentFraming) { this._output.append('/**' + this.eol + ' *'); } this._lineState = LineState.StartOfLine; } this._previousLineHadContent = this._lineState === LineState.MiddleOfLine; if (this._emitCommentFraming) { this._output.append(this.eol + ' *'); } else { this._output.append(this.eol); } this._lineState = LineState.StartOfLine; this._hangingParagraph = false; } // Closes the comment, adding the final "*/" delimiter _writeEnd() { if (this._lineState === LineState.MiddleOfLine) { if (this._emitCommentFraming) { this._writeNewline(); } } if (this._lineState !== LineState.Closed) { if (this._emitCommentFraming) { this._output.append('/' + this.eol); } this._lineState = LineState.Closed; } } } exports.TSDocEmitter = TSDocEmitter; //# sourceMappingURL=TSDocEmitter.js.map