satteri 0.9.4 → 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 +295 -70
- 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 +55 -5
- package/dist/hast/hast-visitor.js +320 -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 +79 -32
- package/dist/mdast/mdast-visitor.js +305 -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 +44 -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 +24 -19
- package/dist/lazy-props.d.ts +0 -10
- package/dist/lazy-props.js +0 -50
- package/satteri_napi.wasi-browser.js +0 -83
- package/satteri_napi.wasi.cjs +0 -134
- package/wasi-worker-browser.mjs +0 -36
- package/wasi-worker.mjs +0 -63
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { materializeHastNode } from "./hast-materializer.js";
|
|
2
|
-
import { HastReader, HAST_ELEMENT, HAST_TEXT, HAST_COMMENT, HAST_RAW, HAST_MDX_JSX_ELEMENT, HAST_MDX_JSX_TEXT_ELEMENT, HAST_MDX_FLOW_EXPRESSION, HAST_MDX_TEXT_EXPRESSION, HAST_MDX_ESM, } from "./hast-reader.js";
|
|
2
|
+
import { HastReader, HAST_ROOT, HAST_ELEMENT, HAST_TEXT, HAST_COMMENT, HAST_RAW, HAST_MDX_JSX_ELEMENT, HAST_MDX_JSX_TEXT_ELEMENT, HAST_MDX_FLOW_EXPRESSION, HAST_MDX_TEXT_EXPRESSION, HAST_MDX_ESM, } from "./hast-reader.js";
|
|
3
3
|
import { TYPE_NAMES, NAME_TO_TYPE, VISITOR_KEYS, HAST_OPSTREAM_TYPES, } from "./generated/node-types.js";
|
|
4
|
-
import { CommandBuffer } from "../command-buffer.js";
|
|
4
|
+
import { acquireCommandBuffer, releaseCommandBuffer, CommandBuffer, STRUCTURAL_CMD, } from "../command-buffer.js";
|
|
5
|
+
import { CMD_SET_CHILDREN } from "../generated/wire-constants.js";
|
|
5
6
|
import { OpWriter, OF_VALUE, OF_TAGNAME, OF_NAME, OF_EXPLICIT, PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_INT, emitMdxAttr, } from "../op-stream.js";
|
|
6
7
|
import { restorePhantomSpaces } from "../phantom.js";
|
|
7
8
|
import { decodeMdxJsxAttr } from "../mdx-attr.js";
|
|
8
9
|
import { decodeElementProp } from "./element-props.js";
|
|
9
10
|
import { readPosition, rstr } from "../wire-read.js";
|
|
10
11
|
import { walkHandle, applyCommandsToHandle, textContentHandle, parseExpression as napiParseExpression, parseEsm as napiParseEsm, } from "#binding";
|
|
11
|
-
import { asArray, makeRequireNid, mergeAndReset, unencodableContentError, } from "../visitor-shared.js";
|
|
12
|
-
import { LazyChildResolver, markHandleMutated } from "../lazy-child-resolver.js";
|
|
12
|
+
import { asArray, makeRequireNid, mergeAndReset, ROOT_NODE_ID, requireRootReplacement, rootReplacementError, crossPipelineForeign, FOREIGN_REF, unencodableContentError, } from "../visitor-shared.js";
|
|
13
|
+
import { LazyChildResolver, markHandleMutated, registerEpochCacheSlot, } from "../lazy-child-resolver.js";
|
|
13
14
|
import { HastChildStub } from "./child-stub.js";
|
|
14
|
-
/** Maps HastNode objects to their arena node IDs without Object.defineProperty overhead. */
|
|
15
|
-
const nodeIdMap = new WeakMap();
|
|
16
15
|
/** Attach `parseExpression()` to an MDX expression or ESM node. */
|
|
17
16
|
function attachParseExpression(node, parseFn) {
|
|
18
17
|
Object.defineProperty(node, "parseExpression", {
|
|
@@ -42,91 +41,87 @@ function attachParseExpression(node, parseFn) {
|
|
|
42
41
|
* the WeakMap or a NON-enumerable `_nodeId` (the materializers' convention,
|
|
43
42
|
* which spread cannot copy).
|
|
44
43
|
*/
|
|
45
|
-
function nid(node) {
|
|
44
|
+
function nid(node, refs) {
|
|
46
45
|
if (node instanceof WalkElement)
|
|
47
|
-
return node._nid;
|
|
46
|
+
return node._refs === refs ? node._nid : FOREIGN_REF;
|
|
48
47
|
if (node instanceof HastChildStub)
|
|
49
|
-
return node._id;
|
|
50
|
-
const id =
|
|
48
|
+
return node._refs === refs ? node._id : FOREIGN_REF;
|
|
49
|
+
const id = refs.get(node);
|
|
51
50
|
if (id !== undefined)
|
|
52
51
|
return id;
|
|
53
52
|
const d = Object.getOwnPropertyDescriptor(node, "_nodeId");
|
|
54
|
-
|
|
53
|
+
if (d !== undefined && !d.enumerable)
|
|
54
|
+
return FOREIGN_REF;
|
|
55
|
+
return crossPipelineForeign(node);
|
|
55
56
|
}
|
|
56
57
|
const requireNid = makeRequireNid(nid);
|
|
57
|
-
|
|
58
|
+
/** `wrapNode` allowlist: an unlisted type fails loud instead of silently
|
|
59
|
+
* mis-wrapping. */
|
|
60
|
+
const HAST_PARENT_TYPES = ["element", "mdxJsxFlowElement", "mdxJsxTextElement"];
|
|
61
|
+
const HAST_PARENT_TYPE_SET = new Set(HAST_PARENT_TYPES);
|
|
62
|
+
/** A leaf wrapper would make the patch engine drop or displace the wrapped node. */
|
|
63
|
+
function assertHastWrapParent(parentNode) {
|
|
64
|
+
const type = parentNode.type;
|
|
65
|
+
if (typeof type === "string" && HAST_PARENT_TYPE_SET.has(type))
|
|
66
|
+
return;
|
|
67
|
+
throw new Error(`wrapNode: "${String(type)}" nodes cannot hold children, so they cannot wrap a node. ` +
|
|
68
|
+
'Wrap in an element instead, e.g. { type: "element", tagName: "div", properties: {}, children: [] } ' +
|
|
69
|
+
'or { raw: "<div></div>" }.');
|
|
70
|
+
}
|
|
71
|
+
function hastReusedId(node, refs) {
|
|
58
72
|
if (node === null || typeof node !== "object")
|
|
59
73
|
return undefined;
|
|
60
|
-
const id = nid(node);
|
|
61
|
-
return
|
|
74
|
+
const id = nid(node, refs);
|
|
75
|
+
return id !== undefined && id !== FOREIGN_REF ? id : undefined;
|
|
62
76
|
}
|
|
63
|
-
|
|
64
|
-
const hastWriter = new OpWriter();
|
|
65
|
-
/** Compile a set-children payload: a root-wrapped child list, the shape
|
|
77
|
+
/** Emit a set-children command in place: a root-wrapped child list, the shape
|
|
66
78
|
* `Patch::SetChildren` splices in. Reused children become refs. */
|
|
67
|
-
function
|
|
79
|
+
function emitHastChildrenCommand(buffer, id, children, refs) {
|
|
68
80
|
if (!Array.isArray(children))
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
hastWriter.open(NAME_TO_TYPE.root);
|
|
81
|
+
return false;
|
|
82
|
+
return buffer.emitOpstreamCommand(CMD_SET_CHILDREN, id, () => {
|
|
83
|
+
buffer.open(HAST_ROOT);
|
|
73
84
|
for (const c of children) {
|
|
74
|
-
if (!emitHastOp(
|
|
75
|
-
return
|
|
85
|
+
if (!emitHastOp(buffer, c, false, refs))
|
|
86
|
+
return false;
|
|
76
87
|
}
|
|
77
|
-
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
finally {
|
|
81
|
-
hastWriter.end();
|
|
82
|
-
}
|
|
88
|
+
buffer.close();
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
83
91
|
}
|
|
84
|
-
/** Encode `node` as the `op` structural command
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (ops === null)
|
|
92
|
+
/** Encode `node` as the `op` structural command, emitting the op-stream
|
|
93
|
+
* payload directly into the command buffer (no intermediate copy). HAST
|
|
94
|
+
* content is always a declarative node (no raw escape hatch), so it
|
|
95
|
+
* compiles or it's a hard error. */
|
|
96
|
+
function emitHastTree(buffer, op, id, node, refs) {
|
|
97
|
+
const ok = buffer.emitOpstreamCommand(STRUCTURAL_CMD[op], id, () => emitHastOp(buffer, node, true, refs));
|
|
98
|
+
if (!ok)
|
|
92
99
|
throw unencodableContentError(node);
|
|
93
|
-
switch (op) {
|
|
94
|
-
case "replace":
|
|
95
|
-
return buffer.replaceOpstream(id, ops);
|
|
96
|
-
case "insertBefore":
|
|
97
|
-
return buffer.insertBeforeOpstream(id, ops);
|
|
98
|
-
case "insertAfter":
|
|
99
|
-
return buffer.insertAfterOpstream(id, ops);
|
|
100
|
-
case "prependChild":
|
|
101
|
-
return buffer.prependChildOpstream(id, ops);
|
|
102
|
-
case "appendChild":
|
|
103
|
-
return buffer.appendChildOpstream(id, ops);
|
|
104
|
-
case "wrapNode":
|
|
105
|
-
return buffer.wrapNodeOpstream(id, ops);
|
|
106
|
-
}
|
|
107
100
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
101
|
+
/** Separate from the per-node encoder, which rejects a `root` payload. */
|
|
102
|
+
function emitHastRootReplace(buffer, root, refs) {
|
|
103
|
+
const ok = buffer.emitOpstreamCommand(STRUCTURAL_CMD.replace, ROOT_NODE_ID, () => emitHastRootOp(buffer, root, refs));
|
|
104
|
+
if (!ok)
|
|
105
|
+
throw unencodableContentError(root);
|
|
106
|
+
}
|
|
107
|
+
function emitHastRootOp(w, n, refs) {
|
|
108
|
+
w.open(HAST_ROOT);
|
|
109
|
+
if (n.data != null)
|
|
110
|
+
w.data(n.data);
|
|
111
|
+
const children = n.children;
|
|
112
|
+
if (Array.isArray(children)) {
|
|
113
|
+
for (const c of children)
|
|
114
|
+
if (!emitHastOp(w, c, false, refs))
|
|
115
|
+
return false;
|
|
123
116
|
}
|
|
117
|
+
w.close();
|
|
118
|
+
return true;
|
|
124
119
|
}
|
|
125
|
-
function emitHastOp(w, node, isRoot) {
|
|
120
|
+
function emitHastOp(w, node, isRoot, refs) {
|
|
126
121
|
if (node === null || typeof node !== "object")
|
|
127
122
|
return false;
|
|
128
123
|
if (!isRoot) {
|
|
129
|
-
const id = hastReusedId(node);
|
|
124
|
+
const id = hastReusedId(node, refs);
|
|
130
125
|
if (id !== undefined) {
|
|
131
126
|
w.ref(id);
|
|
132
127
|
return true;
|
|
@@ -167,7 +162,7 @@ function emitHastOp(w, node, isRoot) {
|
|
|
167
162
|
const children = n.children;
|
|
168
163
|
if (Array.isArray(children)) {
|
|
169
164
|
for (const c of children)
|
|
170
|
-
if (!emitHastOp(w, c, false))
|
|
165
|
+
if (!emitHastOp(w, c, false, refs))
|
|
171
166
|
return false;
|
|
172
167
|
}
|
|
173
168
|
w.close();
|
|
@@ -188,24 +183,29 @@ function emitHastProp(w, name, value) {
|
|
|
188
183
|
w.prop(name, PROP_SPACE_SEP, value.filter((v) => typeof v === "string").join(" "));
|
|
189
184
|
}
|
|
190
185
|
class HastVisitorContextImpl {
|
|
191
|
-
#commandBuffer =
|
|
192
|
-
#diagnostics
|
|
186
|
+
#commandBuffer = acquireCommandBuffer();
|
|
187
|
+
#diagnostics;
|
|
193
188
|
/** Track accumulated node state for multiple setProperty calls on the same node. */
|
|
194
189
|
#pendingNodes = new Map();
|
|
195
190
|
#handle;
|
|
196
191
|
#getSource;
|
|
197
192
|
#resolver;
|
|
193
|
+
#refs;
|
|
198
194
|
/** One canonical object per parent id, so visitors can dedupe by identity.
|
|
199
195
|
* Null until the first `parent()` call; most passes never make one. */
|
|
200
196
|
#parentsById = null;
|
|
201
197
|
fileURL;
|
|
202
198
|
data;
|
|
203
|
-
|
|
199
|
+
sourceFormat;
|
|
200
|
+
constructor(handle, getSource, fileURL, resolver, data, sourceFormat, diagnostics) {
|
|
204
201
|
this.#handle = handle;
|
|
205
202
|
this.#getSource = getSource;
|
|
206
203
|
this.fileURL = fileURL;
|
|
207
204
|
this.#resolver = resolver;
|
|
205
|
+
this.#refs = resolver.refs;
|
|
208
206
|
this.data = data;
|
|
207
|
+
this.sourceFormat = sourceFormat;
|
|
208
|
+
this.#diagnostics = diagnostics;
|
|
209
209
|
}
|
|
210
210
|
get source() {
|
|
211
211
|
const value = this.#getSource();
|
|
@@ -213,37 +213,71 @@ class HastVisitorContextImpl {
|
|
|
213
213
|
return value;
|
|
214
214
|
}
|
|
215
215
|
removeNode(node) {
|
|
216
|
-
this.#commandBuffer.removeNode(requireNid(node, "removeNode"));
|
|
216
|
+
this.#commandBuffer.removeNode(requireNid(node, "removeNode", this.#refs));
|
|
217
217
|
}
|
|
218
218
|
replaceNode(node, newNode) {
|
|
219
|
-
const id = requireNid(node, "replaceNode");
|
|
220
|
-
|
|
219
|
+
const id = requireNid(node, "replaceNode", this.#refs);
|
|
220
|
+
if (Array.isArray(newNode)) {
|
|
221
|
+
if (id === ROOT_NODE_ID && newNode.length > 1)
|
|
222
|
+
throw rootReplacementError(newNode);
|
|
223
|
+
// The last node carries the `replace` so refs back to the target still splice.
|
|
224
|
+
let previous;
|
|
225
|
+
for (const n of newNode) {
|
|
226
|
+
if (previous !== undefined)
|
|
227
|
+
emitHastTree(this.#commandBuffer, "insertBefore", id, previous, this.#refs);
|
|
228
|
+
previous = n;
|
|
229
|
+
}
|
|
230
|
+
if (previous === undefined) {
|
|
231
|
+
// Replacing with nothing drops the node, like removeNode.
|
|
232
|
+
this.removeNode(node);
|
|
233
|
+
}
|
|
234
|
+
else if (id === ROOT_NODE_ID) {
|
|
235
|
+
emitHastRootReplace(this.#commandBuffer, requireRootReplacement(previous), this.#refs);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
emitHastTree(this.#commandBuffer, "replace", id, previous, this.#refs);
|
|
239
|
+
}
|
|
240
|
+
// A stale queued replacement would win: setProperty folds into it, landing last.
|
|
241
|
+
this.#pendingNodes.delete(id);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (id === ROOT_NODE_ID) {
|
|
245
|
+
emitHastRootReplace(this.#commandBuffer, requireRootReplacement(newNode), this.#refs);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
emitHastTree(this.#commandBuffer, "replace", id, newNode, this.#refs);
|
|
221
249
|
// Track the replacement so a later mdxJsx setProperty can fold into it.
|
|
222
250
|
this.#pendingNodes.set(id, newNode);
|
|
223
251
|
}
|
|
224
252
|
insertBefore(node, newNode) {
|
|
225
|
-
const id = requireNid(node, "insertBefore");
|
|
253
|
+
const id = requireNid(node, "insertBefore", this.#refs);
|
|
226
254
|
for (const n of asArray(newNode))
|
|
227
|
-
emitHastTree(this.#commandBuffer, "insertBefore", id, n);
|
|
255
|
+
emitHastTree(this.#commandBuffer, "insertBefore", id, n, this.#refs);
|
|
228
256
|
}
|
|
229
257
|
insertAfter(node, newNode) {
|
|
230
|
-
const id = requireNid(node, "insertAfter");
|
|
258
|
+
const id = requireNid(node, "insertAfter", this.#refs);
|
|
231
259
|
for (const n of asArray(newNode))
|
|
232
|
-
emitHastTree(this.#commandBuffer, "insertAfter", id, n);
|
|
260
|
+
emitHastTree(this.#commandBuffer, "insertAfter", id, n, this.#refs);
|
|
233
261
|
}
|
|
234
262
|
wrapNode(node, parentNode) {
|
|
235
|
-
const id = requireNid(node, "wrapNode");
|
|
236
|
-
|
|
263
|
+
const id = requireNid(node, "wrapNode", this.#refs);
|
|
264
|
+
if (typeof parentNode.raw === "string" ||
|
|
265
|
+
typeof parentNode.rawHtml === "string") {
|
|
266
|
+
this.#commandBuffer.wrapNode(id, parentNode);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
assertHastWrapParent(parentNode);
|
|
270
|
+
emitHastTree(this.#commandBuffer, "wrapNode", id, parentNode, this.#refs);
|
|
237
271
|
}
|
|
238
272
|
prependChild(node, childNode) {
|
|
239
|
-
const id = requireNid(node, "prependChild");
|
|
273
|
+
const id = requireNid(node, "prependChild", this.#refs);
|
|
240
274
|
for (const n of asArray(childNode))
|
|
241
|
-
emitHastTree(this.#commandBuffer, "prependChild", id, n);
|
|
275
|
+
emitHastTree(this.#commandBuffer, "prependChild", id, n, this.#refs);
|
|
242
276
|
}
|
|
243
277
|
appendChild(node, childNode) {
|
|
244
|
-
const id = requireNid(node, "appendChild");
|
|
278
|
+
const id = requireNid(node, "appendChild", this.#refs);
|
|
245
279
|
for (const n of asArray(childNode))
|
|
246
|
-
emitHastTree(this.#commandBuffer, "appendChild", id, n);
|
|
280
|
+
emitHastTree(this.#commandBuffer, "appendChild", id, n, this.#refs);
|
|
247
281
|
}
|
|
248
282
|
insertChildAt(node, index, childNode) {
|
|
249
283
|
const children = "children" in node ? node.children : [];
|
|
@@ -263,14 +297,13 @@ class HastVisitorContextImpl {
|
|
|
263
297
|
this.removeNode(child);
|
|
264
298
|
}
|
|
265
299
|
setProperty(node, key, value) {
|
|
266
|
-
const id = requireNid(node, "setProperty");
|
|
300
|
+
const id = requireNid(node, "setProperty", this.#refs);
|
|
267
301
|
if (key === "children") {
|
|
268
302
|
// children is structural: set-children keeps the node and swaps only its
|
|
269
303
|
// child list (reused children keep their id).
|
|
270
|
-
|
|
271
|
-
if (!ops)
|
|
304
|
+
if (!emitHastChildrenCommand(this.#commandBuffer, id, value, this.#refs)) {
|
|
272
305
|
throw unencodableContentError(value);
|
|
273
|
-
|
|
306
|
+
}
|
|
274
307
|
return;
|
|
275
308
|
}
|
|
276
309
|
if (key === "data") {
|
|
@@ -319,10 +352,10 @@ class HastVisitorContextImpl {
|
|
|
319
352
|
this.#commandBuffer.setProperty(id, key, value);
|
|
320
353
|
}
|
|
321
354
|
textContent(node) {
|
|
322
|
-
return textContentHandle(this.#handle, requireNid(node, "textContent"));
|
|
355
|
+
return textContentHandle(this.#handle, requireNid(node, "textContent", this.#refs));
|
|
323
356
|
}
|
|
324
357
|
parent(node) {
|
|
325
|
-
const parentId = this.#resolver.parentIdOf(requireNid(node, "parent"));
|
|
358
|
+
const parentId = this.#resolver.parentIdOf(requireNid(node, "parent", this.#refs));
|
|
326
359
|
if (parentId === undefined)
|
|
327
360
|
return undefined;
|
|
328
361
|
const byId = (this.#parentsById ??= new Map());
|
|
@@ -334,10 +367,15 @@ class HastVisitorContextImpl {
|
|
|
334
367
|
return parent;
|
|
335
368
|
}
|
|
336
369
|
indexOf(node) {
|
|
337
|
-
return this.#resolver.indexInParent(requireNid(node, "indexOf"));
|
|
370
|
+
return this.#resolver.indexInParent(requireNid(node, "indexOf", this.#refs));
|
|
338
371
|
}
|
|
339
372
|
report({ message, node, severity = "error", }) {
|
|
340
|
-
|
|
373
|
+
const id = node ? nid(node, this.#refs) : undefined;
|
|
374
|
+
this.#diagnostics.push({
|
|
375
|
+
message,
|
|
376
|
+
nodeId: id === FOREIGN_REF ? undefined : id,
|
|
377
|
+
severity,
|
|
378
|
+
});
|
|
341
379
|
}
|
|
342
380
|
getCommandBuffer() {
|
|
343
381
|
return this.#commandBuffer;
|
|
@@ -348,7 +386,40 @@ class HastVisitorContextImpl {
|
|
|
348
386
|
}
|
|
349
387
|
/** Node types that use filtered visitors (have tag/component names). */
|
|
350
388
|
const FILTERED_METHODS = new Set(["element", "mdxJsxFlowElement", "mdxJsxTextElement"]);
|
|
389
|
+
const subscriptionCache = new WeakMap();
|
|
351
390
|
export function resolveSubscriptions(plugin) {
|
|
391
|
+
const cached = subscriptionCache.get(plugin);
|
|
392
|
+
if (cached !== undefined)
|
|
393
|
+
return cached.subs;
|
|
394
|
+
const built = buildSubscriptions(plugin);
|
|
395
|
+
subscriptionCache.set(plugin, built);
|
|
396
|
+
return built.subs;
|
|
397
|
+
}
|
|
398
|
+
/** Get the (cached) Rust-side projection of `subs` that strips visitFn so it
|
|
399
|
+
* can cross NAPI. Computed once per plugin object alongside `subs`. */
|
|
400
|
+
function getRustSubs(plugin) {
|
|
401
|
+
const cached = subscriptionCache.get(plugin);
|
|
402
|
+
if (cached !== undefined)
|
|
403
|
+
return cached.rustSubs;
|
|
404
|
+
const built = buildSubscriptions(plugin);
|
|
405
|
+
subscriptionCache.set(plugin, built);
|
|
406
|
+
return built.rustSubs;
|
|
407
|
+
}
|
|
408
|
+
function isFilteredVisitor(value) {
|
|
409
|
+
return (value !== null &&
|
|
410
|
+
typeof value === "object" &&
|
|
411
|
+
Array.isArray(value.filter) &&
|
|
412
|
+
typeof value.visit === "function");
|
|
413
|
+
}
|
|
414
|
+
/** Caught pre-wire so the failure names the API shape, not the internal `tagFilter` field. */
|
|
415
|
+
function malformedFilteredVisitorError(plugin, methodName) {
|
|
416
|
+
const name = plugin.name;
|
|
417
|
+
const pluginName = typeof name === "string" && name !== "" ? name : "(unnamed)";
|
|
418
|
+
return new Error(`hast plugin "${pluginName}": "${methodName}" visitors filter by tag/component name, ` +
|
|
419
|
+
`so each must be an object { filter: string[], visit: function } (or an array of those). ` +
|
|
420
|
+
`Use filter: [] to visit every "${methodName}" node.`);
|
|
421
|
+
}
|
|
422
|
+
function buildSubscriptions(plugin) {
|
|
352
423
|
const subs = [];
|
|
353
424
|
for (const [methodName, nodeType] of Object.entries(METHOD_TO_TYPE)) {
|
|
354
425
|
const value = plugin[methodName];
|
|
@@ -357,6 +428,8 @@ export function resolveSubscriptions(plugin) {
|
|
|
357
428
|
if (FILTERED_METHODS.has(methodName)) {
|
|
358
429
|
const items = Array.isArray(value) ? value : [value];
|
|
359
430
|
for (const fv of items) {
|
|
431
|
+
if (!isFilteredVisitor(fv))
|
|
432
|
+
throw malformedFilteredVisitorError(plugin, methodName);
|
|
360
433
|
subs.push({
|
|
361
434
|
nodeType,
|
|
362
435
|
tagFilter: fv.filter,
|
|
@@ -369,7 +442,8 @@ export function resolveSubscriptions(plugin) {
|
|
|
369
442
|
subs.push({ nodeType, tagFilter: [], visitFn: value });
|
|
370
443
|
}
|
|
371
444
|
}
|
|
372
|
-
|
|
445
|
+
const rustSubs = subs.map((s) => ({ nodeType: s.nodeType, tagFilter: s.tagFilter }));
|
|
446
|
+
return { subs, rustSubs };
|
|
373
447
|
}
|
|
374
448
|
/** Visitor method name → node-type tag (method names are the subscribable AST names). */
|
|
375
449
|
const METHOD_TO_TYPE = Object.fromEntries([...VISITOR_KEYS].map((name) => [name, NAME_TO_TYPE[name]]));
|
|
@@ -393,11 +467,18 @@ function decodeProperties(view, buf, pos) {
|
|
|
393
467
|
return properties;
|
|
394
468
|
}
|
|
395
469
|
/** Build the child-stub list for a matched node from the wire's `[child_ids]
|
|
396
|
-
* [child_types]` blocks
|
|
397
|
-
*
|
|
398
|
-
*
|
|
470
|
+
* [child_types]` blocks, no arena snapshot. Stale ids are caught at
|
|
471
|
+
* materialization: the resolver's epoch check refuses a snapshot once the
|
|
472
|
+
* arena has mutated or been dropped. */
|
|
399
473
|
function readChildStubs(view, buf, idsPos, typesPos, count, resolver) {
|
|
400
|
-
|
|
474
|
+
// With a hot snapshot a stub's deferral buys nothing; real nodes skip its per-field getters.
|
|
475
|
+
if (resolver.hasHotSnapshot()) {
|
|
476
|
+
const nodes = new Array(count);
|
|
477
|
+
for (let i = 0; i < count; i++) {
|
|
478
|
+
nodes[i] = resolver.materializeOne(view.getUint32(idsPos + i * 4, true));
|
|
479
|
+
}
|
|
480
|
+
return nodes;
|
|
481
|
+
}
|
|
401
482
|
const stubs = new Array(count);
|
|
402
483
|
for (let i = 0; i < count; i++) {
|
|
403
484
|
stubs[i] = new HastChildStub(resolver, view.getUint32(idsPos + i * 4, true), buf[typesPos + i]);
|
|
@@ -421,23 +502,19 @@ class WalkElement {
|
|
|
421
502
|
type = "element";
|
|
422
503
|
tagName;
|
|
423
504
|
#nodeId;
|
|
424
|
-
#
|
|
425
|
-
#buf;
|
|
505
|
+
#wire;
|
|
426
506
|
#propsPos;
|
|
427
507
|
#childIdsPos;
|
|
428
508
|
#childTypesPos;
|
|
429
509
|
#childCount;
|
|
430
|
-
|
|
431
|
-
constructor(tagName, nodeId, view, buf, propsPos, propCount, childIdsPos, childTypesPos, childCount, resolver) {
|
|
510
|
+
constructor(tagName, nodeId, wire, propsPos, propCount, childIdsPos, childTypesPos, childCount) {
|
|
432
511
|
this.tagName = tagName;
|
|
433
512
|
this.#nodeId = nodeId;
|
|
434
|
-
this.#
|
|
435
|
-
this.#buf = buf;
|
|
513
|
+
this.#wire = wire;
|
|
436
514
|
this.#propsPos = propsPos;
|
|
437
515
|
this.#childIdsPos = childIdsPos;
|
|
438
516
|
this.#childTypesPos = childTypesPos;
|
|
439
517
|
this.#childCount = childCount;
|
|
440
|
-
this.#resolver = resolver;
|
|
441
518
|
if (propCount === 0) {
|
|
442
519
|
this.properties = {};
|
|
443
520
|
}
|
|
@@ -455,12 +532,17 @@ class WalkElement {
|
|
|
455
532
|
get _nid() {
|
|
456
533
|
return this.#nodeId;
|
|
457
534
|
}
|
|
535
|
+
/** @internal */
|
|
536
|
+
get _refs() {
|
|
537
|
+
return this.#wire.resolver.refs;
|
|
538
|
+
}
|
|
458
539
|
static {
|
|
459
540
|
WALK_PROPS_DESC = {
|
|
460
541
|
enumerable: true,
|
|
461
542
|
configurable: true,
|
|
462
543
|
get() {
|
|
463
|
-
const
|
|
544
|
+
const w = this.#wire;
|
|
545
|
+
const val = decodeProperties(w.view, w.buf, this.#propsPos);
|
|
464
546
|
Object.defineProperty(this, "properties", {
|
|
465
547
|
value: val,
|
|
466
548
|
writable: true,
|
|
@@ -474,7 +556,8 @@ class WalkElement {
|
|
|
474
556
|
enumerable: true,
|
|
475
557
|
configurable: true,
|
|
476
558
|
get() {
|
|
477
|
-
const
|
|
559
|
+
const w = this.#wire;
|
|
560
|
+
const val = readChildStubs(w.view, w.buf, this.#childIdsPos, this.#childTypesPos, this.#childCount, w.resolver);
|
|
478
561
|
Object.defineProperty(this, "children", {
|
|
479
562
|
value: val,
|
|
480
563
|
writable: true,
|
|
@@ -488,15 +571,15 @@ class WalkElement {
|
|
|
488
571
|
}
|
|
489
572
|
/** Read the tail of a matched element node (tag + properties).
|
|
490
573
|
* Common prelude (data/position/children) is already consumed by `readMatchedNode`. */
|
|
491
|
-
function readElementFromBinary(
|
|
574
|
+
function readElementFromBinary(wire, offset, nodeId, position, childIdsPos, childTypesPos, childCount, data) {
|
|
492
575
|
let pos = offset;
|
|
493
576
|
// Eager: tagName (almost always accessed by visitors)
|
|
494
|
-
const tagLen = view.getUint16(pos, true);
|
|
577
|
+
const tagLen = wire.view.getUint16(pos, true);
|
|
495
578
|
pos += 2;
|
|
496
|
-
const tagName = rstr(buf, pos, tagLen);
|
|
579
|
+
const tagName = rstr(wire.buf, pos, tagLen);
|
|
497
580
|
pos += tagLen;
|
|
498
|
-
const propCount = view.getUint16(pos, true);
|
|
499
|
-
const node = new WalkElement(tagName, nodeId,
|
|
581
|
+
const propCount = wire.view.getUint16(pos, true);
|
|
582
|
+
const node = new WalkElement(tagName, nodeId, wire, pos, propCount, childIdsPos, childTypesPos, childCount);
|
|
500
583
|
if (position !== undefined)
|
|
501
584
|
node.position = position;
|
|
502
585
|
if (data !== null)
|
|
@@ -505,7 +588,7 @@ function readElementFromBinary(view, buf, offset, nodeId, resolver, position, ch
|
|
|
505
588
|
}
|
|
506
589
|
/** Value-carrying types read by `readTextFromBinary` (tag → AST name). */
|
|
507
590
|
const TEXT_NODE_TYPES = Object.fromEntries(["text", "comment", "raw", "mdxFlowExpression", "mdxTextExpression", "mdxjsEsm"].map((name) => [NAME_TO_TYPE[name], name]));
|
|
508
|
-
function readTextFromBinary(view, buf, offset, nodeId, nodeType, position, data) {
|
|
591
|
+
function readTextFromBinary(view, buf, offset, nodeId, nodeType, position, data, refs) {
|
|
509
592
|
const valLen = view.getUint32(offset, true);
|
|
510
593
|
const rawValue = rstr(buf, offset + 4, valLen);
|
|
511
594
|
// MDX flow/text expressions store phantom-space sentinels; restore them so
|
|
@@ -519,7 +602,7 @@ function readTextFromBinary(view, buf, offset, nodeId, nodeType, position, data)
|
|
|
519
602
|
if (data !== null)
|
|
520
603
|
base.data = data;
|
|
521
604
|
const node = base;
|
|
522
|
-
|
|
605
|
+
refs.set(node, nodeId);
|
|
523
606
|
if (nodeType === HAST_MDX_FLOW_EXPRESSION || nodeType === HAST_MDX_TEXT_EXPRESSION) {
|
|
524
607
|
attachParseExpression(node, napiParseExpression);
|
|
525
608
|
}
|
|
@@ -557,11 +640,12 @@ function readMdxJsxFromBinary(view, buf, offset, nodeId, nodeType, resolver, pos
|
|
|
557
640
|
base.position = position;
|
|
558
641
|
if (data !== null)
|
|
559
642
|
base.data = data;
|
|
560
|
-
|
|
643
|
+
resolver.refs.set(base, nodeId);
|
|
561
644
|
makeLazyChildren(base, view, buf, childIdsPos, childTypesPos, childCount, resolver);
|
|
562
645
|
return base;
|
|
563
646
|
}
|
|
564
|
-
function readMatchedNode(
|
|
647
|
+
function readMatchedNode(wire, offset, nodeId, nodeType) {
|
|
648
|
+
const { view, buf, resolver } = wire;
|
|
565
649
|
let pos = offset;
|
|
566
650
|
// Shared prelude (matches serialize_hast_node_inline / serialize_mdast_node_inline):
|
|
567
651
|
// [data_len: u32][data_bytes][position: 24B][child_count: u32][child_ids: N×u32][child_types: N×u8]
|
|
@@ -591,7 +675,7 @@ function readMatchedNode(view, buf, offset, nodeId, nodeType, resolver) {
|
|
|
591
675
|
pos += childCount;
|
|
592
676
|
// Dispatch to type-specific tail (pos now sits at the type-specific section)
|
|
593
677
|
if (nodeType === HAST_ELEMENT) {
|
|
594
|
-
return readElementFromBinary(
|
|
678
|
+
return readElementFromBinary(wire, pos, nodeId, position, childIdsPos, childTypesPos, childCount, data);
|
|
595
679
|
}
|
|
596
680
|
else if (nodeType === HAST_TEXT ||
|
|
597
681
|
nodeType === HAST_COMMENT ||
|
|
@@ -599,27 +683,40 @@ function readMatchedNode(view, buf, offset, nodeId, nodeType, resolver) {
|
|
|
599
683
|
nodeType === HAST_MDX_FLOW_EXPRESSION ||
|
|
600
684
|
nodeType === HAST_MDX_TEXT_EXPRESSION ||
|
|
601
685
|
nodeType === HAST_MDX_ESM) {
|
|
602
|
-
return readTextFromBinary(view, buf, pos, nodeId, nodeType, position, data);
|
|
686
|
+
return readTextFromBinary(view, buf, pos, nodeId, nodeType, position, data, resolver.refs);
|
|
603
687
|
}
|
|
604
688
|
else if (nodeType === HAST_MDX_JSX_ELEMENT || nodeType === HAST_MDX_JSX_TEXT_ELEMENT) {
|
|
605
689
|
return readMdxJsxFromBinary(view, buf, pos, nodeId, nodeType, resolver, position, childIdsPos, childTypesPos, childCount, data);
|
|
606
690
|
}
|
|
607
|
-
// Fallback
|
|
691
|
+
// Fallback: root and doctype.
|
|
608
692
|
const base = { type: TYPE_NAMES[nodeType] ?? `unknown(${nodeType})` };
|
|
609
693
|
if (position !== undefined)
|
|
610
694
|
base.position = position;
|
|
611
695
|
if (data !== null)
|
|
612
696
|
base.data = data;
|
|
697
|
+
if (nodeType === HAST_ROOT) {
|
|
698
|
+
// `...root.children` has to work in a hook.
|
|
699
|
+
if (childCount > 0) {
|
|
700
|
+
makeLazyChildren(base, view, buf, childIdsPos, childTypesPos, childCount, resolver);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
base.children = [];
|
|
704
|
+
}
|
|
705
|
+
}
|
|
613
706
|
const node = base;
|
|
614
|
-
|
|
707
|
+
resolver.refs.set(node, nodeId);
|
|
615
708
|
return node;
|
|
616
709
|
}
|
|
710
|
+
const HAST_EPOCH_CACHE = registerEpochCacheSlot(new WeakMap());
|
|
617
711
|
class HastLazyChildResolver extends LazyChildResolver {
|
|
712
|
+
cacheSlot() {
|
|
713
|
+
return HAST_EPOCH_CACHE;
|
|
714
|
+
}
|
|
618
715
|
createReader(wire) {
|
|
619
716
|
return new HastReader(wire);
|
|
620
717
|
}
|
|
621
|
-
materializeNode(reader, nodeId) {
|
|
622
|
-
return materializeHastNode(reader, nodeId);
|
|
718
|
+
materializeNode(reader, nodeId, refs) {
|
|
719
|
+
return materializeHastNode(reader, nodeId, true, refs);
|
|
623
720
|
}
|
|
624
721
|
readParentId(reader, nodeId) {
|
|
625
722
|
return reader.getParentId(nodeId);
|
|
@@ -649,27 +746,54 @@ function makeLazyChildren(node, view, buf, childIdsPos, childTypesPos, childCoun
|
|
|
649
746
|
});
|
|
650
747
|
}
|
|
651
748
|
/** A result that is the same object as the input node is a no-op, so context
|
|
652
|
-
* mutations (e.g. setProperty) are not clobbered.
|
|
653
|
-
|
|
749
|
+
* mutations (e.g. setProperty) are not clobbered.
|
|
750
|
+
*
|
|
751
|
+
* A same-type text-like node carrying only a new `value` becomes a
|
|
752
|
+
* setProperty("value") rather than a structural replace, which would force
|
|
753
|
+
* the arena into a full rebuild for a shape that didn't change. */
|
|
754
|
+
function applyHastVisitResult(result, nodeId, returnBuffer, originalNode, refs) {
|
|
654
755
|
if (result == null)
|
|
655
|
-
return
|
|
756
|
+
return;
|
|
656
757
|
if (result === originalNode)
|
|
657
|
-
return
|
|
758
|
+
return;
|
|
759
|
+
if (isTextValueSwap(result, originalNode)) {
|
|
760
|
+
returnBuffer.setProperty(nodeId, "value", result.value);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
emitHastTree(returnBuffer, "replace", nodeId, result, refs);
|
|
764
|
+
}
|
|
765
|
+
function handleVisitResult(result, nodeId, returnBuffer, deferred, originalNode, refs) {
|
|
658
766
|
if (result instanceof Promise) {
|
|
659
767
|
const list = deferred ?? [];
|
|
660
768
|
list.push({ nodeId, promise: result, originalNode });
|
|
661
769
|
return list;
|
|
662
770
|
}
|
|
663
|
-
|
|
771
|
+
applyHastVisitResult(result, nodeId, returnBuffer, originalNode, refs);
|
|
664
772
|
return deferred;
|
|
665
773
|
}
|
|
666
|
-
/**
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
|
|
774
|
+
/** True when `result` is a same-type text-like node carrying only `type` +
|
|
775
|
+
* `value`. The explicit `=== undefined` checks avoid the array alloc of
|
|
776
|
+
* `Object.keys().length` on this per-text-node hot path. */
|
|
777
|
+
function isTextValueSwap(result, original) {
|
|
778
|
+
if (result.type !== original.type)
|
|
779
|
+
return false;
|
|
780
|
+
if (result.type !== "text" && result.type !== "comment" && result.type !== "raw")
|
|
781
|
+
return false;
|
|
782
|
+
const r = result;
|
|
783
|
+
if (typeof r.value !== "string")
|
|
784
|
+
return false;
|
|
785
|
+
return (r.children === undefined &&
|
|
786
|
+
r.position === undefined &&
|
|
787
|
+
r.data === undefined &&
|
|
788
|
+
r.tagName === undefined &&
|
|
789
|
+
r.properties === undefined &&
|
|
790
|
+
r.name === undefined &&
|
|
791
|
+
r.attributes === undefined);
|
|
792
|
+
}
|
|
670
793
|
function dispatchMatches(matchBuf, subs, ctx, returnBuffer, resolver) {
|
|
671
794
|
const matchView = new DataView(matchBuf.buffer, matchBuf.byteOffset, matchBuf.byteLength);
|
|
672
795
|
const matchCount = matchView.getUint32(0, true);
|
|
796
|
+
const wire = { view: matchView, buf: matchBuf, resolver };
|
|
673
797
|
let deferred = null;
|
|
674
798
|
for (let i = 0; i < matchCount; i++) {
|
|
675
799
|
const indexBase = 4 + i * 10;
|
|
@@ -677,9 +801,9 @@ function dispatchMatches(matchBuf, subs, ctx, returnBuffer, resolver) {
|
|
|
677
801
|
const subIndex = matchBuf[indexBase + 4];
|
|
678
802
|
const dataOffset = matchView.getUint32(indexBase + 6, true);
|
|
679
803
|
const sub = subs[subIndex];
|
|
680
|
-
const node = readMatchedNode(
|
|
804
|
+
const node = readMatchedNode(wire, dataOffset, nodeId, sub.nodeType);
|
|
681
805
|
const result = sub.visitFn(node, ctx);
|
|
682
|
-
deferred = handleVisitResult(result, nodeId, returnBuffer, deferred, node);
|
|
806
|
+
deferred = handleVisitResult(result, nodeId, returnBuffer, deferred, node, resolver.refs);
|
|
683
807
|
}
|
|
684
808
|
return deferred;
|
|
685
809
|
}
|
|
@@ -691,35 +815,77 @@ function dispatchMatches(matchBuf, subs, ctx, returnBuffer, resolver) {
|
|
|
691
815
|
* replaced earlier in the same pass (the caller warns when non-zero), or a
|
|
692
816
|
* Promise of that count if any visitor is async.
|
|
693
817
|
*/
|
|
694
|
-
export function visitHastHandle(handle, plugin, subs, source, fileURL, data = {}) {
|
|
818
|
+
export function visitHastHandle(handle, plugin, subs, source, fileURL, data = {}, sourceFormat = "markdown", diagnostics = []) {
|
|
819
|
+
const result = visitHastHandleCollect(handle, plugin, subs, source, fileURL, data, sourceFormat, diagnostics);
|
|
820
|
+
if (result instanceof Promise) {
|
|
821
|
+
return result.then((commands) => applyCollectedCommands(handle, commands));
|
|
822
|
+
}
|
|
823
|
+
return applyCollectedCommands(handle, result);
|
|
824
|
+
}
|
|
825
|
+
/** Apply commands collected by `visitHastHandleCollect`; returns the number of
|
|
826
|
+
* patches dropped as stranded (0 when none). */
|
|
827
|
+
function applyCollectedCommands(handle, commands) {
|
|
828
|
+
if (commands.length === 0)
|
|
829
|
+
return 0;
|
|
830
|
+
markHandleMutated(handle);
|
|
831
|
+
return applyCommandsToHandle(handle, commands);
|
|
832
|
+
}
|
|
833
|
+
/** Run a HAST visitor, build the command buffer, but do NOT apply it. Returns
|
|
834
|
+
* the merged commands so the caller can choose how to dispatch: either via
|
|
835
|
+
* `applyCommandsToHandle` (intermediate plugins in a chain) or via a fused
|
|
836
|
+
* NAPI call like `applyCommandsAndRenderHandle` (final plugin, saves one
|
|
837
|
+
* apply + one render + one drop crossing). Empty result means no mutations.
|
|
838
|
+
*/
|
|
839
|
+
export function visitHastHandleCollect(handle, plugin, subs, source, fileURL, data = {}, sourceFormat = "markdown", diagnostics = []) {
|
|
695
840
|
const getSource = typeof source === "function" ? source : () => source;
|
|
696
841
|
const resolver = new HastLazyChildResolver(handle);
|
|
697
|
-
const ctx = new HastVisitorContextImpl(handle, getSource, fileURL, resolver, data);
|
|
698
|
-
const returnBuffer =
|
|
699
|
-
const rustSubs =
|
|
842
|
+
const ctx = new HastVisitorContextImpl(handle, getSource, fileURL, resolver, data, sourceFormat, diagnostics);
|
|
843
|
+
const returnBuffer = acquireCommandBuffer();
|
|
844
|
+
const rustSubs = getRustSubs(plugin);
|
|
700
845
|
const deferred = dispatchMatches(walkHandle(handle, rustSubs), subs, ctx, returnBuffer, resolver);
|
|
701
846
|
if (deferred) {
|
|
702
847
|
return Promise.all(deferred.map((d) => d.promise.then((result) => ({ nodeId: d.nodeId, result, originalNode: d.originalNode })))).then((results) => {
|
|
703
848
|
for (const { nodeId, result, originalNode } of results) {
|
|
704
|
-
|
|
705
|
-
emitHastTree(returnBuffer, "replace", nodeId, result);
|
|
706
|
-
}
|
|
849
|
+
applyHastVisitResult(result, nodeId, returnBuffer, originalNode, resolver.refs);
|
|
707
850
|
}
|
|
708
|
-
|
|
709
|
-
// this point would resolve match-time child ids against wrong nodes.
|
|
710
|
-
resolver.seal();
|
|
711
|
-
return applyMutations(handle, returnBuffer, ctx);
|
|
851
|
+
return collectCommands(returnBuffer, ctx);
|
|
712
852
|
});
|
|
713
853
|
}
|
|
714
|
-
|
|
715
|
-
|
|
854
|
+
return collectCommands(returnBuffer, ctx);
|
|
855
|
+
}
|
|
856
|
+
const HAST_ROOT_SUBS = [
|
|
857
|
+
{ nodeType: HAST_ROOT, tagFilter: [] },
|
|
858
|
+
];
|
|
859
|
+
/** Its own pass so the caller can apply what `before` queued before the
|
|
860
|
+
* visitors walk, and the visitors' mutations before `after` reads the tree. */
|
|
861
|
+
export function visitHastHookCollect(handle, plugin, hook, source, fileURL, data = {}, sourceFormat = "markdown", diagnostics = []) {
|
|
862
|
+
const getSource = typeof source === "function" ? source : () => source;
|
|
863
|
+
const resolver = new HastLazyChildResolver(handle);
|
|
864
|
+
const ctx = new HastVisitorContextImpl(handle, getSource, fileURL, resolver, data, sourceFormat, diagnostics);
|
|
865
|
+
const returnBuffer = acquireCommandBuffer();
|
|
866
|
+
const matchBuf = walkHandle(handle, HAST_ROOT_SUBS);
|
|
867
|
+
const matchView = new DataView(matchBuf.buffer, matchBuf.byteOffset, matchBuf.byteLength);
|
|
868
|
+
if (matchView.getUint32(0, true) === 0)
|
|
869
|
+
return collectCommands(returnBuffer, ctx);
|
|
870
|
+
const wire = { view: matchView, buf: matchBuf, resolver };
|
|
871
|
+
const root = readMatchedNode(wire, matchView.getUint32(10, true), matchView.getUint32(4, true), HAST_ROOT);
|
|
872
|
+
const result = hook.call(plugin, root, ctx);
|
|
873
|
+
if (result instanceof Promise)
|
|
874
|
+
return result.then(() => collectCommands(returnBuffer, ctx));
|
|
875
|
+
return collectCommands(returnBuffer, ctx);
|
|
716
876
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
markHandleMutated(handle);
|
|
722
|
-
return applyCommandsToHandle(handle, merged);
|
|
877
|
+
export function visitHastHook(handle, plugin, hook, source, fileURL, data = {}, sourceFormat = "markdown", diagnostics = []) {
|
|
878
|
+
const result = visitHastHookCollect(handle, plugin, hook, source, fileURL, data, sourceFormat, diagnostics);
|
|
879
|
+
if (result instanceof Promise) {
|
|
880
|
+
return result.then((commands) => applyCollectedCommands(handle, commands));
|
|
723
881
|
}
|
|
724
|
-
return
|
|
882
|
+
return applyCollectedCommands(handle, result);
|
|
883
|
+
}
|
|
884
|
+
function collectCommands(returnBuffer, ctx) {
|
|
885
|
+
const { merged } = mergeAndReset(returnBuffer, ctx);
|
|
886
|
+
// Return the buffers to the pool. The merged Uint8Array above already
|
|
887
|
+
// copied the bytes out, so the underlying ArrayBuffers can be reused.
|
|
888
|
+
releaseCommandBuffer(returnBuffer);
|
|
889
|
+
releaseCommandBuffer(ctx.getCommandBuffer());
|
|
890
|
+
return merged;
|
|
725
891
|
}
|