UNPKG

8.9 kBJavaScriptView Raw
1"use strict";
2// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
3// See LICENSE in the project root for license information.
4Object.defineProperty(exports, "__esModule", { value: true });
5exports.MarkdownEmitter = void 0;
6const tsdoc_1 = require("@microsoft/tsdoc");
7const node_core_library_1 = require("@rushstack/node-core-library");
8const IndentedWriter_1 = require("../utils/IndentedWriter");
9/**
10 * Renders MarkupElement content in the Markdown file format.
11 * For more info: https://en.wikipedia.org/wiki/Markdown
12 */
13class MarkdownEmitter {
14 emit(stringBuilder, docNode, options) {
15 const writer = new IndentedWriter_1.IndentedWriter(stringBuilder);
16 const context = {
17 writer,
18 insideTable: false,
19 boldRequested: false,
20 italicRequested: false,
21 writingBold: false,
22 writingItalic: false,
23 options
24 };
25 this.writeNode(docNode, context, false);
26 writer.ensureNewLine(); // finish the last line
27 return writer.toString();
28 }
29 getEscapedText(text) {
30 const textWithBackslashes = text
31 .replace(/\\/g, '\\\\') // first replace the escape character
32 .replace(/[*#[\]_|`~]/g, (x) => '\\' + x) // then escape any special characters
33 .replace(/---/g, '\\-\\-\\-') // hyphens only if it's 3 or more
34 .replace(/&/g, '&')
35 .replace(/</g, '&lt;')
36 .replace(/>/g, '&gt;');
37 return textWithBackslashes;
38 }
39 getTableEscapedText(text) {
40 return text
41 .replace(/&/g, '&amp;')
42 .replace(/"/g, '&quot;')
43 .replace(/</g, '&lt;')
44 .replace(/>/g, '&gt;')
45 .replace(/\|/g, '&#124;');
46 }
47 /**
48 * @virtual
49 */
50 writeNode(docNode, context, docNodeSiblings) {
51 const writer = context.writer;
52 switch (docNode.kind) {
53 case tsdoc_1.DocNodeKind.PlainText: {
54 const docPlainText = docNode;
55 this.writePlainText(docPlainText.text, context);
56 break;
57 }
58 case tsdoc_1.DocNodeKind.HtmlStartTag:
59 case tsdoc_1.DocNodeKind.HtmlEndTag: {
60 const docHtmlTag = docNode;
61 // write the HTML element verbatim into the output
62 writer.write(docHtmlTag.emitAsHtml());
63 break;
64 }
65 case tsdoc_1.DocNodeKind.CodeSpan: {
66 const docCodeSpan = docNode;
67 if (context.insideTable) {
68 writer.write('<code>');
69 }
70 else {
71 writer.write('`');
72 }
73 if (context.insideTable) {
74 const code = this.getTableEscapedText(docCodeSpan.code);
75 const parts = code.split(/\r?\n/g);
76 writer.write(parts.join('</code><br/><code>'));
77 }
78 else {
79 writer.write(docCodeSpan.code);
80 }
81 if (context.insideTable) {
82 writer.write('</code>');
83 }
84 else {
85 writer.write('`');
86 }
87 break;
88 }
89 case tsdoc_1.DocNodeKind.LinkTag: {
90 const docLinkTag = docNode;
91 if (docLinkTag.codeDestination) {
92 this.writeLinkTagWithCodeDestination(docLinkTag, context);
93 }
94 else if (docLinkTag.urlDestination) {
95 this.writeLinkTagWithUrlDestination(docLinkTag, context);
96 }
97 else if (docLinkTag.linkText) {
98 this.writePlainText(docLinkTag.linkText, context);
99 }
100 break;
101 }
102 case tsdoc_1.DocNodeKind.Paragraph: {
103 const docParagraph = docNode;
104 const trimmedParagraph = tsdoc_1.DocNodeTransforms.trimSpacesInParagraph(docParagraph);
105 if (context.insideTable) {
106 if (docNodeSiblings) {
107 writer.write('<p>');
108 this.writeNodes(trimmedParagraph.nodes, context);
109 writer.write('</p>');
110 }
111 else {
112 // Special case: If we are the only element inside this table cell, then we can omit the <p></p> container.
113 this.writeNodes(trimmedParagraph.nodes, context);
114 }
115 }
116 else {
117 this.writeNodes(trimmedParagraph.nodes, context);
118 writer.ensureNewLine();
119 writer.writeLine();
120 }
121 break;
122 }
123 case tsdoc_1.DocNodeKind.FencedCode: {
124 const docFencedCode = docNode;
125 writer.ensureNewLine();
126 writer.write('```');
127 writer.write(docFencedCode.language);
128 writer.writeLine();
129 writer.write(docFencedCode.code);
130 writer.ensureNewLine();
131 writer.writeLine('```');
132 break;
133 }
134 case tsdoc_1.DocNodeKind.Section: {
135 const docSection = docNode;
136 this.writeNodes(docSection.nodes, context);
137 break;
138 }
139 case tsdoc_1.DocNodeKind.SoftBreak: {
140 if (!/^\s?$/.test(writer.peekLastCharacter())) {
141 writer.write(' ');
142 }
143 break;
144 }
145 case tsdoc_1.DocNodeKind.EscapedText: {
146 const docEscapedText = docNode;
147 this.writePlainText(docEscapedText.decodedText, context);
148 break;
149 }
150 case tsdoc_1.DocNodeKind.ErrorText: {
151 const docErrorText = docNode;
152 this.writePlainText(docErrorText.text, context);
153 break;
154 }
155 case tsdoc_1.DocNodeKind.InlineTag: {
156 break;
157 }
158 case tsdoc_1.DocNodeKind.BlockTag: {
159 const tagNode = docNode;
160 console.warn('Unsupported block tag: ' + tagNode.tagName);
161 break;
162 }
163 default:
164 throw new node_core_library_1.InternalError('Unsupported DocNodeKind kind: ' + docNode.kind);
165 }
166 }
167 /** @virtual */
168 writeLinkTagWithCodeDestination(docLinkTag, context) {
169 // The subclass needs to implement this to support code destinations
170 throw new node_core_library_1.InternalError('writeLinkTagWithCodeDestination()');
171 }
172 /** @virtual */
173 writeLinkTagWithUrlDestination(docLinkTag, context) {
174 const linkText = docLinkTag.linkText !== undefined ? docLinkTag.linkText : docLinkTag.urlDestination;
175 const encodedLinkText = this.getEscapedText(linkText.replace(/\s+/g, ' '));
176 context.writer.write('[');
177 context.writer.write(encodedLinkText);
178 context.writer.write(`](${docLinkTag.urlDestination})`);
179 }
180 writePlainText(text, context) {
181 const writer = context.writer;
182 // split out the [ leading whitespace, content, trailing whitespace ]
183 const parts = text.match(/^(\s*)(.*?)(\s*)$/) || [];
184 writer.write(parts[1]); // write leading whitespace
185 const middle = parts[2];
186 if (middle !== '') {
187 switch (writer.peekLastCharacter()) {
188 case '':
189 case '\n':
190 case ' ':
191 case '[':
192 case '>':
193 // okay to put a symbol
194 break;
195 default:
196 // This is no problem: "**one** *two* **three**"
197 // But this is trouble: "**one***two***three**"
198 // The most general solution: "**one**<!-- -->*two*<!-- -->**three**"
199 writer.write('<!-- -->');
200 break;
201 }
202 if (context.boldRequested) {
203 writer.write('<b>');
204 }
205 if (context.italicRequested) {
206 writer.write('<i>');
207 }
208 writer.write(this.getEscapedText(middle));
209 if (context.italicRequested) {
210 writer.write('</i>');
211 }
212 if (context.boldRequested) {
213 writer.write('</b>');
214 }
215 }
216 writer.write(parts[3]); // write trailing whitespace
217 }
218 writeNodes(docNodes, context) {
219 for (const docNode of docNodes) {
220 this.writeNode(docNode, context, docNodes.length > 1);
221 }
222 }
223}
224exports.MarkdownEmitter = MarkdownEmitter;
225//# sourceMappingURL=MarkdownEmitter.js.map
\No newline at end of file