UNPKG

9.35 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 // This tentative write is necessary to avoid writing empty paragraph tags (i.e. `<p></p>`). At the
108 // time this code runs, we do not know whether the `writeNodes` call below will actually write
109 // anything. Thus, we want to only write a `<p>` tag (as well as eventually a corresponding
110 // `</p>` tag) if something ends up being written within the tags.
111 writer.writeTentative('<p>', '</p>', () => {
112 this.writeNodes(trimmedParagraph.nodes, context);
113 });
114 }
115 else {
116 // Special case: If we are the only element inside this table cell, then we can omit the <p></p> container.
117 this.writeNodes(trimmedParagraph.nodes, context);
118 }
119 }
120 else {
121 this.writeNodes(trimmedParagraph.nodes, context);
122 writer.ensureNewLine();
123 writer.writeLine();
124 }
125 break;
126 }
127 case tsdoc_1.DocNodeKind.FencedCode: {
128 const docFencedCode = docNode;
129 writer.ensureNewLine();
130 writer.write('```');
131 writer.write(docFencedCode.language);
132 writer.writeLine();
133 writer.write(docFencedCode.code);
134 writer.ensureNewLine();
135 writer.writeLine('```');
136 break;
137 }
138 case tsdoc_1.DocNodeKind.Section: {
139 const docSection = docNode;
140 this.writeNodes(docSection.nodes, context);
141 break;
142 }
143 case tsdoc_1.DocNodeKind.SoftBreak: {
144 if (!/^\s?$/.test(writer.peekLastCharacter())) {
145 writer.write(' ');
146 }
147 break;
148 }
149 case tsdoc_1.DocNodeKind.EscapedText: {
150 const docEscapedText = docNode;
151 this.writePlainText(docEscapedText.decodedText, context);
152 break;
153 }
154 case tsdoc_1.DocNodeKind.ErrorText: {
155 const docErrorText = docNode;
156 this.writePlainText(docErrorText.text, context);
157 break;
158 }
159 case tsdoc_1.DocNodeKind.InlineTag: {
160 break;
161 }
162 case tsdoc_1.DocNodeKind.BlockTag: {
163 const tagNode = docNode;
164 console.warn('Unsupported block tag: ' + tagNode.tagName);
165 break;
166 }
167 default:
168 throw new node_core_library_1.InternalError('Unsupported DocNodeKind kind: ' + docNode.kind);
169 }
170 }
171 /** @virtual */
172 writeLinkTagWithCodeDestination(docLinkTag, context) {
173 // The subclass needs to implement this to support code destinations
174 throw new node_core_library_1.InternalError('writeLinkTagWithCodeDestination()');
175 }
176 /** @virtual */
177 writeLinkTagWithUrlDestination(docLinkTag, context) {
178 const linkText = docLinkTag.linkText !== undefined ? docLinkTag.linkText : docLinkTag.urlDestination;
179 const encodedLinkText = this.getEscapedText(linkText.replace(/\s+/g, ' '));
180 context.writer.write('[');
181 context.writer.write(encodedLinkText);
182 context.writer.write(`](${docLinkTag.urlDestination})`);
183 }
184 writePlainText(text, context) {
185 const writer = context.writer;
186 // split out the [ leading whitespace, content, trailing whitespace ]
187 const parts = text.match(/^(\s*)(.*?)(\s*)$/) || [];
188 writer.write(parts[1]); // write leading whitespace
189 const middle = parts[2];
190 if (middle !== '') {
191 switch (writer.peekLastCharacter()) {
192 case '':
193 case '\n':
194 case ' ':
195 case '[':
196 case '>':
197 // okay to put a symbol
198 break;
199 default:
200 // This is no problem: "**one** *two* **three**"
201 // But this is trouble: "**one***two***three**"
202 // The most general solution: "**one**<!-- -->*two*<!-- -->**three**"
203 writer.write('<!-- -->');
204 break;
205 }
206 if (context.boldRequested) {
207 writer.write('**');
208 }
209 if (context.italicRequested) {
210 writer.write('_');
211 }
212 writer.write(this.getEscapedText(middle));
213 if (context.italicRequested) {
214 writer.write('_');
215 }
216 if (context.boldRequested) {
217 writer.write('**');
218 }
219 }
220 writer.write(parts[3]); // write trailing whitespace
221 }
222 writeNodes(docNodes, context) {
223 for (const docNode of docNodes) {
224 this.writeNode(docNode, context, docNodes.length > 1);
225 }
226 }
227}
228exports.MarkdownEmitter = MarkdownEmitter;
229//# sourceMappingURL=MarkdownEmitter.js.map
\No newline at end of file