@tabnas/markdown 0.6.2 → 0.7.1

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 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.