@effected/markdown 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Markdown.js +20 -5
- package/MarkdownDiagnostic.js +25 -37
- package/MarkdownDocument.js +67 -7
- package/MarkdownTransform.js +28 -0
- package/README.md +5 -3
- package/index.d.ts +137 -129
- package/index.js +3 -2
- package/internal/blockParser.js +8 -18
- package/internal/fidelity.js +25 -0
- package/internal/phrasing.js +6 -15
- package/internal/point.js +32 -0
- package/internal/treeDepth.js +19 -0
- package/package.json +7 -4
- package/tsdoc-metadata.json +1 -1
- package/internal/lineIndex.js +0 -75
package/Markdown.js
CHANGED
|
@@ -3,7 +3,9 @@ import { isGuardExceeded, isRawMarkdownError } from "./internal/carriers.js";
|
|
|
3
3
|
import { parseBlocks } from "./internal/blockParser.js";
|
|
4
4
|
import { parsePhrasingText } from "./internal/phrasing.js";
|
|
5
5
|
import { stringifyBlocks } from "./internal/stringify.js";
|
|
6
|
+
import { assertWithinNestingDepth } from "./internal/treeDepth.js";
|
|
6
7
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
8
|
+
import { MarkdownTransform } from "./MarkdownTransform.js";
|
|
7
9
|
import { Effect, Result, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
8
10
|
|
|
9
11
|
// src/Markdown.ts
|
|
@@ -282,8 +284,8 @@ var Markdown = class Markdown {
|
|
|
282
284
|
* surgical editing goes through the offset-splice edit layer instead.
|
|
283
285
|
*
|
|
284
286
|
* **The canonical form is a stability commitment, so a test may assert
|
|
285
|
-
* against these bytes.**
|
|
286
|
-
*
|
|
287
|
+
* against these bytes.** The one option, `canonical`, only selects
|
|
288
|
+
* between these bytes and the author's spelling; the choices below are pinned by byte-level tests, and
|
|
287
289
|
* the engine is cross-checked against commonmark.js over the full
|
|
288
290
|
* CommonMark 0.31.2 corpus. Changing any of them is a **breaking** change
|
|
289
291
|
* to this package, never a patch. A consumer asserting on rendered
|
|
@@ -352,13 +354,24 @@ var Markdown = class Markdown {
|
|
|
352
354
|
* trip on a tree nesting past the depth cap fails, and only synthesized
|
|
353
355
|
* or decoded trees can nest that far.
|
|
354
356
|
*
|
|
357
|
+
* **`canonical: true` ignores fidelity fields.** With
|
|
358
|
+
* {@link MarkdownStringifyOptions.canonical} set, every fidelity field is
|
|
359
|
+
* cleared first, so a parsed `* a` list, setext heading, `_em_`, `~~~` fence
|
|
360
|
+
* or `***` break all emit the canonical spelling. It is the same as
|
|
361
|
+
* serializing `MarkdownTransform.canonicalize(node)`, without the
|
|
362
|
+
* `Mdast.toMdast` / `Mdast.fromMdastResult` round trip. The default keeps
|
|
363
|
+
* honouring fidelity fields.
|
|
364
|
+
*
|
|
355
365
|
* @param node - The document tree, or a single flow node, to serialize.
|
|
366
|
+
* @param options - Optional {@link MarkdownStringifyOptions}.
|
|
356
367
|
* @returns A `Result` succeeding with markdown source, or failing with
|
|
357
368
|
* {@link MarkdownStringifyError}.
|
|
358
369
|
*/
|
|
359
|
-
static stringifyResult(node) {
|
|
370
|
+
static stringifyResult(node, options) {
|
|
360
371
|
try {
|
|
361
|
-
|
|
372
|
+
if (options?.canonical === true) assertWithinNestingDepth(node);
|
|
373
|
+
const source = options?.canonical === true ? MarkdownTransform.canonicalize(node) : node;
|
|
374
|
+
return Result.succeed(stringifyBlocks(source.type === "root" ? source.children : [source]));
|
|
362
375
|
} catch (caught) {
|
|
363
376
|
if (isGuardExceeded(caught)) return Result.fail(new MarkdownStringifyError({ diagnostic: MarkdownDiagnostic.make({
|
|
364
377
|
code: caught.reason,
|
|
@@ -394,10 +407,12 @@ var Markdown = class Markdown {
|
|
|
394
407
|
* see {@link Markdown.stringifyResult}.
|
|
395
408
|
*
|
|
396
409
|
* @param node - The document tree, or a single flow node, to serialize.
|
|
410
|
+
* @param options - Optional {@link MarkdownStringifyOptions}; `canonical: true`
|
|
411
|
+
* discards fidelity fields.
|
|
397
412
|
* @returns An `Effect` that succeeds with markdown source, or fails with
|
|
398
413
|
* {@link MarkdownStringifyError}.
|
|
399
414
|
*/
|
|
400
|
-
static stringify = Effect.fn("Markdown.stringify")((node) => Effect.fromResult(Markdown.stringifyResult(node)));
|
|
415
|
+
static stringify = Effect.fn("Markdown.stringify")((node, options) => Effect.fromResult(Markdown.stringifyResult(node, options)));
|
|
401
416
|
/**
|
|
402
417
|
* A `Schema<Root, string>` decoding markdown source into a {@link Root}
|
|
403
418
|
* tree and encoding a tree back to canonical markdown via
|
package/MarkdownDiagnostic.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MARKDOWN_PARSE_ERROR_CODES } from "./internal/carriers.js";
|
|
2
|
+
import { LineIndex } from "@effected/source";
|
|
2
3
|
import { Schema } from "effect";
|
|
3
4
|
|
|
4
5
|
// src/MarkdownDiagnostic.ts
|
|
@@ -16,14 +17,20 @@ const MarkdownParseErrorCode = Schema.Literals(MARKDOWN_PARSE_ERROR_CODES);
|
|
|
16
17
|
* zero-based `line`/`character`).
|
|
17
18
|
*
|
|
18
19
|
* @remarks
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* Satisfies `@effected/source`'s `SourceDiagnostic<MarkdownParseErrorCode>`:
|
|
21
|
+
* the positional core (`code`/`message`/`offset`/`length`/`line`/`character`)
|
|
22
|
+
* the kit's format packages share, so a consumer printing or serving
|
|
23
|
+
* diagnostics (an LSP server, a CLI annotation) is written once against that
|
|
24
|
+
* shape. `line`/`character` are zero-based, `character` in UTF-16 code units,
|
|
25
|
+
* with `\n`, `\r\n` and a bare `\r` as line breaks: exactly
|
|
26
|
+
* `LineIndex.make(source).positionAt(offset)`, and the same index
|
|
27
|
+
* {@link MarkdownDocument.lineIndex} returns. An offset at the `\n` of a
|
|
28
|
+
* `\r\n` reads as the end of its line.
|
|
29
|
+
*
|
|
30
|
+
* The one-based `line`/`column` unist `Point`s carried on `MarkdownNode`
|
|
31
|
+
* positions read the same index, one-based: a deliberate, different
|
|
32
|
+
* convention for the AST rather than a mismatch to reconcile (see
|
|
33
|
+
* {@link MarkdownDocument.lineIndex} for the exact mapping).
|
|
27
34
|
*
|
|
28
35
|
* @public
|
|
29
36
|
*/
|
|
@@ -40,12 +47,20 @@ var MarkdownDiagnostic = class MarkdownDiagnostic extends Schema.Class("Markdown
|
|
|
40
47
|
* from `offset` against the source text. Advanced — the parse entry
|
|
41
48
|
* points call this for you.
|
|
42
49
|
*
|
|
43
|
-
* @
|
|
50
|
+
* @remarks
|
|
51
|
+
* Pass a line index already built over the source to materialize many
|
|
52
|
+
* records without rescanning the text; a string builds one. The
|
|
53
|
+
* parameter is the structural `SourceIndex`, so an index from any
|
|
54
|
+
* installed copy of `@effected/source` is accepted. An offset
|
|
55
|
+
* outside the source clamps into it, as `LineIndex.positionAt` does.
|
|
56
|
+
*
|
|
57
|
+
* @param source - The source text the record's `offset` indexes into, or
|
|
58
|
+
* a `LineIndex` over it.
|
|
44
59
|
* @param raw - The engine record: `code`, `message`, `offset` and `length`.
|
|
45
60
|
* @returns The diagnostic with `line` and `character` filled in.
|
|
46
61
|
*/
|
|
47
62
|
static fromRaw(source, raw) {
|
|
48
|
-
const { line, character } =
|
|
63
|
+
const { line, character } = (typeof source === "string" ? LineIndex.make(source) : source).positionAt(raw.offset);
|
|
49
64
|
return MarkdownDiagnostic.make({
|
|
50
65
|
code: raw.code,
|
|
51
66
|
message: raw.message,
|
|
@@ -56,32 +71,5 @@ var MarkdownDiagnostic = class MarkdownDiagnostic extends Schema.Class("Markdown
|
|
|
56
71
|
});
|
|
57
72
|
}
|
|
58
73
|
};
|
|
59
|
-
/**
|
|
60
|
-
* Compute the zero-based line/character position of `offset` within `text`.
|
|
61
|
-
* Recognizes `\n`, `\r` and `\r\n` as line breaks; a CRLF pair counts as a
|
|
62
|
-
* single newline (toml `TomlDiagnostic` precedent — kept independent of
|
|
63
|
-
* `internal/lineIndex.ts`, which answers a different, one-based question
|
|
64
|
-
* for AST positions).
|
|
65
|
-
*/
|
|
66
|
-
function lineChar(text, offset) {
|
|
67
|
-
let line = 0;
|
|
68
|
-
let lineStart = 0;
|
|
69
|
-
const limit = Math.min(offset, text.length);
|
|
70
|
-
for (let i = 0; i < limit; i++) {
|
|
71
|
-
const ch = text.charCodeAt(i);
|
|
72
|
-
if (ch === 10) {
|
|
73
|
-
line++;
|
|
74
|
-
lineStart = i + 1;
|
|
75
|
-
} else if (ch === 13) {
|
|
76
|
-
if (i + 1 < text.length && text.charCodeAt(i + 1) === 10) i++;
|
|
77
|
-
line++;
|
|
78
|
-
lineStart = i + 1;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
line,
|
|
83
|
-
character: offset - lineStart
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
74
|
|
|
87
75
|
export { MarkdownDiagnostic, MarkdownParseErrorCode };
|
package/MarkdownDocument.js
CHANGED
|
@@ -4,8 +4,9 @@ import { scanFrontmatter } from "./internal/blocks/frontmatter.js";
|
|
|
4
4
|
import { preprocessLines } from "./internal/preprocess.js";
|
|
5
5
|
import { normalizeLabelText } from "./internal/references.js";
|
|
6
6
|
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
7
|
-
import { parsePassResult } from "./Markdown.js";
|
|
8
7
|
import { rootHeadingBounds } from "./internal/sectionBounds.js";
|
|
8
|
+
import { parsePassResult } from "./Markdown.js";
|
|
9
|
+
import { LineIndex } from "@effected/source";
|
|
9
10
|
import { Effect, Option, Result, Schema } from "effect";
|
|
10
11
|
|
|
11
12
|
// src/MarkdownDocument.ts
|
|
@@ -122,6 +123,12 @@ const phrasingText = (nodes) => {
|
|
|
122
123
|
return out;
|
|
123
124
|
};
|
|
124
125
|
/**
|
|
126
|
+
* Each document's line index, built at most once. A side table rather than a
|
|
127
|
+
* field: the index is derived from `source`, so it is no part of the
|
|
128
|
+
* document's schema, its equality or its encoded form.
|
|
129
|
+
*/
|
|
130
|
+
const lineIndexes = /* @__PURE__ */ new WeakMap();
|
|
131
|
+
/**
|
|
125
132
|
* A parsed markdown document: the original `source`, the mdast-shaped
|
|
126
133
|
* {@link Root} tree, the non-fatal {@link MarkdownDiagnostic}s the parse
|
|
127
134
|
* produced, and the link-reference `definitions` index.
|
|
@@ -213,6 +220,55 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
213
220
|
return scanFrontmatter(preprocessLines(this.source), this.source) !== null;
|
|
214
221
|
}
|
|
215
222
|
/**
|
|
223
|
+
* The line index over `source`: offsets to zero-based `{ line, character }`
|
|
224
|
+
* and back, with exactly the rules this package's own positions use.
|
|
225
|
+
*
|
|
226
|
+
* @remarks
|
|
227
|
+
* A `LineIndex` from `@effected/source` (re-exported from this package's
|
|
228
|
+
* entry point), typed as the structural `SourceIndex` so a consumer on
|
|
229
|
+
* another installed copy of `@effected/source` can pass it on, with its
|
|
230
|
+
* default `"lsp"` line breaks: `\n`, `\r\n` and a
|
|
231
|
+
* bare `\r`, `character` in UTF-16 code units, an offset at the `\n` of a
|
|
232
|
+
* `\r\n` reading as the end of its line. So for any offset into
|
|
233
|
+
* `source`:
|
|
234
|
+
*
|
|
235
|
+
* - `doc.lineIndex.positionAt(offset)` is the `line`/`character` a
|
|
236
|
+
* {@link MarkdownDiagnostic} at that offset carries;
|
|
237
|
+
* - a node's unist `Point` has `line` = `lineAt(offset) + 1` and
|
|
238
|
+
* `column` = `offset - lineStart(line) + 1`: `positionAt` plus one on
|
|
239
|
+
* both axes everywhere except at the `\n` of a `\r\n`, which a Point
|
|
240
|
+
* counts as one column past the `\r` (as `vfile-location` does) and
|
|
241
|
+
* `positionAt` reads as the end of its line;
|
|
242
|
+
* - `offsetAt`, `spanOf` and `rangeOf` convert an LSP `Position` or
|
|
243
|
+
* `Range` back to an offset or a `MarkdownRange`-shaped
|
|
244
|
+
* `{ offset, length }`.
|
|
245
|
+
*
|
|
246
|
+
* A parsed document returns the index the parse itself built; a document
|
|
247
|
+
* constructed with `make` builds one on first access. Either way it is
|
|
248
|
+
* built once per document and shared by every later access. To index a
|
|
249
|
+
* text with no document, `LineIndex.make(text)` gives the same answers.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```ts
|
|
253
|
+
* import { MarkdownDocument } from "@effected/markdown";
|
|
254
|
+
* import { Result } from "effect";
|
|
255
|
+
*
|
|
256
|
+
* const result = MarkdownDocument.parseResult("# Title\r\n\r\nBody\n");
|
|
257
|
+
* if (Result.isSuccess(result)) {
|
|
258
|
+
* const index = result.success.lineIndex;
|
|
259
|
+
* console.log(index.positionAt(12)); // => { line: 2, character: 1 }
|
|
260
|
+
* console.log(index.offsetAt({ line: 2, character: 0 })); // => 11
|
|
261
|
+
* }
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
get lineIndex() {
|
|
265
|
+
const cached = lineIndexes.get(this);
|
|
266
|
+
if (cached !== void 0) return cached;
|
|
267
|
+
const index = LineIndex.make(this.source);
|
|
268
|
+
lineIndexes.set(this, index);
|
|
269
|
+
return index;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
216
272
|
* Every heading in the document, in document order, wherever it sits —
|
|
217
273
|
* including headings nested inside blockquotes and list items.
|
|
218
274
|
*
|
|
@@ -379,12 +435,16 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
379
435
|
* `MarkdownParseError`.
|
|
380
436
|
*/
|
|
381
437
|
static parseResult(text, options) {
|
|
382
|
-
return Result.map(parsePassResult(text, options), (pass) =>
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
438
|
+
return Result.map(parsePassResult(text, options), (pass) => {
|
|
439
|
+
const document = MarkdownDocument.make({
|
|
440
|
+
source: text,
|
|
441
|
+
root: pass.root,
|
|
442
|
+
diagnostics: pass.carriers.map((carrier) => MarkdownDiagnostic.fromRaw(pass.lineIndex, carrier)),
|
|
443
|
+
definitions: pass.refmap
|
|
444
|
+
});
|
|
445
|
+
lineIndexes.set(document, pass.lineIndex);
|
|
446
|
+
return document;
|
|
447
|
+
});
|
|
388
448
|
}
|
|
389
449
|
/**
|
|
390
450
|
* Parse markdown into a {@link MarkdownDocument}. Defined in terms of
|
package/MarkdownTransform.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FIDELITY_FIELDS, clearFidelity } from "./internal/fidelity.js";
|
|
1
2
|
import { rootHeadingBounds } from "./internal/sectionBounds.js";
|
|
2
3
|
|
|
3
4
|
// src/MarkdownTransform.ts
|
|
@@ -76,6 +77,32 @@ var MarkdownTransform = class {
|
|
|
76
77
|
} });
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
80
|
+
* Return a copy of the tree with every fidelity field cleared, so
|
|
81
|
+
* `Markdown.stringify` emits the canonical form instead of the author's
|
|
82
|
+
* spelling.
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* Clears `headingStyle`, `markerChar` (emphasis, strong, thematic break),
|
|
86
|
+
* `fenceChar` and `fenceLength`, `bulletChar` and `delimiter`, and
|
|
87
|
+
* `breakStyle`. `Text`'s `escapeStyle` is an emitter instruction rather
|
|
88
|
+
* than a recorded spelling and is kept. Positions and all mdast fields are
|
|
89
|
+
* untouched. The input is never mutated; a subtree carrying no fidelity
|
|
90
|
+
* field is returned as the same instance, following
|
|
91
|
+
* {@link MarkdownTransform.(map:1)}, on which this is built. Like `map` it
|
|
92
|
+
* recurses without a depth guard, so a tree nested past the stack throws a
|
|
93
|
+
* `RangeError`; `Markdown.stringifyResult` with `canonical: true` checks
|
|
94
|
+
* depth first and fails typed at the stringifier's own limit instead.
|
|
95
|
+
*
|
|
96
|
+
* `Markdown.stringify(tree, { canonical: true })` is exactly
|
|
97
|
+
* `Markdown.stringify(MarkdownTransform.canonicalize(tree))`.
|
|
98
|
+
*
|
|
99
|
+
* @param node - The node whose fidelity fields to clear.
|
|
100
|
+
* @returns The node with fidelity fields cleared.
|
|
101
|
+
*/
|
|
102
|
+
static canonicalize(node) {
|
|
103
|
+
return mapNode(node, canonicalHandlers);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
79
106
|
* Locate the root-level sections of one heading depth by child index —
|
|
80
107
|
* no source text needed, so it works on a tree a
|
|
81
108
|
* `MarkdownTransform.map` has reshaped.
|
|
@@ -140,6 +167,7 @@ var MarkdownTransform = class {
|
|
|
140
167
|
}
|
|
141
168
|
};
|
|
142
169
|
const isHeadingDepth = (value) => Number.isInteger(value) && value >= 1 && value <= 6;
|
|
170
|
+
const canonicalHandlers = Object.fromEntries(Object.keys(FIDELITY_FIELDS).map((type) => [type, clearFidelity]));
|
|
143
171
|
function mapNode(node, handlers) {
|
|
144
172
|
let current = node;
|
|
145
173
|
if ("children" in node) {
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
CommonMark 0.31.2 and GFM parsing, editing and transformation expressed as Effect schemas and pure functions. Parse markdown into mdast-shaped nodes carrying byte offsets, navigate headings, sections and links, compute surgical offset-splice edits, normalize markers, decode frontmatter into a validated domain schema, project to and from plain mdast for the remark ecosystem, and walk a document as a `Stream`.
|
|
9
9
|
|
|
10
10
|
> **Pre-`1.0.0`.** This package is part of the `@effected/*` kit, built on stable
|
|
11
11
|
> Effect v4 (`effect` `^4.0.0`) and still in `0.x` development. Stable Effect
|
|
@@ -41,7 +41,7 @@ npm install @effected/markdown effect
|
|
|
41
41
|
pnpm add @effected/markdown effect
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Requires Node.js >=24.11.0. `effect` v4 is a peer dependency; the package
|
|
44
|
+
Requires Node.js >=24.11.0. `effect` v4 is a peer dependency; the package's one runtime dependency is the dependency-free `@effected/source`, whose `LineIndex` it re-exports.
|
|
45
45
|
|
|
46
46
|
`@effected/yaml`, `@effected/toml` and `@effected/jsonc` are **optional** peers, required only by the frontmatter codec module you import. A consumer decoding yaml frontmatter installs `@effected/yaml` and nothing else; a consumer who never touches frontmatter installs none of them.
|
|
47
47
|
|
|
@@ -314,7 +314,9 @@ Serialization matches `mdxJsxToMarkdown`'s defaults — `"` quotes, spaced self-
|
|
|
314
314
|
|
|
315
315
|
## The canonical form is stable
|
|
316
316
|
|
|
317
|
-
`Markdown.stringify`
|
|
317
|
+
`Markdown.stringify` emits one canonical form, that form is pinned by byte-level tests, the engine behind it is cross-checked against commonmark.js over the full CommonMark 0.31.2 corpus, and **changing any of it is a breaking change to this package rather than a patch**. So a test may assert on these bytes, and a pipeline that needs stable rendered markdown should serialize through here instead of a third-party stringifier whose defaults are free to move between releases.
|
|
318
|
+
|
|
319
|
+
By default a node's fidelity fields (`bulletChar`, `headingStyle`, `markerChar`, `fenceChar`, `delimiter`) win, so a parsed `* a` list or setext heading re-emits as written. To drop them, pass `{ canonical: true }` to `stringify` or `stringifyResult`, or call the pure `MarkdownTransform.canonicalize(tree)` to get a cleared copy of the tree (the input is never mutated). Both give identical output and replace the `Mdast.toMdast` / `Mdast.fromMdastResult` round trip.
|
|
318
320
|
|
|
319
321
|
For a node carrying no fidelity field:
|
|
320
322
|
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LineBreaks, LineIndex, LineIndexOptions, SourceDiagnostic, SourceIndex, SourceIndex as SourceIndex$1, SourceLocation, SourcePosition, SourceRange, SourceSpan } from "@effected/source";
|
|
1
2
|
import { Data, Effect, Option, Result, Schema, Stream } from "effect";
|
|
2
3
|
declare const Point_base: Schema.Class<Point, Schema.Struct<{
|
|
3
4
|
readonly line: Schema.Number;
|
|
@@ -968,14 +969,20 @@ declare const MarkdownDiagnostic_base: Schema.Class<MarkdownDiagnostic, Schema.S
|
|
|
968
969
|
* zero-based `line`/`character`).
|
|
969
970
|
*
|
|
970
971
|
* @remarks
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
* the
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
972
|
+
* Satisfies `@effected/source`'s `SourceDiagnostic<MarkdownParseErrorCode>`:
|
|
973
|
+
* the positional core (`code`/`message`/`offset`/`length`/`line`/`character`)
|
|
974
|
+
* the kit's format packages share, so a consumer printing or serving
|
|
975
|
+
* diagnostics (an LSP server, a CLI annotation) is written once against that
|
|
976
|
+
* shape. `line`/`character` are zero-based, `character` in UTF-16 code units,
|
|
977
|
+
* with `\n`, `\r\n` and a bare `\r` as line breaks: exactly
|
|
978
|
+
* `LineIndex.make(source).positionAt(offset)`, and the same index
|
|
979
|
+
* {@link MarkdownDocument.lineIndex} returns. An offset at the `\n` of a
|
|
980
|
+
* `\r\n` reads as the end of its line.
|
|
981
|
+
*
|
|
982
|
+
* The one-based `line`/`column` unist `Point`s carried on `MarkdownNode`
|
|
983
|
+
* positions read the same index, one-based: a deliberate, different
|
|
984
|
+
* convention for the AST rather than a mismatch to reconcile (see
|
|
985
|
+
* {@link MarkdownDocument.lineIndex} for the exact mapping).
|
|
979
986
|
*
|
|
980
987
|
* @public
|
|
981
988
|
*/
|
|
@@ -985,11 +992,19 @@ export declare class MarkdownDiagnostic extends MarkdownDiagnostic_base {
|
|
|
985
992
|
* from `offset` against the source text. Advanced — the parse entry
|
|
986
993
|
* points call this for you.
|
|
987
994
|
*
|
|
988
|
-
* @
|
|
995
|
+
* @remarks
|
|
996
|
+
* Pass a line index already built over the source to materialize many
|
|
997
|
+
* records without rescanning the text; a string builds one. The
|
|
998
|
+
* parameter is the structural `SourceIndex`, so an index from any
|
|
999
|
+
* installed copy of `@effected/source` is accepted. An offset
|
|
1000
|
+
* outside the source clamps into it, as `LineIndex.positionAt` does.
|
|
1001
|
+
*
|
|
1002
|
+
* @param source - The source text the record's `offset` indexes into, or
|
|
1003
|
+
* a `LineIndex` over it.
|
|
989
1004
|
* @param raw - The engine record: `code`, `message`, `offset` and `length`.
|
|
990
1005
|
* @returns The diagnostic with `line` and `character` filled in.
|
|
991
1006
|
*/
|
|
992
|
-
static fromRaw(source: string, raw: {
|
|
1007
|
+
static fromRaw(source: string | SourceIndex$1, raw: {
|
|
993
1008
|
readonly code: MarkdownParseErrorCode;
|
|
994
1009
|
readonly message: string;
|
|
995
1010
|
readonly offset: number;
|
|
@@ -1013,6 +1028,19 @@ export declare const MarkdownDialect: Schema.Literals<readonly ["commonmark", "g
|
|
|
1013
1028
|
* @public
|
|
1014
1029
|
*/
|
|
1015
1030
|
export type MarkdownDialect = typeof MarkdownDialect.Type;
|
|
1031
|
+
/**
|
|
1032
|
+
* Options for `Markdown.stringify` and `Markdown.stringifyResult`.
|
|
1033
|
+
*
|
|
1034
|
+
* @public
|
|
1035
|
+
*/
|
|
1036
|
+
interface MarkdownStringifyOptions {
|
|
1037
|
+
/**
|
|
1038
|
+
* Ignore fidelity fields (marker characters, fence style, heading
|
|
1039
|
+
* spelling) and emit the canonical form. Defaults to `false`, which honours
|
|
1040
|
+
* them.
|
|
1041
|
+
*/
|
|
1042
|
+
readonly canonical?: boolean;
|
|
1043
|
+
}
|
|
1016
1044
|
declare const MarkdownParseOptions_base: Schema.Class<MarkdownParseOptions, Schema.Struct<{
|
|
1017
1045
|
readonly dialect: Schema.optionalKey<Schema.Literals<readonly ["commonmark", "gfm"]>>;
|
|
1018
1046
|
readonly frontmatter: Schema.optionalKey<Schema.Boolean>;
|
|
@@ -1225,8 +1253,8 @@ export declare class Markdown {
|
|
|
1225
1253
|
* surgical editing goes through the offset-splice edit layer instead.
|
|
1226
1254
|
*
|
|
1227
1255
|
* **The canonical form is a stability commitment, so a test may assert
|
|
1228
|
-
* against these bytes.**
|
|
1229
|
-
*
|
|
1256
|
+
* against these bytes.** The one option, `canonical`, only selects
|
|
1257
|
+
* between these bytes and the author's spelling; the choices below are pinned by byte-level tests, and
|
|
1230
1258
|
* the engine is cross-checked against commonmark.js over the full
|
|
1231
1259
|
* CommonMark 0.31.2 corpus. Changing any of them is a **breaking** change
|
|
1232
1260
|
* to this package, never a patch. A consumer asserting on rendered
|
|
@@ -1295,11 +1323,20 @@ export declare class Markdown {
|
|
|
1295
1323
|
* trip on a tree nesting past the depth cap fails, and only synthesized
|
|
1296
1324
|
* or decoded trees can nest that far.
|
|
1297
1325
|
*
|
|
1326
|
+
* **`canonical: true` ignores fidelity fields.** With
|
|
1327
|
+
* {@link MarkdownStringifyOptions.canonical} set, every fidelity field is
|
|
1328
|
+
* cleared first, so a parsed `* a` list, setext heading, `_em_`, `~~~` fence
|
|
1329
|
+
* or `***` break all emit the canonical spelling. It is the same as
|
|
1330
|
+
* serializing `MarkdownTransform.canonicalize(node)`, without the
|
|
1331
|
+
* `Mdast.toMdast` / `Mdast.fromMdastResult` round trip. The default keeps
|
|
1332
|
+
* honouring fidelity fields.
|
|
1333
|
+
*
|
|
1298
1334
|
* @param node - The document tree, or a single flow node, to serialize.
|
|
1335
|
+
* @param options - Optional {@link MarkdownStringifyOptions}.
|
|
1299
1336
|
* @returns A `Result` succeeding with markdown source, or failing with
|
|
1300
1337
|
* {@link MarkdownStringifyError}.
|
|
1301
1338
|
*/
|
|
1302
|
-
static stringifyResult(node: Root | FlowContent): Result.Result<string, MarkdownStringifyError>;
|
|
1339
|
+
static stringifyResult(node: Root | FlowContent, options?: MarkdownStringifyOptions): Result.Result<string, MarkdownStringifyError>;
|
|
1303
1340
|
/**
|
|
1304
1341
|
* Serialize a {@link Root} tree, or a single {@link (FlowContent:type)} node, to
|
|
1305
1342
|
* canonical markdown. Defined in terms of {@link Markdown.stringifyResult}
|
|
@@ -1323,10 +1360,12 @@ export declare class Markdown {
|
|
|
1323
1360
|
* see {@link Markdown.stringifyResult}.
|
|
1324
1361
|
*
|
|
1325
1362
|
* @param node - The document tree, or a single flow node, to serialize.
|
|
1363
|
+
* @param options - Optional {@link MarkdownStringifyOptions}; `canonical: true`
|
|
1364
|
+
* discards fidelity fields.
|
|
1326
1365
|
* @returns An `Effect` that succeeds with markdown source, or fails with
|
|
1327
1366
|
* {@link MarkdownStringifyError}.
|
|
1328
1367
|
*/
|
|
1329
|
-
static readonly stringify: (node: Root | FlowContent) => Effect.Effect<string, MarkdownStringifyError, never>;
|
|
1368
|
+
static readonly stringify: (node: Root | FlowContent, options?: MarkdownStringifyOptions | undefined) => Effect.Effect<string, MarkdownStringifyError, never>;
|
|
1330
1369
|
/**
|
|
1331
1370
|
* A `Schema<Root, string>` decoding markdown source into a {@link Root}
|
|
1332
1371
|
* tree and encoding a tree back to canonical markdown via
|
|
@@ -1629,6 +1668,49 @@ export declare class MarkdownDocument extends MarkdownDocument_base {
|
|
|
1629
1668
|
* checking repeatedly.
|
|
1630
1669
|
*/
|
|
1631
1670
|
get hasFrontmatterBlock(): boolean;
|
|
1671
|
+
/**
|
|
1672
|
+
* The line index over `source`: offsets to zero-based `{ line, character }`
|
|
1673
|
+
* and back, with exactly the rules this package's own positions use.
|
|
1674
|
+
*
|
|
1675
|
+
* @remarks
|
|
1676
|
+
* A `LineIndex` from `@effected/source` (re-exported from this package's
|
|
1677
|
+
* entry point), typed as the structural `SourceIndex` so a consumer on
|
|
1678
|
+
* another installed copy of `@effected/source` can pass it on, with its
|
|
1679
|
+
* default `"lsp"` line breaks: `\n`, `\r\n` and a
|
|
1680
|
+
* bare `\r`, `character` in UTF-16 code units, an offset at the `\n` of a
|
|
1681
|
+
* `\r\n` reading as the end of its line. So for any offset into
|
|
1682
|
+
* `source`:
|
|
1683
|
+
*
|
|
1684
|
+
* - `doc.lineIndex.positionAt(offset)` is the `line`/`character` a
|
|
1685
|
+
* {@link MarkdownDiagnostic} at that offset carries;
|
|
1686
|
+
* - a node's unist `Point` has `line` = `lineAt(offset) + 1` and
|
|
1687
|
+
* `column` = `offset - lineStart(line) + 1`: `positionAt` plus one on
|
|
1688
|
+
* both axes everywhere except at the `\n` of a `\r\n`, which a Point
|
|
1689
|
+
* counts as one column past the `\r` (as `vfile-location` does) and
|
|
1690
|
+
* `positionAt` reads as the end of its line;
|
|
1691
|
+
* - `offsetAt`, `spanOf` and `rangeOf` convert an LSP `Position` or
|
|
1692
|
+
* `Range` back to an offset or a `MarkdownRange`-shaped
|
|
1693
|
+
* `{ offset, length }`.
|
|
1694
|
+
*
|
|
1695
|
+
* A parsed document returns the index the parse itself built; a document
|
|
1696
|
+
* constructed with `make` builds one on first access. Either way it is
|
|
1697
|
+
* built once per document and shared by every later access. To index a
|
|
1698
|
+
* text with no document, `LineIndex.make(text)` gives the same answers.
|
|
1699
|
+
*
|
|
1700
|
+
* @example
|
|
1701
|
+
* ```ts
|
|
1702
|
+
* import { MarkdownDocument } from "@effected/markdown";
|
|
1703
|
+
* import { Result } from "effect";
|
|
1704
|
+
*
|
|
1705
|
+
* const result = MarkdownDocument.parseResult("# Title\r\n\r\nBody\n");
|
|
1706
|
+
* if (Result.isSuccess(result)) {
|
|
1707
|
+
* const index = result.success.lineIndex;
|
|
1708
|
+
* console.log(index.positionAt(12)); // => { line: 2, character: 1 }
|
|
1709
|
+
* console.log(index.offsetAt({ line: 2, character: 0 })); // => 11
|
|
1710
|
+
* }
|
|
1711
|
+
* ```
|
|
1712
|
+
*/
|
|
1713
|
+
get lineIndex(): SourceIndex$1;
|
|
1632
1714
|
/**
|
|
1633
1715
|
* Every heading in the document, in document order, wherever it sits —
|
|
1634
1716
|
* including headings nested inside blockquotes and list items.
|
|
@@ -2873,6 +2955,30 @@ export declare class MarkdownTransform {
|
|
|
2873
2955
|
* a heading depth or `min > max` — a wiring error, not a data error.
|
|
2874
2956
|
*/
|
|
2875
2957
|
static shiftHeadings<N extends MarkdownNode>(node: N, by: number, options?: ShiftHeadingsOptions): N;
|
|
2958
|
+
/**
|
|
2959
|
+
* Return a copy of the tree with every fidelity field cleared, so
|
|
2960
|
+
* `Markdown.stringify` emits the canonical form instead of the author's
|
|
2961
|
+
* spelling.
|
|
2962
|
+
*
|
|
2963
|
+
* @remarks
|
|
2964
|
+
* Clears `headingStyle`, `markerChar` (emphasis, strong, thematic break),
|
|
2965
|
+
* `fenceChar` and `fenceLength`, `bulletChar` and `delimiter`, and
|
|
2966
|
+
* `breakStyle`. `Text`'s `escapeStyle` is an emitter instruction rather
|
|
2967
|
+
* than a recorded spelling and is kept. Positions and all mdast fields are
|
|
2968
|
+
* untouched. The input is never mutated; a subtree carrying no fidelity
|
|
2969
|
+
* field is returned as the same instance, following
|
|
2970
|
+
* {@link MarkdownTransform.(map:1)}, on which this is built. Like `map` it
|
|
2971
|
+
* recurses without a depth guard, so a tree nested past the stack throws a
|
|
2972
|
+
* `RangeError`; `Markdown.stringifyResult` with `canonical: true` checks
|
|
2973
|
+
* depth first and fails typed at the stringifier's own limit instead.
|
|
2974
|
+
*
|
|
2975
|
+
* `Markdown.stringify(tree, { canonical: true })` is exactly
|
|
2976
|
+
* `Markdown.stringify(MarkdownTransform.canonicalize(tree))`.
|
|
2977
|
+
*
|
|
2978
|
+
* @param node - The node whose fidelity fields to clear.
|
|
2979
|
+
* @returns The node with fidelity fields cleared.
|
|
2980
|
+
*/
|
|
2981
|
+
static canonicalize<N extends MarkdownNode>(node: N): N;
|
|
2876
2982
|
/**
|
|
2877
2983
|
* Locate the root-level sections of one heading depth by child index —
|
|
2878
2984
|
* no source text needed, so it works on a tree a
|
|
@@ -2956,120 +3062,22 @@ export type MarkdownVisitorEvent = Data.TaggedEnum<{
|
|
|
2956
3062
|
*
|
|
2957
3063
|
* @public
|
|
2958
3064
|
*/
|
|
2959
|
-
export declare const MarkdownVisitorEvent: {
|
|
2960
|
-
readonly
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
readonly _tag: "Exit";
|
|
2976
|
-
readonly node: MarkdownNode;
|
|
2977
|
-
readonly path: MarkdownPath;
|
|
2978
|
-
readonly depth: number;
|
|
2979
|
-
}, {
|
|
2980
|
-
readonly _tag: Tag;
|
|
2981
|
-
}>;
|
|
2982
|
-
readonly $match: {
|
|
2983
|
-
<Cases extends {
|
|
2984
|
-
readonly Enter: (args: {
|
|
2985
|
-
readonly _tag: "Enter";
|
|
2986
|
-
readonly node: MarkdownNode;
|
|
2987
|
-
readonly path: MarkdownPath;
|
|
2988
|
-
readonly depth: number;
|
|
2989
|
-
}) => any;
|
|
2990
|
-
readonly Error: (args: {
|
|
2991
|
-
readonly _tag: "Error";
|
|
2992
|
-
readonly diagnostic: MarkdownDiagnostic;
|
|
2993
|
-
readonly path: MarkdownPath;
|
|
2994
|
-
readonly depth: number;
|
|
2995
|
-
}) => any;
|
|
2996
|
-
readonly Exit: (args: {
|
|
2997
|
-
readonly _tag: "Exit";
|
|
2998
|
-
readonly node: MarkdownNode;
|
|
2999
|
-
readonly path: MarkdownPath;
|
|
3000
|
-
readonly depth: number;
|
|
3001
|
-
}) => any;
|
|
3002
|
-
}>(cases: Cases): (value: {
|
|
3003
|
-
readonly _tag: "Enter";
|
|
3004
|
-
readonly node: MarkdownNode;
|
|
3005
|
-
readonly path: MarkdownPath;
|
|
3006
|
-
readonly depth: number;
|
|
3007
|
-
} | {
|
|
3008
|
-
readonly _tag: "Error";
|
|
3009
|
-
readonly diagnostic: MarkdownDiagnostic;
|
|
3010
|
-
readonly path: MarkdownPath;
|
|
3011
|
-
readonly depth: number;
|
|
3012
|
-
} | {
|
|
3013
|
-
readonly _tag: "Exit";
|
|
3014
|
-
readonly node: MarkdownNode;
|
|
3015
|
-
readonly path: MarkdownPath;
|
|
3016
|
-
readonly depth: number;
|
|
3017
|
-
}) => import("effect/Unify").Unify<ReturnType<Cases["Enter" | "Error" | "Exit"]>>;
|
|
3018
|
-
<Cases extends {
|
|
3019
|
-
readonly Enter: (args: {
|
|
3020
|
-
readonly _tag: "Enter";
|
|
3021
|
-
readonly node: MarkdownNode;
|
|
3022
|
-
readonly path: MarkdownPath;
|
|
3023
|
-
readonly depth: number;
|
|
3024
|
-
}) => any;
|
|
3025
|
-
readonly Error: (args: {
|
|
3026
|
-
readonly _tag: "Error";
|
|
3027
|
-
readonly diagnostic: MarkdownDiagnostic;
|
|
3028
|
-
readonly path: MarkdownPath;
|
|
3029
|
-
readonly depth: number;
|
|
3030
|
-
}) => any;
|
|
3031
|
-
readonly Exit: (args: {
|
|
3032
|
-
readonly _tag: "Exit";
|
|
3033
|
-
readonly node: MarkdownNode;
|
|
3034
|
-
readonly path: MarkdownPath;
|
|
3035
|
-
readonly depth: number;
|
|
3036
|
-
}) => any;
|
|
3037
|
-
}>(value: {
|
|
3038
|
-
readonly _tag: "Enter";
|
|
3039
|
-
readonly node: MarkdownNode;
|
|
3040
|
-
readonly path: MarkdownPath;
|
|
3041
|
-
readonly depth: number;
|
|
3042
|
-
} | {
|
|
3043
|
-
readonly _tag: "Error";
|
|
3044
|
-
readonly diagnostic: MarkdownDiagnostic;
|
|
3045
|
-
readonly path: MarkdownPath;
|
|
3046
|
-
readonly depth: number;
|
|
3047
|
-
} | {
|
|
3048
|
-
readonly _tag: "Exit";
|
|
3049
|
-
readonly node: MarkdownNode;
|
|
3050
|
-
readonly path: MarkdownPath;
|
|
3051
|
-
readonly depth: number;
|
|
3052
|
-
}, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Enter" | "Error" | "Exit"]>>;
|
|
3053
|
-
};
|
|
3054
|
-
readonly Enter: Data.TaggedEnum.ConstructorFrom<{
|
|
3055
|
-
readonly _tag: "Enter";
|
|
3056
|
-
readonly node: MarkdownNode;
|
|
3057
|
-
readonly path: MarkdownPath;
|
|
3058
|
-
readonly depth: number;
|
|
3059
|
-
}, "_tag">;
|
|
3060
|
-
readonly Error: Data.TaggedEnum.ConstructorFrom<{
|
|
3061
|
-
readonly _tag: "Error";
|
|
3062
|
-
readonly diagnostic: MarkdownDiagnostic;
|
|
3063
|
-
readonly path: MarkdownPath;
|
|
3064
|
-
readonly depth: number;
|
|
3065
|
-
}, "_tag">;
|
|
3066
|
-
readonly Exit: Data.TaggedEnum.ConstructorFrom<{
|
|
3067
|
-
readonly _tag: "Exit";
|
|
3068
|
-
readonly node: MarkdownNode;
|
|
3069
|
-
readonly path: MarkdownPath;
|
|
3070
|
-
readonly depth: number;
|
|
3071
|
-
}, "_tag">;
|
|
3072
|
-
};
|
|
3065
|
+
export declare const MarkdownVisitorEvent: Data.TaggedEnum.Constructor<{
|
|
3066
|
+
readonly _tag: "Enter";
|
|
3067
|
+
readonly node: MarkdownNode;
|
|
3068
|
+
readonly path: MarkdownPath;
|
|
3069
|
+
readonly depth: number;
|
|
3070
|
+
} | {
|
|
3071
|
+
readonly _tag: "Error";
|
|
3072
|
+
readonly diagnostic: MarkdownDiagnostic;
|
|
3073
|
+
readonly path: MarkdownPath;
|
|
3074
|
+
readonly depth: number;
|
|
3075
|
+
} | {
|
|
3076
|
+
readonly _tag: "Exit";
|
|
3077
|
+
readonly node: MarkdownNode;
|
|
3078
|
+
readonly path: MarkdownPath;
|
|
3079
|
+
readonly depth: number;
|
|
3080
|
+
}>;
|
|
3073
3081
|
/**
|
|
3074
3082
|
* Walks a parsed markdown tree as a lazy sequence of `Enter`/`Exit` events in
|
|
3075
3083
|
* document pre-order — synchronously with {@link MarkdownVisitor.walk}, or as
|
|
@@ -3395,5 +3403,5 @@ export declare const TomlFrontmatter: FrontmatterCodec;
|
|
|
3395
3403
|
* @public
|
|
3396
3404
|
*/
|
|
3397
3405
|
export declare const YamlFrontmatter: FrontmatterCodec;
|
|
3398
|
-
export type
|
|
3406
|
+
export { type DocumentHeading, type DocumentLink, type FrontmatterCodec, type FrontmatterResolveError, type FrontmatterSchemaError, type FrontmatterSchemaResolver, type FrontmatterWriteError, type LineBreaks, LineIndex, type LineIndexOptions, type LinkBearingNode, type MarkdownNodeOfType, type MarkdownNodeType, type MarkdownPath, type MarkdownPhrasingOnlyType, type MarkdownRangeLike, type MarkdownSegment, type MarkdownStringifyOptions, type MarkdownTransformHandlers, type MarkdownTransformResult, type MdastNode, type MdastToTextOptions, type SectionHeadingMatch, type SectionQueryOptions, type SectionSpan, type SectionSpanOptions, type ShiftHeadingsOptions, type SourceDiagnostic, type SourceIndex, type SourceLocation, type SourcePosition, type SourceRange, type SourceSpan };
|
|
3399
3407
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -5,13 +5,14 @@ import { SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, Sc
|
|
|
5
5
|
import { FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit } from "./FrontmatterSource.js";
|
|
6
6
|
import { JsonFrontmatter } from "./JsonFrontmatter.js";
|
|
7
7
|
import { MarkdownDiagnostic, MarkdownParseErrorCode } from "./MarkdownDiagnostic.js";
|
|
8
|
+
import { MarkdownTransform } from "./MarkdownTransform.js";
|
|
8
9
|
import { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError } from "./Markdown.js";
|
|
9
10
|
import { DocumentSection, MarkdownDocument } from "./MarkdownDocument.js";
|
|
10
11
|
import { CodeBlockStyle, MarkdownFormat, MarkdownFormattingOptions, MarkdownModificationError, MarkdownModificationErrorCode } from "./MarkdownFormat.js";
|
|
11
|
-
import { MarkdownTransform } from "./MarkdownTransform.js";
|
|
12
12
|
import { MarkdownVisitor, MarkdownVisitorEvent } from "./MarkdownVisitor.js";
|
|
13
13
|
import { Mdast, MdastDecodeError } from "./Mdast.js";
|
|
14
14
|
import { TomlFrontmatter } from "./TomlFrontmatter.js";
|
|
15
15
|
import { YamlFrontmatter } from "./YamlFrontmatter.js";
|
|
16
|
+
import { LineIndex } from "@effected/source";
|
|
16
17
|
|
|
17
|
-
export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownTransform, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
|
|
18
|
+
export { Blockquote, Break, BreakStyle, BulletChar, Code, CodeBlockStyle, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterMissingReason, FrontmatterNewline, FrontmatterSource, FrontmatterSourceBlock, FrontmatterSourceSplit, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, LineIndex, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownTransform, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, MdxFlowExpression, MdxJsxAttribute, MdxJsxAttributeContent, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
|
package/internal/blockParser.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { Frontmatter,
|
|
1
|
+
import { Frontmatter, Position, Root } from "../MarkdownNode.js";
|
|
2
2
|
import { scanFrontmatter } from "./blocks/frontmatter.js";
|
|
3
3
|
import { makeBlockNode } from "./blockTypes.js";
|
|
4
4
|
import { columnsToNextTabStop, preprocessLines } from "./preprocess.js";
|
|
5
5
|
import { isHtmlBlockEnd } from "./blocks/htmlBlock.js";
|
|
6
6
|
import { blockDialect } from "./blockRegistry.js";
|
|
7
7
|
import { GuardExceeded } from "./carriers.js";
|
|
8
|
-
import {
|
|
8
|
+
import { pointAt } from "./point.js";
|
|
9
9
|
import { prepareInline } from "./rawInline.js";
|
|
10
|
+
import { LineIndex } from "@effected/source";
|
|
10
11
|
|
|
11
12
|
// src/internal/blockParser.ts
|
|
12
13
|
/** Upstream's cheap pre-filter: a line that cannot start any block. */
|
|
@@ -42,9 +43,7 @@ var BlockParser = class {
|
|
|
42
43
|
constructor(text, dialect, dialectName, frontmatter = false) {
|
|
43
44
|
this.lines = preprocessLines(text);
|
|
44
45
|
this.frontmatterCapture = frontmatter ? scanFrontmatter(this.lines, text) : null;
|
|
45
|
-
|
|
46
|
-
if (text.endsWith("\n")) starts.push(text.length);
|
|
47
|
-
this.lineIndex = LineIndex.fromLineStarts(text, starts);
|
|
46
|
+
this.lineIndex = LineIndex.make(text);
|
|
48
47
|
this.sourceLength = text.length;
|
|
49
48
|
this.dialect = dialect;
|
|
50
49
|
this.dialectName = dialectName;
|
|
@@ -306,19 +305,9 @@ var BlockParser = class {
|
|
|
306
305
|
position(startOffset, endOffset) {
|
|
307
306
|
const start = Math.min(Math.max(startOffset, 0), this.sourceLength);
|
|
308
307
|
const end = Math.min(Math.max(endOffset, start), this.sourceLength);
|
|
309
|
-
const startPoint = this.lineIndex.positionAt(start);
|
|
310
|
-
const endPoint = this.lineIndex.positionAt(end);
|
|
311
308
|
return Position.make({
|
|
312
|
-
start:
|
|
313
|
-
|
|
314
|
-
column: startPoint.column,
|
|
315
|
-
offset: start
|
|
316
|
-
}),
|
|
317
|
-
end: Point.make({
|
|
318
|
-
line: endPoint.line,
|
|
319
|
-
column: endPoint.column,
|
|
320
|
-
offset: end
|
|
321
|
-
})
|
|
309
|
+
start: pointAt(this.lineIndex, start),
|
|
310
|
+
end: pointAt(this.lineIndex, end)
|
|
322
311
|
});
|
|
323
312
|
}
|
|
324
313
|
/**
|
|
@@ -402,7 +391,8 @@ var BlockParser = class {
|
|
|
402
391
|
rawInlines,
|
|
403
392
|
carriers: [],
|
|
404
393
|
refmap,
|
|
405
|
-
footnoteLabels
|
|
394
|
+
footnoteLabels,
|
|
395
|
+
lineIndex: this.lineIndex
|
|
406
396
|
};
|
|
407
397
|
}
|
|
408
398
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/internal/fidelity.ts
|
|
2
|
+
/** Node type to the fidelity keys that node may carry. */
|
|
3
|
+
const FIDELITY_FIELDS = {
|
|
4
|
+
heading: ["headingStyle"],
|
|
5
|
+
emphasis: ["markerChar"],
|
|
6
|
+
strong: ["markerChar"],
|
|
7
|
+
thematicBreak: ["markerChar"],
|
|
8
|
+
code: ["fenceChar", "fenceLength"],
|
|
9
|
+
list: ["bulletChar", "delimiter"],
|
|
10
|
+
break: ["breakStyle"]
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Return `node` without its fidelity keys; the same instance when it carries
|
|
14
|
+
* none, so structural sharing holds.
|
|
15
|
+
*/
|
|
16
|
+
const clearFidelity = (node) => {
|
|
17
|
+
const keys = FIDELITY_FIELDS[node.type];
|
|
18
|
+
if (keys === void 0 || !keys.some((key) => key in node)) return node;
|
|
19
|
+
const rest = { ...node };
|
|
20
|
+
for (const key of keys) delete rest[key];
|
|
21
|
+
const ctor = node.constructor;
|
|
22
|
+
return new ctor(rest, { disableChecks: true });
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export { FIDELITY_FIELDS, clearFidelity };
|
package/internal/phrasing.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Position } from "../MarkdownNode.js";
|
|
2
2
|
import { preprocessLines } from "./preprocess.js";
|
|
3
|
-
import {
|
|
3
|
+
import { pointAt } from "./point.js";
|
|
4
4
|
import { parseInlines } from "./inlineParser.js";
|
|
5
5
|
import { trimWithSegments } from "./rawInline.js";
|
|
6
|
+
import { LineIndex } from "@effected/source";
|
|
6
7
|
|
|
7
8
|
// src/internal/phrasing.ts
|
|
8
9
|
const EMPTY_REFMAP = /* @__PURE__ */ new Map();
|
|
@@ -26,24 +27,14 @@ const parsePhrasingText = (text, dialect) => {
|
|
|
26
27
|
if (trimmed.text.length === 0) return [];
|
|
27
28
|
const first = trimmed.segments[0];
|
|
28
29
|
const startOffset = first === void 0 ? 0 : first.sourceOffset;
|
|
29
|
-
const lineIndex = LineIndex.
|
|
30
|
+
const lineIndex = LineIndex.make(text);
|
|
30
31
|
const sourceLength = text.length;
|
|
31
32
|
const positionOf = (start, end) => {
|
|
32
33
|
const clampedStart = Math.min(Math.max(start, 0), sourceLength);
|
|
33
34
|
const clampedEnd = Math.min(Math.max(end, clampedStart), sourceLength);
|
|
34
|
-
const startPoint = lineIndex.positionAt(clampedStart);
|
|
35
|
-
const endPoint = lineIndex.positionAt(clampedEnd);
|
|
36
35
|
return Position.make({
|
|
37
|
-
start:
|
|
38
|
-
|
|
39
|
-
column: startPoint.column,
|
|
40
|
-
offset: clampedStart
|
|
41
|
-
}),
|
|
42
|
-
end: Point.make({
|
|
43
|
-
line: endPoint.line,
|
|
44
|
-
column: endPoint.column,
|
|
45
|
-
offset: clampedEnd
|
|
46
|
-
})
|
|
36
|
+
start: pointAt(lineIndex, clampedStart),
|
|
37
|
+
end: pointAt(lineIndex, clampedEnd)
|
|
47
38
|
});
|
|
48
39
|
};
|
|
49
40
|
return parseInlines({
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Point } from "../MarkdownNode.js";
|
|
2
|
+
|
|
3
|
+
// src/internal/point.ts
|
|
4
|
+
/**
|
|
5
|
+
* The unist `Point` of `offset` within `index`'s text: one-based `line` and
|
|
6
|
+
* `column`, plus the offset itself.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The caller clamps `offset` into `[0, text.length]` first, so `offset` is
|
|
10
|
+
* always the point's own offset.
|
|
11
|
+
*
|
|
12
|
+
* The column is `offset - lineStart(line) + 1`, NOT `positionAt(offset)`
|
|
13
|
+
* plus one. The two differ at exactly one offset class, the `\n` of a
|
|
14
|
+
* `\r\n`: `positionAt` reads it as the end of its line (LSP cannot name the
|
|
15
|
+
* middle of a terminator), while a unist Point counts it as one column past
|
|
16
|
+
* the `\r`, as `vfile-location` and `mdast-util-from-markdown` do. Node
|
|
17
|
+
* positions DO land there: an empty list item before a CRLF ends at its `\n`
|
|
18
|
+
* (`"*\r\na"` ends the item at offset 2, line 1, column 3), so the
|
|
19
|
+
* `positionAt` form would change published positions.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
const pointAt = (index, offset) => {
|
|
24
|
+
const line = index.lineAt(offset);
|
|
25
|
+
return Point.make({
|
|
26
|
+
line: line + 1,
|
|
27
|
+
column: offset - index.lineStart(line) + 1,
|
|
28
|
+
offset
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { pointAt };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { GuardExceeded } from "./carriers.js";
|
|
2
|
+
|
|
3
|
+
// src/internal/treeDepth.ts
|
|
4
|
+
/**
|
|
5
|
+
* Throws {@link GuardExceeded} when `node` nests deeper than `MAX_NESTING_DEPTH`;
|
|
6
|
+
* returns otherwise. Never recurses, so it is safe on a tree of any depth.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
const assertWithinNestingDepth = (node) => {
|
|
11
|
+
const stack = [[node, 0]];
|
|
12
|
+
while (stack.length > 0) {
|
|
13
|
+
const [current, depth] = stack.pop();
|
|
14
|
+
if (current.type !== "root" && depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, current.position.start.offset);
|
|
15
|
+
if ("children" in current) for (const child of current.children) stack.push([child, depth + 1]);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export { assertWithinNestingDepth };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/markdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CommonMark and GFM markdown parse, edit and transform schemas for Effect",
|
|
6
6
|
"keywords": [
|
|
@@ -38,10 +38,13 @@
|
|
|
38
38
|
},
|
|
39
39
|
"./package.json": "./package.json"
|
|
40
40
|
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@effected/source": "^0.1.0"
|
|
43
|
+
},
|
|
41
44
|
"peerDependencies": {
|
|
42
|
-
"@effected/jsonc": "^0.
|
|
43
|
-
"@effected/toml": "^0.
|
|
44
|
-
"@effected/yaml": "^0.
|
|
45
|
+
"@effected/jsonc": "^0.21.0",
|
|
46
|
+
"@effected/toml": "^0.17.0",
|
|
47
|
+
"@effected/yaml": "^0.26.0",
|
|
45
48
|
"effect": "^4.0.0"
|
|
46
49
|
},
|
|
47
50
|
"peerDependenciesMeta": {
|
package/tsdoc-metadata.json
CHANGED
package/internal/lineIndex.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
// src/internal/lineIndex.ts
|
|
2
|
-
/**
|
|
3
|
-
* A precomputed index of line-start offsets over a fixed source text,
|
|
4
|
-
* answering `positionAt(offset)` in `O(log n)` via binary search rather than
|
|
5
|
-
* rescanning the text per query.
|
|
6
|
-
*
|
|
7
|
-
* @remarks
|
|
8
|
-
* Only `\n` begins a new line. A `\r` immediately before a `\n` (CRLF) is
|
|
9
|
-
* left as the trailing character of the prior line — the pair still reads as
|
|
10
|
-
* a single boundary because the following `\n` is what advances the line
|
|
11
|
-
* counter, not the `\r`. A bare `\r` (old Mac line endings) is not
|
|
12
|
-
* recognized as a break; CommonMark's preprocessing pass normalizes those
|
|
13
|
-
* before this index is ever built.
|
|
14
|
-
*/
|
|
15
|
-
var LineIndex = class LineIndex {
|
|
16
|
-
text;
|
|
17
|
-
lineStarts;
|
|
18
|
-
constructor(text, lineStarts) {
|
|
19
|
-
this.text = text;
|
|
20
|
-
this.lineStarts = lineStarts;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Build a {@link LineIndex} over `text` with one forward scan.
|
|
24
|
-
*
|
|
25
|
-
* @remarks
|
|
26
|
-
* Recognizes only `\n`, which is why the block pass hands over its own
|
|
27
|
-
* line table instead — see {@link LineIndex.fromLineStarts}.
|
|
28
|
-
*/
|
|
29
|
-
static make(text) {
|
|
30
|
-
const lineStarts = [0];
|
|
31
|
-
for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) lineStarts.push(i + 1);
|
|
32
|
-
return new LineIndex(text, lineStarts);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Build a {@link LineIndex} over `text` from a line table someone else
|
|
36
|
-
* already computed.
|
|
37
|
-
*
|
|
38
|
-
* @remarks
|
|
39
|
-
* The parser splits lines on `\r\n`, `\n` AND a bare `\r`; this index's
|
|
40
|
-
* own scan recognizes only `\n`. For any document without a bare `\r` the
|
|
41
|
-
* two agree, but for one with them they disagree about what a line is, and
|
|
42
|
-
* every reported line number would be wrong. Handing the parser's own
|
|
43
|
-
* table over removes the possibility rather than documenting it: there is
|
|
44
|
-
* one definition of a line, and it belongs to the preprocessor.
|
|
45
|
-
*
|
|
46
|
-
* `starts` must be ascending and begin at 0; a table that is neither is a
|
|
47
|
-
* wiring bug and dies as a defect.
|
|
48
|
-
*/
|
|
49
|
-
static fromLineStarts(text, starts) {
|
|
50
|
-
if (starts.length === 0 || starts[0] !== 0) throw new TypeError("line index: a line table must be non-empty and start at offset 0");
|
|
51
|
-
return new LineIndex(text, starts);
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* The 1-based `{ line, column }` of `offset` within this index's text.
|
|
55
|
-
* Out-of-range offsets clamp to the nearest valid position (`0` or
|
|
56
|
-
* `text.length`) rather than throwing.
|
|
57
|
-
*/
|
|
58
|
-
positionAt(offset) {
|
|
59
|
-
const clamped = Math.min(Math.max(offset, 0), this.text.length);
|
|
60
|
-
let low = 0;
|
|
61
|
-
let high = this.lineStarts.length - 1;
|
|
62
|
-
while (low < high) {
|
|
63
|
-
const mid = low + high + 1 >> 1;
|
|
64
|
-
if ((this.lineStarts[mid] ?? 0) <= clamped) low = mid;
|
|
65
|
-
else high = mid - 1;
|
|
66
|
-
}
|
|
67
|
-
const lineStart = this.lineStarts[low] ?? 0;
|
|
68
|
-
return {
|
|
69
|
-
line: low + 1,
|
|
70
|
-
column: clamped - lineStart + 1
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
export { LineIndex };
|