@tabnas/markdown 0.6.2 → 0.7.2
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/dist/block.d.ts +257 -0
- package/dist/block.js +213 -68
- package/dist/block.js.map +1 -1
- package/dist/engine-block.d.ts +69 -0
- package/dist/engine-block.js +99 -0
- package/dist/engine-block.js.map +1 -0
- package/dist/engine-inline.d.ts +25 -0
- package/dist/engine-inline.js +237 -0
- package/dist/engine-inline.js.map +1 -0
- package/dist/inline.d.ts +250 -0
- package/dist/inline.js +395 -215
- package/dist/inline.js.map +1 -1
- package/dist/markdown.d.ts +2 -3
- package/dist/markdown.js +91 -63
- package/dist/markdown.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/block.ts +224 -75
- package/src/engine-block.ts +151 -0
- package/src/engine-inline.ts +284 -0
- package/src/inline.ts +422 -218
- package/src/markdown.ts +95 -65
package/dist/block.d.ts
CHANGED
|
@@ -1,5 +1,262 @@
|
|
|
1
1
|
import { MdNode } from './node.ts';
|
|
2
|
+
import type { NodeType, TableAlign } from './node.ts';
|
|
2
3
|
import type { ParserOptions, RefMap } from './options.ts';
|
|
4
|
+
export declare function isBlank(s: string): boolean;
|
|
5
|
+
/** One physical line of `src` starting at `pos`, with §2.3's NUL replacement. */
|
|
6
|
+
export type LineSegment = {
|
|
7
|
+
text: string;
|
|
8
|
+
next: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Cut the next physical line out of `src` at `pos`: up to (not including)
|
|
12
|
+
* the next `\n`, `\r` or `\r\n`, with `next` past the terminator. Null at
|
|
13
|
+
* the end of the source — which is why a final line ending does not
|
|
14
|
+
* introduce a trailing blank line. Insecure NULs are replaced here, before
|
|
15
|
+
* anything else looks at the text.
|
|
16
|
+
*/
|
|
17
|
+
export declare function segmentNextLine(src: string, pos: number): LineSegment | null;
|
|
18
|
+
/** §4.1: is `rest` a thematic break? */
|
|
19
|
+
export declare function isThematicBreakLine(rest: string): boolean;
|
|
20
|
+
/** §4.2: the ATX marker opening `rest`, with its heading level. */
|
|
21
|
+
export declare function matchAtxHeading(rest: string): {
|
|
22
|
+
marker: string;
|
|
23
|
+
level: number;
|
|
24
|
+
} | null;
|
|
25
|
+
/**
|
|
26
|
+
* §4.2: drop an optional closing sequence of `#`s, preceded by spaces or
|
|
27
|
+
* tabs; a line of nothing but `#`s has empty content.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stripAtxClosing(content: string): string;
|
|
30
|
+
/** §4.5: the opening code fence at the start of `rest`, or null. */
|
|
31
|
+
export declare function matchCodeFence(rest: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* §4.5: the closing-fence candidate at the start of `rest`, or null. The
|
|
34
|
+
* caller still checks the fence character and length against the open block.
|
|
35
|
+
*/
|
|
36
|
+
export declare function matchClosingCodeFence(rest: string): string | null;
|
|
37
|
+
/** §4.3: setext underline level — 1 for `=`, 2 for `-`, 0 if neither. */
|
|
38
|
+
export declare function setextHeadingLevel(rest: string): number;
|
|
39
|
+
/**
|
|
40
|
+
* §4.6: which of the seven HTML block kinds `rest` opens, or 0. The type-7
|
|
41
|
+
* paragraph-interrupt restriction is contextual and stays with the caller.
|
|
42
|
+
*/
|
|
43
|
+
export declare function htmlBlockOpenKind(rest: string): number;
|
|
44
|
+
/** §4.6: does `rest` contain the closing condition for HTML block `type`? */
|
|
45
|
+
export declare function htmlBlockCloses(rest: string, type: number): boolean;
|
|
46
|
+
/** §5.2: the bullet list marker opening `rest`, or null. */
|
|
47
|
+
export declare function matchBulletListMarker(rest: string): {
|
|
48
|
+
marker: string;
|
|
49
|
+
char: string;
|
|
50
|
+
} | null;
|
|
51
|
+
/** §5.2: the ordered list marker opening `rest`, or null. */
|
|
52
|
+
export declare function matchOrderedListMarker(rest: string): {
|
|
53
|
+
marker: string;
|
|
54
|
+
start: number;
|
|
55
|
+
delimiter: string;
|
|
56
|
+
} | null;
|
|
57
|
+
/**
|
|
58
|
+
* Split one row of a table into its cell texts.
|
|
59
|
+
*
|
|
60
|
+
* Cells are separated by *unescaped* pipes; a leading and a trailing pipe are
|
|
61
|
+
* both optional and may differ from row to row, so they are stripped before
|
|
62
|
+
* the split rather than producing empty cells at the ends. A trailing pipe is
|
|
63
|
+
* only a delimiter if it is itself unescaped, which is why the backslash run
|
|
64
|
+
* before it is counted.
|
|
65
|
+
*
|
|
66
|
+
* `\|` is resolved to a literal `|` *here*, as the extension requires ("include
|
|
67
|
+
* a pipe in a cell's content by escaping it, including inside other inline
|
|
68
|
+
* spans"). Doing it at split time is the whole point: the inline phase never
|
|
69
|
+
* sees the backslash, so a code span in the cell yields a real pipe —
|
|
70
|
+
* `` b `\|` az `` is `b <code>|</code> az`, which no amount of unescaping
|
|
71
|
+
* *after* inline parsing could produce. Every other backslash escape is left
|
|
72
|
+
* exactly as written for the inline phase to handle in the usual way, so no
|
|
73
|
+
* cell text is unescaped twice.
|
|
74
|
+
*
|
|
75
|
+
* Scanning skips the character after any backslash, so `\\` cannot shield the
|
|
76
|
+
* pipe that follows it: `a\\|b` is two cells, the first holding `a\\`.
|
|
77
|
+
*
|
|
78
|
+
* A row of nothing but whitespace has no cells at all, which is how a header
|
|
79
|
+
* candidate that is really empty fails the cell-count test instead of matching
|
|
80
|
+
* a one-column delimiter row.
|
|
81
|
+
*/
|
|
82
|
+
export declare function splitTableRow(line: string): string[];
|
|
83
|
+
/**
|
|
84
|
+
* Read a line as a table's delimiter row, returning one alignment per column,
|
|
85
|
+
* or null if it is not one.
|
|
86
|
+
*/
|
|
87
|
+
export declare function parseDelimiterRow(line: string): TableAlign[] | null;
|
|
88
|
+
export declare class BlockParser {
|
|
89
|
+
options: ParserOptions;
|
|
90
|
+
refmap: RefMap;
|
|
91
|
+
doc: MdNode;
|
|
92
|
+
/** Deepest open block; null only once the document itself is finalized. */
|
|
93
|
+
tip: MdNode | null;
|
|
94
|
+
/** The tip as it was before the current line was walked. */
|
|
95
|
+
oldtip: MdNode;
|
|
96
|
+
lastMatchedContainer: MdNode;
|
|
97
|
+
/** False while some open block failed its continuation condition. */
|
|
98
|
+
allClosed: boolean;
|
|
99
|
+
currentLine: string;
|
|
100
|
+
lineNumber: number;
|
|
101
|
+
lastLineLength: number;
|
|
102
|
+
/**
|
|
103
|
+
* GFM table probe gate. True by default — the batch driver probes every
|
|
104
|
+
* candidate line, as it always has. The engine driver sets it per line
|
|
105
|
+
* from the `#LB` token's `tblArm` bit (a provable superset of "could be
|
|
106
|
+
* a delimiter row"; see engine-block.ts), which is what lets the GFM alt
|
|
107
|
+
* be a genuine, subtractable extension seam without moving any table
|
|
108
|
+
* decision out of this file.
|
|
109
|
+
*/
|
|
110
|
+
tableArmed: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* The length of the line before the one `lastLineLength` measures.
|
|
113
|
+
*
|
|
114
|
+
* `finalize` dates a block's end from `lastLineLength`, which is right for
|
|
115
|
+
* every block that ends on the line before the current one. A GFM table
|
|
116
|
+
* splits its paragraph *two* lines back — the header row is the previous
|
|
117
|
+
* line and stays with the table, so the paragraph left behind ends on the
|
|
118
|
+
* line before that — and needs this one instead.
|
|
119
|
+
*/
|
|
120
|
+
prevLineLength: number;
|
|
121
|
+
colAnchorOffset: number;
|
|
122
|
+
colAnchorChars: number;
|
|
123
|
+
/** UTF-16 index into `currentLine`. */
|
|
124
|
+
offset: number;
|
|
125
|
+
/** Tab-expanded display column matching `offset` (§2.2). */
|
|
126
|
+
column: number;
|
|
127
|
+
/** True when `offset` sits inside a tab whose columns were partly consumed. */
|
|
128
|
+
partiallyConsumedTab: boolean;
|
|
129
|
+
nextNonspace: number;
|
|
130
|
+
nextNonspaceColumn: number;
|
|
131
|
+
/** Columns between `column` and the next non-space character. */
|
|
132
|
+
indent: number;
|
|
133
|
+
indented: boolean;
|
|
134
|
+
blank: boolean;
|
|
135
|
+
/** Empty cells inserted so far to pad short table rows. See MAX_AUTOCOMPLETED_CELLS. */
|
|
136
|
+
autocompletedCells: number;
|
|
137
|
+
/**
|
|
138
|
+
* The text `addLine` last appended, the column it started in, and the block
|
|
139
|
+
* it went to.
|
|
140
|
+
*
|
|
141
|
+
* `tryOpenTable` needs the *last line* of an open paragraph, and reading it
|
|
142
|
+
* back out of `stringContent` is not affordable: that content is built by
|
|
143
|
+
* repeated `+=`, so every index into it flattens a fresh rope — O(n) per
|
|
144
|
+
* line, O(n²) over a paragraph whose every other line looks like a delimiter
|
|
145
|
+
* row, which untrusted input can write. Keeping the line itself here costs
|
|
146
|
+
* two field writes and never touches the accumulated string.
|
|
147
|
+
*
|
|
148
|
+
* The node is recorded alongside it because "the previous line went to this
|
|
149
|
+
* very paragraph" is the extension's own rule, not a cache-validity trick:
|
|
150
|
+
* a header row is by definition the line immediately above the delimiter
|
|
151
|
+
* row. If some other block took that line, there is no header row.
|
|
152
|
+
*
|
|
153
|
+
* The column is what a table's sourcepos starts at: a table opens on its
|
|
154
|
+
* header row, which is a line already consumed by the time the delimiter row
|
|
155
|
+
* identifies it, so its column has to have been recorded when it went past.
|
|
156
|
+
*/
|
|
157
|
+
lastAddedLine: string;
|
|
158
|
+
lastAddedColumn: number;
|
|
159
|
+
lastAddedTo: MdNode | null;
|
|
160
|
+
/** Step 2's fast reject, widened for a table's delimiter row when `gfm` is on. */
|
|
161
|
+
readonly maybeSpecial: RegExp;
|
|
162
|
+
private htmlBlockTypes;
|
|
163
|
+
constructor(options: ParserOptions);
|
|
164
|
+
/** The deepest open block. Only valid while a line is being processed. */
|
|
165
|
+
get openTip(): MdNode;
|
|
166
|
+
htmlBlockType(block: MdNode): number;
|
|
167
|
+
setHtmlBlockType(block: MdNode, type: number): void;
|
|
168
|
+
/**
|
|
169
|
+
* Advance `count` characters (`columns` false) or `count` columns (`columns`
|
|
170
|
+
* true), expanding tabs to the next 4-column tab stop (§2.2). A tab whose
|
|
171
|
+
* columns are only partly consumed leaves `offset` on the tab itself and
|
|
172
|
+
* sets `partiallyConsumedTab`, so `addLine` can re-emit the remainder as
|
|
173
|
+
* spaces.
|
|
174
|
+
*/
|
|
175
|
+
advanceOffset(count: number, columns: boolean): void;
|
|
176
|
+
advanceNextNonspace(): void;
|
|
177
|
+
/** Locate the next non-space character and the indent leading up to it. */
|
|
178
|
+
findNextNonspace(): void;
|
|
179
|
+
/** Add the rest of the current line to the tip's raw content. */
|
|
180
|
+
addLine(): void;
|
|
181
|
+
/**
|
|
182
|
+
* Open a block of `tag` as a child of the tip, closing blocks that cannot
|
|
183
|
+
* contain it first.
|
|
184
|
+
*/
|
|
185
|
+
/**
|
|
186
|
+
* Characters of `currentLine` before UTF-16 offset `offset` — what a
|
|
187
|
+
* sourcepos column counts.
|
|
188
|
+
*
|
|
189
|
+
* Not simply `offset`: a JavaScript string index counts UTF-16 units, so an
|
|
190
|
+
* astral character (emoji, rare CJK) would advance the column by two. A
|
|
191
|
+
* column means a character, so `\u{1F600} *x*` must report the same columns
|
|
192
|
+
* in both runtimes.
|
|
193
|
+
*
|
|
194
|
+
* Counted incrementally from the last offset already measured. `addChild`
|
|
195
|
+
* runs once per container opened, so counting from the start of the line
|
|
196
|
+
* every time would make a line of n nested containers — `> - > - …` — cost
|
|
197
|
+
* O(n²); untrusted input picks n. Offsets advance monotonically as a line is
|
|
198
|
+
* consumed, making this amortized O(1); a smaller offset than the anchor is
|
|
199
|
+
* still correct, just recounted from the start.
|
|
200
|
+
*/
|
|
201
|
+
sourceColumn(offset: number): number;
|
|
202
|
+
addChild(tag: NodeType, offset: number): MdNode;
|
|
203
|
+
/**
|
|
204
|
+
* GFM tables: try to read the current line as the delimiter row of a table
|
|
205
|
+
* whose header row is the last line of the open paragraph `container`.
|
|
206
|
+
*
|
|
207
|
+
* The two rows must agree on the number of cells; if they do not, there is
|
|
208
|
+
* no table and the line is nothing special (spec example 6 keeps the whole
|
|
209
|
+
* thing a paragraph). The header row is the paragraph's **last** line only,
|
|
210
|
+
* so a paragraph that had earlier lines is split: those lines stay a
|
|
211
|
+
* paragraph — finalized here, which is what still lets them contribute link
|
|
212
|
+
* reference definitions — and the table is opened after it.
|
|
213
|
+
*
|
|
214
|
+
* On success the table is the tip and the caller returns "leaf started", so
|
|
215
|
+
* step 3 adds the delimiter row itself to the table's raw content. It is
|
|
216
|
+
* skipped again by `finalizeTable`; keeping it costs nothing and makes the
|
|
217
|
+
* table's accumulated lines line up one-for-one with the source lines.
|
|
218
|
+
*/
|
|
219
|
+
tryOpenTable(container: MdNode): boolean;
|
|
220
|
+
/** Close every block that failed its continuation condition on this line. */
|
|
221
|
+
closeUnmatchedBlocks(): void;
|
|
222
|
+
/**
|
|
223
|
+
* Close a block, record its end position, and run its finalizer.
|
|
224
|
+
*
|
|
225
|
+
* `endColumn` defaults to the previous line's length, which is where every
|
|
226
|
+
* block that ends on `lineNumber` ends. Only a table's paragraph split
|
|
227
|
+
* passes it: that one closes a block two lines back, where the default is
|
|
228
|
+
* one line too recent.
|
|
229
|
+
*/
|
|
230
|
+
finalize(block: MdNode, lineNumber: number, endColumn?: number): void;
|
|
231
|
+
/**
|
|
232
|
+
* Incorporate one line into the tree — the three steps of Appendix A.
|
|
233
|
+
*/
|
|
234
|
+
incorporateLine(ln: string): void;
|
|
235
|
+
parse(input: string): {
|
|
236
|
+
doc: MdNode;
|
|
237
|
+
refmap: RefMap;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Close every still-open block, stamp the parse-time `gfm` flag, and run
|
|
241
|
+
* the task-list post-pass. `lineNumber` is the count of incorporated
|
|
242
|
+
* lines, so an incremental driver — one that feeds `incorporateLine`
|
|
243
|
+
* itself instead of calling `parse` — finishes with exactly the same
|
|
244
|
+
* finalization the batch loop above gets.
|
|
245
|
+
*/
|
|
246
|
+
finish(): {
|
|
247
|
+
doc: MdNode;
|
|
248
|
+
refmap: RefMap;
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* GFM: the task list item marker opening `content`, or null. Whitespace
|
|
253
|
+
* between the brackets is unchecked; `x`/`X` is checked. `length` is what the
|
|
254
|
+
* caller strips, trailing whitespace included.
|
|
255
|
+
*/
|
|
256
|
+
export declare function matchTaskListMarker(content: string): {
|
|
257
|
+
checked: boolean;
|
|
258
|
+
length: number;
|
|
259
|
+
} | null;
|
|
3
260
|
/**
|
|
4
261
|
* Phase 1: build the block tree and collect link reference definitions.
|
|
5
262
|
* Paragraph and heading text is left raw in `stringContent` for phase 2.
|