satteri 0.9.5 → 0.10.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 +11 -1
- package/dist/binding.browser.d.ts +1 -1
- package/dist/binding.browser.js +1 -1
- package/dist/binding.d.ts +1 -1
- package/dist/binding.js +1 -1
- package/dist/byte-writer.d.ts +4 -0
- package/dist/byte-writer.js +13 -4
- package/dist/child-stub.d.ts +7 -3
- package/dist/child-stub.js +18 -7
- package/dist/command-buffer.d.ts +36 -50
- package/dist/command-buffer.js +102 -49
- package/dist/compile.d.ts +80 -32
- package/dist/compile.js +293 -69
- package/dist/freeze.d.ts +2 -0
- package/dist/freeze.js +10 -0
- package/dist/generated/wire-constants.d.ts +3 -2
- package/dist/generated/wire-constants.js +4 -2
- package/dist/hast/child-stub.d.ts +2 -3
- package/dist/hast/child-stub.js +18 -11
- package/dist/hast/element-props.d.ts +1 -1
- package/dist/hast/element-props.js +14 -6
- package/dist/hast/hast-materializer.d.ts +6 -2
- package/dist/hast/hast-materializer.js +48 -134
- package/dist/hast/hast-reader.d.ts +7 -13
- package/dist/hast/hast-reader.js +76 -21
- package/dist/hast/hast-visitor.d.ts +48 -4
- package/dist/hast/hast-visitor.js +318 -154
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1 -1
- package/dist/lazy-child-resolver.d.ts +20 -13
- package/dist/lazy-child-resolver.js +66 -38
- package/dist/materializer-cache.d.ts +42 -0
- package/dist/materializer-cache.js +192 -0
- package/dist/mdast/child-stub.d.ts +6 -3
- package/dist/mdast/child-stub.js +71 -8
- package/dist/mdast/generated/layout.d.ts +2 -3
- package/dist/mdast/generated/layout.js +24 -28
- package/dist/mdast/generated/node-types.d.ts +4 -0
- package/dist/mdast/generated/node-types.js +25 -0
- package/dist/mdast/mdast-materializer.d.ts +12 -3
- package/dist/mdast/mdast-materializer.js +83 -78
- package/dist/mdast/mdast-reader.d.ts +15 -0
- package/dist/mdast/mdast-reader.js +55 -10
- package/dist/mdast/mdast-visitor.d.ts +73 -32
- package/dist/mdast/mdast-visitor.js +298 -122
- package/dist/op-stream.d.ts +5 -12
- package/dist/op-stream.js +6 -21
- package/dist/plugin.d.ts +36 -10
- package/dist/plugin.js +50 -2
- package/dist/string-pool.d.ts +13 -0
- package/dist/string-pool.js +91 -0
- package/dist/types.d.ts +38 -1
- package/dist/visitor-shared.d.ts +27 -4
- package/dist/visitor-shared.js +36 -10
- package/dist/wire-read.js +3 -1
- package/index.d.ts +130 -7
- package/index.js +89 -68
- package/package.json +23 -15
- package/dist/lazy-props.d.ts +0 -10
- package/dist/lazy-props.js +0 -50
package/dist/compile.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { visitHastHandle, resolveSubscriptions } from "./hast/hast-visitor.js";
|
|
2
|
-
import { visitMdastHandle, resolveMdastSubscriptions, } from "./mdast/mdast-visitor.js";
|
|
3
|
-
import {
|
|
1
|
+
import { visitHastHandle, visitHastHandleCollect, visitHastHook, visitHastHookCollect, resolveSubscriptions, } from "./hast/hast-visitor.js";
|
|
2
|
+
import { visitMdastHandle, visitMdastHook, resolveMdastSubscriptions, } from "./mdast/mdast-visitor.js";
|
|
3
|
+
import { normalizePlugins } from "./plugin.js";
|
|
4
|
+
import { applyCommandsAndCompileHandle, applyCommandsAndRenderHandle, applyCommandsToMdastHandle, applyMdastCommandsAndConvertAndCompile, applyMdastCommandsAndConvertAndRender, compileHandle, convertMdastToHastHandle, createHastHandle, createHastHandleFromHtml, createMdastHandle, createMdxHastHandle, createMdxMdastHandle, dropHandle, getHandleSource, createHastHandleWithFrontmatter, createMdxHastHandleWithFrontmatter, getMdastFrontmatter, markdownToHtmlFast, markdownToJsFast, mdxToJsFast, renderHandle, serializeHandle, } from "#binding";
|
|
4
5
|
import { MdastReader } from "./mdast/mdast-reader.js";
|
|
5
6
|
import { materializeMdastTree } from "./mdast/mdast-materializer.js";
|
|
6
7
|
import { markHandleMutated } from "./lazy-child-resolver.js";
|
|
@@ -65,6 +66,8 @@ function featuresToNative(features) {
|
|
|
65
66
|
result.subscript = features.subscript;
|
|
66
67
|
if (features.wikilinks !== undefined)
|
|
67
68
|
result.wikilinks = features.wikilinks;
|
|
69
|
+
if (features.definitionList !== undefined)
|
|
70
|
+
result.definitionList = features.definitionList;
|
|
68
71
|
if (features.smartPunctuation !== undefined) {
|
|
69
72
|
if (typeof features.smartPunctuation === "object") {
|
|
70
73
|
result.smartPunctuationOptions = features.smartPunctuation;
|
|
@@ -73,6 +76,12 @@ function featuresToNative(features) {
|
|
|
73
76
|
result.smartPunctuation = features.smartPunctuation;
|
|
74
77
|
}
|
|
75
78
|
}
|
|
79
|
+
if (features.rawHtml !== undefined) {
|
|
80
|
+
// Routed to convert options so every pipeline (fast paths, plugin paths,
|
|
81
|
+
// *ToHast) applies the reparse.
|
|
82
|
+
convertOptions = convertOptions ?? {};
|
|
83
|
+
convertOptions.rawHtml = features.rawHtml;
|
|
84
|
+
}
|
|
76
85
|
return { features: result, convertOptions };
|
|
77
86
|
}
|
|
78
87
|
/** Free a handle's arena. A plugin's visitor can hand child stubs to user code;
|
|
@@ -86,64 +95,131 @@ function releaseHandle(handle, invalidateStubs) {
|
|
|
86
95
|
function warnDroppedTransforms(plugin, dropped, kind) {
|
|
87
96
|
const name = plugin.name ?? "<anonymous>";
|
|
88
97
|
const noun = dropped === 1 ? "transform" : "transforms";
|
|
89
|
-
console.warn(`satteri: plugin "${name}" queued ${dropped} ${kind} ${noun} on node(s) that
|
|
90
|
-
`
|
|
98
|
+
console.warn(`satteri: plugin "${name}" queued ${dropped} ${kind} ${noun} on node(s) that a plugin had ` +
|
|
99
|
+
`already removed or replaced; ${dropped === 1 ? "it was" : "they were"} dropped.`);
|
|
100
|
+
}
|
|
101
|
+
function settle(value, fn) {
|
|
102
|
+
if (value instanceof Promise)
|
|
103
|
+
return value.then(fn);
|
|
104
|
+
fn(value);
|
|
105
|
+
return undefined;
|
|
91
106
|
}
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
/** Stays synchronous until a step actually returns a promise. */
|
|
108
|
+
function sequence(steps) {
|
|
109
|
+
let pending;
|
|
110
|
+
for (const step of steps) {
|
|
111
|
+
if (step === undefined)
|
|
112
|
+
continue;
|
|
113
|
+
pending = pending === undefined ? step() : pending.then(step);
|
|
114
|
+
}
|
|
115
|
+
return pending;
|
|
116
|
+
}
|
|
117
|
+
function runMdastPluginsOnHandle(handle, plugins, fileURL, data, sourceFormat, collectLast = false) {
|
|
118
|
+
const out = { handle };
|
|
119
|
+
// A plugin's visitors run once over the tree. A transform that passes a child
|
|
94
120
|
// through (returning it inside the replacement) keeps that child's identity,
|
|
95
|
-
// so a patch the same pass queued on it still applies
|
|
96
|
-
// one pass.
|
|
97
|
-
//
|
|
98
|
-
const runPlugin = (plugin) => {
|
|
121
|
+
// so a patch the same pass queued on it still applies: nesting composes in
|
|
122
|
+
// one pass. Visitor-built nodes are not re-walked; transform them up front,
|
|
123
|
+
// or hand off to a later plugin that sees the materialized tree.
|
|
124
|
+
const runPlugin = (plugin, isLastPlugin) => {
|
|
99
125
|
const subs = resolveMdastSubscriptions(plugin);
|
|
100
|
-
const
|
|
101
|
-
const apply = (r) => {
|
|
126
|
+
const apply = (r, isFinalPass) => {
|
|
102
127
|
if (!r.hasMutations)
|
|
103
128
|
return;
|
|
129
|
+
if (collectLast && isLastPlugin && isFinalPass) {
|
|
130
|
+
out.pendingCommands = r.commandBuffer;
|
|
131
|
+
out.lastPlugin = plugin;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
104
134
|
markHandleMutated(handle);
|
|
105
135
|
const dropped = applyCommandsToMdastHandle(handle, r.commandBuffer);
|
|
106
136
|
if (dropped)
|
|
107
137
|
warnDroppedTransforms(plugin, dropped, "mdast");
|
|
108
138
|
};
|
|
109
|
-
|
|
139
|
+
const before = typeof plugin.before === "function" ? plugin.before : undefined;
|
|
140
|
+
const after = typeof plugin.after === "function" ? plugin.after : undefined;
|
|
141
|
+
const hasVisitors = subs.length > 0;
|
|
142
|
+
// Threaded like `data`: one context per pass, since a resolver is epoch-bound.
|
|
143
|
+
const diagnostics = [];
|
|
144
|
+
const runHook = (hook, isFinalPass) => () => settle(visitMdastHook(handle, plugin, hook, () => getHandleSource(handle), fileURL, data, sourceFormat, diagnostics), (r) => apply(r, isFinalPass));
|
|
145
|
+
const runVisitors = (isFinalPass) => () => settle(visitMdastHandle(handle, plugin, subs, () => getHandleSource(handle), fileURL, data, sourceFormat, diagnostics), (r) => apply(r, isFinalPass));
|
|
146
|
+
return sequence([
|
|
147
|
+
before ? runHook(before, !hasVisitors && after === undefined) : undefined,
|
|
148
|
+
hasVisitors ? runVisitors(after === undefined) : undefined,
|
|
149
|
+
after ? runHook(after, true) : undefined,
|
|
150
|
+
]);
|
|
110
151
|
};
|
|
111
152
|
let i = 0;
|
|
112
153
|
const runNext = () => {
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
154
|
+
for (;;) {
|
|
155
|
+
const plugin = plugins[i];
|
|
156
|
+
if (plugin === undefined)
|
|
157
|
+
break;
|
|
158
|
+
i++;
|
|
159
|
+
const r = runPlugin(plugin, i === plugins.length);
|
|
117
160
|
if (r instanceof Promise)
|
|
118
161
|
return r.then(runNext);
|
|
119
162
|
}
|
|
120
|
-
return
|
|
163
|
+
return out;
|
|
121
164
|
};
|
|
122
165
|
return runNext();
|
|
123
166
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
167
|
+
const EMPTY_COMMAND_BUFFER = new Uint8Array(0);
|
|
168
|
+
const NO_HAST_COMMANDS = {
|
|
169
|
+
commands: EMPTY_COMMAND_BUFFER,
|
|
170
|
+
lastPlugin: null,
|
|
171
|
+
};
|
|
172
|
+
/** Run every HAST plugin except the last one normally (each applies its
|
|
173
|
+
* commands inline), then return the last plugin's commands without applying.
|
|
174
|
+
* Lets the caller fuse the final apply with the downstream NAPI step
|
|
175
|
+
* (`render` or `compile`), saving one NAPI roundtrip per compile. */
|
|
176
|
+
function runHastPluginsCollectLast(handle, plugins, source, fileURL, data, sourceFormat) {
|
|
127
177
|
let i = 0;
|
|
128
178
|
const runNext = () => {
|
|
129
|
-
|
|
130
|
-
const
|
|
179
|
+
for (;;) {
|
|
180
|
+
const plugin = plugins[i];
|
|
181
|
+
if (plugin === undefined)
|
|
182
|
+
break;
|
|
183
|
+
const isLastPlugin = i === plugins.length - 1;
|
|
131
184
|
i++;
|
|
132
|
-
const plugin = typeof raw === "function" ? raw() : raw;
|
|
133
185
|
const subs = resolveSubscriptions(plugin);
|
|
134
|
-
const
|
|
186
|
+
const { before, after } = plugin;
|
|
187
|
+
const passes = [];
|
|
188
|
+
if (typeof before === "function")
|
|
189
|
+
passes.push(before);
|
|
190
|
+
if (subs.length > 0)
|
|
191
|
+
passes.push("visitors");
|
|
192
|
+
if (typeof after === "function")
|
|
193
|
+
passes.push(after);
|
|
194
|
+
const finalPass = passes.pop();
|
|
195
|
+
if (finalPass === undefined)
|
|
196
|
+
continue;
|
|
135
197
|
const warnIfDropped = (dropped) => {
|
|
136
198
|
if (dropped)
|
|
137
199
|
warnDroppedTransforms(plugin, dropped, "hast");
|
|
138
200
|
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
201
|
+
// Threaded like `data`: one context per pass, since a resolver is epoch-bound.
|
|
202
|
+
const diagnostics = [];
|
|
203
|
+
const runApplied = (pass) => () => settle(pass === "visitors"
|
|
204
|
+
? visitHastHandle(handle, plugin, subs, source, fileURL, data, sourceFormat, diagnostics)
|
|
205
|
+
: visitHastHook(handle, plugin, pass, source, fileURL, data, sourceFormat, diagnostics), warnIfDropped);
|
|
206
|
+
const collectFinal = () => {
|
|
207
|
+
const collected = finalPass === "visitors"
|
|
208
|
+
? visitHastHandleCollect(handle, plugin, subs, source, fileURL, data, sourceFormat, diagnostics)
|
|
209
|
+
: visitHastHookCollect(handle, plugin, finalPass, source, fileURL, data, sourceFormat, diagnostics);
|
|
210
|
+
return collected instanceof Promise
|
|
211
|
+
? collected.then((commands) => ({ commands, lastPlugin: plugin }))
|
|
212
|
+
: { commands: collected, lastPlugin: plugin };
|
|
213
|
+
};
|
|
214
|
+
const head = sequence(passes.map(runApplied));
|
|
215
|
+
if (isLastPlugin) {
|
|
216
|
+
return head instanceof Promise ? head.then(collectFinal) : collectFinal();
|
|
144
217
|
}
|
|
145
|
-
|
|
218
|
+
const tail = head instanceof Promise ? head.then(runApplied(finalPass)) : runApplied(finalPass)();
|
|
219
|
+
if (tail instanceof Promise)
|
|
220
|
+
return tail.then(runNext);
|
|
146
221
|
}
|
|
222
|
+
return NO_HAST_COMMANDS;
|
|
147
223
|
};
|
|
148
224
|
return runNext();
|
|
149
225
|
}
|
|
@@ -191,71 +267,200 @@ function mdxOptionsToNative(opts) {
|
|
|
191
267
|
return result;
|
|
192
268
|
}
|
|
193
269
|
export function markdownToHtml(source, options = {}) {
|
|
194
|
-
const {
|
|
270
|
+
const { features, fileURL, data = {} } = options;
|
|
271
|
+
const mdastPlugins = normalizePlugins(options.mdastPlugins ?? [], "mdastPlugins", source, fileURL, "markdown", data);
|
|
272
|
+
const hastPlugins = normalizePlugins(options.hastPlugins ?? [], "hastPlugins", source, fileURL, "markdown", data);
|
|
195
273
|
const hastMayHaveStubs = hastPlugins.length > 0;
|
|
196
274
|
const { features: nativeFeatures, convertOptions: nativeConvertOptions } = featuresToNative(features);
|
|
197
|
-
|
|
198
|
-
|
|
275
|
+
// Fast path: no plugins → parse, convert, render, and frontmatter all happen
|
|
276
|
+
// inside a single NAPI call. Skips 5 handle-NAPI roundtrips that the
|
|
277
|
+
// plugin-capable path needs to keep the arena live across passes.
|
|
278
|
+
if (mdastPlugins.length === 0 && hastPlugins.length === 0) {
|
|
279
|
+
const { html, frontmatter } = markdownToHtmlFast(source, nativeFeatures, nativeConvertOptions);
|
|
280
|
+
return { html, frontmatter: frontmatter ?? null, data };
|
|
281
|
+
}
|
|
282
|
+
// Track source positions only when some plugin opts in with `position: true`;
|
|
283
|
+
// otherwise the parse skips the LineIndex build and per-node line/column lookups.
|
|
284
|
+
const trackPositions = mdastPlugins.some((p) => p.options?.position) || hastPlugins.some((p) => p.options?.position);
|
|
285
|
+
// Fused tail for MDAST-plugins-only (no HAST plugins): after the MDAST
|
|
286
|
+
// plugin pass returns its pending commands, apply + convert + render all
|
|
287
|
+
// happen inside a single NAPI roundtrip. Saves the convert-to-hast handle
|
|
288
|
+
// create + render + drop crossings the generic path makes separately.
|
|
289
|
+
if (hastPlugins.length === 0) {
|
|
290
|
+
const mdastHandle = createMdastHandle(source, nativeFeatures, trackPositions);
|
|
199
291
|
try {
|
|
200
|
-
const
|
|
201
|
-
|
|
292
|
+
const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, fileURL, data, "markdown", true);
|
|
293
|
+
const finishMdast = (r) => {
|
|
294
|
+
try {
|
|
295
|
+
// Fused tail: apply pending commands (empty buffer is a Rust no-op),
|
|
296
|
+
// extract frontmatter post-mutation, convert, render, all in one
|
|
297
|
+
// NAPI roundtrip.
|
|
298
|
+
const commands = r.pendingCommands ?? EMPTY_COMMAND_BUFFER;
|
|
299
|
+
const { html, frontmatter, droppedTransforms } = applyMdastCommandsAndConvertAndRender(r.handle, commands, nativeConvertOptions);
|
|
300
|
+
if (droppedTransforms && r.lastPlugin)
|
|
301
|
+
warnDroppedTransforms(r.lastPlugin, droppedTransforms, "mdast");
|
|
302
|
+
return {
|
|
303
|
+
html,
|
|
304
|
+
frontmatter: frontmatter ?? null,
|
|
305
|
+
data,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
releaseHandle(r.handle, true);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
if (mdastResult instanceof Promise) {
|
|
313
|
+
return mdastResult.then(finishMdast, (err) => {
|
|
314
|
+
releaseHandle(mdastHandle, true);
|
|
315
|
+
throw err;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return finishMdast(mdastResult);
|
|
202
319
|
}
|
|
203
|
-
|
|
204
|
-
releaseHandle(
|
|
320
|
+
catch (err) {
|
|
321
|
+
releaseHandle(mdastHandle, true);
|
|
322
|
+
throw err;
|
|
205
323
|
}
|
|
206
|
-
}
|
|
324
|
+
}
|
|
325
|
+
const result = createHastHandleFromMdast(source, mdastPlugins, false, fileURL, nativeFeatures, nativeConvertOptions, data, trackPositions);
|
|
207
326
|
const runHastThenRender = (r) => {
|
|
208
|
-
|
|
327
|
+
// Run all but the last HAST plugin normally (each one applies its
|
|
328
|
+
// commands inline); collect the last plugin's commands and fuse apply +
|
|
329
|
+
// render + handle-drop into one NAPI call.
|
|
330
|
+
let collected;
|
|
209
331
|
try {
|
|
210
|
-
|
|
332
|
+
collected = runHastPluginsCollectLast(r.hastHandle, hastPlugins, source, fileURL, data, "markdown");
|
|
211
333
|
}
|
|
212
334
|
catch (err) {
|
|
213
335
|
releaseHandle(r.hastHandle, hastMayHaveStubs);
|
|
214
336
|
throw err;
|
|
215
337
|
}
|
|
216
|
-
if (
|
|
217
|
-
return
|
|
338
|
+
if (collected instanceof Promise) {
|
|
339
|
+
return collected.then((c) => finishHastRender(r.hastHandle, c, r.frontmatter), (err) => {
|
|
218
340
|
releaseHandle(r.hastHandle, hastMayHaveStubs);
|
|
219
341
|
throw err;
|
|
220
342
|
});
|
|
221
343
|
}
|
|
222
|
-
return
|
|
344
|
+
return finishHastRender(r.hastHandle, collected, r.frontmatter);
|
|
345
|
+
};
|
|
346
|
+
const finishHastRender = (h, collected, frontmatter) => {
|
|
347
|
+
try {
|
|
348
|
+
let html;
|
|
349
|
+
if (collected.commands.length > 0) {
|
|
350
|
+
const result = applyCommandsAndRenderHandle(h, collected.commands);
|
|
351
|
+
if (result.droppedTransforms && collected.lastPlugin)
|
|
352
|
+
warnDroppedTransforms(collected.lastPlugin, result.droppedTransforms, "hast");
|
|
353
|
+
html = result.html;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
html = renderHandle(h);
|
|
357
|
+
}
|
|
358
|
+
return { html, frontmatter, data };
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
releaseHandle(h, hastMayHaveStubs);
|
|
362
|
+
}
|
|
223
363
|
};
|
|
224
364
|
if (result instanceof Promise)
|
|
225
365
|
return result.then(runHastThenRender);
|
|
226
366
|
return runHastThenRender(result);
|
|
227
367
|
}
|
|
228
368
|
export function mdxToJs(source, options = {}) {
|
|
229
|
-
|
|
369
|
+
return toJsImpl(source, options, true);
|
|
370
|
+
}
|
|
371
|
+
export function markdownToJs(source, options = {}) {
|
|
372
|
+
return toJsImpl(source, options, false);
|
|
373
|
+
}
|
|
374
|
+
/** `mdx` picks the parser; the pipeline is identical from MDAST on. */
|
|
375
|
+
function toJsImpl(source, options, mdx) {
|
|
376
|
+
const { mdastPlugins: mdastInput = [], hastPlugins: hastInput = [], features, fileURL, data = {}, ...mdxFields } = options;
|
|
377
|
+
const sourceFormat = mdx ? "mdx" : "markdown";
|
|
378
|
+
const mdastPlugins = normalizePlugins(mdastInput, "mdastPlugins", source, fileURL, sourceFormat, data);
|
|
379
|
+
const hastPlugins = normalizePlugins(hastInput, "hastPlugins", source, fileURL, sourceFormat, data);
|
|
230
380
|
const hastMayHaveStubs = hastPlugins.length > 0;
|
|
231
381
|
const mdxOptions = mdxOptionsToNative(mdxFields);
|
|
232
382
|
const { features: nativeFeatures, convertOptions: nativeConvertOptions } = featuresToNative(features);
|
|
233
|
-
|
|
234
|
-
|
|
383
|
+
// Fast path: same trick as `markdownToHtml`. Parse → MDAST → HAST → JS plus
|
|
384
|
+
// frontmatter extraction all happen inside a single NAPI call. Skips 5 of
|
|
385
|
+
// the 6 handle-based crossings the plugin-capable path needs.
|
|
386
|
+
if (mdastPlugins.length === 0 && hastPlugins.length === 0) {
|
|
387
|
+
const { code, frontmatter } = (mdx ? mdxToJsFast : markdownToJsFast)(source, nativeFeatures, mdxOptions, nativeConvertOptions);
|
|
388
|
+
return { code, frontmatter: frontmatter ?? null, data };
|
|
389
|
+
}
|
|
390
|
+
// Track positions only when a plugin opts in (see `markdownToHtml`).
|
|
391
|
+
const trackPositions = mdastPlugins.some((p) => p.options?.position) || hastPlugins.some((p) => p.options?.position);
|
|
392
|
+
// MDAST-plugins-only fused tail (no HAST plugins): apply + extract
|
|
393
|
+
// frontmatter + convert + simplify + compile happen in one NAPI call.
|
|
394
|
+
if (hastPlugins.length === 0) {
|
|
395
|
+
const mdastHandle = mdx
|
|
396
|
+
? createMdxMdastHandle(source, nativeFeatures, trackPositions)
|
|
397
|
+
: createMdastHandle(source, nativeFeatures, trackPositions);
|
|
235
398
|
try {
|
|
236
|
-
const
|
|
237
|
-
|
|
399
|
+
const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, fileURL, data, mdx ? "mdx" : "markdown", true);
|
|
400
|
+
const finishMdast = (r) => {
|
|
401
|
+
try {
|
|
402
|
+
const commands = r.pendingCommands ?? EMPTY_COMMAND_BUFFER;
|
|
403
|
+
const { code, frontmatter, droppedTransforms } = applyMdastCommandsAndConvertAndCompile(r.handle, commands, mdxOptions, nativeConvertOptions);
|
|
404
|
+
if (droppedTransforms && r.lastPlugin)
|
|
405
|
+
warnDroppedTransforms(r.lastPlugin, droppedTransforms, "mdast");
|
|
406
|
+
return {
|
|
407
|
+
code,
|
|
408
|
+
frontmatter: frontmatter ?? null,
|
|
409
|
+
data,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
releaseHandle(r.handle, true);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
if (mdastResult instanceof Promise) {
|
|
417
|
+
return mdastResult.then(finishMdast, (err) => {
|
|
418
|
+
releaseHandle(mdastHandle, true);
|
|
419
|
+
throw err;
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
return finishMdast(mdastResult);
|
|
238
423
|
}
|
|
239
|
-
|
|
240
|
-
releaseHandle(
|
|
424
|
+
catch (err) {
|
|
425
|
+
releaseHandle(mdastHandle, true);
|
|
426
|
+
throw err;
|
|
241
427
|
}
|
|
242
|
-
}
|
|
428
|
+
}
|
|
429
|
+
const result = createHastHandleFromMdast(source, mdastPlugins, mdx, fileURL, nativeFeatures, nativeConvertOptions, data, trackPositions);
|
|
243
430
|
const runHastThenCompile = (r) => {
|
|
244
|
-
let
|
|
431
|
+
let collected;
|
|
245
432
|
try {
|
|
246
|
-
|
|
433
|
+
collected = runHastPluginsCollectLast(r.hastHandle, hastPlugins, source, fileURL, data, mdx ? "mdx" : "markdown");
|
|
247
434
|
}
|
|
248
435
|
catch (err) {
|
|
249
436
|
releaseHandle(r.hastHandle, hastMayHaveStubs);
|
|
250
437
|
throw err;
|
|
251
438
|
}
|
|
252
|
-
if (
|
|
253
|
-
return
|
|
439
|
+
if (collected instanceof Promise) {
|
|
440
|
+
return collected.then((c) => finishHastCompile(r.hastHandle, c, r.frontmatter), (err) => {
|
|
254
441
|
releaseHandle(r.hastHandle, hastMayHaveStubs);
|
|
255
442
|
throw err;
|
|
256
443
|
});
|
|
257
444
|
}
|
|
258
|
-
return
|
|
445
|
+
return finishHastCompile(r.hastHandle, collected, r.frontmatter);
|
|
446
|
+
};
|
|
447
|
+
const finishHastCompile = (h, collected, frontmatter) => {
|
|
448
|
+
try {
|
|
449
|
+
let code;
|
|
450
|
+
if (collected.commands.length > 0) {
|
|
451
|
+
const result = applyCommandsAndCompileHandle(h, collected.commands, mdxOptions);
|
|
452
|
+
if (result.droppedTransforms && collected.lastPlugin)
|
|
453
|
+
warnDroppedTransforms(collected.lastPlugin, result.droppedTransforms, "hast");
|
|
454
|
+
code = result.code;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
code = compileHandle(h, mdxOptions);
|
|
458
|
+
}
|
|
459
|
+
return { code, frontmatter, data };
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
releaseHandle(h, hastMayHaveStubs);
|
|
463
|
+
}
|
|
259
464
|
};
|
|
260
465
|
if (result instanceof Promise)
|
|
261
466
|
return result.then(runHastThenCompile);
|
|
@@ -292,10 +497,19 @@ function createHastHandleFromMdast(source, mdastPlugins, mdx, fileURL,
|
|
|
292
497
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
293
498
|
nativeFeatures,
|
|
294
499
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
295
|
-
nativeConvertOptions, data) {
|
|
500
|
+
nativeConvertOptions, data, trackPositions) {
|
|
501
|
+
if (mdastPlugins.length === 0) {
|
|
502
|
+
const [hastHandle, raw] = mdx
|
|
503
|
+
? createMdxHastHandleWithFrontmatter(source, nativeFeatures, nativeConvertOptions, trackPositions)
|
|
504
|
+
: createHastHandleWithFrontmatter(source, nativeFeatures, nativeConvertOptions, trackPositions);
|
|
505
|
+
return {
|
|
506
|
+
hastHandle,
|
|
507
|
+
frontmatter: raw ? { kind: raw.kind === "toml" ? "toml" : "yaml", value: raw.value } : null,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
296
510
|
const mdastHandle = mdx
|
|
297
|
-
? createMdxMdastHandle(source, nativeFeatures)
|
|
298
|
-
: createMdastHandle(source, nativeFeatures);
|
|
511
|
+
? createMdxMdastHandle(source, nativeFeatures, trackPositions)
|
|
512
|
+
: createMdastHandle(source, nativeFeatures, trackPositions);
|
|
299
513
|
const sourceFormat = mdx ? "mdx" : "markdown";
|
|
300
514
|
const mdastMayHaveStubs = mdastPlugins.length > 0;
|
|
301
515
|
// finally{release} is intentional: convertMdastToHastHandle empties the arena
|
|
@@ -314,9 +528,6 @@ nativeConvertOptions, data) {
|
|
|
314
528
|
}
|
|
315
529
|
};
|
|
316
530
|
try {
|
|
317
|
-
if (mdastPlugins.length === 0) {
|
|
318
|
-
return finalize({ handle: mdastHandle });
|
|
319
|
-
}
|
|
320
531
|
const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, fileURL, data, sourceFormat);
|
|
321
532
|
if (mdastResult instanceof Promise) {
|
|
322
533
|
return mdastResult.then(finalize, (err) => {
|
|
@@ -331,10 +542,9 @@ nativeConvertOptions, data) {
|
|
|
331
542
|
throw err;
|
|
332
543
|
}
|
|
333
544
|
}
|
|
334
|
-
// Step-by-step API: individual pipeline stages with materialized trees
|
|
335
545
|
/** Parse Markdown source into a materialized mdast tree. */
|
|
336
546
|
export function markdownToMdast(source, options = {}) {
|
|
337
|
-
const handle = createMdastHandle(source, featuresToNative(options.features).features);
|
|
547
|
+
const handle = createMdastHandle(source, featuresToNative(options.features).features, options.position);
|
|
338
548
|
try {
|
|
339
549
|
return materializeMdastTree(new MdastReader(serializeHandle(handle)));
|
|
340
550
|
}
|
|
@@ -344,7 +554,7 @@ export function markdownToMdast(source, options = {}) {
|
|
|
344
554
|
}
|
|
345
555
|
/** Parse MDX source into a materialized mdast tree. */
|
|
346
556
|
export function mdxToMdast(source, options = {}) {
|
|
347
|
-
const handle = createMdxMdastHandle(source, featuresToNative(options.features).features);
|
|
557
|
+
const handle = createMdxMdastHandle(source, featuresToNative(options.features).features, options.position);
|
|
348
558
|
try {
|
|
349
559
|
return materializeMdastTree(new MdastReader(serializeHandle(handle)));
|
|
350
560
|
}
|
|
@@ -355,7 +565,7 @@ export function mdxToMdast(source, options = {}) {
|
|
|
355
565
|
/** Convert Markdown source to a materialized hast tree. */
|
|
356
566
|
export function markdownToHast(source, options = {}) {
|
|
357
567
|
const { features: nativeFeatures, convertOptions } = featuresToNative(options.features);
|
|
358
|
-
const handle = createHastHandle(source, nativeFeatures, convertOptions);
|
|
568
|
+
const handle = createHastHandle(source, nativeFeatures, convertOptions, options.position);
|
|
359
569
|
try {
|
|
360
570
|
return materializeHastTree(new HastReader(serializeHandle(handle)));
|
|
361
571
|
}
|
|
@@ -366,7 +576,21 @@ export function markdownToHast(source, options = {}) {
|
|
|
366
576
|
/** Convert MDX source to a materialized hast tree. */
|
|
367
577
|
export function mdxToHast(source, options = {}) {
|
|
368
578
|
const { features: nativeFeatures, convertOptions } = featuresToNative(options.features);
|
|
369
|
-
const handle = createMdxHastHandle(source, nativeFeatures, convertOptions);
|
|
579
|
+
const handle = createMdxHastHandle(source, nativeFeatures, convertOptions, options.position);
|
|
580
|
+
try {
|
|
581
|
+
return materializeHastTree(new HastReader(serializeHandle(handle)));
|
|
582
|
+
}
|
|
583
|
+
finally {
|
|
584
|
+
releaseHandle(handle, true);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Parse an HTML string into a materialized hast tree: a `root` whose children
|
|
589
|
+
* are the doctype (if any) and the implied `<html>` subtree. Only available
|
|
590
|
+
* in builds that include the `from-html` feature.
|
|
591
|
+
*/
|
|
592
|
+
export function htmlToHast(html) {
|
|
593
|
+
const handle = createHastHandleFromHtml(html);
|
|
370
594
|
try {
|
|
371
595
|
return materializeHastTree(new HastReader(serializeHandle(handle)));
|
|
372
596
|
}
|
package/dist/freeze.d.ts
ADDED
package/dist/freeze.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Freeze JSON-shaped data recursively; never call on objects with lazy getters. */
|
|
2
|
+
export function deepFreeze(value) {
|
|
3
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
4
|
+
Object.freeze(value);
|
|
5
|
+
for (const key of Object.keys(value)) {
|
|
6
|
+
deepFreeze(value[key]);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
@@ -35,9 +35,9 @@ export declare const CMD_WRAP = 9;
|
|
|
35
35
|
export declare const CMD_REPLACE = 11;
|
|
36
36
|
export declare const CMD_SET_PROPERTY = 12;
|
|
37
37
|
export declare const CMD_SET_CHILDREN = 13;
|
|
38
|
-
export declare const
|
|
39
|
-
export declare const PAYLOAD_RAW_HTML = 17;
|
|
38
|
+
export declare const PAYLOAD_RAW = 16;
|
|
40
39
|
export declare const PAYLOAD_OPSTREAM = 20;
|
|
40
|
+
export declare const RAW_LITERAL_BRACES = 1;
|
|
41
41
|
export declare const PROP_STRING = 0;
|
|
42
42
|
export declare const PROP_BOOL_TRUE = 1;
|
|
43
43
|
export declare const PROP_BOOL_FALSE = 2;
|
|
@@ -45,6 +45,7 @@ export declare const PROP_SPACE_SEP = 3;
|
|
|
45
45
|
export declare const PROP_COMMA_SEP = 4;
|
|
46
46
|
export declare const PROP_INT = 5;
|
|
47
47
|
export declare const PROP_NULL = 6;
|
|
48
|
+
export declare const PROP_COMMA_SEP_NUM = 7;
|
|
48
49
|
export declare const MDX_ATTR_BOOLEAN_PROP = 0;
|
|
49
50
|
export declare const MDX_ATTR_LITERAL_PROP = 1;
|
|
50
51
|
export declare const MDX_ATTR_EXPRESSION_PROP = 2;
|
|
@@ -45,9 +45,10 @@ export const CMD_REPLACE = 0x0b;
|
|
|
45
45
|
export const CMD_SET_PROPERTY = 0x0c; // [valueType: u8][name str][value str], PROP_* value kinds
|
|
46
46
|
export const CMD_SET_CHILDREN = 0x0d; // payload is a Root-wrapped child list
|
|
47
47
|
// Structural-command payload types (0x10+, a range distinct from commands).
|
|
48
|
-
export const
|
|
49
|
-
export const PAYLOAD_RAW_HTML = 0x11; // [len: u32 LE][utf8] — re-parsed as HTML/MDX
|
|
48
|
+
export const PAYLOAD_RAW = 0x10; // [flags: u8][len: u32 LE][utf8] — re-parsed as markdown; see RAW_* flags
|
|
50
49
|
export const PAYLOAD_OPSTREAM = 0x14; // [len: u32 LE][op bytes] — replayed straight into the arena
|
|
50
|
+
// Flag bits for the `PAYLOAD_RAW` payload.
|
|
51
|
+
export const RAW_LITERAL_BRACES = 0x01; // keep MDX `{…}` as literal text (only affects MDX output)
|
|
51
52
|
// Property value kinds (HAST element properties and SET_PROPERTY commands).
|
|
52
53
|
export const PROP_STRING = 0; // UTF-8 value
|
|
53
54
|
export const PROP_BOOL_TRUE = 1; // no value bytes
|
|
@@ -56,6 +57,7 @@ export const PROP_SPACE_SEP = 3; // space-separated list (UTF-8)
|
|
|
56
57
|
export const PROP_COMMA_SEP = 4; // comma-separated list (UTF-8)
|
|
57
58
|
export const PROP_INT = 5; // decimal string, parsed to i64
|
|
58
59
|
export const PROP_NULL = 6; // no value bytes
|
|
60
|
+
export const PROP_COMMA_SEP_NUM = 7; // comma-separated list, numeric items parsed to numbers
|
|
59
61
|
// MDX JSX attribute kinds (MDAST and HAST MDX JSX element type_data).
|
|
60
62
|
export const MDX_ATTR_BOOLEAN_PROP = 0; // name only, no value
|
|
61
63
|
export const MDX_ATTR_LITERAL_PROP = 1; // name="literal"
|
|
@@ -5,9 +5,8 @@ type HastResolver = LazyChildResolver<HastReader, HastNode>;
|
|
|
5
5
|
/**
|
|
6
6
|
* Walk-path child stub: arena id + `type` eagerly, every other field a lazy
|
|
7
7
|
* forward to the materialized node (first read snapshots the arena via
|
|
8
|
-
* `materializeOne`, which enforces the handle epoch
|
|
9
|
-
*
|
|
10
|
-
* in hast-visitor.ts.
|
|
8
|
+
* `materializeOne`, which enforces the handle epoch). Spread/identity rules
|
|
9
|
+
* are enforced by `nid()` in hast-visitor.ts.
|
|
11
10
|
*/
|
|
12
11
|
export declare class HastChildStub {
|
|
13
12
|
_resolver: HastResolver;
|
package/dist/hast/child-stub.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { flatByTag, stubDescriptors } from "../child-stub.js";
|
|
1
|
+
import { flatByTag, installStubDescriptors, stubDescriptors, } from "../child-stub.js";
|
|
2
2
|
import { NAME_TO_TYPE, TYPE_NAMES } from "./generated/node-types.js";
|
|
3
|
+
import { HAST_CONTAINER_TYPES } from "./hast-materializer.js";
|
|
3
4
|
const N = NAME_TO_TYPE;
|
|
4
|
-
/** Per-type stub fields
|
|
5
|
+
/** Per-type stub fields; must mirror `materializeHastNode`'s populate switch. */
|
|
5
6
|
const HAST_STUB_FIELDS = {
|
|
6
|
-
[N.root]: [
|
|
7
|
-
[N.element]: ["tagName", "properties"
|
|
7
|
+
[N.root]: [],
|
|
8
|
+
[N.element]: ["tagName", "properties"],
|
|
8
9
|
[N.text]: ["value"],
|
|
9
10
|
[N.comment]: ["value"],
|
|
10
11
|
[N.doctype]: [],
|
|
11
12
|
[N.raw]: ["value"],
|
|
12
|
-
[N.mdxJsxFlowElement]: ["name", "attributes"
|
|
13
|
-
[N.mdxJsxTextElement]: ["name", "attributes"
|
|
13
|
+
[N.mdxJsxFlowElement]: ["name", "attributes"],
|
|
14
|
+
[N.mdxJsxTextElement]: ["name", "attributes"],
|
|
14
15
|
[N.mdxFlowExpression]: ["value"],
|
|
15
16
|
[N.mdxTextExpression]: ["value"],
|
|
16
17
|
[N.mdxjsEsm]: ["value"],
|
|
@@ -19,16 +20,18 @@ const TYPE_NAME_BY_TAG = flatByTag(TYPE_NAMES);
|
|
|
19
20
|
const HAST_STUB_DESCRIPTORS = [];
|
|
20
21
|
for (const tag of Object.keys(HAST_STUB_FIELDS)) {
|
|
21
22
|
const nodeType = Number(tag);
|
|
22
|
-
|
|
23
|
+
const fields = [...HAST_STUB_FIELDS[nodeType]];
|
|
24
|
+
if (HAST_CONTAINER_TYPES.has(nodeType))
|
|
25
|
+
fields.push("children");
|
|
26
|
+
HAST_STUB_DESCRIPTORS[nodeType] = stubDescriptors(fields);
|
|
23
27
|
}
|
|
24
28
|
/** Unknown node types still expose the prelude-backed lazy fields. */
|
|
25
29
|
const FALLBACK_DESCRIPTORS = stubDescriptors([]);
|
|
26
30
|
/**
|
|
27
31
|
* Walk-path child stub: arena id + `type` eagerly, every other field a lazy
|
|
28
32
|
* forward to the materialized node (first read snapshots the arena via
|
|
29
|
-
* `materializeOne`, which enforces the handle epoch
|
|
30
|
-
*
|
|
31
|
-
* in hast-visitor.ts.
|
|
33
|
+
* `materializeOne`, which enforces the handle epoch). Spread/identity rules
|
|
34
|
+
* are enforced by `nid()` in hast-visitor.ts.
|
|
32
35
|
*/
|
|
33
36
|
export class HastChildStub {
|
|
34
37
|
_resolver;
|
|
@@ -38,6 +41,10 @@ export class HastChildStub {
|
|
|
38
41
|
this._resolver = resolver;
|
|
39
42
|
this._id = id;
|
|
40
43
|
this.type = TYPE_NAME_BY_TAG[nodeType] ?? `unknown(${nodeType})`;
|
|
41
|
-
|
|
44
|
+
installStubDescriptors(this, HAST_STUB_DESCRIPTORS[nodeType] ?? FALLBACK_DESCRIPTORS);
|
|
45
|
+
}
|
|
46
|
+
/** @internal */
|
|
47
|
+
get _refs() {
|
|
48
|
+
return this._resolver.refs;
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type HastPropertyValue = string | number | boolean | string[];
|
|
1
|
+
export type HastPropertyValue = string | number | boolean | (string | number)[];
|
|
2
2
|
export declare function decodeElementProp(kind: number, value: string): HastPropertyValue;
|