import { DocNode , DocNodeKind , StringBuilder , DocPlainText , DocHtmlStartTag , DocHtmlEndTag , DocCodeSpan , DocLinkTag , DocParagraph , DocFencedCode , DocSection , DocNodeTransforms , DocEscapedText , DocErrorText , DocBlockTag } from '@microsoft/tsdoc' ; import { InternalError } from '@rushstack/node-core-library' ; import { IndentedWriter } from '../utils/IndentedWriter' ; export interface IMarkdownEmitterOptions { } export interface IMarkdownEmitterContext < TOptions = IMarkdownEmitterOptions > { writer : IndentedWriter ; insideTable : boolean ; boldRequested : boolean ; italicRequested : boolean ; writingBold : boolean ; writingItalic : boolean ; options : TOptions ; } /**
 * Renders MarkupElement content in the Markdown file format.
 * For more info:  https://en.wikipedia.org/wiki/Markdown
 */ export class MarkdownEmitter { public emit ( stringBuilder , docNode , options ) { const writer = new IndentedWriter ( stringBuilder ) ; const context = { writer , insideTable : false , boldRequested : false , italicRequested : false , writingBold : false , writingItalic : false , options } ; this . writeNode ( docNode , context , false ) ; writer . ensureNewLine ( ) ; return writer . toString ( ) ; } protected getEscapedText ( text ) { const textWithBackslashes = text . replace ( /\\/g , '\\\\' ) . replace ( /[*#[\]_|`~]/g , ( x ) => '\\' + x ) . replace ( /---/g , '\\-\\-\\-' ) . replace ( /&/g , '&amp;' ) . replace ( /</g , '&lt;' ) . replace ( />/g , '&gt;' ) ; return textWithBackslashes ; } protected getTableEscapedText ( text ) { return text . replace ( /&/g , '&amp;' ) . replace ( /"/g , '&quot;' ) . replace ( /</g , '&lt;' ) . replace ( />/g , '&gt;' ) . replace ( /\|/g , '&#124;' ) ; } virtual protected writeNode ( docNode , context , docNodeSiblings ) { const writer = context . writer ; switch ( docNode . kind ) { case DocNodeKind . PlainText : { const docPlainText = docNode as DocPlainText ; this . writePlainText ( docPlainText . text , context ) ; break ; } case DocNodeKind . HtmlStartTag : case DocNodeKind . HtmlEndTag : { const docHtmlTag = docNode as DocHtmlStartTag | DocHtmlEndTag ; writer . write ( docHtmlTag . emitAsHtml ( ) ) ; break ; } case DocNodeKind . CodeSpan : { const docCodeSpan = docNode as DocCodeSpan ; if ( context . insideTable ) { writer . write ( '<code>' ) ; } else { writer . write ( '`' ) ; } if ( context . insideTable ) { const code = this . getTableEscapedText ( docCodeSpan . code ) ; const parts = code . split ( /\r?\n/g ) ; writer . write ( parts . join ( '</code><br/><code>' ) ) ; } else { writer . write ( docCodeSpan . code ) ; } if ( context . insideTable ) { writer . write ( '</code>' ) ; } else { writer . write ( '`' ) ; } break ; } case DocNodeKind . LinkTag : { const docLinkTag = docNode as DocLinkTag ; if ( docLinkTag . codeDestination ) { this . writeLinkTagWithCodeDestination ( docLinkTag , context ) ; } else if ( docLinkTag . urlDestination ) { this . writeLinkTagWithUrlDestination ( docLinkTag , context ) ; } else if ( docLinkTag . linkText ) { this . writePlainText ( docLinkTag . linkText , context ) ; } break ; } case DocNodeKind . Paragraph : { const docParagraph = docNode as DocParagraph ; const trimmedParagraph = DocNodeTransforms . trimSpacesInParagraph ( docParagraph ) ; if ( context . insideTable ) { if ( docNodeSiblings ) { writer . write ( '<p>' ) ; this . writeNodes ( trimmedParagraph . nodes , context ) ; writer . write ( '</p>' ) ; } else { this . writeNodes ( trimmedParagraph . nodes , context ) ; } } else { this . writeNodes ( trimmedParagraph . nodes , context ) ; writer . ensureNewLine ( ) ; writer . writeLine ( ) ; } break ; } case DocNodeKind . FencedCode : { const docFencedCode = docNode as DocFencedCode ; writer . ensureNewLine ( ) ; writer . write ( '```' ) ; writer . write ( docFencedCode . language ) ; writer . writeLine ( ) ; writer . write ( docFencedCode . code ) ; writer . ensureNewLine ( ) ; writer . writeLine ( '```' ) ; break ; } case DocNodeKind . Section : { const docSection = docNode as DocSection ; this . writeNodes ( docSection . nodes , context ) ; break ; } case DocNodeKind . SoftBreak : { if ( ! /^\s?$/ . test ( writer . peekLastCharacter ( ) ) ) { writer . write ( ' ' ) ; } break ; } case DocNodeKind . EscapedText : { const docEscapedText = docNode as DocEscapedText ; this . writePlainText ( docEscapedText . decodedText , context ) ; break ; } case DocNodeKind . ErrorText : { const docErrorText = docNode as DocErrorText ; this . writePlainText ( docErrorText . text , context ) ; break ; } case DocNodeKind . InlineTag : { break ; } case DocNodeKind . BlockTag : { const tagNode = docNode as DocBlockTag ; console . warn ( 'Unsupported block tag: ' + tagNode . tagName ) ; break ; } default : throw new InternalError ( 'Unsupported DocNodeKind kind: ' + docNode . kind ) ; } } virtual protected writeLinkTagWithCodeDestination ( docLinkTag , context ) { throw new InternalError ( 'writeLinkTagWithCodeDestination()' ) ; } virtual protected writeLinkTagWithUrlDestination ( docLinkTag , context ) { const linkText = docLinkTag . linkText !== undefined ? docLinkTag . linkText : docLinkTag . urlDestination ! ; const encodedLinkText = this . getEscapedText ( linkText . replace ( /\s+/g , ' ' ) ) ; context . writer . write ( '[' ) ; context . writer . write ( encodedLinkText ) ; context . writer . write ( `](${ docLinkTag . urlDestination ! })` ) ; } protected writePlainText ( text , context ) { const writer = context . writer ; const parts = text . match ( /^(\s*)(.*?)(\s*)$/ ) || [ ] ; writer . write ( parts [ 1 ] ) ; const middle = parts [ 2 ] ; if ( middle !== '' ) { switch ( writer . peekLastCharacter ( ) ) { case '' : case '\n' : case ' ' : case '[' : case '>' : break ; default : writer . write ( '<!-- -->' ) ; break ; } if ( context . boldRequested ) { writer . write ( '<b>' ) ; } if ( context . italicRequested ) { writer . write ( '<i>' ) ; } writer . write ( this . getEscapedText ( middle ) ) ; if ( context . italicRequested ) { writer . write ( '</i>' ) ; } if ( context . boldRequested ) { writer . write ( '</b>' ) ; } } writer . write ( parts [ 3 ] ) ; } protected writeNodes ( docNodes , context ) { for ( const docNode of docNodes ) { this . writeNode ( docNode , context , docNodes . length > 1 ) ; } } }