satteri 0.6.3 → 0.8.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/README.md +14 -1
- package/dist/compile.d.ts +87 -6
- package/dist/compile.js +106 -53
- package/dist/hast/hast-visitor.d.ts +15 -3
- package/dist/hast/hast-visitor.js +113 -80
- package/dist/mdast/mdast-visitor.d.ts +12 -3
- package/dist/mdast/mdast-visitor.js +45 -6
- package/index.d.ts +68 -12
- package/index.js +52 -52
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -163,6 +163,14 @@ heading(node) {
|
|
|
163
163
|
}
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
+
### How transforms compose
|
|
167
|
+
|
|
168
|
+
Unlike remark and rehype, which re-walk the tree until it stops changing, each Sätteri plugin walks the tree **once**. Within that single pass:
|
|
169
|
+
|
|
170
|
+
- **Passed-through children keep their identity.** When a visitor returns a replacement that reuses the original node's children (e.g. `{ ...node, children: [...node.children] }`), those children are spliced back unchanged — so a transform the same pass queues on a nested one still applies. This is what lets a `containerDirective` visitor turn both an outer `:::note` and a nested `:::tip` into asides in one go.
|
|
171
|
+
- **A plugin's own freshly-built nodes are not re-walked by that plugin.** If a visitor returns a brand-new node (one that didn't come from the tree), the same plugin won't visit it. Produce its final shape directly, or hand it off to a later plugin — every plugin runs over the fully materialized output of the ones before it.
|
|
172
|
+
- **Dropping a subtree drops transforms queued inside it.** If one visitor removes or replaces a node while another (in the same pass) had queued a transform on something inside that subtree, the orphaned transform is dropped and a warning is logged. This is usually intentional — you discarded that subtree on purpose — but the warning helps catch the cases where it isn't.
|
|
173
|
+
|
|
166
174
|
### Async plugins
|
|
167
175
|
|
|
168
176
|
Visitors can optionally be async. When any visitor is async, `markdownToHtml` and `mdxToJs` return a `Promise<string>` instead of `string`. For performance reasons, it is typically best to avoid async visitors, especially if your visitor matches a large number of nodes.
|
|
@@ -289,7 +297,8 @@ Type-safe wrapper for HAST plugin definitions.
|
|
|
289
297
|
interface CompileOptions {
|
|
290
298
|
mdastPlugins?: MdastPluginDefinition[];
|
|
291
299
|
hastPlugins?: HastPluginDefinition[];
|
|
292
|
-
|
|
300
|
+
features?: Features;
|
|
301
|
+
fileURL?: URL;
|
|
293
302
|
}
|
|
294
303
|
|
|
295
304
|
// mdxToJs accepts MdxCompileOptions, which extends CompileOptions
|
|
@@ -298,6 +307,10 @@ interface MdxCompileOptions extends CompileOptions {
|
|
|
298
307
|
}
|
|
299
308
|
```
|
|
300
309
|
|
|
310
|
+
`fileURL` is the `URL` of the document being processed, surfaced to plugins as `ctx.fileURL` (a `URL`, or `undefined` when omitted). Pass a file URL such as Astro's `fileURL`, or convert a filesystem path with Node's `pathToFileURL`; read it back with `fileURLToPath(ctx.fileURL)`.
|
|
311
|
+
|
|
312
|
+
`Features` controls the Markdown extensions Sätteri's parser recognizes: `gfm`, `frontmatter`, `math`, `directive`, `smartPunctuation`, etc. `gfm`, `math`, and `smartPunctuation` also accept granular options. For example, `math: { singleDollarTextMath: false }` keeps single `$` as literal text. See the [Features reference](https://satteri.dev/docs/features/) for the full list.
|
|
313
|
+
|
|
301
314
|
## License
|
|
302
315
|
|
|
303
316
|
MIT
|
package/dist/compile.d.ts
CHANGED
|
@@ -16,15 +16,91 @@ export interface SmartPunctuationOptions {
|
|
|
16
16
|
/** Replace `...` with ellipsis (`…`). Default: true. */
|
|
17
17
|
ellipses?: boolean;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Per-backref callback. Invoked once per anchor in the footnotes section
|
|
21
|
+
* with 1-based `referenceNumber` and `rerunIndex` (1 on the first backref
|
|
22
|
+
* to that definition, 2 on the second, and so on). Must return the final
|
|
23
|
+
* string used as the backref content or `aria-label`.
|
|
24
|
+
*/
|
|
25
|
+
export type FootnoteBackrefCallback = (referenceNumber: number, rerunIndex: number) => string;
|
|
26
|
+
/**
|
|
27
|
+
* i18n strings for the GFM footnotes section. Mirrors `footnoteLabel`,
|
|
28
|
+
* `footnoteBackLabel`, and `footnoteBackContent` from remark-rehype.
|
|
29
|
+
*
|
|
30
|
+
* `backContent` and `backLabel` each accept either a template string with
|
|
31
|
+
* the `{reference}` placeholder (substituted with `1` or `1-2` to match
|
|
32
|
+
* remark-rehype's default suffix), or a callback receiving the raw
|
|
33
|
+
* `(referenceNumber, rerunIndex)` pair.
|
|
34
|
+
*
|
|
35
|
+
* Passing this object enables footnotes. To turn them off, use
|
|
36
|
+
* `gfm: { footnotes: false }`.
|
|
37
|
+
*/
|
|
38
|
+
export interface FootnoteOptions {
|
|
39
|
+
/** `<h2>` label opening the footnotes section. Default: `"Footnotes"`. */
|
|
40
|
+
label?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Backref `<a>` content. Default: `"↩"`.
|
|
43
|
+
*
|
|
44
|
+
* Template form: the string is used as-is, and a `<sup>K</sup>` marker
|
|
45
|
+
* is auto-appended on reruns (k > 1). Callback form: returns the full
|
|
46
|
+
* content for each backref; no auto-sup is added.
|
|
47
|
+
*/
|
|
48
|
+
backContent?: string | FootnoteBackrefCallback;
|
|
49
|
+
/**
|
|
50
|
+
* Backref `aria-label`. Default: `"Back to reference {reference}"`.
|
|
51
|
+
*
|
|
52
|
+
* Template form: `{reference}` becomes `n` for the first backref, `n-K`
|
|
53
|
+
* for subsequent ones. Callback form: returns the `aria-label` string
|
|
54
|
+
* for each backref.
|
|
55
|
+
*/
|
|
56
|
+
backLabel?: string | FootnoteBackrefCallback;
|
|
57
|
+
}
|
|
58
|
+
/** Granular GFM toggles, nested under {@link Features.gfm}. */
|
|
59
|
+
export interface GfmOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Enable GFM footnotes (`[^id]`). Default: true.
|
|
62
|
+
*
|
|
63
|
+
* Pass `false` to drop footnote parsing while keeping the rest of GFM;
|
|
64
|
+
* pass an object to enable footnotes with custom i18n strings (see
|
|
65
|
+
* {@link FootnoteOptions}).
|
|
66
|
+
*/
|
|
67
|
+
footnotes?: boolean | FootnoteOptions;
|
|
68
|
+
}
|
|
69
|
+
/** Granular math toggles, nested under {@link Features.math}. */
|
|
70
|
+
export interface MathOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Treat single-dollar runs (`$ ... $`) as inline math. Default: true.
|
|
73
|
+
*
|
|
74
|
+
* Set `false` to keep single `$` as literal text while still parsing
|
|
75
|
+
* `$$ ... $$` display math. Mirrors `singleDollarTextMath` from
|
|
76
|
+
* remark-math.
|
|
77
|
+
*/
|
|
78
|
+
singleDollarTextMath?: boolean;
|
|
79
|
+
}
|
|
19
80
|
/** Parser feature toggles. All default to their documented value when omitted. */
|
|
20
81
|
export interface Features {
|
|
21
|
-
/**
|
|
22
|
-
|
|
82
|
+
/**
|
|
83
|
+
* GFM: tables, footnotes, strikethrough, task lists. Default: true.
|
|
84
|
+
*
|
|
85
|
+
* Pass an options object for granular control:
|
|
86
|
+
* ```ts
|
|
87
|
+
* gfm: { footnotes: false } // skip footnotes only
|
|
88
|
+
* gfm: { footnotes: { label: "Notes" } } // localize footnotes
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
gfm?: boolean | GfmOptions;
|
|
23
92
|
/** Frontmatter: YAML (`--- ... ---`) and TOML (`+++ ... +++`). Default: true. */
|
|
24
93
|
frontmatter?: boolean;
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Math blocks and inline math. Default: false.
|
|
96
|
+
*
|
|
97
|
+
* Pass an options object for granular control:
|
|
98
|
+
* ```ts
|
|
99
|
+
* math: { singleDollarTextMath: false } // $$..$$ only, $..$ literal
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
math?: boolean | MathOptions;
|
|
103
|
+
/** Heading attributes (`# text { #id .class }`). Default: false. */
|
|
28
104
|
headingAttributes?: boolean;
|
|
29
105
|
/** Colon-delimited container directive blocks (`:::`). Default: false. */
|
|
30
106
|
directive?: boolean;
|
|
@@ -48,7 +124,12 @@ export interface CompileOptions {
|
|
|
48
124
|
mdastPlugins?: MdastPluginInput[];
|
|
49
125
|
hastPlugins?: HastPluginInput[];
|
|
50
126
|
features?: Features;
|
|
51
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The document being processed, surfaced to plugins as `ctx.fileURL`. Must
|
|
129
|
+
* be a `URL` (e.g. Astro's `fileURL`); convert a filesystem path with Node's
|
|
130
|
+
* `pathToFileURL` before passing it.
|
|
131
|
+
*/
|
|
132
|
+
fileURL?: URL;
|
|
52
133
|
}
|
|
53
134
|
/**
|
|
54
135
|
* MDX-only compile options.
|
package/dist/compile.js
CHANGED
|
@@ -5,16 +5,55 @@ import { MdastReader } from "./mdast/mdast-reader.js";
|
|
|
5
5
|
import { materializeMdastTree } from "./mdast/mdast-materializer.js";
|
|
6
6
|
import { HastReader } from "./hast/hast-reader.js";
|
|
7
7
|
import { materializeHastTree } from "./hast/hast-materializer.js";
|
|
8
|
+
/**
|
|
9
|
+
* Split the user-facing `Features` (with nested unions) into the flat napi
|
|
10
|
+
* `JsFeatures` shape plus the conversion-side `JsConvertOptions` carrying
|
|
11
|
+
* the footnote i18n strings. The public API only exposes `features`; the
|
|
12
|
+
* footnote strings are routed to napi internally.
|
|
13
|
+
*/
|
|
8
14
|
function featuresToNative(features) {
|
|
9
15
|
if (!features)
|
|
10
|
-
return undefined;
|
|
16
|
+
return { features: undefined, convertOptions: undefined };
|
|
11
17
|
const result = {};
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
let convertOptions;
|
|
19
|
+
if (features.gfm !== undefined) {
|
|
20
|
+
if (typeof features.gfm === "object") {
|
|
21
|
+
const g = features.gfm;
|
|
22
|
+
const gfmOpts = {};
|
|
23
|
+
if (g.footnotes !== undefined) {
|
|
24
|
+
if (typeof g.footnotes === "object") {
|
|
25
|
+
gfmOpts.footnotes = true;
|
|
26
|
+
convertOptions = convertOptions ?? {};
|
|
27
|
+
if (g.footnotes.label !== undefined)
|
|
28
|
+
convertOptions.footnoteLabel = g.footnotes.label;
|
|
29
|
+
if (g.footnotes.backContent !== undefined)
|
|
30
|
+
convertOptions.footnoteBackContent = g.footnotes.backContent;
|
|
31
|
+
if (g.footnotes.backLabel !== undefined)
|
|
32
|
+
convertOptions.footnoteBackLabel = g.footnotes.backLabel;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
gfmOpts.footnotes = g.footnotes;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
result.gfmOptions = gfmOpts;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
result.gfm = features.gfm;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
14
44
|
if (features.frontmatter !== undefined)
|
|
15
45
|
result.frontmatter = features.frontmatter;
|
|
16
|
-
if (features.math !== undefined)
|
|
17
|
-
|
|
46
|
+
if (features.math !== undefined) {
|
|
47
|
+
if (typeof features.math === "object") {
|
|
48
|
+
const mathOpts = {};
|
|
49
|
+
if (features.math.singleDollarTextMath !== undefined)
|
|
50
|
+
mathOpts.singleDollarTextMath = features.math.singleDollarTextMath;
|
|
51
|
+
result.mathOptions = mathOpts;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
result.math = features.math;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
18
57
|
if (features.headingAttributes !== undefined)
|
|
19
58
|
result.headingAttributes = features.headingAttributes;
|
|
20
59
|
if (features.directive !== undefined)
|
|
@@ -33,41 +72,46 @@ function featuresToNative(features) {
|
|
|
33
72
|
result.smartPunctuation = features.smartPunctuation;
|
|
34
73
|
}
|
|
35
74
|
}
|
|
36
|
-
return result;
|
|
75
|
+
return { features: result, convertOptions };
|
|
76
|
+
}
|
|
77
|
+
function warnDroppedTransforms(plugin, dropped, kind) {
|
|
78
|
+
const name = plugin.name ?? "<anonymous>";
|
|
79
|
+
const noun = dropped === 1 ? "transform" : "transforms";
|
|
80
|
+
console.warn(`satteri: plugin "${name}" queued ${dropped} ${kind} ${noun} on node(s) that were removed or ` +
|
|
81
|
+
`replaced earlier in the same pass; ${dropped === 1 ? "it was" : "they were"} dropped.`);
|
|
37
82
|
}
|
|
38
|
-
function runMdastPluginsOnHandle(handle, plugins,
|
|
39
|
-
|
|
83
|
+
function runMdastPluginsOnHandle(handle, plugins, fileURL) {
|
|
84
|
+
// Each plugin runs once over the tree. A transform that passes a child
|
|
85
|
+
// through (returning it inside the replacement) keeps that child's identity,
|
|
86
|
+
// so a patch the same pass queued on it still applies — nesting composes in
|
|
87
|
+
// one pass. A plugin's own freshly-built nodes are not re-walked; transform
|
|
88
|
+
// them up front, or hand off to a later plugin that sees the materialized tree.
|
|
89
|
+
const runPlugin = (plugin) => {
|
|
90
|
+
const subs = resolveMdastSubscriptions(plugin);
|
|
91
|
+
const result = visitMdastHandle(handle, plugin, subs, () => getHandleSource(handle), fileURL);
|
|
92
|
+
const apply = (r) => {
|
|
93
|
+
if (!r.hasMutations)
|
|
94
|
+
return;
|
|
95
|
+
const dropped = applyCommandsToMdastHandle(handle, r.commandBuffer);
|
|
96
|
+
if (dropped)
|
|
97
|
+
warnDroppedTransforms(plugin, dropped, "mdast");
|
|
98
|
+
};
|
|
99
|
+
return result instanceof Promise ? result.then(apply) : apply(result);
|
|
100
|
+
};
|
|
40
101
|
let i = 0;
|
|
41
102
|
const runNext = () => {
|
|
42
103
|
while (i < plugins.length) {
|
|
43
|
-
const
|
|
44
|
-
const raw = plugins[idx];
|
|
104
|
+
const raw = plugins[i++];
|
|
45
105
|
const plugin = typeof raw === "function" ? raw() : raw;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return result.then((r) => {
|
|
50
|
-
applyMdastResult(r, idx, plugins.length, handle);
|
|
51
|
-
return runNext();
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
applyMdastResult(result, idx, plugins.length, handle);
|
|
106
|
+
const r = runPlugin(plugin);
|
|
107
|
+
if (r instanceof Promise)
|
|
108
|
+
return r.then(runNext);
|
|
55
109
|
}
|
|
56
|
-
return { handle
|
|
110
|
+
return { handle };
|
|
57
111
|
};
|
|
58
|
-
function applyMdastResult(result, idx, total, h) {
|
|
59
|
-
if (result.hasMutations) {
|
|
60
|
-
if (idx === total - 1) {
|
|
61
|
-
pendingCommands = result.commandBuffer;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
applyCommandsToMdastHandle(h, result.commandBuffer);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
112
|
return runNext();
|
|
69
113
|
}
|
|
70
|
-
function runHastPluginsOnHandle(handle, plugins, source,
|
|
114
|
+
function runHastPluginsOnHandle(handle, plugins, source, fileURL) {
|
|
71
115
|
if (plugins.length === 0)
|
|
72
116
|
return;
|
|
73
117
|
let i = 0;
|
|
@@ -77,10 +121,18 @@ function runHastPluginsOnHandle(handle, plugins, source, filename) {
|
|
|
77
121
|
i++;
|
|
78
122
|
const plugin = typeof raw === "function" ? raw() : raw;
|
|
79
123
|
const subs = resolveSubscriptions(plugin);
|
|
80
|
-
const result = visitHastHandle(handle, plugin, subs, source,
|
|
124
|
+
const result = visitHastHandle(handle, plugin, subs, source, fileURL);
|
|
125
|
+
const warnIfDropped = (dropped) => {
|
|
126
|
+
if (dropped)
|
|
127
|
+
warnDroppedTransforms(plugin, dropped, "hast");
|
|
128
|
+
};
|
|
81
129
|
if (result instanceof Promise) {
|
|
82
|
-
return result.then(
|
|
130
|
+
return result.then((dropped) => {
|
|
131
|
+
warnIfDropped(dropped);
|
|
132
|
+
return runNext();
|
|
133
|
+
});
|
|
83
134
|
}
|
|
135
|
+
warnIfDropped(result);
|
|
84
136
|
}
|
|
85
137
|
};
|
|
86
138
|
return runNext();
|
|
@@ -129,9 +181,9 @@ function mdxOptionsToNative(opts) {
|
|
|
129
181
|
return result;
|
|
130
182
|
}
|
|
131
183
|
export function markdownToHtml(source, options = {}) {
|
|
132
|
-
const { mdastPlugins = [], hastPlugins = [], features,
|
|
133
|
-
const nativeFeatures = featuresToNative(features);
|
|
134
|
-
const result = createHastHandleFromMdast(source, mdastPlugins, false,
|
|
184
|
+
const { mdastPlugins = [], hastPlugins = [], features, fileURL } = options;
|
|
185
|
+
const { features: nativeFeatures, convertOptions: nativeConvertOptions } = featuresToNative(features);
|
|
186
|
+
const result = createHastHandleFromMdast(source, mdastPlugins, false, fileURL, nativeFeatures, nativeConvertOptions);
|
|
135
187
|
const renderAndDrop = (h, frontmatter) => {
|
|
136
188
|
try {
|
|
137
189
|
const html = renderHandle(h);
|
|
@@ -144,7 +196,7 @@ export function markdownToHtml(source, options = {}) {
|
|
|
144
196
|
const runHastThenRender = (r) => {
|
|
145
197
|
let hastResult;
|
|
146
198
|
try {
|
|
147
|
-
hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source,
|
|
199
|
+
hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source, fileURL);
|
|
148
200
|
}
|
|
149
201
|
catch (err) {
|
|
150
202
|
dropHandle(r.hastHandle);
|
|
@@ -163,10 +215,10 @@ export function markdownToHtml(source, options = {}) {
|
|
|
163
215
|
return runHastThenRender(result);
|
|
164
216
|
}
|
|
165
217
|
export function mdxToJs(source, options = {}) {
|
|
166
|
-
const { mdastPlugins = [], hastPlugins = [], features,
|
|
218
|
+
const { mdastPlugins = [], hastPlugins = [], features, fileURL, ...mdxFields } = options;
|
|
167
219
|
const mdxOptions = mdxOptionsToNative(mdxFields);
|
|
168
|
-
const nativeFeatures = featuresToNative(features);
|
|
169
|
-
const result = createHastHandleFromMdast(source, mdastPlugins, true,
|
|
220
|
+
const { features: nativeFeatures, convertOptions: nativeConvertOptions } = featuresToNative(features);
|
|
221
|
+
const result = createHastHandleFromMdast(source, mdastPlugins, true, fileURL, nativeFeatures, nativeConvertOptions);
|
|
170
222
|
const compileAndDrop = (h, frontmatter) => {
|
|
171
223
|
try {
|
|
172
224
|
const code = compileHandle(h, mdxOptions);
|
|
@@ -179,7 +231,7 @@ export function mdxToJs(source, options = {}) {
|
|
|
179
231
|
const runHastThenCompile = (r) => {
|
|
180
232
|
let hastResult;
|
|
181
233
|
try {
|
|
182
|
-
hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source,
|
|
234
|
+
hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source, fileURL);
|
|
183
235
|
}
|
|
184
236
|
catch (err) {
|
|
185
237
|
dropHandle(r.hastHandle);
|
|
@@ -224,9 +276,11 @@ function readFrontmatter(handle) {
|
|
|
224
276
|
/** Parse, run mdast plugins, capture frontmatter, then convert to HAST.
|
|
225
277
|
* Frontmatter is read from the post-plugin MDAST so visitor mutations to
|
|
226
278
|
* the yaml/toml node are reflected in the returned value. */
|
|
227
|
-
function createHastHandleFromMdast(source, mdastPlugins, mdx,
|
|
279
|
+
function createHastHandleFromMdast(source, mdastPlugins, mdx, fileURL,
|
|
280
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
281
|
+
nativeFeatures,
|
|
228
282
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
229
|
-
|
|
283
|
+
nativeConvertOptions) {
|
|
230
284
|
const mdastHandle = mdx
|
|
231
285
|
? createMdxMdastHandle(source, nativeFeatures)
|
|
232
286
|
: createMdastHandle(source, nativeFeatures);
|
|
@@ -234,11 +288,8 @@ nativeFeatures) {
|
|
|
234
288
|
// on success, but if any step here throws the handle would otherwise leak.
|
|
235
289
|
const finalize = (r) => {
|
|
236
290
|
try {
|
|
237
|
-
if (r.pendingCommands) {
|
|
238
|
-
applyCommandsToMdastHandle(r.handle, r.pendingCommands);
|
|
239
|
-
}
|
|
240
291
|
const frontmatter = readFrontmatter(r.handle);
|
|
241
|
-
const hastHandle = convertMdastToHastHandle(r.handle);
|
|
292
|
+
const hastHandle = convertMdastToHastHandle(r.handle, nativeConvertOptions);
|
|
242
293
|
return { hastHandle, frontmatter };
|
|
243
294
|
}
|
|
244
295
|
finally {
|
|
@@ -247,9 +298,9 @@ nativeFeatures) {
|
|
|
247
298
|
};
|
|
248
299
|
try {
|
|
249
300
|
if (mdastPlugins.length === 0) {
|
|
250
|
-
return finalize({ handle: mdastHandle
|
|
301
|
+
return finalize({ handle: mdastHandle });
|
|
251
302
|
}
|
|
252
|
-
const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins,
|
|
303
|
+
const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, fileURL);
|
|
253
304
|
if (mdastResult instanceof Promise) {
|
|
254
305
|
return mdastResult.then(finalize, (err) => {
|
|
255
306
|
dropHandle(mdastHandle);
|
|
@@ -266,7 +317,7 @@ nativeFeatures) {
|
|
|
266
317
|
// Step-by-step API: individual pipeline stages with materialized trees
|
|
267
318
|
/** Parse Markdown source into a materialized mdast tree. */
|
|
268
319
|
export function markdownToMdast(source, options = {}) {
|
|
269
|
-
const handle = createMdastHandle(source, featuresToNative(options.features));
|
|
320
|
+
const handle = createMdastHandle(source, featuresToNative(options.features).features);
|
|
270
321
|
try {
|
|
271
322
|
return materializeMdastTree(new MdastReader(serializeHandle(handle)));
|
|
272
323
|
}
|
|
@@ -276,7 +327,7 @@ export function markdownToMdast(source, options = {}) {
|
|
|
276
327
|
}
|
|
277
328
|
/** Parse MDX source into a materialized mdast tree. */
|
|
278
329
|
export function mdxToMdast(source, options = {}) {
|
|
279
|
-
const handle = createMdxMdastHandle(source, featuresToNative(options.features));
|
|
330
|
+
const handle = createMdxMdastHandle(source, featuresToNative(options.features).features);
|
|
280
331
|
try {
|
|
281
332
|
return materializeMdastTree(new MdastReader(serializeHandle(handle)));
|
|
282
333
|
}
|
|
@@ -286,7 +337,8 @@ export function mdxToMdast(source, options = {}) {
|
|
|
286
337
|
}
|
|
287
338
|
/** Convert Markdown source to a materialized hast tree. */
|
|
288
339
|
export function markdownToHast(source, options = {}) {
|
|
289
|
-
const
|
|
340
|
+
const { features: nativeFeatures, convertOptions } = featuresToNative(options.features);
|
|
341
|
+
const handle = createHastHandle(source, nativeFeatures, convertOptions);
|
|
290
342
|
try {
|
|
291
343
|
return materializeHastTree(new HastReader(serializeHandle(handle)));
|
|
292
344
|
}
|
|
@@ -296,7 +348,8 @@ export function markdownToHast(source, options = {}) {
|
|
|
296
348
|
}
|
|
297
349
|
/** Convert MDX source to a materialized hast tree. */
|
|
298
350
|
export function mdxToHast(source, options = {}) {
|
|
299
|
-
const
|
|
351
|
+
const { features: nativeFeatures, convertOptions } = featuresToNative(options.features);
|
|
352
|
+
const handle = createMdxHastHandle(source, nativeFeatures, convertOptions);
|
|
300
353
|
try {
|
|
301
354
|
return materializeHastTree(new HastReader(serializeHandle(handle)));
|
|
302
355
|
}
|
|
@@ -15,11 +15,21 @@ export interface HastDiagnostic {
|
|
|
15
15
|
}
|
|
16
16
|
export interface HastVisitorContext {
|
|
17
17
|
readonly source: string;
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The URL of the document being processed (the compile `fileURL` option),
|
|
20
|
+
* or `undefined` when none was given. Use `fileURLToPath(ctx.fileURL)` for a
|
|
21
|
+
* decoded filesystem path.
|
|
22
|
+
*/
|
|
23
|
+
readonly fileURL: URL | undefined;
|
|
19
24
|
removeNode(node: Readonly<HastNode>): void;
|
|
20
25
|
replaceNode(node: Readonly<HastNode>, newNode: HastNode): void;
|
|
21
26
|
insertBefore(node: Readonly<HastNode>, newNode: HastNode): void;
|
|
22
27
|
insertAfter(node: Readonly<HastNode>, newNode: HastNode): void;
|
|
28
|
+
/**
|
|
29
|
+
* Wrap `node` in `parentNode`, making it `parentNode`'s first child. Any
|
|
30
|
+
* children `parentNode` declares are kept after it, so a `div` with an anchor
|
|
31
|
+
* child wraps a heading as `div > [heading, anchor]`.
|
|
32
|
+
*/
|
|
23
33
|
wrapNode(node: Readonly<HastNode>, parentNode: HastNode): void;
|
|
24
34
|
prependChild(node: Readonly<HastNode>, childNode: HastNode): void;
|
|
25
35
|
appendChild(node: Readonly<HastNode>, childNode: HastNode): void;
|
|
@@ -68,7 +78,9 @@ export declare function resolveSubscriptions(plugin: HastVisitorInstance): Resol
|
|
|
68
78
|
* Walk a handle's arena in Rust, dispatch matched nodes to JS visitor functions,
|
|
69
79
|
* and apply mutations back to the handle. No arena buffers cross NAPI.
|
|
70
80
|
*
|
|
71
|
-
* Returns
|
|
81
|
+
* Returns the number of patches dropped because their target was removed or
|
|
82
|
+
* replaced earlier in the same pass (the caller warns when non-zero), or a
|
|
83
|
+
* Promise of that count if any visitor is async.
|
|
72
84
|
*/
|
|
73
|
-
export declare function visitHastHandle(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string),
|
|
85
|
+
export declare function visitHastHandle(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string), fileURL: URL | undefined): number | Promise<number>;
|
|
74
86
|
export {};
|