@effected/markdown 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Markdown.js +10 -1
- package/MarkdownNode.js +49 -0
- package/Mdast.js +13 -2
- package/README.md +33 -2
- package/index.d.ts +65 -1
- package/internal/stringify.js +145 -2
- package/package.json +1 -1
package/Markdown.js
CHANGED
|
@@ -295,6 +295,13 @@ var Markdown = class Markdown {
|
|
|
295
295
|
* rows describe a synthesized node, which is the case a test asserting on
|
|
296
296
|
* generated markdown actually has.
|
|
297
297
|
*
|
|
298
|
+
* **Text escaping is canonical unless a `Text` node opts out.** A `Text`
|
|
299
|
+
* carrying `escapeStyle: "literal"` is emitted verbatim apart from the
|
|
300
|
+
* escapes that protect block structure (a table cell's `|`, a newline in a
|
|
301
|
+
* single-line container, line-start block openers); its exact set is
|
|
302
|
+
* documented on {@link Text}. The opt-out is additive: a tree that never
|
|
303
|
+
* sets it serializes byte-identically to the canonical form.
|
|
304
|
+
*
|
|
298
305
|
* **Representability wins over the table.** The canonical form never emits
|
|
299
306
|
* text that would re-parse as something else, so a row yields where the two
|
|
300
307
|
* conflict. The case that reaches a consumer is the indented code block: an
|
|
@@ -364,7 +371,9 @@ var Markdown = class Markdown {
|
|
|
364
371
|
* admits spec mdast and strips everything outside it, so a `fenceChar`,
|
|
365
372
|
* `headingStyle`, `markerChar` or `delimiter` placed on a plain mdast tree
|
|
366
373
|
* before admission is silently dropped and the canonical default applies.
|
|
367
|
-
* Set them on the nodes that come back.
|
|
374
|
+
* Set them on the nodes that come back. The exception is `Text`'s
|
|
375
|
+
* `escapeStyle`, an emitter instruction rather than a source spelling,
|
|
376
|
+
* which the boundary admits.
|
|
368
377
|
*
|
|
369
378
|
* @param root - The document tree to serialize.
|
|
370
379
|
* @returns An `Effect` that succeeds with markdown source, or fails with
|
package/MarkdownNode.js
CHANGED
|
@@ -145,11 +145,60 @@ const TableAlign = Schema.Literals([
|
|
|
145
145
|
* Text — a run of literal characters, with entity references and backslash
|
|
146
146
|
* escapes already resolved into `value`.
|
|
147
147
|
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* `escapeStyle` is an opt-in emitter extra, the one fidelity field the
|
|
150
|
+
* parser never sets: absent (or `"canonical"`), `Markdown.stringify` escapes
|
|
151
|
+
* the value canonically so it re-parses to the same text. `"literal"` is the
|
|
152
|
+
* caller vouching that `value` is already safe markdown source, so the
|
|
153
|
+
* emitter writes it verbatim and applies no escaping aimed at inline syntax:
|
|
154
|
+
* `~0.2.1`, `^1.0.0`, `some_pkg` and `@scope/pkg` come out as written, as do
|
|
155
|
+
* `*`, `_`, `[`, `]`, `~`, `&`, `\`, the backtick and autolink-shaped
|
|
156
|
+
* text.
|
|
157
|
+
*
|
|
158
|
+
* Escaping that defends the containing BLOCK'S structure still applies,
|
|
159
|
+
* because its absence would corrupt the document around the text rather
|
|
160
|
+
* than add formatting inside it:
|
|
161
|
+
*
|
|
162
|
+
* - in a table cell, every `|` not already backslash-escaped is written
|
|
163
|
+
* `\|` (a bare pipe splits the cell);
|
|
164
|
+
*
|
|
165
|
+
* - in any container, a value-final `\` is doubled when more content
|
|
166
|
+
* follows it (it would otherwise pair with that content's first
|
|
167
|
+
* character, escaping an inline-code backtick or freeing a cell's `\|`);
|
|
168
|
+
*
|
|
169
|
+
* - in a table cell or heading, a newline becomes a space (neither can
|
|
170
|
+
* hold a line break);
|
|
171
|
+
*
|
|
172
|
+
* - in a heading, a trailing `#` run that would read as the ATX closing
|
|
173
|
+
* sequence is escaped;
|
|
174
|
+
*
|
|
175
|
+
* - elsewhere, a newline that would form a blank line (ending the
|
|
176
|
+
* paragraph) becomes ` `, and at every line start leading whitespace
|
|
177
|
+
* becomes a character reference, and a line start that can open a block
|
|
178
|
+
* is escaped: a `-`, `+` or `*` bullet or an ordered-list marker followed
|
|
179
|
+
* by a space, tab or line end; one to six `#` followed by the same; `>`;
|
|
180
|
+
* three or more backticks or tildes; a thematic-break or setext run
|
|
181
|
+
* (`***`, `___`, `==`); a `---` or `+++` frontmatter fence; a line of
|
|
182
|
+
* only `-`, `:`, `|` and whitespace; and, conservatively, any `<`, `[`,
|
|
183
|
+
* `|` or `:`. Text that cannot open a block there, such as `~0.2.1`,
|
|
184
|
+
* `1.0.0`, `-x` or `#x`, is written as is;
|
|
185
|
+
*
|
|
186
|
+
* - in a tree carrying MDX nodes, `{` and `<` stay escaped, since MDX reads
|
|
187
|
+
* a stray one as a syntax error rather than as text.
|
|
188
|
+
*
|
|
189
|
+
* **A literal text whose value parses as markdown does not round-trip to the
|
|
190
|
+
* same value**: `*a*` emits verbatim and re-parses as emphasis. Keeping the
|
|
191
|
+
* value free of inline syntax is the caller's promise, not something the
|
|
192
|
+
* emitter checks. `Mdast.fromMdast` admits this field on a `text` node (it is
|
|
193
|
+
* the one emitter instruction a plain tree can carry in), and `Mdast.toMdast`
|
|
194
|
+
* projects it back out when present.
|
|
195
|
+
*
|
|
148
196
|
* @public
|
|
149
197
|
*/
|
|
150
198
|
var Text = class extends Schema.Class("Text")({
|
|
151
199
|
type: Schema.tag("text"),
|
|
152
200
|
value: Schema.String,
|
|
201
|
+
escapeStyle: Schema.optionalKey(Schema.Literals(["canonical", "literal"])),
|
|
153
202
|
position: NodePosition
|
|
154
203
|
}) {};
|
|
155
204
|
/**
|
package/Mdast.js
CHANGED
|
@@ -110,7 +110,12 @@ const projectNode = (node) => {
|
|
|
110
110
|
children: projectChildren(node.children),
|
|
111
111
|
position
|
|
112
112
|
};
|
|
113
|
-
case "text":
|
|
113
|
+
case "text": return {
|
|
114
|
+
type: "text",
|
|
115
|
+
value: node.value,
|
|
116
|
+
...node.escapeStyle === void 0 ? {} : { escapeStyle: node.escapeStyle },
|
|
117
|
+
position
|
|
118
|
+
};
|
|
114
119
|
case "html":
|
|
115
120
|
case "inlineCode": return {
|
|
116
121
|
type: node.type,
|
|
@@ -263,7 +268,7 @@ const admittedFields = {
|
|
|
263
268
|
tableCell: [],
|
|
264
269
|
break: [],
|
|
265
270
|
thematicBreak: [],
|
|
266
|
-
text: ["value"],
|
|
271
|
+
text: ["value", "escapeStyle"],
|
|
267
272
|
html: ["value"],
|
|
268
273
|
inlineCode: ["value"],
|
|
269
274
|
heading: ["depth"],
|
|
@@ -434,6 +439,12 @@ var Mdast = class Mdast {
|
|
|
434
439
|
* admits spec mdast and nothing else) but it is silent, which is why it is
|
|
435
440
|
* called out here.
|
|
436
441
|
*
|
|
442
|
+
* **One exception: `escapeStyle` on a `text` node is admitted.** It
|
|
443
|
+
* records no source spelling; it is the caller's instruction to the
|
|
444
|
+
* emitter (`"literal"` writes the value verbatim, see {@link Text}), so a
|
|
445
|
+
* plain tree built for `Markdown.stringify` can carry it straight in. A
|
|
446
|
+
* value outside `"canonical" | "literal"` fails the decode typed.
|
|
447
|
+
*
|
|
437
448
|
* @param input - A plain mdast tree, typically a `root`.
|
|
438
449
|
* @returns A `Result` succeeding with the decoded {@link Root}, or
|
|
439
450
|
* failing with {@link MdastDecodeError} carrying the structured issue.
|
package/README.md
CHANGED
|
@@ -248,7 +248,7 @@ Effect.runPromise(program).then(console.log);
|
|
|
248
248
|
|
|
249
249
|
## mdast interop
|
|
250
250
|
|
|
251
|
-
The node classes already use mdast's type names and field shapes; `Mdast.toMdast` strips the fidelity fields this package adds — bullet characters, fence style, ATX-versus-setext spelling — and emits plain spec-valid mdast JSON that the remark ecosystem consumes directly, including `mdast-util-to-hast` if you want hast:
|
|
251
|
+
The node classes already use mdast's type names and field shapes; `Mdast.toMdast` strips the fidelity fields this package adds — bullet characters, fence style, ATX-versus-setext spelling — (keeping only a `Text` node's `escapeStyle` when set, see [Literal text](#literal-text)) and emits plain spec-valid mdast JSON that the remark ecosystem consumes directly, including `mdast-util-to-hast` if you want hast:
|
|
252
252
|
|
|
253
253
|
```ts
|
|
254
254
|
import { Markdown, Mdast } from "@effected/markdown";
|
|
@@ -317,10 +317,41 @@ MDX nodes are part of the same commitment: the serialization choices listed unde
|
|
|
317
317
|
|
|
318
318
|
The posture that makes this a non-issue — and the one to adopt if you build trees and assert on their bytes — is to set `fenceChar` on every `Code` node carrying neither a `lang` nor one already, in a post-decode walk that reaches nested nodes. The choice then leaves the emitter entirely and no output depends on a node's neighbours.
|
|
319
319
|
|
|
320
|
-
Fidelity fields must be set on the **decoded** tree: `Mdast.fromMdast` admits spec mdast and strips everything outside it, so a `fenceChar` placed on a plain mdast tree before admission is silently dropped.
|
|
320
|
+
Fidelity fields must be set on the **decoded** tree: `Mdast.fromMdast` admits spec mdast and strips everything outside it, so a `fenceChar` placed on a plain mdast tree before admission is silently dropped. The one exception is `escapeStyle` on a `text` node, described next.
|
|
321
321
|
|
|
322
322
|
A node that carries a fidelity field overrides the matching row — `headingStyle`, `markerChar`, `fenceChar`, `delimiter` — which is how a parsed document re-serializes in its author's spelling. The table describes a synthesized node, which is what a test asserting on generated markdown actually holds.
|
|
323
323
|
|
|
324
|
+
### Literal text
|
|
325
|
+
|
|
326
|
+
Text escaping is canonical by default: `~0.2.1` in a table cell emits as `\~0.2.1`. A `Text` node carrying `escapeStyle: "literal"` opts out — the caller vouches that the value is already safe markdown, and the emitter writes it verbatim with none of the escaping aimed at inline syntax. That suits generated content such as a dependency table, where `~0.2.1`, `^1.0.0` and `@scope/pkg` should read as written:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import { Markdown, Mdast } from "@effected/markdown";
|
|
330
|
+
import { Result } from "effect";
|
|
331
|
+
|
|
332
|
+
const cell = (value: string) => ({ type: "tableCell", children: [{ type: "text", value, escapeStyle: "literal" }] });
|
|
333
|
+
const tree = Mdast.fromMdastResult({
|
|
334
|
+
type: "root",
|
|
335
|
+
children: [{ type: "table", children: [{ type: "tableRow", children: [cell("Range"), cell("~0.2.1 | ^1.0.0")] }] }],
|
|
336
|
+
});
|
|
337
|
+
if (Result.isSuccess(tree)) {
|
|
338
|
+
console.log(Result.getOrThrow(Markdown.stringifyResult(tree.success)));
|
|
339
|
+
// | Range | ~0.2.1 \| ^1.0.0 |
|
|
340
|
+
// | --- | --- |
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Escapes that protect the surrounding **block** still apply, because dropping them would corrupt the document rather than add formatting:
|
|
345
|
+
|
|
346
|
+
- in a table cell, every `|` not already backslash-escaped becomes `\|`;
|
|
347
|
+
- in any container, a value-final `\` is doubled when more content follows it, so it cannot escape that content's first character;
|
|
348
|
+
- in a table cell or heading, a newline becomes a space;
|
|
349
|
+
- in a heading, a trailing `#` run that would read as the closing sequence is escaped;
|
|
350
|
+
- elsewhere, a newline that would form a blank line becomes ` `, leading whitespace at a line start becomes a character reference, and a line start that can open a block is escaped — a `-`, `+` or `*` bullet or an ordered-list marker followed by a space, tab or line end, one to six `#` followed by the same, `>`, a run of three backticks or tildes, a thematic-break or setext run, a `---` or `+++` frontmatter fence, a line of only `-`, `:`, `|` and whitespace, and conservatively any `<`, `[`, `|` or `:` — while text that cannot, such as `~0.2.1`, `1.0.0`, `-x` or `#x`, is written as is;
|
|
351
|
+
- in a tree carrying MDX nodes, `{` and `<` stay escaped.
|
|
352
|
+
|
|
353
|
+
**A literal value that parses as markdown does not round-trip**: `*a*` emits verbatim and re-parses as emphasis. Keeping the value free of inline syntax is the caller's promise; the emitter does not check it. The parser never sets `escapeStyle`, and `Mdast.fromMdast` admits it on a `text` node — the one fidelity field that crosses that boundary, because it is an instruction to the emitter rather than a record of source spelling — so a plain mdast tree can carry it straight in. The opt-out is additive: a tree that never sets it serializes byte-identically to the canonical form above.
|
|
354
|
+
|
|
324
355
|
To *normalize* an existing document to different choices, use `MarkdownFormat` with `MarkdownFormattingOptions`. That is the configurable surface; this one deliberately is not.
|
|
325
356
|
|
|
326
357
|
## Features
|
package/index.d.ts
CHANGED
|
@@ -170,12 +170,61 @@ export type TableAlign = typeof TableAlign.Type;
|
|
|
170
170
|
declare const Text_base: Schema.Class<Text, Schema.Struct<{
|
|
171
171
|
readonly type: Schema.tag<"text">;
|
|
172
172
|
readonly value: Schema.String;
|
|
173
|
+
readonly escapeStyle: Schema.optionalKey<Schema.Literals<readonly ["canonical", "literal"]>>;
|
|
173
174
|
readonly position: Schema.withConstructorDefault<typeof Position>;
|
|
174
175
|
}>, {}>;
|
|
175
176
|
/**
|
|
176
177
|
* Text — a run of literal characters, with entity references and backslash
|
|
177
178
|
* escapes already resolved into `value`.
|
|
178
179
|
*
|
|
180
|
+
* @remarks
|
|
181
|
+
* `escapeStyle` is an opt-in emitter extra, the one fidelity field the
|
|
182
|
+
* parser never sets: absent (or `"canonical"`), `Markdown.stringify` escapes
|
|
183
|
+
* the value canonically so it re-parses to the same text. `"literal"` is the
|
|
184
|
+
* caller vouching that `value` is already safe markdown source, so the
|
|
185
|
+
* emitter writes it verbatim and applies no escaping aimed at inline syntax:
|
|
186
|
+
* `~0.2.1`, `^1.0.0`, `some_pkg` and `@scope/pkg` come out as written, as do
|
|
187
|
+
* `*`, `_`, `[`, `]`, `~`, `&`, `\`, the backtick and autolink-shaped
|
|
188
|
+
* text.
|
|
189
|
+
*
|
|
190
|
+
* Escaping that defends the containing BLOCK'S structure still applies,
|
|
191
|
+
* because its absence would corrupt the document around the text rather
|
|
192
|
+
* than add formatting inside it:
|
|
193
|
+
*
|
|
194
|
+
* - in a table cell, every `|` not already backslash-escaped is written
|
|
195
|
+
* `\|` (a bare pipe splits the cell);
|
|
196
|
+
*
|
|
197
|
+
* - in any container, a value-final `\` is doubled when more content
|
|
198
|
+
* follows it (it would otherwise pair with that content's first
|
|
199
|
+
* character, escaping an inline-code backtick or freeing a cell's `\|`);
|
|
200
|
+
*
|
|
201
|
+
* - in a table cell or heading, a newline becomes a space (neither can
|
|
202
|
+
* hold a line break);
|
|
203
|
+
*
|
|
204
|
+
* - in a heading, a trailing `#` run that would read as the ATX closing
|
|
205
|
+
* sequence is escaped;
|
|
206
|
+
*
|
|
207
|
+
* - elsewhere, a newline that would form a blank line (ending the
|
|
208
|
+
* paragraph) becomes ` `, and at every line start leading whitespace
|
|
209
|
+
* becomes a character reference, and a line start that can open a block
|
|
210
|
+
* is escaped: a `-`, `+` or `*` bullet or an ordered-list marker followed
|
|
211
|
+
* by a space, tab or line end; one to six `#` followed by the same; `>`;
|
|
212
|
+
* three or more backticks or tildes; a thematic-break or setext run
|
|
213
|
+
* (`***`, `___`, `==`); a `---` or `+++` frontmatter fence; a line of
|
|
214
|
+
* only `-`, `:`, `|` and whitespace; and, conservatively, any `<`, `[`,
|
|
215
|
+
* `|` or `:`. Text that cannot open a block there, such as `~0.2.1`,
|
|
216
|
+
* `1.0.0`, `-x` or `#x`, is written as is;
|
|
217
|
+
*
|
|
218
|
+
* - in a tree carrying MDX nodes, `{` and `<` stay escaped, since MDX reads
|
|
219
|
+
* a stray one as a syntax error rather than as text.
|
|
220
|
+
*
|
|
221
|
+
* **A literal text whose value parses as markdown does not round-trip to the
|
|
222
|
+
* same value**: `*a*` emits verbatim and re-parses as emphasis. Keeping the
|
|
223
|
+
* value free of inline syntax is the caller's promise, not something the
|
|
224
|
+
* emitter checks. `Mdast.fromMdast` admits this field on a `text` node (it is
|
|
225
|
+
* the one emitter instruction a plain tree can carry in), and `Mdast.toMdast`
|
|
226
|
+
* projects it back out when present.
|
|
227
|
+
*
|
|
179
228
|
* @public
|
|
180
229
|
*/
|
|
181
230
|
export declare class Text extends Text_base {}
|
|
@@ -1191,6 +1240,13 @@ export declare class Markdown {
|
|
|
1191
1240
|
* rows describe a synthesized node, which is the case a test asserting on
|
|
1192
1241
|
* generated markdown actually has.
|
|
1193
1242
|
*
|
|
1243
|
+
* **Text escaping is canonical unless a `Text` node opts out.** A `Text`
|
|
1244
|
+
* carrying `escapeStyle: "literal"` is emitted verbatim apart from the
|
|
1245
|
+
* escapes that protect block structure (a table cell's `|`, a newline in a
|
|
1246
|
+
* single-line container, line-start block openers); its exact set is
|
|
1247
|
+
* documented on {@link Text}. The opt-out is additive: a tree that never
|
|
1248
|
+
* sets it serializes byte-identically to the canonical form.
|
|
1249
|
+
*
|
|
1194
1250
|
* **Representability wins over the table.** The canonical form never emits
|
|
1195
1251
|
* text that would re-parse as something else, so a row yields where the two
|
|
1196
1252
|
* conflict. The case that reaches a consumer is the indented code block: an
|
|
@@ -1246,7 +1302,9 @@ export declare class Markdown {
|
|
|
1246
1302
|
* admits spec mdast and strips everything outside it, so a `fenceChar`,
|
|
1247
1303
|
* `headingStyle`, `markerChar` or `delimiter` placed on a plain mdast tree
|
|
1248
1304
|
* before admission is silently dropped and the canonical default applies.
|
|
1249
|
-
* Set them on the nodes that come back.
|
|
1305
|
+
* Set them on the nodes that come back. The exception is `Text`'s
|
|
1306
|
+
* `escapeStyle`, an emitter instruction rather than a source spelling,
|
|
1307
|
+
* which the boundary admits.
|
|
1250
1308
|
*
|
|
1251
1309
|
* @param root - The document tree to serialize.
|
|
1252
1310
|
* @returns An `Effect` that succeeds with markdown source, or fails with
|
|
@@ -2778,6 +2836,12 @@ export declare class Mdast {
|
|
|
2778
2836
|
* admits spec mdast and nothing else) but it is silent, which is why it is
|
|
2779
2837
|
* called out here.
|
|
2780
2838
|
*
|
|
2839
|
+
* **One exception: `escapeStyle` on a `text` node is admitted.** It
|
|
2840
|
+
* records no source spelling; it is the caller's instruction to the
|
|
2841
|
+
* emitter (`"literal"` writes the value verbatim, see {@link Text}), so a
|
|
2842
|
+
* plain tree built for `Markdown.stringify` can carry it straight in. A
|
|
2843
|
+
* value outside `"canonical" | "literal"` fails the decode typed.
|
|
2844
|
+
*
|
|
2781
2845
|
* @param input - A plain mdast tree, typically a `root`.
|
|
2782
2846
|
* @returns A `Result` succeeding with the decoded {@link Root}, or
|
|
2783
2847
|
* failing with {@link MdastDecodeError} carrying the structured issue.
|
package/internal/stringify.js
CHANGED
|
@@ -184,6 +184,132 @@ const escapeText = (value, context, atLineStart, mdx, followingText, nonTextFoll
|
|
|
184
184
|
atLineStart: lineStart
|
|
185
185
|
};
|
|
186
186
|
};
|
|
187
|
+
/** Space or tab — the whitespace a list marker or ATX run must be followed by. */
|
|
188
|
+
const isSpaceOrTab = (char) => char === " " || char === " ";
|
|
189
|
+
/** Every character of `line` is `marker` or space/tab — a thematic-break or setext-shaped run. */
|
|
190
|
+
const isRunOf = (line, marker) => line.length > 0 && [...line].every((char) => char === marker || char === " " || char === " ");
|
|
191
|
+
/** Only the characters a GFM delimiter row is made of (re2c's `spacechar` included). */
|
|
192
|
+
const reDelimiterRowShaped = /^[-:| \t\v\f]+$/;
|
|
193
|
+
/**
|
|
194
|
+
* Whether a literal line starting with `line` can open a block, so its first
|
|
195
|
+
* character must be escaped. `line` is the text known to be on the line —
|
|
196
|
+
* the value up to its next newline, then the following text siblings — and
|
|
197
|
+
* `open` says the line may carry more than that (no newline ended it).
|
|
198
|
+
* Beyond the known text sits either the end of the line or content this
|
|
199
|
+
* function cannot see (a non-text sibling, a parent's closing delimiter), so
|
|
200
|
+
* a marker that needs "space, tab or end of line" after it is escaped when
|
|
201
|
+
* it reaches the end of `line`. Every test errs toward escaping: an extra
|
|
202
|
+
* backslash is inert, a missing one changes the document.
|
|
203
|
+
*/
|
|
204
|
+
const literalLineOpensBlock = (line, open) => {
|
|
205
|
+
const char = line[0];
|
|
206
|
+
switch (char) {
|
|
207
|
+
case ">":
|
|
208
|
+
case "<":
|
|
209
|
+
case "[":
|
|
210
|
+
case "|":
|
|
211
|
+
case ":": return true;
|
|
212
|
+
case "#": {
|
|
213
|
+
const run = /^#{1,6}/.exec(line)?.[0] ?? "";
|
|
214
|
+
return run.length > 0 && (run.length === line.length || isSpaceOrTab(line[run.length]));
|
|
215
|
+
}
|
|
216
|
+
case "`":
|
|
217
|
+
case "~": return line.startsWith(char.repeat(3)) || open && [...line].every((each) => each === char);
|
|
218
|
+
case "=": return /^=+[ \t]*$/.test(line);
|
|
219
|
+
case "+":
|
|
220
|
+
case "-":
|
|
221
|
+
case "*":
|
|
222
|
+
case "_": {
|
|
223
|
+
const bullet = char !== "_" && (line.length === 1 || isSpaceOrTab(line[1]));
|
|
224
|
+
const thematic = char !== "+" && isRunOf(line, char) && (open || [...line].filter((each) => each === char).length >= 3);
|
|
225
|
+
const frontmatter = (char === "-" || char === "+") && line.startsWith(char.repeat(3));
|
|
226
|
+
const dashLine = char === "-" && reDelimiterRowShaped.test(line);
|
|
227
|
+
return bullet || thematic || frontmatter || dashLine;
|
|
228
|
+
}
|
|
229
|
+
default: return false;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
/** ASCII punctuation: what a backslash escapes, so `\x` is one cell character. */
|
|
233
|
+
const reAsciiPunctuation = /[!-/:-@[-`{-~]/;
|
|
234
|
+
const literalText = (value, context, atLineStart, mdx, followingText, nonTextFollows) => {
|
|
235
|
+
let out = "";
|
|
236
|
+
let lineStart = atLineStart;
|
|
237
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
238
|
+
const char = value[index];
|
|
239
|
+
if (char === "\n") {
|
|
240
|
+
const adjacent = value[index - 1] === "\n" || value[index + 1] === "\n";
|
|
241
|
+
if (context.singleLine) {
|
|
242
|
+
out += " ";
|
|
243
|
+
lineStart = false;
|
|
244
|
+
} else if (adjacent || lineStart) {
|
|
245
|
+
out += " ";
|
|
246
|
+
lineStart = false;
|
|
247
|
+
} else {
|
|
248
|
+
out += "\n";
|
|
249
|
+
lineStart = true;
|
|
250
|
+
}
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (lineStart) {
|
|
254
|
+
lineStart = false;
|
|
255
|
+
if (char === " ") {
|
|
256
|
+
out += "	";
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (char === " ") {
|
|
260
|
+
out += " ";
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const lineEnd = value.indexOf("\n", index);
|
|
264
|
+
let line = value.slice(index, lineEnd === -1 ? void 0 : lineEnd);
|
|
265
|
+
let open = false;
|
|
266
|
+
if (lineEnd === -1) {
|
|
267
|
+
const followingEnd = followingText.indexOf("\n");
|
|
268
|
+
line += followingText.slice(0, followingEnd === -1 ? void 0 : followingEnd);
|
|
269
|
+
open = followingEnd === -1;
|
|
270
|
+
}
|
|
271
|
+
const ordered = ORDERED_MARKER.exec(line);
|
|
272
|
+
if (ordered !== null) {
|
|
273
|
+
const after = line[ordered[0].length];
|
|
274
|
+
if (after === void 0 || isSpaceOrTab(after)) {
|
|
275
|
+
out += `${ordered[0].slice(0, -1)}\\${ordered[0].slice(-1)}`;
|
|
276
|
+
index += ordered[0].length - 1;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
} else if (literalLineOpensBlock(line, open)) {
|
|
280
|
+
out += `\\${char}`;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (char === "\\") {
|
|
285
|
+
const next = value[index + 1];
|
|
286
|
+
if (next !== void 0 && reAsciiPunctuation.test(next)) {
|
|
287
|
+
out += `\\${next}`;
|
|
288
|
+
index += 1;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
out += next === void 0 && (followingText !== "" || nonTextFollows) ? "\\\\" : "\\";
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (char === "|" && context.inTable) {
|
|
295
|
+
out += "\\|";
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (mdx && (char === "{" || char === "<")) {
|
|
299
|
+
out += `\\${char}`;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (context.inHeading && char === "#" && isClosingHashRun(value, index, followingText, nonTextFollows)) {
|
|
303
|
+
out += "\\#";
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
out += char;
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
text: out,
|
|
310
|
+
atLineStart: lineStart
|
|
311
|
+
};
|
|
312
|
+
};
|
|
187
313
|
/** Pick the effective emphasis marker, flipping away from a forbidden char. */
|
|
188
314
|
const emphasisMarker = (fidelity, forbidden) => {
|
|
189
315
|
const chosen = fidelity ?? "*";
|
|
@@ -260,7 +386,7 @@ const serializeInlines = (children, context, state, startsLine, junctionGuard) =
|
|
|
260
386
|
followingText += children[next].value;
|
|
261
387
|
next += 1;
|
|
262
388
|
}
|
|
263
|
-
const escaped = escapeText(child.value, context, atLineStart, state.mdx, followingText, next < children.length);
|
|
389
|
+
const escaped = (child.escapeStyle === "literal" ? literalText : escapeText)(child.value, context, atLineStart, state.mdx, followingText, next < children.length);
|
|
264
390
|
out += escaped.text;
|
|
265
391
|
atLineStart = escaped.atLineStart;
|
|
266
392
|
break;
|
|
@@ -509,13 +635,30 @@ const alignCell = (align) => {
|
|
|
509
635
|
default: return "---";
|
|
510
636
|
}
|
|
511
637
|
};
|
|
638
|
+
/**
|
|
639
|
+
* Escape every cell pipe the GFM cell splitter would read as a column
|
|
640
|
+
* boundary. The splitter consumes a backslash and the punctuation after it as
|
|
641
|
+
* one character, so `\\` is an escaped backslash: a pipe is escaped already
|
|
642
|
+
* iff the run of backslashes directly before it has odd length, and needs a
|
|
643
|
+
* `\` iff that run is even, zero included.
|
|
644
|
+
*/
|
|
645
|
+
const escapeCellPipes = (content) => {
|
|
646
|
+
let out = "";
|
|
647
|
+
let backslashes = 0;
|
|
648
|
+
for (const char of content) {
|
|
649
|
+
if (char === "|" && backslashes % 2 === 0) out += "\\";
|
|
650
|
+
backslashes = char === "\\" ? backslashes + 1 : 0;
|
|
651
|
+
out += char;
|
|
652
|
+
}
|
|
653
|
+
return out;
|
|
654
|
+
};
|
|
512
655
|
const serializeTable = (table, state) => {
|
|
513
656
|
const columnCount = Math.max(1, ...table.children.map((row) => row.children.length));
|
|
514
657
|
const rows = table.children.map((row) => {
|
|
515
658
|
guard(state, row);
|
|
516
659
|
const cells = row.children.map((cell) => {
|
|
517
660
|
guard(state, cell);
|
|
518
|
-
const content = serializeInlines(cell.children, CELL_CONTEXT, state, false)
|
|
661
|
+
const content = escapeCellPipes(serializeInlines(cell.children, CELL_CONTEXT, state, false));
|
|
519
662
|
unguard(state);
|
|
520
663
|
return content;
|
|
521
664
|
});
|