@orkestrel/markdown 0.0.11 → 0.0.13

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.
@@ -13,21 +13,22 @@ import { OptionalShape } from '@orkestrel/contract';
13
13
  import { StringShape } from '@orkestrel/contract';
14
14
  import { TextNode as TextNode_2 } from '@orkestrel/html';
15
15
 
16
- /** A node that can appear at the block level of a document (or inside a list item / blockquote). */
16
+ /** Represents a node that can appear at the block level of a document (or inside a list item / blockquote). */
17
17
  export declare type BlockNode = HeadingNode | ParagraphNode | ListNode | TableNode | CodeBlockNode | BlockquoteNode | ThematicBreakNode;
18
18
 
19
- /** A blockquote - `>`-prefixed lines; `children` the block content parsed from the de-quoted lines (so quotes nest). */
19
+ /** Represents a blockquote - `>`-prefixed lines; `children` the block content parsed from the de-quoted lines (so quotes nest). */
20
20
  export declare interface BlockquoteNode {
21
21
  readonly element: 'blockquote';
22
- /** The block content of the quote (the `>`-stripped lines, re-parsed as blocks). */
22
+ /** Holds the block content of the quote (the `>`-stripped lines, re-parsed as blocks). */
23
23
  readonly children: readonly BlockNode[];
24
24
  }
25
25
 
26
26
  /**
27
- * Merge adjacent text nodes into one - the inline scanner emits a text node per
27
+ * Merges adjacent text nodes into one - the inline scanner emits a text node per
28
28
  * unrecognized character, so coalescing keeps the AST clean and assertion-friendly.
29
29
  *
30
30
  * @param nodes - The inline nodes (possibly with adjacent text runs)
31
+ * @param spans - The optional operation-owned node span recorder
31
32
  * @returns The nodes with consecutive text nodes concatenated
32
33
  *
33
34
  * @example
@@ -36,24 +37,24 @@ export declare interface BlockquoteNode {
36
37
  * // [{ element: 'text', value: 'ab' }]
37
38
  * ```
38
39
  */
39
- export declare function coalesceText(nodes: readonly InlineNode[]): readonly InlineNode[];
40
+ export declare function coalesceText(nodes: readonly InlineNode[], spans?: Map<MarkdownNode, MarkdownSpan>): readonly InlineNode[];
40
41
 
41
42
  /**
42
- * A fenced code block - ```` ```lang ````. `code` is the verbatim block content (no
43
+ * Represents a fenced code block - ```` ```lang ````. `code` is the verbatim block content (no
43
44
  * inner markdown; the closing fence and the trailing newline are stripped), `lang`
44
45
  * the info-string language tag (the first word after the opening fence), absent when
45
46
  * none was given.
46
47
  */
47
48
  export declare interface CodeBlockNode {
48
49
  readonly element: 'codeBlock';
49
- /** The info-string language tag (first word after the opening fence), if any. */
50
+ /** Holds the info-string language tag (first word after the opening fence), if any. */
50
51
  readonly lang?: string;
51
- /** The verbatim code content (no inner markdown; HTML-escaped at render). */
52
+ /** Holds the verbatim code content (no inner markdown; HTML-escaped at render). */
52
53
  readonly code: string;
53
54
  }
54
55
 
55
56
  /**
56
- * The shape of a {@link CodeBlockNode} - a fenced code block. `lang` is
57
+ * Describes the shape of a {@link CodeBlockNode} - a fenced code block. `lang` is
57
58
  * optional (absent when the opening fence carries no info-string).
58
59
  *
59
60
  * @example
@@ -73,18 +74,29 @@ export declare const codeBlockShape: ObjectShape<{
73
74
  }, false>;
74
75
 
75
76
  /**
76
- * An inline code span - `` `code` ``. `value` is the verbatim span text; no inner
77
+ * Represents the located extent of one inline code span - the value the inline phase's code
78
+ * scanner returns for a matched backtick run.
79
+ */
80
+ export declare interface CodeSpanMatch {
81
+ /** Holds the span's literal text, with one padding space stripped from each end. */
82
+ readonly value: string;
83
+ /** Holds the index one past the span's closing backtick run, exclusive. */
84
+ readonly end: number;
85
+ }
86
+
87
+ /**
88
+ * Represents an inline code span - `` `code` ``. `value` is the verbatim span text; no inner
77
89
  * markdown is parsed (code is literal), and the renderer HTML-escapes it inside a
78
90
  * `<code>` element.
79
91
  */
80
92
  export declare interface CodeSpanNode {
81
93
  readonly element: 'codeSpan';
82
- /** The verbatim code text (no inner markdown; HTML-escaped at render). */
94
+ /** Holds the verbatim code text (no inner markdown; HTML-escaped at render). */
83
95
  readonly value: string;
84
96
  }
85
97
 
86
98
  /**
87
- * The shape of a {@link CodeSpanNode} - an inline code span (`` `code` ``).
99
+ * Describes the shape of a {@link CodeSpanNode} - an inline code span (`` `code` ``).
88
100
  *
89
101
  * @example
90
102
  * ```ts
@@ -107,17 +119,16 @@ export declare const codeSpanShape: ObjectShape<{
107
119
  * @param lines - The markdown lines to scan.
108
120
  * @param start - The index of the first list item.
109
121
  * @param depth - The current recursion depth (each item recurses at `depth + 1`).
122
+ * @param spans - The optional operation-owned node span recorder.
123
+ * @param end - The original-source end of this line run, including a removed terminator.
110
124
  * @returns The parsed list node and the index of the first line after it.
111
125
  *
112
126
  * @example
113
127
  * ```ts
114
- * collectList(['- item'], 0, 0) // { node: { element: 'list', ... }, next: 1 }
128
+ * collectList(splitLines('- item'), 0, 0) // { node: { element: 'list', ... }, next: 1 }
115
129
  * ```
116
130
  */
117
- export declare function collectList(lines: readonly string[], start: number, depth: number): {
118
- readonly node: ListNode;
119
- readonly next: number;
120
- };
131
+ export declare function collectList(lines: readonly MarkdownSource[], start: number, depth: number, spans?: Map<MarkdownNode, MarkdownSpan>, end?: number): ListCollection;
121
132
 
122
133
  /**
123
134
  * Collects a GFM table starting at a header row, parsing the header, the
@@ -125,20 +136,18 @@ export declare function collectList(lines: readonly string[], start: number, dep
125
136
  *
126
137
  * @param lines - The markdown lines to scan.
127
138
  * @param start - The index of the header row.
139
+ * @param spans - The optional operation-owned node span recorder.
128
140
  * @returns The parsed table node and the index of the first line after it.
129
141
  *
130
142
  * @example
131
143
  * ```ts
132
- * collectTable(['| a |', '| - |'], 0) // { node: { element: 'table', ... }, next: 2 }
144
+ * collectTable(splitLines('| a |\n| - |'), 0) // { node: { element: 'table', ... }, next: 2 }
133
145
  * ```
134
146
  */
135
- export declare function collectTable(lines: readonly string[], start: number): {
136
- readonly node: TableNode;
137
- readonly next: number;
138
- };
147
+ export declare function collectTable(lines: readonly MarkdownSource[], start: number, spans?: Map<MarkdownNode, MarkdownSpan>): TableCollection;
139
148
 
140
149
  /**
141
- * The count of leading space / tab characters on `line` (a tab counts as one) - the
150
+ * Counts the leading space / tab characters on `line` (a tab counts as one) - the
142
151
  * indent that decides whether a list item's continuation belongs to the item.
143
152
  *
144
153
  * @param line - The line to measure
@@ -152,9 +161,9 @@ export declare function collectTable(lines: readonly string[], start: number): {
152
161
  export declare function countIndent(line: string): number;
153
162
 
154
163
  /**
155
- * Compile the {@link codeBlockShape} into a {@link ContractInterface} for
164
+ * Compiles the {@link codeBlockShape} into a {@link ContractInterface} for
156
165
  * {@link CodeBlockNode} - a guard, coercing parser, JSON Schema, and seeded
157
- * generator from one shape declaration (AGENTS §14).
166
+ * generator from one shape declaration.
158
167
  *
159
168
  * @returns A `CodeBlockNode` contract bundling `schema` / `is` / `parse` / `generate`
160
169
  *
@@ -169,9 +178,9 @@ export declare function countIndent(line: string): number;
169
178
  export declare function createCodeBlockContract(): ContractInterface<CodeBlockNode>;
170
179
 
171
180
  /**
172
- * Compile the {@link codeSpanShape} into a {@link ContractInterface} for
181
+ * Compiles the {@link codeSpanShape} into a {@link ContractInterface} for
173
182
  * {@link CodeSpanNode} - a guard, coercing parser, JSON Schema, and seeded
174
- * generator from one shape declaration (AGENTS §14).
183
+ * generator from one shape declaration.
175
184
  *
176
185
  * @returns A `CodeSpanNode` contract bundling `schema` / `is` / `parse` / `generate`
177
186
  *
@@ -186,7 +195,7 @@ export declare function createCodeBlockContract(): ContractInterface<CodeBlockNo
186
195
  export declare function createCodeSpanContract(): ContractInterface<CodeSpanNode>;
187
196
 
188
197
  /**
189
- * Compile the {@link lineBreakShape} into a {@link ContractInterface} for
198
+ * Compiles the {@link lineBreakShape} into a {@link ContractInterface} for
190
199
  * {@link LineBreakNode}.
191
200
  *
192
201
  * @returns A `LineBreakNode` contract bundling `schema` / `is` / `parse` / `generate`
@@ -201,7 +210,7 @@ export declare function createCodeSpanContract(): ContractInterface<CodeSpanNode
201
210
  export declare function createLineBreakContract(): ContractInterface<LineBreakNode>;
202
211
 
203
212
  /**
204
- * Create a stateful markdown handle from a markdown string or an already-parsed
213
+ * Creates a stateful markdown handle from a markdown string or an already-parsed
205
214
  * {@link MarkdownDocument} - a typed AST plus the query, rewrite, and fold operations
206
215
  * {@link MarkdownInterface} exposes.
207
216
  *
@@ -229,7 +238,7 @@ export declare function createLineBreakContract(): ContractInterface<LineBreakNo
229
238
  export declare function createMarkdown(input: string | MarkdownDocument): MarkdownInterface;
230
239
 
231
240
  /**
232
- * Create an HTML-to-markdown projection with absent fields defaulted from
241
+ * Builds an HTML-to-markdown projection with absent fields defaulted from
233
242
  * {@link EMPTY_PROJECTION} and the block/inline exclusivity invariant enforced.
234
243
  *
235
244
  * @remarks
@@ -251,9 +260,9 @@ export declare function createMarkdown(input: string | MarkdownDocument): Markdo
251
260
  export declare function createProjection(parts?: Partial<MarkdownProjection>): MarkdownProjection;
252
261
 
253
262
  /**
254
- * Compile the {@link textShape} into a {@link ContractInterface} for
263
+ * Compiles the {@link textShape} into a {@link ContractInterface} for
255
264
  * {@link TextNode} - a guard, coercing parser, JSON Schema, and seeded
256
- * generator from one shape declaration (AGENTS §14).
265
+ * generator from one shape declaration.
257
266
  *
258
267
  * @returns A `TextNode` contract bundling `schema` / `is` / `parse` / `generate`
259
268
  *
@@ -268,9 +277,9 @@ export declare function createProjection(parts?: Partial<MarkdownProjection>): M
268
277
  export declare function createTextContract(): ContractInterface<TextNode>;
269
278
 
270
279
  /**
271
- * Compile the {@link thematicBreakShape} into a {@link ContractInterface} for
280
+ * Compiles the {@link thematicBreakShape} into a {@link ContractInterface} for
272
281
  * {@link ThematicBreakNode} - a guard, coercing parser, JSON Schema, and
273
- * seeded generator from one shape declaration (AGENTS §14).
282
+ * seeded generator from one shape declaration.
274
283
  *
275
284
  * @returns A `ThematicBreakNode` contract bundling `schema` / `is` / `parse` / `generate`
276
285
  *
@@ -285,7 +294,7 @@ export declare function createTextContract(): ContractInterface<TextNode>;
285
294
  export declare function createThematicBreakContract(): ContractInterface<ThematicBreakNode>;
286
295
 
287
296
  /**
288
- * Derive the per-column {@link TableAlign} list from a GFM delimiter row - `:---`
297
+ * Derives the per-column {@link TableAlign} list from a GFM delimiter row - `:---`
289
298
  * left, `---:` right, `:---:` center, and `---` as the explicit no-alignment
290
299
  * marker represented by `null`.
291
300
  *
@@ -300,21 +309,47 @@ export declare function createThematicBreakContract(): ContractInterface<Themati
300
309
  export declare function delimiterToAlignments(delimiter: string): ReadonlyArray<TableAlign | null>;
301
310
 
302
311
  /**
303
- * Emphasized inline content - `*italic*` / `_italic_` (`strong: false`) or
312
+ * Represents the located content and syntax bounds of one emphasis run - the value the inline
313
+ * phase's emphasis locator returns for a matched marker run.
314
+ */
315
+ export declare interface EmphasisBounds {
316
+ /** Holds `true` for a doubled marker (`**strong**`), `false` for a single one (`*em*`). */
317
+ readonly strong: boolean;
318
+ /** Holds the index of the run's first content character. */
319
+ readonly open: number;
320
+ /** Holds the index of the closing marker run's first character. */
321
+ readonly close: number;
322
+ /** Holds the index one past the closing marker run, exclusive. */
323
+ readonly end: number;
324
+ }
325
+
326
+ /**
327
+ * Represents emphasized inline content - `*italic*` / `_italic_` (`strong: false`) or
304
328
  * `**bold**` / `__bold__` (`strong: true`). `children` are the nested inline nodes,
305
329
  * so emphasis composes (a `**bold _and italic_**` is a strong node wrapping a text
306
330
  * node and an emphasis node).
307
331
  */
308
332
  export declare interface EmphasisNode {
309
333
  readonly element: 'emphasis';
310
- /** `true` for strong (`**` / `__`, → `<strong>`); `false` for ordinary emphasis (`*` / `_`, → `<em>`). */
334
+ /** Holds `true` for strong (`**` / `__`, → `<strong>`); `false` for ordinary emphasis (`*` / `_`, → `<em>`). */
311
335
  readonly strong: boolean;
312
- /** The emphasized inline content. */
336
+ /** Holds the emphasized inline content. */
313
337
  readonly children: readonly InlineNode[];
314
338
  }
315
339
 
316
340
  /**
317
- * The frozen empty HTML-to-markdown projection from which projection factories
341
+ * Represents the scanned result of one emphasis run - the node the inline phase's emphasis
342
+ * scanner built from {@link EmphasisBounds} and where the scan resumes.
343
+ */
344
+ export declare interface EmphasisScan {
345
+ /** Holds the scanned emphasis run, its content already scanned into inline children. */
346
+ readonly node: EmphasisNode;
347
+ /** Holds the index one past the closing marker run, exclusive. */
348
+ readonly end: number;
349
+ }
350
+
351
+ /**
352
+ * Holds the frozen empty HTML-to-markdown projection from which projection factories
318
353
  * default every absent field.
319
354
  *
320
355
  * @example
@@ -326,7 +361,7 @@ export declare interface EmphasisNode {
326
361
  export declare const EMPTY_PROJECTION: MarkdownProjection;
327
362
 
328
363
  /**
329
- * Extract a fenced-code opening line (```` ``` ```` or `~~~`, optionally with an info
364
+ * Extracts a fenced-code opening line (```` ``` ```` or `~~~`, optionally with an info
330
365
  * string) into its `{ marker, lang }`, or `undefined` when `line` is not a fence
331
366
  * opener. `marker` is the exact fence run (the closer must match the same character +
332
367
  * at least the same length); `lang` is the first word of the info string.
@@ -339,32 +374,26 @@ export declare const EMPTY_PROJECTION: MarkdownProjection;
339
374
  * extractFence('```ts') // { marker: '```', lang: 'ts' }
340
375
  * ```
341
376
  */
342
- export declare function extractFence(line: string): {
343
- readonly marker: string;
344
- readonly lang: string | undefined;
345
- } | undefined;
377
+ export declare function extractFence(line: string): FenceMatch | undefined;
346
378
 
347
379
  /**
348
- * Extract an ATX heading line (`#` … `######` followed by text) into its
349
- * `{ level, text }`, or `undefined` when `line` is not a heading. A run of more than 6
350
- * `#`s, or `#`s not followed by whitespace + text, is not a
351
- * heading; an optional closing `###` run is stripped.
380
+ * Extracts an ATX heading line (`#` … `######` followed by text) into its level,
381
+ * trimmed text, and the text's offset inside the line. A run of more than 6 `#`s, or
382
+ * `#`s not followed by whitespace + text, is not a heading; an optional closing
383
+ * `###` run is stripped.
352
384
  *
353
385
  * @param line - The candidate line
354
- * @returns The heading level (1–6) and its raw inline text, or `undefined`
386
+ * @returns The heading level (1–6), raw inline text, and text offset, or `undefined`
355
387
  *
356
388
  * @example
357
389
  * ```ts
358
- * extractHeading('## Title') // { level: 2, text: 'Title' }
390
+ * extractHeading('## Title') // { level: 2, text: 'Title', offset: 3 }
359
391
  * ```
360
392
  */
361
- export declare function extractHeading(line: string): {
362
- readonly level: number;
363
- readonly text: string;
364
- } | undefined;
393
+ export declare function extractHeading(line: string): HeadingMatch | undefined;
365
394
 
366
395
  /**
367
- * Extract a list-item line (`-` / `*` / `+` bullet, or `1.` / `1)` ordinal, followed by
396
+ * Extracts a list-item line (`-` / `*` / `+` bullet, or `1.` / `1)` ordinal, followed by
368
397
  * a space) into its {@link ListItemMatch}, or `undefined` when `line` is not a list
369
398
  * item. `content` is the text after the marker; `marker` is the full marker-plus-space
370
399
  * width (for measuring a continuation's indent).
@@ -380,7 +409,18 @@ export declare function extractHeading(line: string): {
380
409
  export declare function extractListItem(line: string): ListItemMatch | undefined;
381
410
 
382
411
  /**
383
- * Concatenate the `value` / `code` content of every descendant text / code-span /
412
+ * Represents the parsed parts of a fenced-code opening line - the value the block phase's fence
413
+ * detector returns for a ```` ``` ```` or `~~~` opener.
414
+ */
415
+ export declare interface FenceMatch {
416
+ /** Holds the exact fence run; a closer must repeat the same character at least as long. */
417
+ readonly marker: string;
418
+ /** Holds the first word of the info string, or `undefined` when the fence declares none. */
419
+ readonly lang: string | undefined;
420
+ }
421
+
422
+ /**
423
+ * Concatenates the `value` / `code` content of every descendant text / code-span /
384
424
  * code-block node under `node`, including image alternative content, in walk order -
385
425
  * the plain-text projection of an AST (search indexing, word counts, a text-only
386
426
  * preview).
@@ -404,7 +444,7 @@ export declare function extractListItem(line: string): ListItemMatch | undefined
404
444
  export declare function flattenText(node: MarkdownNode): string;
405
445
 
406
446
  /**
407
- * Fold a {@link MarkdownNode} into a `T` via a total catamorphism - children are
447
+ * Folds a {@link MarkdownNode} into a `T` through a total catamorphism - children are
408
448
  * folded first (post-order), then the node's own {@link MarkdownHandler} is invoked
409
449
  * with the already-folded children.
410
450
  *
@@ -421,35 +461,48 @@ export declare function flattenText(node: MarkdownNode): string;
421
461
  * with an empty children list instead of recursing further.
422
462
  *
423
463
  * @param node - The AST node to fold
424
- * @param handlers - The total {@link MarkdownHandlers} table, one handler per element
464
+ * @param handlers - The total {@link MarkdownHandlerMap} table, one handler per element
425
465
  * @param depth - The starting recursion depth (pass `0` at the entry point)
426
466
  * @returns The folded `T`
427
467
  *
428
468
  * @example
429
469
  * ```ts
430
- * const countHandlers: MarkdownHandlers<number> = {
470
+ * const countHandlers: MarkdownHandlerMap<number> = {
431
471
  * document: (_, children) => children.reduce((a, b) => a + b, 1),
432
472
  * // ...one handler per element, each summing its folded children
433
473
  * }
434
474
  * foldNode(document, countHandlers, 0) // total node count
435
475
  * ```
436
476
  */
437
- export declare function foldNode<T>(node: MarkdownNode, handlers: MarkdownHandlers<T>, depth: number): T;
477
+ export declare function foldNode<T>(node: MarkdownNode, handlers: MarkdownHandlerMap<T>, depth: number): T;
438
478
 
439
479
  /**
440
- * An ATX heading - `#` `######`. `level` is 1–6 (the number of leading `#`),
480
+ * Represents the parsed parts of a single ATX heading line - the value the block phase's heading
481
+ * detector returns for a `#` … `######` line.
482
+ */
483
+ export declare interface HeadingMatch {
484
+ /** Holds the heading's level, 1 to 6. */
485
+ readonly level: number;
486
+ /** Holds the heading's raw inline text, with an optional closing `#` run stripped. */
487
+ readonly text: string;
488
+ /** Holds the offset of {@link HeadingMatch.text} inside the original line. */
489
+ readonly offset: number;
490
+ }
491
+
492
+ /**
493
+ * Represents an ATX heading - `#` … `######`. `level` is 1–6 (the number of leading `#`),
441
494
  * `children` the inline content of the heading text.
442
495
  */
443
496
  export declare interface HeadingNode {
444
497
  readonly element: 'heading';
445
- /** The heading level, 1 (`#`) through 6 (`######`). */
498
+ /** Holds the heading level, 1 (`#`) through 6 (`######`). */
446
499
  readonly level: number;
447
- /** The inline content of the heading text. */
500
+ /** Holds the inline content of the heading text. */
448
501
  readonly children: readonly InlineNode[];
449
502
  }
450
503
 
451
504
  /**
452
- * Project an `@orkestrel/html` {@link HTMLNode} into a {@link MarkdownDocument} - the
505
+ * Projects an `@orkestrel/html` {@link HTMLNode} into a {@link MarkdownDocument} - the
453
506
  * HTML→markdown direction, and the inverse of {@link markdownToHTML}.
454
507
  *
455
508
  * @remarks
@@ -495,27 +548,27 @@ export declare interface HeadingNode {
495
548
  export declare function htmlToMarkdown(node: HTMLNode): MarkdownDocument;
496
549
 
497
550
  /**
498
- * An inline image - `![alt](src)`. `children` are the inline nodes of the
551
+ * Represents an inline image - `![alt](src)`. `children` are the inline nodes of the
499
552
  * alternative content and `src` is the image destination.
500
553
  */
501
554
  export declare interface ImageNode {
502
555
  readonly element: 'image';
503
- /** The image destination. */
556
+ /** Holds the image destination. */
504
557
  readonly src: string;
505
- /** The inline alternative content. */
558
+ /** Holds the inline alternative content. */
506
559
  readonly children: readonly InlineNode[];
507
560
  }
508
561
 
509
- /** A node that can appear inside inline content (a heading / paragraph / cell / list item / link text). */
562
+ /** Represents a node that can appear inside inline content (a heading / paragraph / cell / list item / link text). */
510
563
  export declare type InlineNode = TextNode | EmphasisNode | CodeSpanNode | LineBreakNode | LinkNode | ImageNode;
511
564
 
512
565
  /**
513
- * Whether `line` is blank - empty, or containing only whitespace - the markdown
566
+ * Checks whether `line` is blank - empty, or containing only whitespace - the markdown
514
567
  * definition of a blank line that block parsing uses to separate paragraphs, skip
515
568
  * gaps, and end list continuations.
516
569
  *
517
570
  * @param line - The candidate line
518
- * @returns `true` when the line is blank
571
+ * @returns True if the line is blank; false otherwise
519
572
  *
520
573
  * @example
521
574
  * ```ts
@@ -525,19 +578,19 @@ export declare type InlineNode = TextNode | EmphasisNode | CodeSpanNode | LineBr
525
578
  export declare function isBlankLine(line: string): boolean;
526
579
 
527
580
  /**
528
- * Determine whether an arbitrary value is a valid {@link BlockNode} - a
581
+ * Determines whether an arbitrary value is a valid {@link BlockNode} - a
529
582
  * heading, paragraph, list, table, code block, blockquote, or thematic break,
530
583
  * recursively validated.
531
584
  *
532
585
  * @remarks
533
586
  * Total: never throws, even on cyclic or pathologically deep input - every
534
587
  * combinator involved (`unionOf`, `recordOf`, `arrayOf`, `lazyOf`) is
535
- * throw-contained per the `@orkestrel/contract` guard contract (AGENTS §14).
588
+ * throw-contained per the `@orkestrel/contract` guard contract.
536
589
  * A list item's shape is inlined here (and in {@link isMarkdownNode}) rather
537
590
  * than named separately - it is used at exactly these two sites.
538
591
  *
539
592
  * @param value - The value to test
540
- * @returns `true` when `value` is a well-formed {@link BlockNode}
593
+ * @returns True if `value` is a well-formed {@link BlockNode}; false otherwise
541
594
  *
542
595
  * @example
543
596
  * ```ts
@@ -550,7 +603,10 @@ export declare function isBlankLine(line: string): boolean;
550
603
  export declare const isBlockNode: Guard<BlockNode>;
551
604
 
552
605
  /**
553
- * Determine whether a node is a blockquote block.
606
+ * Determines whether a node is a blockquote block.
607
+ *
608
+ * @param node - The AST node to test
609
+ * @returns True if the node is a {@link BlockquoteNode}; false otherwise
554
610
  *
555
611
  * @example
556
612
  * ```ts
@@ -560,7 +616,10 @@ export declare const isBlockNode: Guard<BlockNode>;
560
616
  export declare function isBlockquoteNode(node: MarkdownNode): node is BlockquoteNode;
561
617
 
562
618
  /**
563
- * Determine whether a node is a fenced code block.
619
+ * Determines whether a node is a fenced code block.
620
+ *
621
+ * @param node - The AST node to test
622
+ * @returns True if the node is a {@link CodeBlockNode}; false otherwise
564
623
  *
565
624
  * @example
566
625
  * ```ts
@@ -570,12 +629,15 @@ export declare function isBlockquoteNode(node: MarkdownNode): node is Blockquote
570
629
  export declare function isCodeBlockNode(node: MarkdownNode): node is CodeBlockNode;
571
630
 
572
631
  /**
573
- * Determine whether a node is an inline code span.
632
+ * Determines whether a node is an inline code span.
574
633
  *
575
634
  * @remarks
576
635
  * Narrows to {@link CodeSpanNode} - the node whose `element` discriminant is
577
636
  * `'codeSpan'`.
578
637
  *
638
+ * @param node - The AST node to test
639
+ * @returns True if the node is a {@link CodeSpanNode}; false otherwise
640
+ *
579
641
  * @example
580
642
  * ```ts
581
643
  * isCodeSpanNode({ element: 'codeSpan', value: 'x' }) // true
@@ -584,7 +646,10 @@ export declare function isCodeBlockNode(node: MarkdownNode): node is CodeBlockNo
584
646
  export declare function isCodeSpanNode(node: MarkdownNode): node is CodeSpanNode;
585
647
 
586
648
  /**
587
- * Determine whether a node is an emphasis run (`*em*` / `**strong**`).
649
+ * Determines whether a node is an emphasis run (`*em*` / `**strong**`).
650
+ *
651
+ * @param node - The AST node to test
652
+ * @returns True if the node is an {@link EmphasisNode}; false otherwise
588
653
  *
589
654
  * @example
590
655
  * ```ts
@@ -594,11 +659,11 @@ export declare function isCodeSpanNode(node: MarkdownNode): node is CodeSpanNode
594
659
  export declare function isEmphasisNode(node: MarkdownNode): node is EmphasisNode;
595
660
 
596
661
  /**
597
- * Whether `character` is escapable by a leading backslash - the ASCII punctuation
662
+ * Checks whether `character` is escapable by a leading backslash - the ASCII punctuation
598
663
  * markdown gives meaning to (so `\*` becomes `*` but `\.` stays `\.`).
599
664
  *
600
665
  * @param character - The single character after a backslash
601
- * @returns `true` when a backslash before it is an escape
666
+ * @returns True if a backslash before it is an escape; false otherwise
602
667
  *
603
668
  * @example
604
669
  * ```ts
@@ -609,12 +674,12 @@ export declare function isEmphasisNode(node: MarkdownNode): node is EmphasisNode
609
674
  export declare function isEscapable(character: string): boolean;
610
675
 
611
676
  /**
612
- * Whether `line` closes a fence opened by `marker` - the same fence character, a run
677
+ * Checks whether `line` closes a fence opened by `marker` - the same fence character, a run
613
678
  * at least as long, and nothing else but surrounding whitespace.
614
679
  *
615
680
  * @param line - The candidate closing line
616
681
  * @param marker - The opening fence's marker run (from {@link extractFence})
617
- * @returns `true` when `line` closes the fence
682
+ * @returns True if `line` closes the fence; false otherwise
618
683
  *
619
684
  * @example
620
685
  * ```ts
@@ -624,11 +689,11 @@ export declare function isEscapable(character: string): boolean;
624
689
  export declare function isFenceClose(line: string, marker: string): boolean;
625
690
 
626
691
  /**
627
- * Whether `character` is a regex-`\s`-equivalent whitespace character - the
692
+ * Checks whether `character` is a regex-`\s`-equivalent whitespace character - the
628
693
  * character class {@link isFenceClose}'s scan treats as surrounding padding.
629
694
  *
630
695
  * @param character - The single character to test, or `undefined` past the end of a line
631
- * @returns `true` when it is whitespace
696
+ * @returns True if it is whitespace; false otherwise
632
697
  *
633
698
  * @example
634
699
  * ```ts
@@ -638,11 +703,39 @@ export declare function isFenceClose(line: string, marker: string): boolean;
638
703
  */
639
704
  export declare function isFenceWhitespace(character: string | undefined): boolean;
640
705
 
641
- /** Determine whether a node is a heading block. */
706
+ /**
707
+ * Checks whether `character` is whitespace under the emphasis flanking rule - a space, a
708
+ * tab, or a newline.
709
+ *
710
+ * @param character - The character to test
711
+ * @returns True if the flanking rule counts it as whitespace; false otherwise
712
+ *
713
+ * @example
714
+ * ```ts
715
+ * isFlankingWhitespace(' ') // true
716
+ * isFlankingWhitespace('a') // false
717
+ * ```
718
+ */
719
+ export declare function isFlankingWhitespace(character: string): boolean;
720
+
721
+ /**
722
+ * Determines whether a node is a heading block.
723
+ *
724
+ * @param node - The AST node to test
725
+ * @returns True if the node is a {@link HeadingNode}; false otherwise
726
+ *
727
+ * @example
728
+ * ```ts
729
+ * isHeadingNode({ element: 'heading', level: 1, children: [] }) // true
730
+ * ```
731
+ */
642
732
  export declare function isHeadingNode(node: MarkdownNode): node is HeadingNode;
643
733
 
644
734
  /**
645
- * Determine whether a node is an image.
735
+ * Determines whether a node is an image.
736
+ *
737
+ * @param node - The AST node to test
738
+ * @returns True if the node is an {@link ImageNode}; false otherwise
646
739
  *
647
740
  * @example
648
741
  * ```ts
@@ -652,16 +745,16 @@ export declare function isHeadingNode(node: MarkdownNode): node is HeadingNode;
652
745
  export declare function isImageNode(node: MarkdownNode): node is ImageNode;
653
746
 
654
747
  /**
655
- * Determine whether an arbitrary value is a valid {@link InlineNode} - a text
748
+ * Determines whether an arbitrary value is a valid {@link InlineNode} - a text
656
749
  * run, emphasis, code span, hard break, link, or image, recursively validated.
657
750
  *
658
751
  * @remarks
659
752
  * Total: never throws, even on cyclic or pathologically deep input - every
660
753
  * combinator involved (`unionOf`, `recordOf`, `arrayOf`, `lazyOf`) is
661
- * throw-contained per the `@orkestrel/contract` guard contract (AGENTS §14).
754
+ * throw-contained per the `@orkestrel/contract` guard contract.
662
755
  *
663
756
  * @param value - The value to test
664
- * @returns `true` when `value` is a well-formed {@link InlineNode}
757
+ * @returns True if `value` is a well-formed {@link InlineNode}; false otherwise
665
758
  *
666
759
  * @example
667
760
  * ```ts
@@ -674,7 +767,10 @@ export declare function isImageNode(node: MarkdownNode): node is ImageNode;
674
767
  export declare const isInlineNode: Guard<InlineNode>;
675
768
 
676
769
  /**
677
- * Determine whether a node is a GFM hard line break.
770
+ * Determines whether a node is a GFM hard line break.
771
+ *
772
+ * @param node - The AST node to test
773
+ * @returns True if the node is a {@link LineBreakNode}; false otherwise
678
774
  *
679
775
  * @example
680
776
  * ```ts
@@ -683,11 +779,24 @@ export declare const isInlineNode: Guard<InlineNode>;
683
779
  */
684
780
  export declare function isLineBreakNode(node: MarkdownNode): node is LineBreakNode;
685
781
 
686
- /** Determine whether a node is a link. */
782
+ /**
783
+ * Determines whether a node is a link.
784
+ *
785
+ * @param node - The AST node to test
786
+ * @returns True if the node is a {@link LinkNode}; false otherwise
787
+ *
788
+ * @example
789
+ * ```ts
790
+ * isLinkNode({ element: 'link', href: 'https://example.dev', children: [] }) // true
791
+ * ```
792
+ */
687
793
  export declare function isLinkNode(node: MarkdownNode): node is LinkNode;
688
794
 
689
795
  /**
690
- * Determine whether a node is a list block.
796
+ * Determines whether a node is a list block.
797
+ *
798
+ * @param node - The AST node to test
799
+ * @returns True if the node is a {@link ListNode}; false otherwise
691
800
  *
692
801
  * @example
693
802
  * ```ts
@@ -697,17 +806,17 @@ export declare function isLinkNode(node: MarkdownNode): node is LinkNode;
697
806
  export declare function isListNode(node: MarkdownNode): node is ListNode;
698
807
 
699
808
  /**
700
- * Determine whether an arbitrary value is a valid {@link MarkdownDocument} -
809
+ * Determines whether an arbitrary value is a valid {@link MarkdownDocument} -
701
810
  * the parsed-AST root {@link parseDocument} returns, recursively
702
811
  * validated.
703
812
  *
704
813
  * @remarks
705
814
  * Total: never throws, even on cyclic or pathologically deep input - every
706
815
  * combinator involved (`recordOf`, `arrayOf`) is throw-contained per the
707
- * `@orkestrel/contract` guard contract (AGENTS §14).
816
+ * `@orkestrel/contract` guard contract.
708
817
  *
709
818
  * @param value - The value to test
710
- * @returns `true` when `value` is a well-formed {@link MarkdownDocument}
819
+ * @returns True if `value` is a well-formed {@link MarkdownDocument}; false otherwise
711
820
  *
712
821
  * @example
713
822
  * ```ts
@@ -720,19 +829,19 @@ export declare function isListNode(node: MarkdownNode): node is ListNode;
720
829
  export declare const isMarkdownDocument: Guard<MarkdownDocument>;
721
830
 
722
831
  /**
723
- * Determine whether an arbitrary value is a valid {@link MarkdownNode} - the
832
+ * Determines whether an arbitrary value is a valid {@link MarkdownNode} - the
724
833
  * {@link MarkdownDocument} root, a {@link BlockNode}, a {@link ListItemNode}, or
725
834
  * an {@link InlineNode}, recursively validated.
726
835
  *
727
836
  * @remarks
728
837
  * Total: never throws, even on cyclic or pathologically deep input - every
729
838
  * combinator involved (`unionOf`, `recordOf`, `arrayOf`, `lazyOf`) is
730
- * throw-contained per the `@orkestrel/contract` guard contract (AGENTS §14).
839
+ * throw-contained per the `@orkestrel/contract` guard contract.
731
840
  * A list item's shape is inlined here (and in {@link isBlockNode}) rather than
732
841
  * named separately - it is used at exactly these two sites.
733
842
  *
734
843
  * @param value - The value to test
735
- * @returns `true` when `value` is a well-formed {@link MarkdownNode}
844
+ * @returns True if `value` is a well-formed {@link MarkdownNode}; false otherwise
736
845
  *
737
846
  * @example
738
847
  * ```ts
@@ -745,7 +854,10 @@ export declare const isMarkdownDocument: Guard<MarkdownDocument>;
745
854
  export declare const isMarkdownNode: Guard<MarkdownNode>;
746
855
 
747
856
  /**
748
- * Determine whether a node is a paragraph block.
857
+ * Determines whether a node is a paragraph block.
858
+ *
859
+ * @param node - The AST node to test
860
+ * @returns True if the node is a {@link ParagraphNode}; false otherwise
749
861
  *
750
862
  * @example
751
863
  * ```ts
@@ -755,11 +867,11 @@ export declare const isMarkdownNode: Guard<MarkdownNode>;
755
867
  export declare function isParagraphNode(node: MarkdownNode): node is ParagraphNode;
756
868
 
757
869
  /**
758
- * Whether `line` is a blockquote line (`>` optionally indented up to three spaces) -
870
+ * Checks whether `line` is a blockquote line (`>` optionally indented up to three spaces) -
759
871
  * its content is de-quoted by {@link stripQuote}.
760
872
  *
761
873
  * @param line - The candidate line
762
- * @returns `true` when the line begins a blockquote
874
+ * @returns True if the line begins a blockquote; false otherwise
763
875
  *
764
876
  * @example
765
877
  * ```ts
@@ -768,17 +880,27 @@ export declare function isParagraphNode(node: MarkdownNode): node is ParagraphNo
768
880
  */
769
881
  export declare function isQuote(line: string): boolean;
770
882
 
771
- /** Determine whether a node is a GFM table block. */
883
+ /**
884
+ * Determines whether a node is a GFM table block.
885
+ *
886
+ * @param node - The AST node to test
887
+ * @returns True if the node is a {@link TableNode}; false otherwise
888
+ *
889
+ * @example
890
+ * ```ts
891
+ * isTableNode({ element: 'table', header: [], rows: [], align: [] }) // true
892
+ * ```
893
+ */
772
894
  export declare function isTableNode(node: MarkdownNode): node is TableNode;
773
895
 
774
896
  /**
775
- * Whether the pair (`header`, `delimiter`) opens a GFM table - `delimiter` is a row of
897
+ * Checks whether the pair (`header`, `delimiter`) opens a GFM table - `delimiter` is a row of
776
898
  * `|`-separated cells each matching `:?-+:?`, the GFM rule that a table requires a
777
899
  * header row IMMEDIATELY followed by a delimiter row.
778
900
  *
779
901
  * @param header - The candidate header line
780
902
  * @param delimiter - The line after it (the candidate delimiter)
781
- * @returns `true` when the two lines open a table
903
+ * @returns True if the two lines open a table; false otherwise
782
904
  *
783
905
  * @example
784
906
  * ```ts
@@ -788,7 +910,10 @@ export declare function isTableNode(node: MarkdownNode): node is TableNode;
788
910
  export declare function isTableStart(header: string, delimiter: string | undefined): boolean;
789
911
 
790
912
  /**
791
- * Determine whether a node is a plain text run.
913
+ * Determines whether a node is a plain text run.
914
+ *
915
+ * @param node - The AST node to test
916
+ * @returns True if the node is a {@link TextNode}; false otherwise
792
917
  *
793
918
  * @example
794
919
  * ```ts
@@ -798,12 +923,12 @@ export declare function isTableStart(header: string, delimiter: string | undefin
798
923
  export declare function isTextNode(node: MarkdownNode): node is TextNode;
799
924
 
800
925
  /**
801
- * Whether `line` is a thematic break (horizontal rule) - three or more of the SAME
926
+ * Checks whether `line` is a thematic break (horizontal rule) - three or more of the SAME
802
927
  * marker `-`, `*`, or `_` (optionally space-separated) and nothing else (`---`,
803
928
  * `***`, `___`, `- - -`).
804
929
  *
805
930
  * @param line - The candidate line
806
- * @returns `true` when the line is a thematic break
931
+ * @returns True if the line is a thematic break; false otherwise
807
932
  *
808
933
  * @example
809
934
  * ```ts
@@ -813,7 +938,10 @@ export declare function isTextNode(node: MarkdownNode): node is TextNode;
813
938
  export declare function isThematicBreak(line: string): boolean;
814
939
 
815
940
  /**
816
- * Determine whether a node is a thematic break (horizontal rule) block.
941
+ * Determines whether a node is a thematic break (horizontal rule) block.
942
+ *
943
+ * @param node - The AST node to test
944
+ * @returns True if the node is a {@link ThematicBreakNode}; false otherwise
817
945
  *
818
946
  * @example
819
947
  * ```ts
@@ -823,27 +951,28 @@ export declare function isThematicBreak(line: string): boolean;
823
951
  export declare function isThematicBreakNode(node: MarkdownNode): node is ThematicBreakNode;
824
952
 
825
953
  /**
826
- * Whether `character` is an inline whitespace character (space / tab / newline) - the
827
- * emphasis flanking rule's space test.
954
+ * Joins offset-bearing markdown sources while mapping a separator to the original
955
+ * region between adjacent mapped sources.
828
956
  *
829
- * @param character - The character to test
830
- * @returns `true` when it is inline whitespace
957
+ * @param sources - The sources to join
958
+ * @param separator - The derived text inserted between sources
959
+ * @returns The joined text and every source-backed segment
831
960
  *
832
961
  * @example
833
962
  * ```ts
834
- * isWhitespace(' ') // true
835
- * isWhitespace('a') // false
963
+ * joinSources(splitLines('a\nb'), '\n')
964
+ * // { text: 'a\nb', segments: [...] }
836
965
  * ```
837
966
  */
838
- export declare function isWhitespace(character: string): boolean;
967
+ export declare function joinSources(sources: readonly MarkdownSource[], separator: string): MarkdownSource;
839
968
 
840
- /** A GFM hard line break - two or more trailing spaces before a newline. */
969
+ /** Represents a GFM hard line break - two or more trailing spaces before a newline. */
841
970
  export declare interface LineBreakNode {
842
971
  readonly element: 'break';
843
972
  }
844
973
 
845
974
  /**
846
- * The shape of a {@link LineBreakNode} - a GFM hard line-break leaf.
975
+ * Describes the shape of a {@link LineBreakNode} - a GFM hard line-break leaf.
847
976
  *
848
977
  * @example
849
978
  * ```ts
@@ -859,37 +988,70 @@ export declare const lineBreakShape: ObjectShape<{
859
988
  }, false>;
860
989
 
861
990
  /**
862
- * An inline link - `[text](href)`. `children` are the inline nodes of the link text.
991
+ * Represents the located syntax bounds of one `[text](href)` link - the value the inline phase's
992
+ * link locator returns for a balanced label followed by a destination.
993
+ */
994
+ export declare interface LinkBounds {
995
+ /** Holds the index of the label's closing `]`. */
996
+ readonly close: number;
997
+ /** Holds the index one past the destination's closing `)`, exclusive. */
998
+ readonly end: number;
999
+ }
1000
+
1001
+ /**
1002
+ * Represents an inline link - `[text](href)`. `children` are the inline nodes of the link text.
863
1003
  * At render, html's floor removes a refused `href` attribute and the link keeps its
864
1004
  * text; {@link htmlToMarkdown} instead stores a refused destination as `''`.
865
1005
  */
866
1006
  export declare interface LinkNode {
867
1007
  readonly element: 'link';
868
- /** The link destination (sanitized + attribute-escaped at render). */
1008
+ /** Holds the link destination (sanitized + attribute-escaped at render). */
869
1009
  readonly href: string;
870
- /** The inline content of the link text. */
1010
+ /** Holds the inline content of the link text. */
871
1011
  readonly children: readonly InlineNode[];
872
1012
  }
873
1013
 
874
1014
  /**
875
- * The parsed parts of a single list-item line - the value the block phase's
1015
+ * Represents the scanned result of one `[text](href)` link - the node the inline phase's link
1016
+ * scanner built from {@link LinkBounds} and where the scan resumes.
1017
+ */
1018
+ export declare interface LinkScan {
1019
+ /** Holds the scanned link, its text already scanned into inline children. */
1020
+ readonly node: LinkNode;
1021
+ /** Holds the index one past the destination's closing `)`, exclusive. */
1022
+ readonly end: number;
1023
+ }
1024
+
1025
+ /**
1026
+ * Represents the result of collecting one list - the node the construct scanner built and where
1027
+ * the block phase resumes.
1028
+ */
1029
+ export declare interface ListCollection {
1030
+ /** Holds the collected list. */
1031
+ readonly node: ListNode;
1032
+ /** Holds the index of the first line after the list. */
1033
+ readonly next: number;
1034
+ }
1035
+
1036
+ /**
1037
+ * Represents the parsed parts of a single list-item line - the value the block phase's
876
1038
  * list detector returns for a `-` / `*` / `+` bullet or a `1.` / `1)` ordinal line.
877
1039
  */
878
1040
  export declare interface ListItemMatch {
879
- /** `true` for an ordered (`1.` / `1)`) item, `false` for a bullet (`-` / `*` / `+`). */
1041
+ /** Holds `true` for an ordered (`1.` / `1)`) item, `false` for a bullet (`-` / `*` / `+`). */
880
1042
  readonly ordered: boolean;
881
- /** The ordinal of an ordered item (its number); `1` for a bullet. */
1043
+ /** Holds the ordinal of an ordered item (its number); `1` for a bullet. */
882
1044
  readonly start: number;
883
- /** The item's text after the marker. */
1045
+ /** Holds the item's text after the marker. */
884
1046
  readonly content: string;
885
- /** The leading-space indent of the marker. */
1047
+ /** Holds the leading-space indent of the marker. */
886
1048
  readonly indent: number;
887
- /** The full marker width (indent + bullet/ordinal + the following space) - the continuation indent. */
1049
+ /** Holds the full marker width (indent + bullet/ordinal + the following space) - the continuation indent. */
888
1050
  readonly marker: number;
889
1051
  }
890
1052
 
891
1053
  /**
892
- * The shape of {@link ListItemMatch} - the parsed parts of a single list-item
1054
+ * Describes the shape of {@link ListItemMatch} - the parsed parts of a single list-item
893
1055
  * line the block phase's list detector returns. Fully non-recursive (no
894
1056
  * nested node fields), so every field shapes directly.
895
1057
  *
@@ -910,43 +1072,94 @@ export declare const listItemMatchShape: ObjectShape<{
910
1072
  marker: NumberShape;
911
1073
  }, false>;
912
1074
 
913
- /** One item of a {@link ListNode} - `children` the block content of the item (typically one paragraph, plus any nested list). */
1075
+ /** Represents one item of a {@link ListNode} - `children` the block content of the item (typically one paragraph, plus any nested list). */
914
1076
  export declare interface ListItemNode {
915
1077
  readonly element: 'listItem';
916
- /** The block content of the list item (its text as a paragraph, plus any nested list). */
1078
+ /** Holds the block content of the list item (its text as a paragraph, plus any nested list). */
917
1079
  readonly children: readonly BlockNode[];
918
1080
  }
919
1081
 
920
1082
  /**
921
- * A list - bulleted (`-` / `*` / `+`, `ordered: false`) or numbered (`1.` / `1)`,
1083
+ * Represents a list - bulleted (`-` / `*` / `+`, `ordered: false`) or numbered (`1.` / `1)`,
922
1084
  * `ordered: true`). `start` is the first ordinal of an ordered list (usually `1`).
923
1085
  * Nesting is expressed by a {@link ListNode} appearing in a {@link ListItemNode}'s
924
1086
  * `children`.
925
1087
  */
926
1088
  export declare interface ListNode {
927
1089
  readonly element: 'list';
928
- /** `true` for an ordered (numbered) list (→ `<ol>`); `false` for a bulleted list (→ `<ul>`). */
1090
+ /** Holds `true` for an ordered (numbered) list (→ `<ol>`); `false` for a bulleted list (→ `<ul>`). */
929
1091
  readonly ordered: boolean;
930
- /** The starting ordinal of an ordered list (the first item's number); `1` for a bulleted list. */
1092
+ /** Holds the starting ordinal of an ordered list (the first item's number); `1` for a bulleted list. */
931
1093
  readonly start: number;
932
- /** The list's items, in order. */
1094
+ /** Holds the list's items, in order. */
933
1095
  readonly items: readonly ListItemNode[];
934
1096
  }
935
1097
 
936
1098
  /**
937
- * A stateful, parsed markdown document - wraps a typed {@link MarkdownDocument} AST
1099
+ * Locates an emphasis run at `start` (`*` / `_`, doubled for strong) - finds the nearest
1100
+ * matching closing run of the same marker + width while skipping complete nested
1101
+ * runs from the other marker family, and requires non-space immediately inside both
1102
+ * delimiters (the CommonMark flanking simplification that blocks `* x *`). Returns
1103
+ * the content and syntax bounds, or `undefined` when no valid closer exists (it then degrades to
1104
+ * a literal marker).
1105
+ *
1106
+ * @param source - The inline source text
1107
+ * @param start - The index of the opening marker
1108
+ * @param to - The exclusive end of the scan window
1109
+ * @returns The content and syntax bounds, or `undefined`
1110
+ *
1111
+ * @example
1112
+ * ```ts
1113
+ * locateEmphasis('*em*', 0, 4) // { strong: false, open: 1, close: 3, end: 4 }
1114
+ * ```
1115
+ */
1116
+ export declare function locateEmphasis(source: string, start: number, to: number): EmphasisBounds | undefined;
1117
+
1118
+ /**
1119
+ * Locates a link `[text](href)` at `start` - the text runs to a BALANCED `]`, then `(`
1120
+ * must immediately follow and the destination runs to the matching `)` (both respect
1121
+ * nested delimiters + escapes). Returns the label close and syntax end, or `undefined` when the shape
1122
+ * does not hold (it then degrades to a literal `[`).
1123
+ *
1124
+ * @param source - The inline source text
1125
+ * @param start - The index of the opening `[`
1126
+ * @param to - The exclusive end of the scan window
1127
+ * @returns The label close and syntax end indices, or `undefined`
1128
+ *
1129
+ * @example
1130
+ * ```ts
1131
+ * locateLink('[text](url)', 0, 11) // { close: 5, end: 11 }
1132
+ * ```
1133
+ */
1134
+ export declare function locateLink(source: string, start: number, to: number): LinkBounds | undefined;
1135
+
1136
+ /**
1137
+ * Wraps a typed {@link MarkdownDocument} AST as a stateful, parsed markdown document
938
1138
  * with the query (`find` / `filter` / `reduce` / iteration), rewrite (`map`), fold, and
939
1139
  * streaming operations {@link MarkdownInterface} declares.
940
1140
  *
941
1141
  * @remarks
942
- * - **Construction.** Given a `string`, the constructor runs {@link parseDocument} (the
943
- * block phase then the inline phase) to build the AST. Given a {@link MarkdownDocument},
944
- * the document is adopted AS-IS and is NOT re-validated - a caller adopting an
945
- * untrusted value should gate it with `isMarkdownDocument` first.
1142
+ * - **Construction.** Given a `string`, the constructor runs {@link parseProvenance} (the
1143
+ * block phase then the inline phase) once, keeping the AST and a COPY of the span map
1144
+ * that parse recorded. Given a {@link MarkdownDocument}, the document is adopted AS-IS
1145
+ * and is NOT re-validated - gate an untrusted value with `isMarkdownDocument` first.
1146
+ * - **Provenance.** {@link span} reads the region of the ORIGINAL constructor string a
1147
+ * node was produced from, and it is handle-relative: a string-constructed handle exposes
1148
+ * the regions of the nodes it parsed, an adopted document exposes none, and a node from
1149
+ * another handle reports `undefined` here whatever that handle reports. Each call
1150
+ * returns a fresh value. A node reports the region THIS handle holds for its identity,
1151
+ * else the region of the direct input a rewrite named for it, else `undefined`: a text
1152
+ * run the parse joined from adjacent scanner output reports the region enclosing its
1153
+ * parts, and only a rewrite output that holds no region of its own and was assembled
1154
+ * from separate source nodes reports `undefined`.
1155
+ * {@link map} carries provenance across the rewrite: an unchanged node keeps its
1156
+ * region, a one-source replacement takes the region of the node it replaced, and a
1157
+ * rebuilt parent takes its original's.
946
1158
  * - **Immutable.** {@link map} never mutates the stored AST - it returns a NEW `Markdown`
947
- * instance; the document root invariant (`element: 'document'`) always holds.
1159
+ * instance; the document root invariant (`element: 'document'`) always holds. An
1160
+ * identity rewrite still returns a new handle, over the same document tree.
948
1161
  * - **Traversal order.** {@link walk} and the `find` / `filter` / `reduce` queries built
949
- * on it walk the AST depth-first, pre-order, root-inclusive (via {@link walkNodes});
1162
+ * on it walk the AST depth-first, pre-order, root-inclusive (through {@link walkNodes});
950
1163
  * `stream` is shallow - only the document's direct block children.
951
1164
  *
952
1165
  * @example
@@ -964,10 +1177,28 @@ export declare interface ListNode {
964
1177
  export declare class Markdown implements MarkdownInterface {
965
1178
  #private;
966
1179
  constructor(input: string | MarkdownDocument);
967
- /** The stored {@link MarkdownDocument} AST root. */
1180
+ /** Holds the stored {@link MarkdownDocument} AST root. */
968
1181
  get document(): MarkdownDocument;
969
1182
  /**
970
- * THE deep traversal - a lazy, depth-first, pre-order, root-inclusive generator
1183
+ * Reads the region of the original markdown string a node of this handle's tree was
1184
+ * produced from.
1185
+ *
1186
+ * @param node - The node whose provenance to read
1187
+ * @returns A fresh {@link MarkdownSpan}, or `undefined` when this handle holds no
1188
+ * region for the node
1189
+ *
1190
+ * @example
1191
+ * ```ts
1192
+ * const source = '# Title\n\npara'
1193
+ * const markdown = new Markdown(source)
1194
+ * const heading = markdown.find(isHeadingNode)
1195
+ * const span = heading && markdown.span(heading)
1196
+ * span && source.slice(span.start, span.end) // '# Title'
1197
+ * ```
1198
+ */
1199
+ span(node: MarkdownNode): MarkdownSpan | undefined;
1200
+ /**
1201
+ * Returns THE deep traversal - a lazy, depth-first, pre-order, root-inclusive generator
971
1202
  * over every {@link MarkdownNode} in the document. `find` / `filter` / `reduce`
972
1203
  * all iterate this single traversal.
973
1204
  *
@@ -988,14 +1219,22 @@ export declare class Markdown implements MarkdownInterface {
988
1219
  find(predicate: (node: MarkdownNode) => boolean): MarkdownNode | undefined;
989
1220
  filter<T extends MarkdownNode>(guard: (node: MarkdownNode) => node is T): readonly T[];
990
1221
  filter(predicate: (node: MarkdownNode) => boolean): readonly MarkdownNode[];
991
- /** Rewrites the AST bottom-up (copy-on-write) and returns a new {@link Markdown}. */
1222
+ /**
1223
+ * Rewrites the AST bottom-up (copy-on-write) and returns a new {@link Markdown},
1224
+ * carrying each output node's provenance across the rewrite. A rewrite that returns
1225
+ * its node unchanged shares that subtree instead of copying it, so an identity
1226
+ * rewrite copies no node and still returns a new handle.
1227
+ *
1228
+ * @param rewrite - The bottom-up node rewrite
1229
+ * @returns A new handle over the rewritten document
1230
+ */
992
1231
  map(rewrite: MarkdownRewriteHandler): MarkdownInterface;
993
1232
  /** Folds the AST depth-first, pre-order into an accumulator. */
994
1233
  reduce<T>(callback: (accumulator: T, node: MarkdownNode) => T, initial: T): T;
995
- /** Runs a total catamorphism over the document using a {@link MarkdownHandlers} table. */
996
- fold<T>(handlers: MarkdownHandlers<T>): T;
1234
+ /** Runs a total catamorphism over the document using a {@link MarkdownHandlerMap} table. */
1235
+ fold<T>(handlers: MarkdownHandlerMap<T>): T;
997
1236
  /**
998
- * A web-standard {@link ReadableStream} over the document's top-level block nodes
1237
+ * Returns a web-standard {@link ReadableStream} over the document's top-level block nodes
999
1238
  * (shallow, source order) - a fresh, pull-based source per call: one block is
1000
1239
  * enqueued per `pull`, so a slow reader's backpressure is respected. Cancellable,
1001
1240
  * async-iterable wherever the platform supports it (Node, Deno), and pipeable
@@ -1010,7 +1249,7 @@ export declare class Markdown implements MarkdownInterface {
1010
1249
  * }
1011
1250
  *
1012
1251
  * // Node / Deno / Firefox support async iteration of ReadableStream natively;
1013
- * // other environments should use the reader loop above instead.
1252
+ * // other environments use the reader loop shown earlier.
1014
1253
  * for await (const block of markdown.stream()) {
1015
1254
  * console.log(block)
1016
1255
  * }
@@ -1019,37 +1258,66 @@ export declare class Markdown implements MarkdownInterface {
1019
1258
  stream(): ReadableStream<BlockNode>;
1020
1259
  }
1021
1260
 
1022
- /** One projected table cell - the inline content and alignment of a `th` / `td`. */
1261
+ /** Represents one projected table cell - the inline content and alignment of a `th` / `td`. */
1023
1262
  export declare interface MarkdownCell {
1024
- /** The alignment the cell's `align` attribute declared; `undefined` when it declared none. */
1263
+ /** Holds the alignment the cell's `align` attribute declared; `undefined` when it declared none. */
1025
1264
  readonly align: TableAlign | undefined;
1026
- /** The cell's inline content - a table cell is inline-only, so block content flattens to text. */
1265
+ /** Holds the cell's inline content - a table cell is inline-only, so block content flattens to text. */
1027
1266
  readonly inlines: readonly InlineNode[];
1028
1267
  }
1029
1268
 
1030
1269
  /**
1031
- * The root of a parsed markdown AST - the ordered block children of the whole
1270
+ * Pairs a rewritten value with the input node each rewritten node was produced from -
1271
+ * what `rewriteDocument` returns, so provenance survives a rewrite instead of ending at
1272
+ * it. `T` is the rewritten value: the document for a whole-document rewrite.
1273
+ *
1274
+ * @remarks
1275
+ * `derivations` is keyed by the nodes of the OUTPUT, and each entry names the DIRECT
1276
+ * input the rewrite drew that output from. {@link MarkdownInterface.map} resolves each
1277
+ * output node against the source handle's own spans in a fixed order, and follows no
1278
+ * second derivation edge:
1279
+ *
1280
+ * - the output identity's OWN span in the source handle wins, whatever the map says,
1281
+ * so an identity the rewrite reused - one node returned for several inputs, or a
1282
+ * node the handler moved elsewhere in the tree - keeps the region it already had;
1283
+ * - otherwise the span of the direct input the entry names, where that input has one;
1284
+ * - otherwise none. Where the output identity holds no region of its own, a node mapped
1285
+ * to `undefined`, a node whose direct input has no span, and a node with no entry at
1286
+ * all each report `undefined`. Own-region resolution runs first, so an identity that
1287
+ * does hold a region keeps it in every one of those cases.
1288
+ *
1289
+ * An absent entry does not by itself mean the output node kept its identity. A node
1290
+ * the handler synthesized beneath its replacement is absent too, and it reports no
1291
+ * span because the rewrite named no input for it.
1292
+ */
1293
+ export declare type MarkdownDerivation<T> = readonly [
1294
+ value: T,
1295
+ derivations: ReadonlyMap<MarkdownNode, MarkdownNode | undefined>
1296
+ ];
1297
+
1298
+ /**
1299
+ * Represents the root of a parsed markdown AST - the ordered block children of the whole
1032
1300
  * document. The value {@link MarkdownInterface.document} holds.
1033
1301
  */
1034
1302
  export declare interface MarkdownDocument {
1035
1303
  readonly element: 'document';
1036
- /** The document's top-level block nodes, in source order. */
1304
+ /** Holds the document's top-level block nodes, in source order. */
1037
1305
  readonly children: readonly BlockNode[];
1038
1306
  }
1039
1307
 
1040
1308
  /**
1041
- * A fold handler for one AST element - receives the node and its children
1309
+ * Represents a fold handler for one AST element - receives the node and its children
1042
1310
  * ALREADY folded to `T`, and produces the node's own `T`. The building block of a
1043
- * {@link MarkdownHandlers} catamorphism table.
1311
+ * {@link MarkdownHandlerMap} catamorphism table.
1044
1312
  */
1045
1313
  export declare type MarkdownHandler<TNode, T> = (node: TNode, children: readonly T[]) => T;
1046
1314
 
1047
1315
  /**
1048
- * The total catamorphism table for {@link MarkdownInterface.fold} - one
1316
+ * Represents the total catamorphism table for {@link MarkdownInterface.fold} - one
1049
1317
  * {@link MarkdownHandler} per AST element, keyed by its `element` discriminant. Every
1050
1318
  * key is required: a fold is total over the AST, so there is no element it can skip.
1051
1319
  */
1052
- export declare interface MarkdownHandlers<T> {
1320
+ export declare interface MarkdownHandlerMap<T> {
1053
1321
  /** Folds a {@link MarkdownDocument} root from its already-folded block children. */
1054
1322
  readonly document: MarkdownHandler<MarkdownDocument, T>;
1055
1323
  /** Folds a {@link HeadingNode} from its already-folded inline children. */
@@ -1089,7 +1357,7 @@ export declare interface MarkdownHandlers<T> {
1089
1357
  }
1090
1358
 
1091
1359
  /**
1092
- * A stateful, parsed markdown document: the typed {@link MarkdownDocument} AST plus
1360
+ * Represents a stateful, parsed markdown document: the typed {@link MarkdownDocument} AST plus
1093
1361
  * the query, rewrite, and fold operations over it.
1094
1362
  *
1095
1363
  * @remarks
@@ -1102,19 +1370,19 @@ export declare interface MarkdownHandlers<T> {
1102
1370
  * - **`stream`.** Returns a web-standard {@link ReadableStream} over the top-level
1103
1371
  * blocks - a fresh, pull-based source per call: exactly one block is enqueued per
1104
1372
  * `pull`, so a slow consumer's backpressure is respected and no work happens ahead
1105
- * of demand. Cancellable via the returned stream's own `cancel()`, async-iterable
1373
+ * of demand. Cancellable through the returned stream's own `cancel()`, async-iterable
1106
1374
  * wherever the platform supports it (Node, Deno, and browsers that ship the
1107
1375
  * proposal), and pipeable through any {@link TransformStream} / {@link WritableStream}.
1108
- * - **The seven-method surface.** `document` (the AST root), `walk` (the deep
1109
- * traversal), `find` / `filter` / `reduce` (queries built on `walk`), `map` (the
1110
- * bottom-up rewrite), `fold` (the total catamorphism), and `stream` (the shallow,
1111
- * backpressured top-level source).
1376
+ * - **The surface.** `document` (the AST root), `walk` (the deep traversal), `find` /
1377
+ * `filter` / `reduce` (queries built on `walk`), `span` (the region of the original
1378
+ * markdown a node was parsed from), `map` (the bottom-up rewrite), `fold` (the
1379
+ * total catamorphism), and `stream` (the shallow, backpressured top-level source).
1112
1380
  */
1113
1381
  export declare interface MarkdownInterface {
1114
- /** The stored {@link MarkdownDocument} AST root. */
1382
+ /** Holds the stored {@link MarkdownDocument} AST root. */
1115
1383
  readonly document: MarkdownDocument;
1116
1384
  /**
1117
- * THE deep traversal - a lazy, depth-first, pre-order, root-inclusive
1385
+ * Returns THE deep traversal - a lazy, depth-first, pre-order, root-inclusive
1118
1386
  * {@link Generator} over every {@link MarkdownNode} in the document. The sync
1119
1387
  * `for (const node of markdown.walk())` surface is also consumable by
1120
1388
  * `for await (const node of markdown.walk())` (JavaScript accepts a sync
@@ -1131,14 +1399,34 @@ export declare interface MarkdownInterface {
1131
1399
  filter<T extends MarkdownNode>(guard: (node: MarkdownNode) => node is T): readonly T[];
1132
1400
  /** Collects every node (depth-first, pre-order) matching a predicate. */
1133
1401
  filter(predicate: (node: MarkdownNode) => boolean): readonly MarkdownNode[];
1402
+ /**
1403
+ * Reads the region of the original markdown string a node was produced from.
1404
+ *
1405
+ * @param node - A node of this handle's document.
1406
+ * @returns A fresh {@link MarkdownSpan}, or `undefined` when this handle holds no
1407
+ * region for the node.
1408
+ *
1409
+ * @remarks
1410
+ * Provenance is per handle and per node identity, so a node reports a region only
1411
+ * where THIS handle holds coordinates for it. A handle constructed from an adopted
1412
+ * {@link MarkdownDocument} reports `undefined` for every node: it parsed no string,
1413
+ * so no coordinates exist to report. A text run the PARSE joined from adjacent
1414
+ * scanner output reports the region enclosing its parts rather than `undefined`;
1415
+ * only a REWRITE output that holds no region of its own and was assembled from
1416
+ * separate source nodes reports `undefined`. The region a node does report is the
1417
+ * original source it was produced from, which can include syntax its value drops
1418
+ * and characters that normalization removed. Each call returns a fresh value rather
1419
+ * than the stored one.
1420
+ */
1421
+ span(node: MarkdownNode): MarkdownSpan | undefined;
1134
1422
  /** Rewrites the AST bottom-up (copy-on-write) and returns a new {@link MarkdownInterface}. */
1135
1423
  map(rewrite: MarkdownRewriteHandler): MarkdownInterface;
1136
1424
  /** Folds the AST depth-first, pre-order into an accumulator. */
1137
1425
  reduce<T>(callback: (accumulator: T, node: MarkdownNode) => T, initial: T): T;
1138
- /** Runs a total catamorphism over the document using a {@link MarkdownHandlers} table. */
1139
- fold<T>(handlers: MarkdownHandlers<T>): T;
1426
+ /** Runs a total catamorphism over the document using a {@link MarkdownHandlerMap} table. */
1427
+ fold<T>(handlers: MarkdownHandlerMap<T>): T;
1140
1428
  /**
1141
- * A web-standard {@link ReadableStream} over the document's top-level block nodes
1429
+ * Returns a web-standard {@link ReadableStream} over the document's top-level block nodes
1142
1430
  * (shallow, source order) - a lazy, pull-based, backpressure-respecting source. A
1143
1431
  * fresh, independently-replayable stream every call; never mutates the document.
1144
1432
  */
@@ -1146,14 +1434,32 @@ export declare interface MarkdownInterface {
1146
1434
  }
1147
1435
 
1148
1436
  /**
1149
- * Any node in a markdown AST - the {@link MarkdownDocument} root, a {@link BlockNode},
1437
+ * Represents any node in a markdown AST - the {@link MarkdownDocument} root, a {@link BlockNode},
1150
1438
  * a {@link ListItemNode}, or an {@link InlineNode}. The exhaustive set every
1151
1439
  * projection's `switch` covers.
1152
1440
  */
1153
1441
  export declare type MarkdownNode = MarkdownDocument | BlockNode | ListItemNode | InlineNode;
1154
1442
 
1155
1443
  /**
1156
- * What one HTML node projects to on the way to markdown - the fold value
1444
+ * Pairs a parsed document with the {@link MarkdownSpan} of each of its nodes - what
1445
+ * `parseProvenance` returns, and what `parseDocument` projects the document out of.
1446
+ *
1447
+ * @remarks
1448
+ * `spans` is keyed by node identity, so it addresses the nodes of THAT document and
1449
+ * no other. A node the parse merged from adjacent scanner output - the text run
1450
+ * `coalesceText` joins - is present and carries the region ENCLOSING its parts, from
1451
+ * the first part's `start` to the last part's `end`, which can include original text
1452
+ * lying between them. Absence means the parse recorded no region for the node, not
1453
+ * that the node was assembled from more than one region. Destructure it as
1454
+ * `const [document, spans] = parseProvenance(markdown)`.
1455
+ */
1456
+ export declare type MarkdownParseResult = readonly [
1457
+ document: MarkdownDocument,
1458
+ spans: ReadonlyMap<MarkdownNode, MarkdownSpan>
1459
+ ];
1460
+
1461
+ /**
1462
+ * Represents what one HTML node projects to on the way to markdown - the fold value
1157
1463
  * `htmlToMarkdown` carries up the AST.
1158
1464
  *
1159
1465
  * @remarks
@@ -1175,27 +1481,112 @@ export declare type MarkdownNode = MarkdownDocument | BlockNode | ListItemNode |
1175
1481
  * them untouched; whatever never reaches a table degrades to paragraphs.
1176
1482
  */
1177
1483
  export declare interface MarkdownProjection {
1178
- /** The node's block content, with any surrounding inline runs already wrapped into paragraphs. */
1484
+ /** Holds the node's block content, with any surrounding inline runs already wrapped into paragraphs. */
1179
1485
  readonly blocks: readonly BlockNode[];
1180
- /** The node's inline content; empty whenever `blocks` is not. */
1486
+ /** Holds the node's inline content; empty whenever `blocks` is not. */
1181
1487
  readonly inlines: readonly InlineNode[];
1182
- /** The raw subtree text, whitespace uncollapsed and escapes unresolved. */
1488
+ /** Holds the raw subtree text, whitespace uncollapsed and escapes unresolved. */
1183
1489
  readonly text: string;
1184
- /** The cells this node contributes to an enclosing row. */
1490
+ /** Holds the cells this node contributes to an enclosing row. */
1185
1491
  readonly cells: readonly MarkdownCell[];
1186
- /** The rows this node contributes to an enclosing table - each its cells, in column order. */
1492
+ /** Holds the rows this node contributes to an enclosing table - each its cells, in column order. */
1187
1493
  readonly rows: ReadonlyArray<readonly MarkdownCell[]>;
1188
1494
  }
1189
1495
 
1190
1496
  /**
1191
- * A copy-on-write node rewrite applied bottom-up by {@link MarkdownInterface.map} -
1497
+ * Represents a copy-on-write node rewrite applied bottom-up by {@link MarkdownInterface.map} -
1192
1498
  * receives one node (its own children already rewritten) and returns its
1193
1499
  * replacement (the same node, unchanged, or a new node).
1194
1500
  */
1195
1501
  export declare type MarkdownRewriteHandler = (node: MarkdownNode) => MarkdownNode;
1196
1502
 
1197
1503
  /**
1198
- * Project a {@link MarkdownNode} into an unsanitized {@link HTMLDocument}.
1504
+ * Maps one run of a {@link MarkdownSource} back to the region of the ORIGINAL
1505
+ * markdown string it was taken from.
1506
+ *
1507
+ * @remarks
1508
+ * `offset` addresses {@link MarkdownSource.text}; `start` and `end` address the
1509
+ * original string. The run's original length derives from `end - start` rather than
1510
+ * being stored beside them, so no length member exists to drift. The run's DERIVED
1511
+ * extent ends where the next segment's `offset` begins, so a run may cover more of the
1512
+ * original than it holds derived: the separator run `joinSources` records over a
1513
+ * normalized `\r\n` terminator is one derived code unit over a two-unit original
1514
+ * region.
1515
+ *
1516
+ * `projectSpan` resolves a derived position `p` against that shape by the following
1517
+ * rules rather than by a single affine relation:
1518
+ *
1519
+ * - strictly inside the run, `p` projects to `start + (p - offset)`;
1520
+ * - at the run's derived end, `p` projects to `end`, so the boundary claims the run's
1521
+ * whole original region instead of the prefix an affine step would reach - which is
1522
+ * how the one-unit `\r\n` separator run above reports its two-unit region;
1523
+ * - a zero-width `p` that coincides with a later segment's `offset` resolves through the
1524
+ * LAST segment whose `offset` equals `p`, skipping every earlier segment at that
1525
+ * position whatever its extent, so a discontinuous abutment reports that final run's
1526
+ * `start` rather than the earlier run's `end`.
1527
+ *
1528
+ * The mapping is therefore affine strictly inside a run and clamped at its end.
1529
+ */
1530
+ export declare interface MarkdownSegment {
1531
+ /** Holds the first code unit of the run inside {@link MarkdownSource.text}. */
1532
+ readonly offset: number;
1533
+ /** Holds the first code unit of the original-string region the run was produced from, inclusive. */
1534
+ readonly start: number;
1535
+ /** Holds the code unit one past that region's last, exclusive. */
1536
+ readonly end: number;
1537
+ }
1538
+
1539
+ /**
1540
+ * Pairs a piece of derived markdown text with the runs mapping it back to the
1541
+ * original string - what `splitLines` returns per line, so every phase downstream of
1542
+ * it keeps original coordinates instead of reconstructing them from node values.
1543
+ *
1544
+ * @remarks
1545
+ * `text` is the line a parser reads: its terminator, `>` quote marker, or leading
1546
+ * indent already removed. `segments` run in ascending `offset` order, one run per
1547
+ * contiguous stretch of the original; a piece assembled from separate stretches
1548
+ * carries one segment per stretch.
1549
+ *
1550
+ * The runs need not cover every position of `text`. `joinSources` records a segment
1551
+ * for its separator only where the two sides leave a gap in the original, so joining
1552
+ * two abutting regions with a separator leaves that separator's derived position
1553
+ * uncovered. `projectSpan` resolves a range's two boundaries against the runs
1554
+ * independently: it reports `undefined` when either boundary lands in an uncovered
1555
+ * position, and it bridges an uncovered interior when both boundaries resolve. Test
1556
+ * coverage with `projectSpan` rather than assuming it.
1557
+ */
1558
+ export declare interface MarkdownSource {
1559
+ /** Holds the derived text a parser reads. */
1560
+ readonly text: string;
1561
+ /** Holds the runs mapping `text` back to the original string, in ascending `offset` order. */
1562
+ readonly segments: readonly MarkdownSegment[];
1563
+ }
1564
+
1565
+ /**
1566
+ * Addresses a half-open region of the ORIGINAL markdown string, in UTF-16 code units -
1567
+ * `start` inclusive, `end` exclusive. The provenance a parse records for a node and
1568
+ * {@link MarkdownInterface.span} reads back.
1569
+ *
1570
+ * @remarks
1571
+ * The coordinates address the string the handle was constructed from, never the line
1572
+ * text a later phase walks, so `markdown.slice(span.start, span.end)` returns the
1573
+ * ORIGINAL source region the node was produced from. That region is not the node's
1574
+ * value: it carries the syntax the value drops, such as a `\` escape marker, and the
1575
+ * characters that normalization removed, such as a trailing space the paragraph phase
1576
+ * trimmed. The text node of `'a \nb'` has the `value` `a\nb` and reports
1577
+ * `{ start: 0, end: 4 }`, which slices the whole `a \nb`. Read a value off the node
1578
+ * and a region off the source; never derive either from the other. The region's length
1579
+ * is `end - start`; no length member exists to drift from the two offsets.
1580
+ */
1581
+ export declare interface MarkdownSpan {
1582
+ /** Holds the first code unit of the region, inclusive. */
1583
+ readonly start: number;
1584
+ /** Holds the code unit one past the region's last, exclusive. */
1585
+ readonly end: number;
1586
+ }
1587
+
1588
+ /**
1589
+ * Projects a {@link MarkdownNode} into an unsanitized {@link HTMLDocument}.
1199
1590
  *
1200
1591
  * @remarks
1201
1592
  * The projection is pure and iterative. Text and attribute values remain literal for
@@ -1216,10 +1607,10 @@ export declare type MarkdownRewriteHandler = (node: MarkdownNode) => MarkdownNod
1216
1607
  export declare function markdownToHTML(node: MarkdownNode): HTMLDocument;
1217
1608
 
1218
1609
  /**
1219
- * The maximum recursion depth the parse pipeline (`parseDocument` and its
1220
- * `parsers.ts` helpers) and the `helpers.ts` traversal / projection functions
1221
- * (`markdownToHTML`, `renderHTML`, `renderMarkdown`, `walkNodes`, `foldNode`,
1222
- * `rewriteDocument`) honor before degrading. It bounds blockquote nesting, inline
1610
+ * Caps the recursion depth the parse pipeline (`parseDocument` and its
1611
+ * `parsers.ts` helpers), the `helpers.ts` traversal / projection functions
1612
+ * (`markdownToHTML`, `renderMarkdown`, `walkNodes`, `foldNode`, `rewriteDocument`),
1613
+ * and the `compilers.ts` renderer (`renderHTML`) honor before degrading. It bounds blockquote nesting, inline
1223
1614
  * nesting (emphasis / links), and traversal / projection recursion so pathological
1224
1615
  * or hostile input cannot exhaust the call stack. {@link htmlToMarkdown} is the
1225
1616
  * inherited exception: its fold and depth cap belong to `@orkestrel/html`.
@@ -1227,7 +1618,7 @@ export declare function markdownToHTML(node: MarkdownNode): HTMLDocument;
1227
1618
  export declare const MAX_DEPTH = 64;
1228
1619
 
1229
1620
  /**
1230
- * Combine the projections of one node's children into the projection of that node -
1621
+ * Combines the projections of one node's children into the projection of that node -
1231
1622
  * the single place inline runs become paragraphs, so no ancestor has to decide it
1232
1623
  * twice.
1233
1624
  *
@@ -1255,7 +1646,7 @@ export declare const MAX_DEPTH = 64;
1255
1646
  export declare function mergeProjections(children: readonly MarkdownProjection[]): MarkdownProjection;
1256
1647
 
1257
1648
  /**
1258
- * Reduce an inline run to the shape markdown can actually write back: adjacent text
1649
+ * Reduces an inline run to the shape markdown can actually write back: adjacent text
1259
1650
  * coalesced, empty text dropped, and every hard break either kept as a real line
1260
1651
  * ending or spent as a space.
1261
1652
  *
@@ -1268,8 +1659,8 @@ export declare function mergeProjections(children: readonly MarkdownProjection[]
1268
1659
  * becomes the space it stood for.
1269
1660
  *
1270
1661
  * @param nodes - The inline run to normalize
1271
- * @param breaks - Whether the target context can carry a hard break at all; `false` for
1272
- * a heading or a table cell, where every break becomes a space
1662
+ * @param breaks - If `true`, keeps each hard break as a real line ending; if `false`, spends
1663
+ * every break as the space it stood for, as a heading or a table cell requires
1273
1664
  * @returns The normalized run
1274
1665
  *
1275
1666
  * @example
@@ -1280,10 +1671,26 @@ export declare function mergeProjections(children: readonly MarkdownProjection[]
1280
1671
  */
1281
1672
  export declare function normalizeInlines(nodes: readonly InlineNode[], breaks: boolean): readonly InlineNode[];
1282
1673
 
1283
- /** A paragraph - a run of non-blank lines that is not another block; `children` its inline content. */
1674
+ /**
1675
+ * Normalizes one paragraph line while retaining the full source run consumed by a
1676
+ * trailing-space hard break.
1677
+ *
1678
+ * @param source - The offset-bearing paragraph line
1679
+ * @param breaks - If `true`, preserves a trailing run of at least two spaces as the
1680
+ * scanner's two-space hard-break syntax; if `false`, trims the line normally
1681
+ * @returns The normalized line and its original-string segments
1682
+ *
1683
+ * @example
1684
+ * ```ts
1685
+ * normalizeParagraphLine(splitLines('text \nnext')[0], true).text // 'text '
1686
+ * ```
1687
+ */
1688
+ export declare function normalizeParagraphLine(source: MarkdownSource, breaks: boolean): MarkdownSource;
1689
+
1690
+ /** Represents a paragraph - a run of non-blank lines that is not another block; `children` its inline content. */
1284
1691
  export declare interface ParagraphNode {
1285
1692
  readonly element: 'paragraph';
1286
- /** The inline content of the paragraph. */
1693
+ /** Holds the inline content of the paragraph. */
1287
1694
  readonly children: readonly InlineNode[];
1288
1695
  }
1289
1696
 
@@ -1293,21 +1700,28 @@ export declare interface ParagraphNode {
1293
1700
  *
1294
1701
  * @param lines - The markdown lines to parse.
1295
1702
  * @param depth - The current recursion depth (blockquotes/lists increment it).
1703
+ * @param spans - The optional operation-owned node span recorder.
1704
+ * @param end - The original-source end of this line run, including a removed terminator.
1296
1705
  * @returns The parsed block nodes.
1297
1706
  *
1298
1707
  * @example
1299
1708
  * ```ts
1300
- * parseBlocks(['# Hi'], 0) // [{ element: 'heading', level: 1, children: [...] }]
1709
+ * parseBlocks(splitLines('# Hi'), 0) // [{ element: 'heading', level: 1, children: [...] }]
1301
1710
  * ```
1302
1711
  */
1303
- export declare function parseBlocks(lines: readonly string[], depth: number): readonly BlockNode[];
1712
+ export declare function parseBlocks(lines: readonly MarkdownSource[], depth: number, spans?: Map<MarkdownNode, MarkdownSpan>, end?: number): readonly BlockNode[];
1304
1713
 
1305
1714
  /**
1306
- * Parses a markdown string into a typed {@link MarkdownDocument} AST via the
1715
+ * Parses a markdown string into a typed {@link MarkdownDocument} AST through the
1307
1716
  * block phase.
1308
1717
  *
1309
1718
  * @param markdown - The markdown source to parse.
1310
1719
  * @returns The parsed document.
1720
+ *
1721
+ * @example
1722
+ * ```ts
1723
+ * parseDocument('# Hi') // { element: 'document', children: [{ element: 'heading', ... }] }
1724
+ * ```
1311
1725
  */
1312
1726
  export declare function parseDocument(markdown: string): MarkdownDocument;
1313
1727
 
@@ -1317,11 +1731,30 @@ export declare function parseDocument(markdown: string): MarkdownDocument;
1317
1731
  *
1318
1732
  * @param text - The inline markdown text to parse.
1319
1733
  * @returns The parsed inline nodes.
1734
+ *
1735
+ * @example
1736
+ * ```ts
1737
+ * parseInline('a *b*') // [{ element: 'text', value: 'a ' }, { element: 'emphasis', ... }]
1738
+ * ```
1320
1739
  */
1321
1740
  export declare function parseInline(text: string): readonly InlineNode[];
1322
1741
 
1323
1742
  /**
1324
- * Project one HTML leaf - a text node, a comment, or a doctype - to its
1743
+ * Parses a markdown string into a document and its original-source spans.
1744
+ *
1745
+ * @param markdown - The markdown source to parse.
1746
+ * @returns The parsed document and its node-identity span map.
1747
+ *
1748
+ * @example
1749
+ * ```ts
1750
+ * const [document, spans] = parseProvenance('# Hi')
1751
+ * spans.get(document) // { start: 0, end: 4 }
1752
+ * ```
1753
+ */
1754
+ export declare function parseProvenance(markdown: string): MarkdownParseResult;
1755
+
1756
+ /**
1757
+ * Projects one HTML leaf - a text node, a comment, or a doctype - to its
1325
1758
  * {@link MarkdownProjection}.
1326
1759
  *
1327
1760
  * @remarks
@@ -1342,7 +1775,7 @@ export declare function parseInline(text: string): readonly InlineNode[];
1342
1775
  export declare function projectHTMLLeaf(leaf: CommentNode | DoctypeNode | TextNode_2): MarkdownProjection;
1343
1776
 
1344
1777
  /**
1345
- * Project one HTML container - the document root or an element - from its children's
1778
+ * Projects one HTML container - the document root or an element - from its children's
1346
1779
  * already-computed projections. THE element mapping, and the only place that decides
1347
1780
  * what an HTML tag becomes in markdown.
1348
1781
  *
@@ -1379,7 +1812,7 @@ export declare function projectHTMLLeaf(leaf: CommentNode | DoctypeNode | TextNo
1379
1812
  export declare function projectHTMLNode(node: ElementNode | HTMLDocument, children: readonly MarkdownProjection[]): MarkdownProjection;
1380
1813
 
1381
1814
  /**
1382
- * Read a projection as BLOCK content - the view a document, a blockquote, and a list
1815
+ * Reads a projection as BLOCK content - the view a document, a blockquote, and a list
1383
1816
  * item each need.
1384
1817
  *
1385
1818
  * @remarks
@@ -1400,7 +1833,7 @@ export declare function projectHTMLNode(node: ElementNode | HTMLDocument, childr
1400
1833
  export declare function projectionToBlocks(projection: MarkdownProjection): readonly BlockNode[];
1401
1834
 
1402
1835
  /**
1403
- * Read a projection as INLINE content - the view a link, an emphasis, and a table cell
1836
+ * Reads a projection as INLINE content - the view a link, an emphasis, and a table cell
1404
1837
  * each need.
1405
1838
  *
1406
1839
  * @remarks
@@ -1421,7 +1854,24 @@ export declare function projectionToBlocks(projection: MarkdownProjection): read
1421
1854
  export declare function projectionToInlines(projection: MarkdownProjection): readonly InlineNode[];
1422
1855
 
1423
1856
  /**
1424
- * Render a {@link MarkdownNode} to sanitized canonical HTML.
1857
+ * Projects a derived text range through its segments to a half-open region of the
1858
+ * original markdown string.
1859
+ *
1860
+ * @param source - The offset-bearing source carrying the range
1861
+ * @param from - The inclusive derived-text boundary
1862
+ * @param to - The exclusive derived-text boundary
1863
+ * @returns The original-string span, or `undefined` when either boundary is unmapped
1864
+ *
1865
+ * @example
1866
+ * ```ts
1867
+ * projectSpan({ text: 'a', segments: [{ offset: 0, start: 4, end: 5 }] }, 0, 1)
1868
+ * // { start: 4, end: 5 }
1869
+ * ```
1870
+ */
1871
+ export declare function projectSpan(source: MarkdownSource, from: number, to: number): MarkdownSpan | undefined;
1872
+
1873
+ /**
1874
+ * Renders a {@link MarkdownNode} to sanitized canonical HTML.
1425
1875
  *
1426
1876
  * @remarks
1427
1877
  * Markdown widens `@orkestrel/html`'s attribute floor by exactly `src`, because image
@@ -1442,7 +1892,7 @@ export declare function projectionToInlines(projection: MarkdownProjection): rea
1442
1892
  export declare function renderHTML(node: MarkdownNode): string;
1443
1893
 
1444
1894
  /**
1445
- * Render a {@link MarkdownNode} to its CANONICAL markdown source - the inverse
1895
+ * Renders a {@link MarkdownNode} to its CANONICAL markdown source - the inverse
1446
1896
  * projection of `renderHTML`, and the serializer a `parse(renderMarkdown(doc))`
1447
1897
  * round-trip is built on. Canonical forms: `*` / `**` emphasis at even emphasis
1448
1898
  * nesting depths and `_` / `__` at odd depths, `- ` bullets, `N. ` sequential
@@ -1451,8 +1901,8 @@ export declare function renderHTML(node: MarkdownNode): string;
1451
1901
  * `> `-prefixed blockquote lines, GFM tables (1-space-padded cells, `\|`-escaped
1452
1902
  * pipes, an alignment delimiter row), `[text](href)` links, `![alt](src)` images,
1453
1903
  * and two-space hard breaks. A `text` node's literal content is backslash-escaped
1454
- * wherever it would otherwise re-parse as markup (AGENTS §14 parse↔render
1455
- * soundness).
1904
+ * wherever it would otherwise re-parse as markup, so parsing the rendered source
1905
+ * returns the node it was rendered from.
1456
1906
  *
1457
1907
  * @remarks
1458
1908
  * Total: never throws. At {@link MAX_DEPTH} a value-bearing node degrades to its
@@ -1473,19 +1923,20 @@ export declare function renderHTML(node: MarkdownNode): string;
1473
1923
  export declare function renderMarkdown(node: MarkdownNode): string;
1474
1924
 
1475
1925
  /**
1476
- * Rewrite a {@link MarkdownDocument} bottom-up (copy-on-write) - each node's children
1926
+ * Rewrites a {@link MarkdownDocument} bottom-up (copy-on-write) - each node's children
1477
1927
  * are rewritten first (post-order), then `rewrite` is applied to the node itself; the
1478
1928
  * document ROOT is never passed to `rewrite` (the `element: 'document'` invariant
1479
1929
  * always holds). A table's inline cells and a list's items ARE rewritten.
1480
1930
  *
1481
1931
  * @remarks
1482
- * Never mutates `document` - every level is rebuilt into a fresh object/array, even
1483
- * when `rewrite` returns its input unchanged. When `rewrite` returns a node whose
1484
- * `element` does not fit the slot it was called for (a block slot handed a
1932
+ * Never mutates `document`. An unchanged subtree keeps its input identity. A parent
1933
+ * is rebuilt only when an accepted child changes, and the returned derivation map
1934
+ * associates each rebuilt output with its input node. When `rewrite` returns a node
1935
+ * whose `element` does not fit the slot it was called for (a block slot handed a
1485
1936
  * non-{@link BlockNode}, an inline slot handed a non-{@link InlineNode}, a list-item
1486
- * slot handed a non-`listItem`), the ill-fitting result is discarded and the
1487
- * freshly-rebuilt (unrewritten-at-this-level) node is kept instead - `rewriteDocument`
1488
- * stays total and never produces a structurally invalid document.
1937
+ * slot handed a non-`listItem`), the ill-fitting result is discarded and the accepted
1938
+ * input child is reused - `rewriteDocument` stays total and never produces a
1939
+ * structurally invalid document.
1489
1940
  *
1490
1941
  * Descent is capped at {@link MAX_DEPTH}, the same cap {@link walkNodes} and
1491
1942
  * {@link foldNode} observe: at `depth >= MAX_DEPTH` the subtree is passed through
@@ -1495,19 +1946,19 @@ export declare function renderMarkdown(node: MarkdownNode): string;
1495
1946
  *
1496
1947
  * @param document - The document AST to rewrite
1497
1948
  * @param rewrite - The bottom-up {@link MarkdownRewriteHandler}
1498
- * @returns A new, rewritten {@link MarkdownDocument}
1949
+ * @returns The rewritten document and its output-to-input derivations
1499
1950
  *
1500
1951
  * @example
1501
1952
  * ```ts
1502
- * rewriteDocument(document, (node) =>
1953
+ * const [rewritten, derivations] = rewriteDocument(document, (node) =>
1503
1954
  * node.element === 'text' ? { element: 'text', value: node.value.toUpperCase() } : node,
1504
1955
  * )
1505
1956
  * ```
1506
1957
  */
1507
- export declare function rewriteDocument(document: MarkdownDocument, rewrite: MarkdownRewriteHandler): MarkdownDocument;
1958
+ export declare function rewriteDocument(document: MarkdownDocument, rewrite: MarkdownRewriteHandler): MarkdownDerivation<MarkdownDocument>;
1508
1959
 
1509
1960
  /**
1510
- * Scan an inline code span at `start` (a `` ` ``-run … a matching `` ` ``-run of the
1961
+ * Scans an inline code span at `start` (a `` ` ``-run … a matching `` ` ``-run of the
1511
1962
  * SAME length, the CommonMark rule that lets a span contain backticks). Returns the
1512
1963
  * span's literal text + end index, or `undefined` when no matching closer exists (it
1513
1964
  * then degrades to literal backticks).
@@ -1522,40 +1973,34 @@ export declare function rewriteDocument(document: MarkdownDocument, rewrite: Mar
1522
1973
  * scanCode('`code`', 0, 6) // { value: 'code', end: 6 }
1523
1974
  * ```
1524
1975
  */
1525
- export declare function scanCode(source: string, start: number, to: number): {
1526
- readonly value: string;
1527
- readonly end: number;
1528
- } | undefined;
1976
+ export declare function scanCode(source: string, start: number, to: number): CodeSpanMatch | undefined;
1529
1977
 
1530
1978
  /**
1531
- * Scan an emphasis run at `start` (`*` / `_`, doubled for strong) - finds the nearest
1532
- * matching closing run of the same marker + width while skipping complete nested
1533
- * runs from the other marker family, and requires non-space immediately inside both
1534
- * delimiters (the CommonMark flanking simplification that blocks `* x *`). Returns
1535
- * the emphasis node, or `undefined` when no valid closer exists (it then degrades to
1536
- * a literal marker).
1979
+ * Scans an emphasis run at `start` (`*` / `_`, doubled for strong) - finds the nearest
1980
+ * matching closing run of the same marker + width while skipping complete nested runs
1981
+ * from the other marker family, and requires non-space immediately inside both
1982
+ * delimiters (the CommonMark flanking simplification that blocks `* x *`) through
1983
+ * {@link locateEmphasis}, and returns the parsed node and end index. Returns
1984
+ * `undefined` when no valid closer exists (it then degrades to a literal marker).
1537
1985
  *
1538
1986
  * @param source - The inline source text
1539
1987
  * @param start - The index of the opening marker
1540
1988
  * @param to - The exclusive end of the scan window
1541
- * @param depth - The current inline-recursion depth (defaults to 0 at the entry point);
1542
- * at {@link MAX_DEPTH} the emphasis's children degrade to literal text instead of
1543
- * recursing further
1544
- * @returns The parsed {@link EmphasisNode} + end index, or `undefined`
1989
+ * @param depth - The current inline-recursion depth, forwarded to {@link scanInline}
1990
+ * incremented by one for the run's children. At {@link MAX_DEPTH} that recursion
1991
+ * emits the content as a single literal text node instead of scanning it.
1992
+ * @returns The parsed emphasis and end index, or `undefined` when no closer exists
1545
1993
  *
1546
1994
  * @example
1547
1995
  * ```ts
1548
1996
  * scanEmphasis('*em*', 0, 4)
1549
- * // { node: { element: 'emphasis', strong: false, children: [...] }, end: 4 }
1997
+ * // { node: { element: 'emphasis', strong: false, children: [{ element: 'text', value: 'em' }] }, end: 4 }
1550
1998
  * ```
1551
1999
  */
1552
- export declare function scanEmphasis(source: string, start: number, to: number, depth?: number): {
1553
- readonly node: EmphasisNode;
1554
- readonly end: number;
1555
- } | undefined;
2000
+ export declare function scanEmphasis(source: string, start: number, to: number, depth?: number): EmphasisScan | undefined;
1556
2001
 
1557
2002
  /**
1558
- * Scan the window `[from, to)` of `source` into inline nodes - the single recursive
2003
+ * Scans the window `[from, to)` of `source` into inline nodes - the single recursive
1559
2004
  * engine the inline phase runs on (emphasis, link text, and image alternative
1560
2005
  * content recurse through it). Linear:
1561
2006
  * each character is consumed once; a failed construct emits its opening character as
@@ -1565,10 +2010,11 @@ export declare function scanEmphasis(source: string, start: number, to: number,
1565
2010
  * @param from - The inclusive start of the scan window
1566
2011
  * @param to - The exclusive end of the scan window
1567
2012
  * @param depth - The current inline-recursion depth (defaults to 0 at the entry point);
1568
- * incremented by one on every recursive descent through {@link scanLink} /
1569
- * {@link scanEmphasis}. At {@link MAX_DEPTH} the window is never scanned for markup -
1570
- * it emits as a single literal text node - so pathological nesting (`[[[[…`,
1571
- * `****…`) cannot exhaust the call stack.
2013
+ * incremented by one on every recursive descent {@link scanInlineSource} makes into
2014
+ * itself for a link's text, an image's alternative content, or an emphasis run's
2015
+ * children. At {@link MAX_DEPTH} the window is never scanned for markup - it emits as
2016
+ * a single literal text node - so pathological nesting (`[[[[…`, `****…`) cannot
2017
+ * exhaust the call stack.
1572
2018
  * @returns The parsed inline nodes (NOT yet coalesced)
1573
2019
  *
1574
2020
  * @example
@@ -1579,50 +2025,91 @@ export declare function scanEmphasis(source: string, start: number, to: number,
1579
2025
  export declare function scanInline(source: string, from: number, to: number, depth?: number): readonly InlineNode[];
1580
2026
 
1581
2027
  /**
1582
- * Scan a link `[text](href)` at `start` - the text runs to a BALANCED `]`, then `(`
2028
+ * Scans an offset-bearing inline window with the same engine as {@link scanInline}
2029
+ * and records each emitted node against the original markdown string.
2030
+ *
2031
+ * @param source - The offset-bearing inline source
2032
+ * @param from - The inclusive start of the scan window
2033
+ * @param to - The exclusive end of the scan window
2034
+ * @param spans - The operation-owned node span recorder
2035
+ * @param depth - The current inline-recursion depth, incremented by one on every
2036
+ * recursive descent this function makes into itself for a link's text, an image's
2037
+ * alternative content, or an emphasis run's children
2038
+ * @returns The parsed inline nodes before adjacent text coalescing
2039
+ *
2040
+ * @example
2041
+ * ```ts
2042
+ * scanInlineSource(
2043
+ * { text: 'hi *there*', segments: [{ offset: 0, start: 0, end: 10 }] },
2044
+ * 0,
2045
+ * 10,
2046
+ * new Map(),
2047
+ * )
2048
+ * // [{ element: 'text', value: 'hi ' }, { element: 'emphasis', ... }]
2049
+ * ```
2050
+ */
2051
+ export declare function scanInlineSource(source: MarkdownSource, from: number, to: number, spans: Map<MarkdownNode, MarkdownSpan>, depth?: number): readonly InlineNode[];
2052
+
2053
+ /**
2054
+ * Scans a link `[text](href)` at `start` - the text runs to a BALANCED `]`, then `(`
1583
2055
  * must immediately follow and the destination runs to the matching `)` (both respect
1584
- * nested delimiters + escapes). Returns the link node, or `undefined` when the shape
1585
- * does not hold (it then degrades to a literal `[`).
2056
+ * nested delimiters + escapes) through {@link locateLink}, and returns the parsed node
2057
+ * and end index. Returns `undefined` when the shape does not hold (it then degrades to
2058
+ * a literal `[`).
1586
2059
  *
1587
2060
  * @param source - The inline source text
1588
2061
  * @param start - The index of the opening `[`
1589
2062
  * @param to - The exclusive end of the scan window
1590
- * @param depth - The current inline-recursion depth (defaults to 0 at the entry point);
1591
- * at {@link MAX_DEPTH} the link's text children degrade to literal text instead of
1592
- * recursing further
1593
- * @returns The parsed {@link LinkNode} + end index, or `undefined`
2063
+ * @param depth - The current inline-recursion depth, forwarded to {@link scanInline}
2064
+ * incremented by one for the link text's children. At {@link MAX_DEPTH} that
2065
+ * recursion emits the text as a single literal text node instead of scanning it.
2066
+ * @returns The parsed link and end index, or `undefined` when the shape does not hold
1594
2067
  *
1595
2068
  * @example
1596
2069
  * ```ts
1597
2070
  * scanLink('[text](url)', 0, 11)
1598
- * // { node: { element: 'link', href: 'url', children: [...] }, end: 11 }
2071
+ * // { node: { element: 'link', href: 'url', children: [{ element: 'text', value: 'text' }] }, end: 11 }
1599
2072
  * ```
1600
2073
  */
1601
- export declare function scanLink(source: string, start: number, to: number, depth?: number): {
1602
- readonly node: LinkNode;
1603
- readonly end: number;
1604
- } | undefined;
2074
+ export declare function scanLink(source: string, start: number, to: number, depth?: number): LinkScan | undefined;
2075
+
2076
+ /**
2077
+ * Slices derived markdown text and narrows each intersecting source segment to the
2078
+ * same text-relative range.
2079
+ *
2080
+ * @param source - The offset-bearing source to slice
2081
+ * @param from - The inclusive text offset
2082
+ * @param to - The exclusive text offset
2083
+ * @returns The sliced text and its narrowed original-string segments
2084
+ *
2085
+ * @example
2086
+ * ```ts
2087
+ * sliceSource({ text: 'abc', segments: [{ offset: 0, start: 4, end: 7 }] }, 1, 3)
2088
+ * // { text: 'bc', segments: [{ offset: 0, start: 5, end: 7 }] }
2089
+ * ```
2090
+ */
2091
+ export declare function sliceSource(source: MarkdownSource, from: number, to: number): MarkdownSource;
1605
2092
 
1606
2093
  /**
1607
- * Normalize line endings to `\n` and split a markdown document into its lines - CRLF
1608
- * (`\r\n`) and bare CR (`\r`) both collapse to `\n` first, so a Windows-origin
1609
- * document parses identically. A single trailing newline does not yield a final
1610
- * empty line.
2094
+ * Splits a markdown document into offset-bearing lines while normalizing CRLF and
2095
+ * bare CR terminators at the line boundary. A single trailing terminator does not
2096
+ * yield a final empty line.
1611
2097
  *
1612
2098
  * @param markdown - The raw markdown source
1613
- * @returns The document's lines, line-terminators stripped
2099
+ * @returns The document's lines with their original-string coordinates
1614
2100
  *
1615
2101
  * @example
1616
2102
  * ```ts
1617
- * splitLines('a\r\nb\nc') // ['a', 'b', 'c']
2103
+ * splitLines('a\r\nb') // [{ text: 'a', segments: [{ offset: 0, start: 0, end: 1 }] }, ...]
1618
2104
  * ```
1619
2105
  */
1620
- export declare function splitLines(markdown: string): readonly string[];
2106
+ export declare function splitLines(markdown: string): readonly MarkdownSource[];
1621
2107
 
1622
2108
  /**
1623
- * Split one GFM table row into its cell strings - outer pipes are optional, an escaped
2109
+ * Splits one GFM table row into its cell strings - outer pipes are optional, an escaped
1624
2110
  * pipe (`\|`) inside a cell is NOT a separator (it becomes a literal `|`), and the
1625
- * empty leading / trailing cell produced by an outer `|` is dropped.
2111
+ * empty leading / trailing cell produced by an outer `|` is dropped. Derives the string
2112
+ * form from {@link splitTableSources}, which owns the escaped-pipe splitting rule.
1626
2113
  *
1627
2114
  * @param row - The raw table row line
1628
2115
  * @returns The row's cells, in column order
@@ -1635,7 +2122,21 @@ export declare function splitLines(markdown: string): readonly string[];
1635
2122
  export declare function splitTableRow(row: string): readonly string[];
1636
2123
 
1637
2124
  /**
1638
- * Whether the line at `index` starts a NEW block kind (heading / fence / thematic
2125
+ * Splits an offset-bearing GFM table row into offset-bearing cells, retaining the
2126
+ * complete source spelling of an escaped pipe while exposing its literal value.
2127
+ *
2128
+ * @param row - The offset-bearing table row
2129
+ * @returns The row's cells with their original-string coordinates
2130
+ *
2131
+ * @example
2132
+ * ```ts
2133
+ * splitTableSources(splitLines('| a\\|b |')[0]).map((cell) => cell.text) // [' a|b ']
2134
+ * ```
2135
+ */
2136
+ export declare function splitTableSources(row: MarkdownSource): readonly MarkdownSource[];
2137
+
2138
+ /**
2139
+ * Checks whether the line at `index` starts a NEW block kind (heading / fence / thematic
1639
2140
  * break / blockquote / list / table) - the paragraph collector stops at such a line
1640
2141
  * so a block following a paragraph without a blank line still parses (a trusted-input
1641
2142
  * caller writing a `##` heading directly under a paragraph, with no intervening blank
@@ -1643,7 +2144,7 @@ export declare function splitTableRow(row: string): readonly string[];
1643
2144
  *
1644
2145
  * @param lines - The document's lines
1645
2146
  * @param index - The line index to test
1646
- * @returns `true` when the line begins a different block
2147
+ * @returns True if the line begins a different block; false otherwise
1647
2148
  *
1648
2149
  * @example
1649
2150
  * ```ts
@@ -1653,21 +2154,23 @@ export declare function splitTableRow(row: string): readonly string[];
1653
2154
  export declare function startsBlock(lines: readonly string[], index: number): boolean;
1654
2155
 
1655
2156
  /**
1656
- * Strip one level of blockquote marker (`>` plus one optional following space) from a
1657
- * blockquote line, so the de-quoted lines re-parse as nested blocks.
2157
+ * Strips one level of blockquote marker (`>` plus one optional following space) from
2158
+ * an offset-bearing blockquote line, so the de-quoted source re-parses as nested
2159
+ * blocks without losing its original coordinates.
1658
2160
  *
1659
- * @param line - A blockquote line (per {@link isQuote})
1660
- * @returns The line with its leading `>` (and one space) removed
2161
+ * @param source - A blockquote line (per {@link isQuote})
2162
+ * @returns The source with its leading `>` and optional space removed
1661
2163
  *
1662
2164
  * @example
1663
2165
  * ```ts
1664
- * stripQuote('> text') // 'text'
2166
+ * stripQuote({ text: '> text', segments: [{ offset: 0, start: 0, end: 6 }] })
2167
+ * // { text: 'text', segments: [{ offset: 0, start: 2, end: 6 }] }
1665
2168
  * ```
1666
2169
  */
1667
- export declare function stripQuote(line: string): string;
2170
+ export declare function stripQuote(source: MarkdownSource): MarkdownSource;
1668
2171
 
1669
2172
  /**
1670
- * The horizontal alignment of a GFM table column, as declared by its delimiter row
2173
+ * Names the horizontal alignment of a GFM table column, as declared by its delimiter row
1671
2174
  * (`:---` left, `---:` right, `:---:` center). A bare `---` delimiter is represented
1672
2175
  * by `null` in {@link TableNode.align}: the positional array requires one entry per
1673
2176
  * column, JSON cannot carry `undefined` in an array, and the bare delimiter is an
@@ -1676,7 +2179,7 @@ export declare function stripQuote(line: string): string;
1676
2179
  export declare type TableAlign = 'left' | 'right' | 'center';
1677
2180
 
1678
2181
  /**
1679
- * The shape of a {@link TableAlign} - the per-column GFM table alignment
2182
+ * Describes the shape of a {@link TableAlign} - the per-column GFM table alignment
1680
2183
  * literal.
1681
2184
  *
1682
2185
  * @example
@@ -1693,19 +2196,30 @@ export declare type TableAlign = 'left' | 'right' | 'center';
1693
2196
  export declare const tableAlignShape: LiteralShape<readonly ["left", "right", "center"]>;
1694
2197
 
1695
2198
  /**
1696
- * A GFM table - `header` the inline content of each header cell, `rows` the body
2199
+ * Represents the result of collecting one GFM table - the node the construct scanner built and
2200
+ * where the block phase resumes.
2201
+ */
2202
+ export declare interface TableCollection {
2203
+ /** Holds the collected table. */
2204
+ readonly node: TableNode;
2205
+ /** Holds the index of the first line after the table. */
2206
+ readonly next: number;
2207
+ }
2208
+
2209
+ /**
2210
+ * Represents a GFM table - `header` the inline content of each header cell, `rows` the body
1697
2211
  * rows (each a list of cells, each cell inline content), `align` the per-column
1698
2212
  * alignment from the delimiter row. A short body row is padded with empty cells; an
1699
2213
  * over-long one is truncated to the header's column count.
1700
2214
  */
1701
2215
  export declare interface TableNode {
1702
2216
  readonly element: 'table';
1703
- /** The header row - one cell of inline content per column. */
2217
+ /** Holds the header row - one cell of inline content per column. */
1704
2218
  readonly header: ReadonlyArray<readonly InlineNode[]>;
1705
- /** The body rows - each a list of cells, each cell inline content. */
2219
+ /** Holds the body rows - each a list of cells, each cell inline content. */
1706
2220
  readonly rows: ReadonlyArray<ReadonlyArray<readonly InlineNode[]>>;
1707
2221
  /**
1708
- * The per-column alignment from the delimiter row, in column order. `null`
2222
+ * Holds the per-column alignment from the delimiter row, in column order. `null`
1709
2223
  * represents a bare `---` delimiter because this positional array requires one
1710
2224
  * entry per column, JSON cannot carry `undefined` in an array, and the delimiter
1711
2225
  * is an explicit no-alignment marker rather than an omitted value.
@@ -1714,19 +2228,19 @@ export declare interface TableNode {
1714
2228
  }
1715
2229
 
1716
2230
  /**
1717
- * A run of plain text - the leaf inline node. `value` is the decoded text with
2231
+ * Represents a run of plain text - the leaf inline node. `value` is the decoded text with
1718
2232
  * markdown escapes (`\*`, `\_`, …) already resolved to their literal characters;
1719
2233
  * html's text encoder escapes `&`, `<`, `>` on the way out; `"` and `'` stay literal
1720
2234
  * in character data.
1721
2235
  */
1722
2236
  export declare interface TextNode {
1723
2237
  readonly element: 'text';
1724
- /** The literal text content (escapes resolved, NOT yet HTML-escaped). */
2238
+ /** Holds the literal text content (escapes resolved, NOT yet HTML-escaped). */
1725
2239
  readonly value: string;
1726
2240
  }
1727
2241
 
1728
2242
  /**
1729
- * The shape of a {@link TextNode} - a plain-text leaf inline run.
2243
+ * Describes the shape of a {@link TextNode} - a plain-text leaf inline run.
1730
2244
  *
1731
2245
  * @example
1732
2246
  * ```ts
@@ -1742,13 +2256,13 @@ export declare const textShape: ObjectShape<{
1742
2256
  value: StringShape;
1743
2257
  }, false>;
1744
2258
 
1745
- /** A thematic break - a horizontal rule (`---` / `***` / `___` on its own line). */
2259
+ /** Represents a thematic break - a horizontal rule (`---` / `***` / `___` on its own line). */
1746
2260
  export declare interface ThematicBreakNode {
1747
2261
  readonly element: 'thematicBreak';
1748
2262
  }
1749
2263
 
1750
2264
  /**
1751
- * The shape of a {@link ThematicBreakNode} - a horizontal rule. Carries no
2265
+ * Describes the shape of a {@link ThematicBreakNode} - a horizontal rule. Carries no
1752
2266
  * fields beyond its `element` discriminant.
1753
2267
  *
1754
2268
  * @example
@@ -1765,7 +2279,7 @@ export declare const thematicBreakShape: ObjectShape<{
1765
2279
  }, false>;
1766
2280
 
1767
2281
  /**
1768
- * Trim the whitespace at the two ends of an inline run - the leading whitespace of a
2282
+ * Trims the whitespace at the two ends of an inline run - the leading whitespace of a
1769
2283
  * leading text node and the trailing whitespace of a trailing one - dropping either
1770
2284
  * node when nothing survives.
1771
2285
  *
@@ -1785,7 +2299,21 @@ export declare const thematicBreakShape: ObjectShape<{
1785
2299
  export declare function trimInlines(nodes: readonly InlineNode[]): readonly InlineNode[];
1786
2300
 
1787
2301
  /**
1788
- * Resolve backslash escapes in a raw string to their literal characters - used for a
2302
+ * Trims an offset-bearing source without losing the coordinates of its retained text.
2303
+ *
2304
+ * @param source - The source to trim
2305
+ * @returns The trimmed text and its narrowed original-string segments
2306
+ *
2307
+ * @example
2308
+ * ```ts
2309
+ * trimSource({ text: ' a ', segments: [{ offset: 0, start: 4, end: 7 }] })
2310
+ * // { text: 'a', segments: [{ offset: 0, start: 5, end: 6 }] }
2311
+ * ```
2312
+ */
2313
+ export declare function trimSource(source: MarkdownSource): MarkdownSource;
2314
+
2315
+ /**
2316
+ * Resolves backslash escapes in a raw string to their literal characters - used for a
1789
2317
  * link `href` (which is not otherwise inline-parsed) and any plain text run.
1790
2318
  *
1791
2319
  * @param text - The raw text possibly carrying `\x` escapes
@@ -1799,7 +2327,7 @@ export declare function trimInlines(nodes: readonly InlineNode[]): readonly Inli
1799
2327
  export declare function unescapeText(text: string): string;
1800
2328
 
1801
2329
  /**
1802
- * Depth-first, pre-order, root-inclusive traversal of a {@link MarkdownNode} - yields
2330
+ * Walks a {@link MarkdownNode} depth-first, pre-order, root-inclusive - yields
1803
2331
  * the node itself, then recurses into its children (block children, list items,
1804
2332
  * image/link inline children, table header/row cells' inline nodes) in walk order.
1805
2333
  *