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
|
@@ -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,26 +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;
|
|
204
|
-
constructor(handle, getSource, fileURL, resolver, data, sourceFormat) {
|
|
200
|
+
constructor(handle, getSource, fileURL, resolver, data, sourceFormat, diagnostics) {
|
|
205
201
|
this.#handle = handle;
|
|
206
202
|
this.#getSource = getSource;
|
|
207
203
|
this.fileURL = fileURL;
|
|
208
204
|
this.#resolver = resolver;
|
|
205
|
+
this.#refs = resolver.refs;
|
|
209
206
|
this.data = data;
|
|
210
207
|
this.sourceFormat = sourceFormat;
|
|
208
|
+
this.#diagnostics = diagnostics;
|
|
211
209
|
}
|
|
212
210
|
get source() {
|
|
213
211
|
const value = this.#getSource();
|
|
@@ -215,37 +213,71 @@ class HastVisitorContextImpl {
|
|
|
215
213
|
return value;
|
|
216
214
|
}
|
|
217
215
|
removeNode(node) {
|
|
218
|
-
this.#commandBuffer.removeNode(requireNid(node, "removeNode"));
|
|
216
|
+
this.#commandBuffer.removeNode(requireNid(node, "removeNode", this.#refs));
|
|
219
217
|
}
|
|
220
218
|
replaceNode(node, newNode) {
|
|
221
|
-
const id = requireNid(node, "replaceNode");
|
|
222
|
-
|
|
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);
|
|
223
249
|
// Track the replacement so a later mdxJsx setProperty can fold into it.
|
|
224
250
|
this.#pendingNodes.set(id, newNode);
|
|
225
251
|
}
|
|
226
252
|
insertBefore(node, newNode) {
|
|
227
|
-
const id = requireNid(node, "insertBefore");
|
|
253
|
+
const id = requireNid(node, "insertBefore", this.#refs);
|
|
228
254
|
for (const n of asArray(newNode))
|
|
229
|
-
emitHastTree(this.#commandBuffer, "insertBefore", id, n);
|
|
255
|
+
emitHastTree(this.#commandBuffer, "insertBefore", id, n, this.#refs);
|
|
230
256
|
}
|
|
231
257
|
insertAfter(node, newNode) {
|
|
232
|
-
const id = requireNid(node, "insertAfter");
|
|
258
|
+
const id = requireNid(node, "insertAfter", this.#refs);
|
|
233
259
|
for (const n of asArray(newNode))
|
|
234
|
-
emitHastTree(this.#commandBuffer, "insertAfter", id, n);
|
|
260
|
+
emitHastTree(this.#commandBuffer, "insertAfter", id, n, this.#refs);
|
|
235
261
|
}
|
|
236
262
|
wrapNode(node, parentNode) {
|
|
237
|
-
const id = requireNid(node, "wrapNode");
|
|
238
|
-
|
|
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);
|
|
239
271
|
}
|
|
240
272
|
prependChild(node, childNode) {
|
|
241
|
-
const id = requireNid(node, "prependChild");
|
|
273
|
+
const id = requireNid(node, "prependChild", this.#refs);
|
|
242
274
|
for (const n of asArray(childNode))
|
|
243
|
-
emitHastTree(this.#commandBuffer, "prependChild", id, n);
|
|
275
|
+
emitHastTree(this.#commandBuffer, "prependChild", id, n, this.#refs);
|
|
244
276
|
}
|
|
245
277
|
appendChild(node, childNode) {
|
|
246
|
-
const id = requireNid(node, "appendChild");
|
|
278
|
+
const id = requireNid(node, "appendChild", this.#refs);
|
|
247
279
|
for (const n of asArray(childNode))
|
|
248
|
-
emitHastTree(this.#commandBuffer, "appendChild", id, n);
|
|
280
|
+
emitHastTree(this.#commandBuffer, "appendChild", id, n, this.#refs);
|
|
249
281
|
}
|
|
250
282
|
insertChildAt(node, index, childNode) {
|
|
251
283
|
const children = "children" in node ? node.children : [];
|
|
@@ -265,14 +297,13 @@ class HastVisitorContextImpl {
|
|
|
265
297
|
this.removeNode(child);
|
|
266
298
|
}
|
|
267
299
|
setProperty(node, key, value) {
|
|
268
|
-
const id = requireNid(node, "setProperty");
|
|
300
|
+
const id = requireNid(node, "setProperty", this.#refs);
|
|
269
301
|
if (key === "children") {
|
|
270
302
|
// children is structural: set-children keeps the node and swaps only its
|
|
271
303
|
// child list (reused children keep their id).
|
|
272
|
-
|
|
273
|
-
if (!ops)
|
|
304
|
+
if (!emitHastChildrenCommand(this.#commandBuffer, id, value, this.#refs)) {
|
|
274
305
|
throw unencodableContentError(value);
|
|
275
|
-
|
|
306
|
+
}
|
|
276
307
|
return;
|
|
277
308
|
}
|
|
278
309
|
if (key === "data") {
|
|
@@ -321,10 +352,10 @@ class HastVisitorContextImpl {
|
|
|
321
352
|
this.#commandBuffer.setProperty(id, key, value);
|
|
322
353
|
}
|
|
323
354
|
textContent(node) {
|
|
324
|
-
return textContentHandle(this.#handle, requireNid(node, "textContent"));
|
|
355
|
+
return textContentHandle(this.#handle, requireNid(node, "textContent", this.#refs));
|
|
325
356
|
}
|
|
326
357
|
parent(node) {
|
|
327
|
-
const parentId = this.#resolver.parentIdOf(requireNid(node, "parent"));
|
|
358
|
+
const parentId = this.#resolver.parentIdOf(requireNid(node, "parent", this.#refs));
|
|
328
359
|
if (parentId === undefined)
|
|
329
360
|
return undefined;
|
|
330
361
|
const byId = (this.#parentsById ??= new Map());
|
|
@@ -336,10 +367,15 @@ class HastVisitorContextImpl {
|
|
|
336
367
|
return parent;
|
|
337
368
|
}
|
|
338
369
|
indexOf(node) {
|
|
339
|
-
return this.#resolver.indexInParent(requireNid(node, "indexOf"));
|
|
370
|
+
return this.#resolver.indexInParent(requireNid(node, "indexOf", this.#refs));
|
|
340
371
|
}
|
|
341
372
|
report({ message, node, severity = "error", }) {
|
|
342
|
-
|
|
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
|
+
});
|
|
343
379
|
}
|
|
344
380
|
getCommandBuffer() {
|
|
345
381
|
return this.#commandBuffer;
|
|
@@ -350,7 +386,40 @@ class HastVisitorContextImpl {
|
|
|
350
386
|
}
|
|
351
387
|
/** Node types that use filtered visitors (have tag/component names). */
|
|
352
388
|
const FILTERED_METHODS = new Set(["element", "mdxJsxFlowElement", "mdxJsxTextElement"]);
|
|
389
|
+
const subscriptionCache = new WeakMap();
|
|
353
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) {
|
|
354
423
|
const subs = [];
|
|
355
424
|
for (const [methodName, nodeType] of Object.entries(METHOD_TO_TYPE)) {
|
|
356
425
|
const value = plugin[methodName];
|
|
@@ -359,6 +428,8 @@ export function resolveSubscriptions(plugin) {
|
|
|
359
428
|
if (FILTERED_METHODS.has(methodName)) {
|
|
360
429
|
const items = Array.isArray(value) ? value : [value];
|
|
361
430
|
for (const fv of items) {
|
|
431
|
+
if (!isFilteredVisitor(fv))
|
|
432
|
+
throw malformedFilteredVisitorError(plugin, methodName);
|
|
362
433
|
subs.push({
|
|
363
434
|
nodeType,
|
|
364
435
|
tagFilter: fv.filter,
|
|
@@ -371,7 +442,8 @@ export function resolveSubscriptions(plugin) {
|
|
|
371
442
|
subs.push({ nodeType, tagFilter: [], visitFn: value });
|
|
372
443
|
}
|
|
373
444
|
}
|
|
374
|
-
|
|
445
|
+
const rustSubs = subs.map((s) => ({ nodeType: s.nodeType, tagFilter: s.tagFilter }));
|
|
446
|
+
return { subs, rustSubs };
|
|
375
447
|
}
|
|
376
448
|
/** Visitor method name → node-type tag (method names are the subscribable AST names). */
|
|
377
449
|
const METHOD_TO_TYPE = Object.fromEntries([...VISITOR_KEYS].map((name) => [name, NAME_TO_TYPE[name]]));
|
|
@@ -395,11 +467,18 @@ function decodeProperties(view, buf, pos) {
|
|
|
395
467
|
return properties;
|
|
396
468
|
}
|
|
397
469
|
/** Build the child-stub list for a matched node from the wire's `[child_ids]
|
|
398
|
-
* [child_types]` blocks
|
|
399
|
-
*
|
|
400
|
-
*
|
|
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. */
|
|
401
473
|
function readChildStubs(view, buf, idsPos, typesPos, count, resolver) {
|
|
402
|
-
|
|
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
|
+
}
|
|
403
482
|
const stubs = new Array(count);
|
|
404
483
|
for (let i = 0; i < count; i++) {
|
|
405
484
|
stubs[i] = new HastChildStub(resolver, view.getUint32(idsPos + i * 4, true), buf[typesPos + i]);
|
|
@@ -423,23 +502,19 @@ class WalkElement {
|
|
|
423
502
|
type = "element";
|
|
424
503
|
tagName;
|
|
425
504
|
#nodeId;
|
|
426
|
-
#
|
|
427
|
-
#buf;
|
|
505
|
+
#wire;
|
|
428
506
|
#propsPos;
|
|
429
507
|
#childIdsPos;
|
|
430
508
|
#childTypesPos;
|
|
431
509
|
#childCount;
|
|
432
|
-
|
|
433
|
-
constructor(tagName, nodeId, view, buf, propsPos, propCount, childIdsPos, childTypesPos, childCount, resolver) {
|
|
510
|
+
constructor(tagName, nodeId, wire, propsPos, propCount, childIdsPos, childTypesPos, childCount) {
|
|
434
511
|
this.tagName = tagName;
|
|
435
512
|
this.#nodeId = nodeId;
|
|
436
|
-
this.#
|
|
437
|
-
this.#buf = buf;
|
|
513
|
+
this.#wire = wire;
|
|
438
514
|
this.#propsPos = propsPos;
|
|
439
515
|
this.#childIdsPos = childIdsPos;
|
|
440
516
|
this.#childTypesPos = childTypesPos;
|
|
441
517
|
this.#childCount = childCount;
|
|
442
|
-
this.#resolver = resolver;
|
|
443
518
|
if (propCount === 0) {
|
|
444
519
|
this.properties = {};
|
|
445
520
|
}
|
|
@@ -457,12 +532,17 @@ class WalkElement {
|
|
|
457
532
|
get _nid() {
|
|
458
533
|
return this.#nodeId;
|
|
459
534
|
}
|
|
535
|
+
/** @internal */
|
|
536
|
+
get _refs() {
|
|
537
|
+
return this.#wire.resolver.refs;
|
|
538
|
+
}
|
|
460
539
|
static {
|
|
461
540
|
WALK_PROPS_DESC = {
|
|
462
541
|
enumerable: true,
|
|
463
542
|
configurable: true,
|
|
464
543
|
get() {
|
|
465
|
-
const
|
|
544
|
+
const w = this.#wire;
|
|
545
|
+
const val = decodeProperties(w.view, w.buf, this.#propsPos);
|
|
466
546
|
Object.defineProperty(this, "properties", {
|
|
467
547
|
value: val,
|
|
468
548
|
writable: true,
|
|
@@ -476,7 +556,8 @@ class WalkElement {
|
|
|
476
556
|
enumerable: true,
|
|
477
557
|
configurable: true,
|
|
478
558
|
get() {
|
|
479
|
-
const
|
|
559
|
+
const w = this.#wire;
|
|
560
|
+
const val = readChildStubs(w.view, w.buf, this.#childIdsPos, this.#childTypesPos, this.#childCount, w.resolver);
|
|
480
561
|
Object.defineProperty(this, "children", {
|
|
481
562
|
value: val,
|
|
482
563
|
writable: true,
|
|
@@ -490,15 +571,15 @@ class WalkElement {
|
|
|
490
571
|
}
|
|
491
572
|
/** Read the tail of a matched element node (tag + properties).
|
|
492
573
|
* Common prelude (data/position/children) is already consumed by `readMatchedNode`. */
|
|
493
|
-
function readElementFromBinary(
|
|
574
|
+
function readElementFromBinary(wire, offset, nodeId, position, childIdsPos, childTypesPos, childCount, data) {
|
|
494
575
|
let pos = offset;
|
|
495
576
|
// Eager: tagName (almost always accessed by visitors)
|
|
496
|
-
const tagLen = view.getUint16(pos, true);
|
|
577
|
+
const tagLen = wire.view.getUint16(pos, true);
|
|
497
578
|
pos += 2;
|
|
498
|
-
const tagName = rstr(buf, pos, tagLen);
|
|
579
|
+
const tagName = rstr(wire.buf, pos, tagLen);
|
|
499
580
|
pos += tagLen;
|
|
500
|
-
const propCount = view.getUint16(pos, true);
|
|
501
|
-
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);
|
|
502
583
|
if (position !== undefined)
|
|
503
584
|
node.position = position;
|
|
504
585
|
if (data !== null)
|
|
@@ -507,7 +588,7 @@ function readElementFromBinary(view, buf, offset, nodeId, resolver, position, ch
|
|
|
507
588
|
}
|
|
508
589
|
/** Value-carrying types read by `readTextFromBinary` (tag → AST name). */
|
|
509
590
|
const TEXT_NODE_TYPES = Object.fromEntries(["text", "comment", "raw", "mdxFlowExpression", "mdxTextExpression", "mdxjsEsm"].map((name) => [NAME_TO_TYPE[name], name]));
|
|
510
|
-
function readTextFromBinary(view, buf, offset, nodeId, nodeType, position, data) {
|
|
591
|
+
function readTextFromBinary(view, buf, offset, nodeId, nodeType, position, data, refs) {
|
|
511
592
|
const valLen = view.getUint32(offset, true);
|
|
512
593
|
const rawValue = rstr(buf, offset + 4, valLen);
|
|
513
594
|
// MDX flow/text expressions store phantom-space sentinels; restore them so
|
|
@@ -521,7 +602,7 @@ function readTextFromBinary(view, buf, offset, nodeId, nodeType, position, data)
|
|
|
521
602
|
if (data !== null)
|
|
522
603
|
base.data = data;
|
|
523
604
|
const node = base;
|
|
524
|
-
|
|
605
|
+
refs.set(node, nodeId);
|
|
525
606
|
if (nodeType === HAST_MDX_FLOW_EXPRESSION || nodeType === HAST_MDX_TEXT_EXPRESSION) {
|
|
526
607
|
attachParseExpression(node, napiParseExpression);
|
|
527
608
|
}
|
|
@@ -559,11 +640,12 @@ function readMdxJsxFromBinary(view, buf, offset, nodeId, nodeType, resolver, pos
|
|
|
559
640
|
base.position = position;
|
|
560
641
|
if (data !== null)
|
|
561
642
|
base.data = data;
|
|
562
|
-
|
|
643
|
+
resolver.refs.set(base, nodeId);
|
|
563
644
|
makeLazyChildren(base, view, buf, childIdsPos, childTypesPos, childCount, resolver);
|
|
564
645
|
return base;
|
|
565
646
|
}
|
|
566
|
-
function readMatchedNode(
|
|
647
|
+
function readMatchedNode(wire, offset, nodeId, nodeType) {
|
|
648
|
+
const { view, buf, resolver } = wire;
|
|
567
649
|
let pos = offset;
|
|
568
650
|
// Shared prelude (matches serialize_hast_node_inline / serialize_mdast_node_inline):
|
|
569
651
|
// [data_len: u32][data_bytes][position: 24B][child_count: u32][child_ids: N×u32][child_types: N×u8]
|
|
@@ -593,7 +675,7 @@ function readMatchedNode(view, buf, offset, nodeId, nodeType, resolver) {
|
|
|
593
675
|
pos += childCount;
|
|
594
676
|
// Dispatch to type-specific tail (pos now sits at the type-specific section)
|
|
595
677
|
if (nodeType === HAST_ELEMENT) {
|
|
596
|
-
return readElementFromBinary(
|
|
678
|
+
return readElementFromBinary(wire, pos, nodeId, position, childIdsPos, childTypesPos, childCount, data);
|
|
597
679
|
}
|
|
598
680
|
else if (nodeType === HAST_TEXT ||
|
|
599
681
|
nodeType === HAST_COMMENT ||
|
|
@@ -601,27 +683,40 @@ function readMatchedNode(view, buf, offset, nodeId, nodeType, resolver) {
|
|
|
601
683
|
nodeType === HAST_MDX_FLOW_EXPRESSION ||
|
|
602
684
|
nodeType === HAST_MDX_TEXT_EXPRESSION ||
|
|
603
685
|
nodeType === HAST_MDX_ESM) {
|
|
604
|
-
return readTextFromBinary(view, buf, pos, nodeId, nodeType, position, data);
|
|
686
|
+
return readTextFromBinary(view, buf, pos, nodeId, nodeType, position, data, resolver.refs);
|
|
605
687
|
}
|
|
606
688
|
else if (nodeType === HAST_MDX_JSX_ELEMENT || nodeType === HAST_MDX_JSX_TEXT_ELEMENT) {
|
|
607
689
|
return readMdxJsxFromBinary(view, buf, pos, nodeId, nodeType, resolver, position, childIdsPos, childTypesPos, childCount, data);
|
|
608
690
|
}
|
|
609
|
-
// Fallback
|
|
691
|
+
// Fallback: root and doctype.
|
|
610
692
|
const base = { type: TYPE_NAMES[nodeType] ?? `unknown(${nodeType})` };
|
|
611
693
|
if (position !== undefined)
|
|
612
694
|
base.position = position;
|
|
613
695
|
if (data !== null)
|
|
614
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
|
+
}
|
|
615
706
|
const node = base;
|
|
616
|
-
|
|
707
|
+
resolver.refs.set(node, nodeId);
|
|
617
708
|
return node;
|
|
618
709
|
}
|
|
710
|
+
const HAST_EPOCH_CACHE = registerEpochCacheSlot(new WeakMap());
|
|
619
711
|
class HastLazyChildResolver extends LazyChildResolver {
|
|
712
|
+
cacheSlot() {
|
|
713
|
+
return HAST_EPOCH_CACHE;
|
|
714
|
+
}
|
|
620
715
|
createReader(wire) {
|
|
621
716
|
return new HastReader(wire);
|
|
622
717
|
}
|
|
623
|
-
materializeNode(reader, nodeId) {
|
|
624
|
-
return materializeHastNode(reader, nodeId);
|
|
718
|
+
materializeNode(reader, nodeId, refs) {
|
|
719
|
+
return materializeHastNode(reader, nodeId, true, refs);
|
|
625
720
|
}
|
|
626
721
|
readParentId(reader, nodeId) {
|
|
627
722
|
return reader.getParentId(nodeId);
|
|
@@ -651,27 +746,54 @@ function makeLazyChildren(node, view, buf, childIdsPos, childTypesPos, childCoun
|
|
|
651
746
|
});
|
|
652
747
|
}
|
|
653
748
|
/** A result that is the same object as the input node is a no-op, so context
|
|
654
|
-
* mutations (e.g. setProperty) are not clobbered.
|
|
655
|
-
|
|
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) {
|
|
656
755
|
if (result == null)
|
|
657
|
-
return
|
|
756
|
+
return;
|
|
658
757
|
if (result === originalNode)
|
|
659
|
-
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) {
|
|
660
766
|
if (result instanceof Promise) {
|
|
661
767
|
const list = deferred ?? [];
|
|
662
768
|
list.push({ nodeId, promise: result, originalNode });
|
|
663
769
|
return list;
|
|
664
770
|
}
|
|
665
|
-
|
|
771
|
+
applyHastVisitResult(result, nodeId, returnBuffer, originalNode, refs);
|
|
666
772
|
return deferred;
|
|
667
773
|
}
|
|
668
|
-
/**
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
|
|
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
|
+
}
|
|
672
793
|
function dispatchMatches(matchBuf, subs, ctx, returnBuffer, resolver) {
|
|
673
794
|
const matchView = new DataView(matchBuf.buffer, matchBuf.byteOffset, matchBuf.byteLength);
|
|
674
795
|
const matchCount = matchView.getUint32(0, true);
|
|
796
|
+
const wire = { view: matchView, buf: matchBuf, resolver };
|
|
675
797
|
let deferred = null;
|
|
676
798
|
for (let i = 0; i < matchCount; i++) {
|
|
677
799
|
const indexBase = 4 + i * 10;
|
|
@@ -679,9 +801,9 @@ function dispatchMatches(matchBuf, subs, ctx, returnBuffer, resolver) {
|
|
|
679
801
|
const subIndex = matchBuf[indexBase + 4];
|
|
680
802
|
const dataOffset = matchView.getUint32(indexBase + 6, true);
|
|
681
803
|
const sub = subs[subIndex];
|
|
682
|
-
const node = readMatchedNode(
|
|
804
|
+
const node = readMatchedNode(wire, dataOffset, nodeId, sub.nodeType);
|
|
683
805
|
const result = sub.visitFn(node, ctx);
|
|
684
|
-
deferred = handleVisitResult(result, nodeId, returnBuffer, deferred, node);
|
|
806
|
+
deferred = handleVisitResult(result, nodeId, returnBuffer, deferred, node, resolver.refs);
|
|
685
807
|
}
|
|
686
808
|
return deferred;
|
|
687
809
|
}
|
|
@@ -693,35 +815,77 @@ function dispatchMatches(matchBuf, subs, ctx, returnBuffer, resolver) {
|
|
|
693
815
|
* replaced earlier in the same pass (the caller warns when non-zero), or a
|
|
694
816
|
* Promise of that count if any visitor is async.
|
|
695
817
|
*/
|
|
696
|
-
export function visitHastHandle(handle, plugin, subs, source, fileURL, data = {}, sourceFormat = "markdown") {
|
|
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 = []) {
|
|
697
840
|
const getSource = typeof source === "function" ? source : () => source;
|
|
698
841
|
const resolver = new HastLazyChildResolver(handle);
|
|
699
|
-
const ctx = new HastVisitorContextImpl(handle, getSource, fileURL, resolver, data, sourceFormat);
|
|
700
|
-
const returnBuffer =
|
|
701
|
-
const rustSubs =
|
|
842
|
+
const ctx = new HastVisitorContextImpl(handle, getSource, fileURL, resolver, data, sourceFormat, diagnostics);
|
|
843
|
+
const returnBuffer = acquireCommandBuffer();
|
|
844
|
+
const rustSubs = getRustSubs(plugin);
|
|
702
845
|
const deferred = dispatchMatches(walkHandle(handle, rustSubs), subs, ctx, returnBuffer, resolver);
|
|
703
846
|
if (deferred) {
|
|
704
847
|
return Promise.all(deferred.map((d) => d.promise.then((result) => ({ nodeId: d.nodeId, result, originalNode: d.originalNode })))).then((results) => {
|
|
705
848
|
for (const { nodeId, result, originalNode } of results) {
|
|
706
|
-
|
|
707
|
-
emitHastTree(returnBuffer, "replace", nodeId, result);
|
|
708
|
-
}
|
|
849
|
+
applyHastVisitResult(result, nodeId, returnBuffer, originalNode, resolver.refs);
|
|
709
850
|
}
|
|
710
|
-
|
|
711
|
-
// this point would resolve match-time child ids against wrong nodes.
|
|
712
|
-
resolver.seal();
|
|
713
|
-
return applyMutations(handle, returnBuffer, ctx);
|
|
851
|
+
return collectCommands(returnBuffer, ctx);
|
|
714
852
|
});
|
|
715
853
|
}
|
|
716
|
-
|
|
717
|
-
|
|
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);
|
|
718
876
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
markHandleMutated(handle);
|
|
724
|
-
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));
|
|
725
881
|
}
|
|
726
|
-
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;
|
|
727
891
|
}
|