Spaces:
Sleeping
Sleeping
| // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | |
| // See LICENSE in the project root for license information. | |
| import { DocNodeKind } from '../nodes/DocNode'; | |
| import { DocNodeTransforms } from '../transforms/DocNodeTransforms'; | |
| import { StandardTags } from '../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. | |
| */ | |
| export 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 DocNodeKind.Block: | |
| const docBlock = docNode; | |
| this._ensureLineSkipped(); | |
| this._renderNode(docBlock.blockTag); | |
| if (docBlock.blockTag.tagNameWithUpperCase === StandardTags.returns.tagNameWithUpperCase || | |
| docBlock.blockTag.tagNameWithUpperCase === StandardTags.defaultValue.tagNameWithUpperCase) { | |
| this._writeContent(' '); | |
| this._hangingParagraph = true; | |
| } | |
| this._renderNode(docBlock.content); | |
| break; | |
| case DocNodeKind.BlockTag: | |
| const docBlockTag = docNode; | |
| if (this._lineState === LineState.MiddleOfLine) { | |
| this._writeContent(' '); | |
| } | |
| this._writeContent(docBlockTag.tagName); | |
| break; | |
| case DocNodeKind.CodeSpan: | |
| const docCodeSpan = docNode; | |
| this._writeContent('`'); | |
| this._writeContent(docCodeSpan.code); | |
| this._writeContent('`'); | |
| break; | |
| case 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 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 DocNodeKind.ErrorText: | |
| const docErrorText = docNode; | |
| this._writeContent(docErrorText.text); | |
| break; | |
| case DocNodeKind.EscapedText: | |
| const docEscapedText = docNode; | |
| this._writeContent(docEscapedText.encodedText); | |
| break; | |
| case 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 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 DocNodeKind.HtmlEndTag: | |
| const docHtmlEndTag = docNode; | |
| this._writeContent('</'); | |
| this._writeContent(docHtmlEndTag.name); | |
| this._writeContent('>'); | |
| break; | |
| case 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 DocNodeKind.InheritDocTag: | |
| const docInheritDocTag = docNode; | |
| this._renderInlineTag(docInheritDocTag, () => { | |
| if (docInheritDocTag.declarationReference) { | |
| this._writeContent(' '); | |
| this._renderNode(docInheritDocTag.declarationReference); | |
| } | |
| }); | |
| break; | |
| case DocNodeKind.InlineTag: | |
| const docInlineTag = docNode; | |
| this._renderInlineTag(docInlineTag, () => { | |
| if (docInlineTag.tagContent.length > 0) { | |
| this._writeContent(' '); | |
| this._writeContent(docInlineTag.tagContent); | |
| } | |
| }); | |
| break; | |
| case 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 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 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 DocNodeKind.MemberSelector: | |
| const docMemberSelector = docNode; | |
| this._writeContent(docMemberSelector.selector); | |
| break; | |
| case DocNodeKind.MemberSymbol: | |
| const docMemberSymbol = docNode; | |
| this._writeContent('['); | |
| this._renderNode(docMemberSymbol.symbolReference); | |
| this._writeContent(']'); | |
| break; | |
| case DocNodeKind.Section: | |
| const docSection = docNode; | |
| this._renderNodes(docSection.nodes); | |
| break; | |
| case DocNodeKind.Paragraph: | |
| const trimmedParagraph = 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 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 DocNodeKind.ParamCollection: | |
| const docParamCollection = docNode; | |
| this._renderNodes(docParamCollection.blocks); | |
| break; | |
| case 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; | |
| } | |
| } | |
| } | |
| //# sourceMappingURL=TSDocEmitter.js.map |