@effected/markdown 0.2.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/Frontmatter.js +272 -0
- package/FrontmatterResolver.js +273 -0
- package/JsonFrontmatter.js +46 -0
- package/LICENSE +21 -0
- package/Markdown.js +251 -0
- package/MarkdownDiagnostic.js +85 -0
- package/MarkdownDocument.js +248 -0
- package/MarkdownEdit.js +52 -0
- package/MarkdownFormat.js +380 -0
- package/MarkdownNode.js +641 -0
- package/MarkdownVisitor.js +88 -0
- package/Mdast.js +384 -0
- package/README.md +255 -0
- package/TomlFrontmatter.js +44 -0
- package/YamlFrontmatter.js +45 -0
- package/index.d.ts +2190 -0
- package/index.js +15 -0
- package/internal/blockParser.js +419 -0
- package/internal/blockRegistry.js +90 -0
- package/internal/blockTypes.js +27 -0
- package/internal/blocks/atxHeading.js +54 -0
- package/internal/blocks/blockquote.js +40 -0
- package/internal/blocks/code.js +90 -0
- package/internal/blocks/document.js +17 -0
- package/internal/blocks/fencedCode.js +26 -0
- package/internal/blocks/footnoteDefinition.js +84 -0
- package/internal/blocks/frontmatter.js +56 -0
- package/internal/blocks/htmlBlock.js +72 -0
- package/internal/blocks/indentedCode.js +16 -0
- package/internal/blocks/linkReferenceDefinition.js +72 -0
- package/internal/blocks/list.js +159 -0
- package/internal/blocks/paragraph.js +27 -0
- package/internal/blocks/setextHeading.js +24 -0
- package/internal/blocks/table.js +378 -0
- package/internal/blocks/taskListItem.js +36 -0
- package/internal/blocks/thematicBreak.js +35 -0
- package/internal/carriers.js +42 -0
- package/internal/entities.js +26 -0
- package/internal/entityMap.js +7 -0
- package/internal/htmlTags.js +18 -0
- package/internal/inlineNode.js +54 -0
- package/internal/inlineParser.js +403 -0
- package/internal/inlineRegistry.js +68 -0
- package/internal/inlines/autolink.js +36 -0
- package/internal/inlines/autolinkLiteral.js +374 -0
- package/internal/inlines/codeSpan.js +32 -0
- package/internal/inlines/emphasis.js +79 -0
- package/internal/inlines/entity.js +21 -0
- package/internal/inlines/escape.js +34 -0
- package/internal/inlines/footnoteReference.js +63 -0
- package/internal/inlines/lineBreak.js +25 -0
- package/internal/inlines/link.js +212 -0
- package/internal/inlines/rawHtml.js +27 -0
- package/internal/inlines/strikethrough.js +81 -0
- package/internal/inlines/text.js +22 -0
- package/internal/lineIndex.js +76 -0
- package/internal/patterns.js +26 -0
- package/internal/preprocess.js +58 -0
- package/internal/rawInline.js +57 -0
- package/internal/references.js +160 -0
- package/internal/segments.js +36 -0
- package/internal/stringify.js +519 -0
- package/internal/unescape.js +18 -0
- package/package.json +61 -0
- package/tsdoc-metadata.json +11 -0
package/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MarkdownEdit, MarkdownRange } from "./MarkdownEdit.js";
|
|
2
|
+
import { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterFormat, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, MarkdownNode, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar } from "./MarkdownNode.js";
|
|
3
|
+
import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterValidationError, MarkdownFrontmatter } from "./Frontmatter.js";
|
|
4
|
+
import { SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError } from "./FrontmatterResolver.js";
|
|
5
|
+
import { JsonFrontmatter } from "./JsonFrontmatter.js";
|
|
6
|
+
import { MarkdownDiagnostic, MarkdownParseErrorCode } from "./MarkdownDiagnostic.js";
|
|
7
|
+
import { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError } from "./Markdown.js";
|
|
8
|
+
import { MarkdownDocument } from "./MarkdownDocument.js";
|
|
9
|
+
import { MarkdownFormat, MarkdownFormattingOptions, MarkdownModificationError, MarkdownModificationErrorCode } from "./MarkdownFormat.js";
|
|
10
|
+
import { MarkdownVisitor, MarkdownVisitorEvent } from "./MarkdownVisitor.js";
|
|
11
|
+
import { Mdast, MdastDecodeError } from "./Mdast.js";
|
|
12
|
+
import { TomlFrontmatter } from "./TomlFrontmatter.js";
|
|
13
|
+
import { YamlFrontmatter } from "./YamlFrontmatter.js";
|
|
14
|
+
|
|
15
|
+
export { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { Frontmatter, Point, Position, Root } from "../MarkdownNode.js";
|
|
2
|
+
import { makeBlockNode } from "./blockTypes.js";
|
|
3
|
+
import { columnsToNextTabStop, preprocessLines } from "./preprocess.js";
|
|
4
|
+
import { isHtmlBlockEnd } from "./blocks/htmlBlock.js";
|
|
5
|
+
import { blockDialect } from "./blockRegistry.js";
|
|
6
|
+
import { scanFrontmatter } from "./blocks/frontmatter.js";
|
|
7
|
+
import { GuardExceeded } from "./carriers.js";
|
|
8
|
+
import { LineIndex } from "./lineIndex.js";
|
|
9
|
+
import { prepareInline } from "./rawInline.js";
|
|
10
|
+
|
|
11
|
+
//#region src/internal/blockParser.ts
|
|
12
|
+
/** Upstream's cheap pre-filter: a line that cannot start any block. */
|
|
13
|
+
const reMaybeSpecial = /^[#`~*+_=<>0-9-]/;
|
|
14
|
+
var BlockParser = class {
|
|
15
|
+
lines;
|
|
16
|
+
lineIndex;
|
|
17
|
+
dialect;
|
|
18
|
+
/**
|
|
19
|
+
* The dialect's NAME, kept alongside its block tables because the inline
|
|
20
|
+
* pass keys its own registry off the same string.
|
|
21
|
+
*/
|
|
22
|
+
dialectName;
|
|
23
|
+
sourceLength;
|
|
24
|
+
doc;
|
|
25
|
+
tipNode;
|
|
26
|
+
oldtip;
|
|
27
|
+
lastMatchedContainer;
|
|
28
|
+
currentLine = "";
|
|
29
|
+
lineStart = 0;
|
|
30
|
+
lineNumber = 0;
|
|
31
|
+
offset = 0;
|
|
32
|
+
column = 0;
|
|
33
|
+
nextNonspace = 0;
|
|
34
|
+
nextNonspaceColumn = 0;
|
|
35
|
+
indent = 0;
|
|
36
|
+
indented = false;
|
|
37
|
+
blank = false;
|
|
38
|
+
allClosed = true;
|
|
39
|
+
partiallyConsumedTab = false;
|
|
40
|
+
lastLineLength = 0;
|
|
41
|
+
frontmatterCapture;
|
|
42
|
+
constructor(text, dialect, dialectName, frontmatter = false) {
|
|
43
|
+
this.lines = preprocessLines(text);
|
|
44
|
+
this.frontmatterCapture = frontmatter ? scanFrontmatter(this.lines, text) : null;
|
|
45
|
+
const starts = this.lines.map((line) => line.start);
|
|
46
|
+
if (text.endsWith("\n")) starts.push(text.length);
|
|
47
|
+
this.lineIndex = LineIndex.fromLineStarts(text, starts);
|
|
48
|
+
this.sourceLength = text.length;
|
|
49
|
+
this.dialect = dialect;
|
|
50
|
+
this.dialectName = dialectName;
|
|
51
|
+
this.doc = makeBlockNode("document", 0, 1);
|
|
52
|
+
this.tipNode = this.doc;
|
|
53
|
+
this.oldtip = this.doc;
|
|
54
|
+
this.lastMatchedContainer = this.doc;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The deepest open block. Only reachable while the document is open — a
|
|
58
|
+
* read after the final finalize is a wiring bug and dies as a defect.
|
|
59
|
+
*/
|
|
60
|
+
get tip() {
|
|
61
|
+
const tip = this.tipNode;
|
|
62
|
+
if (tip === void 0) throw new TypeError("block parser: the tip was read after the document was finalized");
|
|
63
|
+
return tip;
|
|
64
|
+
}
|
|
65
|
+
constructOf(type) {
|
|
66
|
+
const construct = this.dialect.constructs.get(type);
|
|
67
|
+
if (construct === void 0) throw new TypeError(`block parser: no construct registered for block type ${type}`);
|
|
68
|
+
return construct;
|
|
69
|
+
}
|
|
70
|
+
advanceOffset(count, columns = false) {
|
|
71
|
+
const line = this.currentLine;
|
|
72
|
+
let remaining = count;
|
|
73
|
+
let char = line.charAt(this.offset);
|
|
74
|
+
while (remaining > 0 && char !== "") {
|
|
75
|
+
if (char === " ") {
|
|
76
|
+
const charsToTab = columnsToNextTabStop(this.column);
|
|
77
|
+
if (columns) {
|
|
78
|
+
this.partiallyConsumedTab = charsToTab > remaining;
|
|
79
|
+
const charsToAdvance = charsToTab > remaining ? remaining : charsToTab;
|
|
80
|
+
this.column += charsToAdvance;
|
|
81
|
+
this.offset += this.partiallyConsumedTab ? 0 : 1;
|
|
82
|
+
remaining -= charsToAdvance;
|
|
83
|
+
} else {
|
|
84
|
+
this.partiallyConsumedTab = false;
|
|
85
|
+
this.column += charsToTab;
|
|
86
|
+
this.offset += 1;
|
|
87
|
+
remaining -= 1;
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
this.partiallyConsumedTab = false;
|
|
91
|
+
this.offset += 1;
|
|
92
|
+
this.column += 1;
|
|
93
|
+
remaining -= 1;
|
|
94
|
+
}
|
|
95
|
+
char = line.charAt(this.offset);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
advanceNextNonspace() {
|
|
99
|
+
this.offset = this.nextNonspace;
|
|
100
|
+
this.column = this.nextNonspaceColumn;
|
|
101
|
+
this.partiallyConsumedTab = false;
|
|
102
|
+
}
|
|
103
|
+
findNextNonspace() {
|
|
104
|
+
const line = this.currentLine;
|
|
105
|
+
let index = this.offset;
|
|
106
|
+
let cols = this.column;
|
|
107
|
+
let char = line.charAt(index);
|
|
108
|
+
while (char !== "") {
|
|
109
|
+
if (char === " ") {
|
|
110
|
+
index += 1;
|
|
111
|
+
cols += 1;
|
|
112
|
+
} else if (char === " ") {
|
|
113
|
+
index += 1;
|
|
114
|
+
cols += columnsToNextTabStop(cols);
|
|
115
|
+
} else break;
|
|
116
|
+
char = line.charAt(index);
|
|
117
|
+
}
|
|
118
|
+
this.blank = char === "\n" || char === "\r" || char === "";
|
|
119
|
+
this.nextNonspace = index;
|
|
120
|
+
this.nextNonspaceColumn = cols;
|
|
121
|
+
this.indent = this.nextNonspaceColumn - this.column;
|
|
122
|
+
this.indented = this.indent >= 4;
|
|
123
|
+
}
|
|
124
|
+
addLine() {
|
|
125
|
+
const tip = this.tip;
|
|
126
|
+
if (this.partiallyConsumedTab) {
|
|
127
|
+
this.offset += 1;
|
|
128
|
+
tip.stringContent += " ".repeat(columnsToNextTabStop(this.column));
|
|
129
|
+
}
|
|
130
|
+
const content = this.currentLine.slice(this.offset);
|
|
131
|
+
tip.segments.push({
|
|
132
|
+
textOffset: tip.stringContent.length,
|
|
133
|
+
sourceOffset: this.lineStart + this.offset,
|
|
134
|
+
length: content.length
|
|
135
|
+
});
|
|
136
|
+
tip.stringContent += `${content}\n`;
|
|
137
|
+
}
|
|
138
|
+
addChild(type, offsetInLine) {
|
|
139
|
+
while (!this.constructOf(this.tip.type).canContain(type)) this.finalizeBlock(this.tip, this.lineNumber - 1);
|
|
140
|
+
const parent = this.tip;
|
|
141
|
+
const depth = parent.depth + 1;
|
|
142
|
+
if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, this.lineStart + offsetInLine);
|
|
143
|
+
const child = makeBlockNode(type, this.lineStart + offsetInLine, this.lineNumber, depth);
|
|
144
|
+
child.parent = parent;
|
|
145
|
+
parent.children.push(child);
|
|
146
|
+
this.tipNode = child;
|
|
147
|
+
return child;
|
|
148
|
+
}
|
|
149
|
+
setScanPosition(offset, column) {
|
|
150
|
+
this.offset = offset;
|
|
151
|
+
this.column = column;
|
|
152
|
+
}
|
|
153
|
+
setLastLineLength(length) {
|
|
154
|
+
this.lastLineLength = length;
|
|
155
|
+
}
|
|
156
|
+
replaceBlock(block, type) {
|
|
157
|
+
const parent = block.parent;
|
|
158
|
+
const replacement = makeBlockNode(type, block.startOffset, block.startLine, block.depth);
|
|
159
|
+
replacement.parent = parent;
|
|
160
|
+
replacement.stringContent = block.stringContent;
|
|
161
|
+
replacement.segments.push(...block.segments);
|
|
162
|
+
replacement.endOffset = block.endOffset;
|
|
163
|
+
replacement.endLine = block.endLine;
|
|
164
|
+
if (parent !== void 0) {
|
|
165
|
+
const at = parent.children.indexOf(block);
|
|
166
|
+
if (at === -1) parent.children.push(replacement);
|
|
167
|
+
else parent.children[at] = replacement;
|
|
168
|
+
}
|
|
169
|
+
block.open = false;
|
|
170
|
+
this.tipNode = replacement;
|
|
171
|
+
return replacement;
|
|
172
|
+
}
|
|
173
|
+
insertBefore(block, type) {
|
|
174
|
+
const parent = block.parent;
|
|
175
|
+
const sibling = makeBlockNode(type, block.startOffset, block.startLine, block.depth);
|
|
176
|
+
sibling.parent = parent;
|
|
177
|
+
sibling.open = false;
|
|
178
|
+
if (parent !== void 0) {
|
|
179
|
+
const at = parent.children.indexOf(block);
|
|
180
|
+
if (at === -1) parent.children.push(sibling);
|
|
181
|
+
else parent.children.splice(at, 0, sibling);
|
|
182
|
+
}
|
|
183
|
+
return sibling;
|
|
184
|
+
}
|
|
185
|
+
closeUnmatchedBlocks() {
|
|
186
|
+
if (this.allClosed) return;
|
|
187
|
+
while (this.oldtip !== this.lastMatchedContainer) {
|
|
188
|
+
const parent = this.oldtip.parent;
|
|
189
|
+
this.finalizeBlock(this.oldtip, this.lineNumber - 1);
|
|
190
|
+
this.oldtip = parent ?? this.doc;
|
|
191
|
+
}
|
|
192
|
+
this.allClosed = true;
|
|
193
|
+
}
|
|
194
|
+
finalizeBlock(block, lineNumber) {
|
|
195
|
+
const above = block.parent;
|
|
196
|
+
block.open = false;
|
|
197
|
+
block.endLine = lineNumber;
|
|
198
|
+
block.endOffset = this.endOfLine(lineNumber, this.lastLineLength);
|
|
199
|
+
this.constructOf(block.type).finalize?.(this, block);
|
|
200
|
+
this.tipNode = above;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The absolute offset `length` characters into line `lineNumber` — the
|
|
204
|
+
* offset form of upstream's `sourcepos[1] = [lineNumber, lastLineLength]`.
|
|
205
|
+
* Out-of-range line numbers clamp rather than throw.
|
|
206
|
+
*/
|
|
207
|
+
endOfLine(lineNumber, length) {
|
|
208
|
+
const index = Math.min(Math.max(lineNumber - 1, 0), this.lines.length - 1);
|
|
209
|
+
const line = this.lines[index];
|
|
210
|
+
if (line === void 0) return 0;
|
|
211
|
+
return line.start + Math.min(Math.max(length, 0), line.text.length);
|
|
212
|
+
}
|
|
213
|
+
incorporateLine(line) {
|
|
214
|
+
let container = this.doc;
|
|
215
|
+
this.oldtip = this.tip;
|
|
216
|
+
this.offset = 0;
|
|
217
|
+
this.column = 0;
|
|
218
|
+
this.blank = false;
|
|
219
|
+
this.partiallyConsumedTab = false;
|
|
220
|
+
this.lineNumber += 1;
|
|
221
|
+
this.currentLine = line.text;
|
|
222
|
+
this.lineStart = line.start;
|
|
223
|
+
let lastChild = container.children[container.children.length - 1];
|
|
224
|
+
while (lastChild?.open) {
|
|
225
|
+
container = lastChild;
|
|
226
|
+
this.findNextNonspace();
|
|
227
|
+
const verdict = this.constructOf(container.type).continue(this, container);
|
|
228
|
+
if (verdict === 2) return;
|
|
229
|
+
if (verdict === 1) {
|
|
230
|
+
container = container.parent ?? this.doc;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
lastChild = container.children[container.children.length - 1];
|
|
234
|
+
}
|
|
235
|
+
this.allClosed = container === this.oldtip;
|
|
236
|
+
this.lastMatchedContainer = container;
|
|
237
|
+
let matchedLeaf = container.type !== "paragraph" && this.constructOf(container.type).acceptsLines;
|
|
238
|
+
const starts = this.dialect.starts;
|
|
239
|
+
while (!matchedLeaf) {
|
|
240
|
+
this.findNextNonspace();
|
|
241
|
+
if (!this.indented) {
|
|
242
|
+
const rest = this.currentLine.slice(this.nextNonspace);
|
|
243
|
+
if (!reMaybeSpecial.test(rest) && !(this.dialect.mayStartBlock?.(rest, container) ?? false)) {
|
|
244
|
+
this.advanceNextNonspace();
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
let index = 0;
|
|
249
|
+
while (index < starts.length) {
|
|
250
|
+
const start = starts[index];
|
|
251
|
+
const result = start === void 0 ? 0 : start.trigger(this, container);
|
|
252
|
+
if (result === 1) {
|
|
253
|
+
container = this.tip;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
if (result === 2) {
|
|
257
|
+
container = this.tip;
|
|
258
|
+
matchedLeaf = true;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
index += 1;
|
|
262
|
+
}
|
|
263
|
+
if (index === starts.length) {
|
|
264
|
+
this.advanceNextNonspace();
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!this.allClosed && !this.blank && this.tip.type === "paragraph") this.addLine();
|
|
269
|
+
else {
|
|
270
|
+
this.closeUnmatchedBlocks();
|
|
271
|
+
if (this.constructOf(container.type).acceptsLines) {
|
|
272
|
+
this.addLine();
|
|
273
|
+
if (container.type === "html" && isHtmlBlockEnd(container, this.currentLine.slice(this.offset))) {
|
|
274
|
+
this.lastLineLength = this.currentLine.length;
|
|
275
|
+
this.finalizeBlock(container, this.lineNumber);
|
|
276
|
+
}
|
|
277
|
+
} else if (this.offset < this.currentLine.length && !this.blank) {
|
|
278
|
+
container = this.addChild("paragraph", this.offset);
|
|
279
|
+
this.advanceNextNonspace();
|
|
280
|
+
this.addLine();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
this.lastLineLength = this.currentLine.length;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Prepend the captured frontmatter node, when there is one. The root's
|
|
287
|
+
* own span widens to cover the block if the body ended before it — the
|
|
288
|
+
* frontmatter-only document, where the block pass saw no lines at all.
|
|
289
|
+
*/
|
|
290
|
+
withFrontmatter(root) {
|
|
291
|
+
const capture = this.frontmatterCapture;
|
|
292
|
+
if (capture === null) return root;
|
|
293
|
+
const node = Frontmatter.make({
|
|
294
|
+
type: "frontmatter",
|
|
295
|
+
format: capture.format,
|
|
296
|
+
value: capture.value,
|
|
297
|
+
position: this.position(0, capture.endOffset)
|
|
298
|
+
});
|
|
299
|
+
const position = root.position.end.offset >= capture.endOffset ? root.position : this.position(0, capture.endOffset);
|
|
300
|
+
return Root.make({
|
|
301
|
+
type: "root",
|
|
302
|
+
children: [node, ...root.children],
|
|
303
|
+
position
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
position(startOffset, endOffset) {
|
|
307
|
+
const start = Math.min(Math.max(startOffset, 0), this.sourceLength);
|
|
308
|
+
const end = Math.min(Math.max(endOffset, start), this.sourceLength);
|
|
309
|
+
const startPoint = this.lineIndex.positionAt(start);
|
|
310
|
+
const endPoint = this.lineIndex.positionAt(end);
|
|
311
|
+
return Position.make({
|
|
312
|
+
start: Point.make({
|
|
313
|
+
line: startPoint.line,
|
|
314
|
+
column: startPoint.column,
|
|
315
|
+
offset: start
|
|
316
|
+
}),
|
|
317
|
+
end: Point.make({
|
|
318
|
+
line: endPoint.line,
|
|
319
|
+
column: endPoint.column,
|
|
320
|
+
offset: end
|
|
321
|
+
})
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Depth-first materialization. Recursion depth is container nesting depth,
|
|
326
|
+
* which `addChild`'s `MAX_NESTING_DEPTH` guard caps at parse time — this
|
|
327
|
+
* walk can never see a tree the line loop refused to build.
|
|
328
|
+
*/
|
|
329
|
+
materializeBlock(block, context, definitions) {
|
|
330
|
+
const children = [];
|
|
331
|
+
for (const child of block.children) {
|
|
332
|
+
const node = this.materializeBlock(child, context, definitions);
|
|
333
|
+
if (node !== void 0 && node.type !== "root") children.push(node);
|
|
334
|
+
}
|
|
335
|
+
const definition = definitions.get(block);
|
|
336
|
+
if (definition !== void 0) return definition;
|
|
337
|
+
return this.constructOf(block.type).materialize(block, children, context);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Index every reference target in the tree — link reference definitions and
|
|
341
|
+
* GFM footnote definition labels — before anything else is built.
|
|
342
|
+
*
|
|
343
|
+
* The inline pass resolves references against these, and a definition can
|
|
344
|
+
* appear anywhere, including after the paragraph that references it, so
|
|
345
|
+
* both maps have to exist before the first leaf is parsed. Walking twice is
|
|
346
|
+
* the price; the alternative is a mutable tree the inline pass patches
|
|
347
|
+
* afterwards.
|
|
348
|
+
*
|
|
349
|
+
* Link reference definitions are MATERIALIZED here, so the tree and the
|
|
350
|
+
* refmap point at one object. Footnote definitions deliberately are not:
|
|
351
|
+
* a footnote definition is a container whose children are flow blocks, and
|
|
352
|
+
* materializing those runs the inline pass over them — which consults this
|
|
353
|
+
* very map. Only the labels are collected, which is all formation needs,
|
|
354
|
+
* and the nodes are built in document order by the ordinary walk. That is
|
|
355
|
+
* why `footnoteLabels` is a set of labels rather than a map to nodes; a
|
|
356
|
+
* consumer wanting the node walks the tree, where it still sits.
|
|
357
|
+
*/
|
|
358
|
+
collectReferences(block, context, nodes, refmap, footnoteLabels) {
|
|
359
|
+
if (block.type === "definition") {
|
|
360
|
+
const materialized = this.constructOf(block.type).materialize(block, [], context);
|
|
361
|
+
const key = block.data.definition?.key;
|
|
362
|
+
if (materialized?.type === "definition" && key !== void 0) {
|
|
363
|
+
nodes.set(block, materialized);
|
|
364
|
+
if (!refmap.has(key)) refmap.set(key, materialized);
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const footnoteKey = block.data.footnote?.key;
|
|
369
|
+
if (footnoteKey !== void 0) footnoteLabels.add(footnoteKey);
|
|
370
|
+
for (const child of block.children) this.collectReferences(child, context, nodes, refmap, footnoteLabels);
|
|
371
|
+
}
|
|
372
|
+
parse() {
|
|
373
|
+
const skippedLines = this.frontmatterCapture?.lineCount ?? 0;
|
|
374
|
+
this.lineNumber = skippedLines;
|
|
375
|
+
for (let index = skippedLines; index < this.lines.length; index += 1) {
|
|
376
|
+
const line = this.lines[index];
|
|
377
|
+
if (line !== void 0) this.incorporateLine(line);
|
|
378
|
+
}
|
|
379
|
+
while (this.tipNode !== void 0) this.finalizeBlock(this.tipNode, this.lines.length);
|
|
380
|
+
this.doc.endOffset = this.sourceLength;
|
|
381
|
+
const refmap = /* @__PURE__ */ new Map();
|
|
382
|
+
const footnoteLabels = /* @__PURE__ */ new Set();
|
|
383
|
+
const definitionNodes = /* @__PURE__ */ new Map();
|
|
384
|
+
const rawInlines = [];
|
|
385
|
+
const context = {
|
|
386
|
+
position: (start, end) => this.position(start, end),
|
|
387
|
+
inlineSlice: (block) => prepareInline(block, (start, end) => this.position(start, end), refmap, this.dialectName, footnoteLabels),
|
|
388
|
+
registerInline: (parent, prepared) => {
|
|
389
|
+
rawInlines.push({
|
|
390
|
+
parent,
|
|
391
|
+
text: prepared.text,
|
|
392
|
+
startOffset: prepared.startOffset,
|
|
393
|
+
segments: prepared.segments
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
this.collectReferences(this.doc, context, definitionNodes, refmap, footnoteLabels);
|
|
398
|
+
const materialized = this.materializeBlock(this.doc, context, definitionNodes);
|
|
399
|
+
if (materialized === void 0 || materialized.type !== "root") throw new TypeError("block parser: the document construct did not materialize a root");
|
|
400
|
+
return {
|
|
401
|
+
root: this.withFrontmatter(materialized),
|
|
402
|
+
rawInlines,
|
|
403
|
+
carriers: [],
|
|
404
|
+
refmap,
|
|
405
|
+
footnoteLabels
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
/**
|
|
410
|
+
* Run the block pass over `text`.
|
|
411
|
+
*
|
|
412
|
+
* Every node carries a complete {@link Position}, leaf blocks have their
|
|
413
|
+
* inline content parsed, and `rawInlines` reports the raw text each leaf was
|
|
414
|
+
* built from.
|
|
415
|
+
*/
|
|
416
|
+
const parseBlocks = (text, dialect = "commonmark", frontmatter = false) => new BlockParser(text, blockDialect(dialect), dialect, frontmatter).parse();
|
|
417
|
+
|
|
418
|
+
//#endregion
|
|
419
|
+
export { parseBlocks };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { atxHeadingStart, headingConstruct } from "./blocks/atxHeading.js";
|
|
2
|
+
import { blockquoteConstruct, blockquoteStart } from "./blocks/blockquote.js";
|
|
3
|
+
import { codeConstruct } from "./blocks/code.js";
|
|
4
|
+
import { documentConstruct } from "./blocks/document.js";
|
|
5
|
+
import { fencedCodeStart } from "./blocks/fencedCode.js";
|
|
6
|
+
import { footnoteDefinitionConstruct, footnoteDefinitionStart } from "./blocks/footnoteDefinition.js";
|
|
7
|
+
import { htmlBlockConstruct, htmlBlockStart } from "./blocks/htmlBlock.js";
|
|
8
|
+
import { indentedCodeStart } from "./blocks/indentedCode.js";
|
|
9
|
+
import { definitionConstruct } from "./blocks/linkReferenceDefinition.js";
|
|
10
|
+
import { listConstruct, listItemConstruct, listItemStart } from "./blocks/list.js";
|
|
11
|
+
import { paragraphConstruct } from "./blocks/paragraph.js";
|
|
12
|
+
import { setextHeadingStart } from "./blocks/setextHeading.js";
|
|
13
|
+
import { tableCellConstruct, tableConstruct, tableHeaderStart, tableRowConstruct, tableRowStart } from "./blocks/table.js";
|
|
14
|
+
import { taskListItemStart } from "./blocks/taskListItem.js";
|
|
15
|
+
import { thematicBreakConstruct, thematicBreakStart } from "./blocks/thematicBreak.js";
|
|
16
|
+
|
|
17
|
+
//#region src/internal/blockRegistry.ts
|
|
18
|
+
const constructTable = (constructs) => new Map(constructs.map((construct) => [construct.type, construct]));
|
|
19
|
+
const commonmarkDialect = {
|
|
20
|
+
constructs: constructTable([
|
|
21
|
+
documentConstruct,
|
|
22
|
+
blockquoteConstruct,
|
|
23
|
+
listConstruct,
|
|
24
|
+
listItemConstruct,
|
|
25
|
+
paragraphConstruct,
|
|
26
|
+
headingConstruct,
|
|
27
|
+
thematicBreakConstruct,
|
|
28
|
+
codeConstruct,
|
|
29
|
+
htmlBlockConstruct,
|
|
30
|
+
definitionConstruct
|
|
31
|
+
]),
|
|
32
|
+
starts: [
|
|
33
|
+
blockquoteStart,
|
|
34
|
+
atxHeadingStart,
|
|
35
|
+
fencedCodeStart,
|
|
36
|
+
htmlBlockStart,
|
|
37
|
+
setextHeadingStart,
|
|
38
|
+
thematicBreakStart,
|
|
39
|
+
listItemStart,
|
|
40
|
+
indentedCodeStart
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
const gfmDialect = {
|
|
44
|
+
constructs: constructTable([
|
|
45
|
+
documentConstruct,
|
|
46
|
+
blockquoteConstruct,
|
|
47
|
+
listConstruct,
|
|
48
|
+
listItemConstruct,
|
|
49
|
+
paragraphConstruct,
|
|
50
|
+
headingConstruct,
|
|
51
|
+
thematicBreakConstruct,
|
|
52
|
+
codeConstruct,
|
|
53
|
+
htmlBlockConstruct,
|
|
54
|
+
definitionConstruct,
|
|
55
|
+
footnoteDefinitionConstruct,
|
|
56
|
+
tableConstruct,
|
|
57
|
+
tableRowConstruct,
|
|
58
|
+
tableCellConstruct
|
|
59
|
+
]),
|
|
60
|
+
starts: [
|
|
61
|
+
blockquoteStart,
|
|
62
|
+
atxHeadingStart,
|
|
63
|
+
fencedCodeStart,
|
|
64
|
+
htmlBlockStart,
|
|
65
|
+
setextHeadingStart,
|
|
66
|
+
thematicBreakStart,
|
|
67
|
+
footnoteDefinitionStart,
|
|
68
|
+
listItemStart,
|
|
69
|
+
indentedCodeStart,
|
|
70
|
+
taskListItemStart,
|
|
71
|
+
tableHeaderStart,
|
|
72
|
+
tableRowStart
|
|
73
|
+
],
|
|
74
|
+
mayStartBlock: (rest, container) => container.type === "table" || rest.startsWith("[") || rest.startsWith("|") || rest.startsWith(":")
|
|
75
|
+
};
|
|
76
|
+
const dialects = /* @__PURE__ */ new Map([["commonmark", commonmarkDialect], ["gfm", gfmDialect]]);
|
|
77
|
+
/**
|
|
78
|
+
* The block tables for `dialect`.
|
|
79
|
+
*
|
|
80
|
+
* An unknown dialect cannot arrive through the schema-typed public surface,
|
|
81
|
+
* so it is programmer error and dies as a defect.
|
|
82
|
+
*/
|
|
83
|
+
const blockDialect = (dialect) => {
|
|
84
|
+
const found = dialects.get(dialect);
|
|
85
|
+
if (found === void 0) throw new TypeError(`unknown markdown dialect: ${String(dialect)}`);
|
|
86
|
+
return found;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
export { blockDialect };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/internal/blockTypes.ts
|
|
2
|
+
/** Narrow materialized children to the flow content most constructs contain. */
|
|
3
|
+
const flowChildren = (children) => children.filter((child) => child.type !== "root" && child.type !== "listItem" && child.type !== "tableRow" && child.type !== "tableCell");
|
|
4
|
+
/** Narrow materialized children to the list items a list contains. */
|
|
5
|
+
const listItemChildren = (children) => children.filter((child) => child.type === "listItem");
|
|
6
|
+
/** Narrow materialized children to the rows a table contains. */
|
|
7
|
+
const tableRowChildren = (children) => children.filter((child) => child.type === "tableRow");
|
|
8
|
+
/** Narrow materialized children to the cells a table row contains. */
|
|
9
|
+
const tableCellChildren = (children) => children.filter((child) => child.type === "tableCell");
|
|
10
|
+
/** Open a fresh {@link BlockNode}. */
|
|
11
|
+
const makeBlockNode = (type, startOffset, startLine, depth = 0) => ({
|
|
12
|
+
type,
|
|
13
|
+
depth,
|
|
14
|
+
parent: void 0,
|
|
15
|
+
children: [],
|
|
16
|
+
open: true,
|
|
17
|
+
stringContent: "",
|
|
18
|
+
segments: [],
|
|
19
|
+
startOffset,
|
|
20
|
+
endOffset: startOffset,
|
|
21
|
+
startLine,
|
|
22
|
+
endLine: startLine,
|
|
23
|
+
data: {}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { flowChildren, listItemChildren, makeBlockNode, tableCellChildren, tableRowChildren };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Heading } from "../../MarkdownNode.js";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/blocks/atxHeading.ts
|
|
4
|
+
const reATXHeadingMarker = /^#{1,6}(?:[ \t]+|$)/;
|
|
5
|
+
const reOnlyTrailingHashes = /^[ \t]*#+[ \t]*$/;
|
|
6
|
+
const reClosingHashes = /[ \t]+#+[ \t]*$/;
|
|
7
|
+
/** Narrow a `#`-run length to the schema's depth literal; the regex caps it at 6. */
|
|
8
|
+
const headingDepth = (hashes) => hashes < 1 ? 1 : hashes > 6 ? 6 : hashes;
|
|
9
|
+
/** Heading: never spans more than one line, and contains no blocks. */
|
|
10
|
+
const headingConstruct = {
|
|
11
|
+
type: "heading",
|
|
12
|
+
acceptsLines: false,
|
|
13
|
+
canContain: () => false,
|
|
14
|
+
continue: () => 1,
|
|
15
|
+
materialize: (block, _children, context) => {
|
|
16
|
+
const inline = context.inlineSlice(block);
|
|
17
|
+
const style = block.data.headingStyle ?? "atx";
|
|
18
|
+
const node = Heading.make({
|
|
19
|
+
depth: block.data.level ?? 1,
|
|
20
|
+
children: inline.children,
|
|
21
|
+
position: context.position(block.startOffset, block.endOffset),
|
|
22
|
+
headingStyle: style
|
|
23
|
+
});
|
|
24
|
+
context.registerInline(node, inline);
|
|
25
|
+
return node;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/** The ATX heading block start: `#` through `######`, optionally closed. */
|
|
29
|
+
const atxHeadingStart = {
|
|
30
|
+
name: "atxHeading",
|
|
31
|
+
trigger: (scanner) => {
|
|
32
|
+
if (scanner.indented) return 0;
|
|
33
|
+
const match = reATXHeadingMarker.exec(scanner.currentLine.slice(scanner.nextNonspace));
|
|
34
|
+
if (match === null) return 0;
|
|
35
|
+
scanner.advanceNextNonspace();
|
|
36
|
+
scanner.advanceOffset(match[0].length, false);
|
|
37
|
+
scanner.closeUnmatchedBlocks();
|
|
38
|
+
const container = scanner.addChild("heading", scanner.nextNonspace);
|
|
39
|
+
container.data.level = headingDepth(match[0].trim().length);
|
|
40
|
+
container.data.headingStyle = "atx";
|
|
41
|
+
const content = scanner.currentLine.slice(scanner.offset).replace(reOnlyTrailingHashes, "").replace(reClosingHashes, "");
|
|
42
|
+
container.stringContent = content;
|
|
43
|
+
container.segments.push({
|
|
44
|
+
textOffset: 0,
|
|
45
|
+
sourceOffset: scanner.lineStart + scanner.offset,
|
|
46
|
+
length: content.length
|
|
47
|
+
});
|
|
48
|
+
scanner.advanceOffset(scanner.currentLine.length - scanner.offset, false);
|
|
49
|
+
return 2;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { atxHeadingStart, headingConstruct };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Blockquote } from "../../MarkdownNode.js";
|
|
2
|
+
import { flowChildren } from "../blockTypes.js";
|
|
3
|
+
import { isSpaceOrTab, peekCode } from "../preprocess.js";
|
|
4
|
+
|
|
5
|
+
//#region src/internal/blocks/blockquote.ts
|
|
6
|
+
const C_GREATERTHAN = 62;
|
|
7
|
+
/** Blockquote: continues while each line carries its `>` marker. */
|
|
8
|
+
const blockquoteConstruct = {
|
|
9
|
+
type: "blockquote",
|
|
10
|
+
acceptsLines: false,
|
|
11
|
+
canContain: (child) => child !== "listItem",
|
|
12
|
+
continue: (scanner) => {
|
|
13
|
+
const line = scanner.currentLine;
|
|
14
|
+
if (scanner.indented || peekCode(line, scanner.nextNonspace) !== C_GREATERTHAN) return 1;
|
|
15
|
+
scanner.advanceNextNonspace();
|
|
16
|
+
scanner.advanceOffset(1, false);
|
|
17
|
+
if (isSpaceOrTab(peekCode(line, scanner.offset))) scanner.advanceOffset(1, true);
|
|
18
|
+
return 0;
|
|
19
|
+
},
|
|
20
|
+
materialize: (block, children, context) => Blockquote.make({
|
|
21
|
+
children: flowChildren(children),
|
|
22
|
+
position: context.position(block.startOffset, block.endOffset)
|
|
23
|
+
})
|
|
24
|
+
};
|
|
25
|
+
/** The blockquote block start: an unindented `>`. */
|
|
26
|
+
const blockquoteStart = {
|
|
27
|
+
name: "blockquote",
|
|
28
|
+
trigger: (scanner) => {
|
|
29
|
+
if (scanner.indented || peekCode(scanner.currentLine, scanner.nextNonspace) !== C_GREATERTHAN) return 0;
|
|
30
|
+
scanner.advanceNextNonspace();
|
|
31
|
+
scanner.advanceOffset(1, false);
|
|
32
|
+
if (isSpaceOrTab(peekCode(scanner.currentLine, scanner.offset))) scanner.advanceOffset(1, true);
|
|
33
|
+
scanner.closeUnmatchedBlocks();
|
|
34
|
+
scanner.addChild("blockquote", scanner.nextNonspace);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { blockquoteConstruct, blockquoteStart };
|