satteri 0.8.0 → 0.9.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 +35 -0
- package/dist/byte-writer.d.ts +31 -0
- package/dist/byte-writer.js +106 -0
- package/dist/child-stub.d.ts +17 -0
- package/dist/child-stub.js +57 -0
- package/dist/command-buffer.d.ts +23 -24
- package/dist/command-buffer.js +65 -128
- package/dist/compile.d.ts +5 -1
- package/dist/compile.js +40 -26
- package/dist/directive-types.d.ts +4 -2
- package/dist/generated/arena-layout.d.ts +37 -0
- package/dist/generated/arena-layout.js +41 -0
- package/dist/generated/wire-constants.d.ts +51 -0
- package/dist/generated/wire-constants.js +63 -0
- package/dist/handles.d.ts +21 -0
- package/dist/handles.js +1 -0
- package/dist/hast/child-stub.d.ts +18 -0
- package/dist/hast/child-stub.js +43 -0
- package/dist/hast/element-props.d.ts +2 -0
- package/dist/hast/element-props.js +23 -0
- package/dist/hast/generated/node-types.d.ts +14 -0
- package/dist/hast/generated/node-types.js +64 -0
- package/dist/hast/hast-materializer.d.ts +2 -1
- package/dist/hast/hast-materializer.js +9 -45
- package/dist/hast/hast-reader.d.ts +18 -23
- package/dist/hast/hast-reader.js +48 -115
- package/dist/hast/hast-visitor.d.ts +38 -12
- package/dist/hast/hast-visitor.js +417 -281
- package/dist/index.d.ts +10 -4
- package/dist/index.js +23 -1
- package/dist/lazy-child-resolver.d.ts +36 -0
- package/dist/lazy-child-resolver.js +95 -0
- package/dist/lazy-props.d.ts +1 -1
- package/dist/mdast/child-stub.d.ts +18 -0
- package/dist/mdast/child-stub.js +46 -0
- package/dist/mdast/column-align.d.ts +1 -0
- package/dist/mdast/column-align.js +6 -0
- package/dist/mdast/generated/layout.d.ts +19 -0
- package/dist/mdast/generated/layout.js +272 -0
- package/dist/mdast/generated/node-types.d.ts +58 -0
- package/dist/mdast/generated/node-types.js +266 -0
- package/dist/mdast/mdast-materializer.d.ts +3 -2
- package/dist/mdast/mdast-materializer.js +9 -93
- package/dist/mdast/mdast-reader.d.ts +4 -120
- package/dist/mdast/mdast-reader.js +45 -272
- package/dist/mdast/mdast-visitor.d.ts +55 -13
- package/dist/mdast/mdast-visitor.js +375 -410
- package/dist/mdx-attr.d.ts +2 -0
- package/dist/mdx-attr.js +20 -0
- package/dist/op-stream.d.ts +38 -0
- package/dist/op-stream.js +125 -0
- package/dist/phantom.d.ts +2 -0
- package/dist/phantom.js +13 -0
- package/dist/types.d.ts +34 -4
- package/dist/visitor-shared.d.ts +28 -0
- package/dist/visitor-shared.js +52 -0
- package/dist/wire-read.d.ts +27 -0
- package/dist/wire-read.js +59 -0
- package/index.js +52 -52
- package/package.json +6 -6
|
@@ -1,75 +1,60 @@
|
|
|
1
|
-
import { materializeNode
|
|
1
|
+
import { materializeNode } from "./mdast-materializer.js";
|
|
2
2
|
import { MdastReader } from "./mdast-reader.js";
|
|
3
3
|
import { CommandBuffer, classifyReturn } from "../command-buffer.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
AppendChild: "appendChild",
|
|
13
|
-
SetProperty: "setProperty",
|
|
14
|
-
};
|
|
15
|
-
const VISITOR_KEYS = new Set([
|
|
16
|
-
"paragraph",
|
|
17
|
-
"heading",
|
|
18
|
-
"thematicBreak",
|
|
19
|
-
"blockquote",
|
|
20
|
-
"list",
|
|
21
|
-
"listItem",
|
|
22
|
-
"html",
|
|
23
|
-
"code",
|
|
24
|
-
"definition",
|
|
25
|
-
"text",
|
|
26
|
-
"emphasis",
|
|
27
|
-
"strong",
|
|
28
|
-
"inlineCode",
|
|
29
|
-
"break",
|
|
30
|
-
"link",
|
|
31
|
-
"image",
|
|
32
|
-
"linkReference",
|
|
33
|
-
"imageReference",
|
|
34
|
-
"footnoteDefinition",
|
|
35
|
-
"footnoteReference",
|
|
36
|
-
"table",
|
|
37
|
-
"tableRow",
|
|
38
|
-
"tableCell",
|
|
39
|
-
"delete",
|
|
40
|
-
"yaml",
|
|
41
|
-
"toml",
|
|
42
|
-
"math",
|
|
43
|
-
"inlineMath",
|
|
44
|
-
"containerDirective",
|
|
45
|
-
"leafDirective",
|
|
46
|
-
"textDirective",
|
|
47
|
-
"mdxJsxFlowElement",
|
|
48
|
-
"mdxJsxTextElement",
|
|
49
|
-
"mdxFlowExpression",
|
|
50
|
-
"mdxTextExpression",
|
|
51
|
-
"mdxjsEsm",
|
|
52
|
-
]);
|
|
4
|
+
import { ru32, rstr, readPosition } from "../wire-read.js";
|
|
5
|
+
import { decodeMdastTypeData } from "./generated/layout.js";
|
|
6
|
+
import { TYPE_NAMES, NAME_TO_TYPE, VISITOR_KEYS, MDAST_OPSTREAM_TYPES, } from "./generated/node-types.js";
|
|
7
|
+
import { OpWriter, OF_VALUE, OF_URL, OF_TITLE, OF_ALT, OF_LANG, OF_META, OF_IDENTIFIER, OF_LABEL, OF_NAME, OF_REFERENCE_TYPE, OF_DEPTH, OF_CHECKED, OF_START, OF_ORDERED, OF_SPREAD, OF_EXPLICIT, PROP_STRING, emitMdxAttr, } from "../op-stream.js";
|
|
8
|
+
import { walkMdastHandle, mdastTextContentHandle } from "#binding";
|
|
9
|
+
import { asArray, makeRequireNid, mergeAndReset, unencodableContentError, } from "../visitor-shared.js";
|
|
10
|
+
import { LazyChildResolver } from "../lazy-child-resolver.js";
|
|
11
|
+
import { MdastChildStub } from "./child-stub.js";
|
|
53
12
|
/** Maps MdastNode objects to their arena node IDs without Object.defineProperty overhead. */
|
|
54
13
|
const mdastNodeIdMap = new WeakMap();
|
|
55
14
|
function nid(node) {
|
|
56
|
-
|
|
15
|
+
// Genuine stubs carry their id as a plain field; a spread copy is not
|
|
16
|
+
// `instanceof` and has no `_nodeId`, so it correctly reads as new content.
|
|
17
|
+
if (node instanceof MdastChildStub)
|
|
18
|
+
return node._id;
|
|
19
|
+
const id = mdastNodeIdMap.get(node);
|
|
20
|
+
if (id !== undefined)
|
|
21
|
+
return id;
|
|
22
|
+
// Plain objects are trusted only via the WeakMap or a NON-enumerable
|
|
23
|
+
// `_nodeId` (the materializer's convention, which spread cannot copy) — an
|
|
24
|
+
// enumerable one rode in on a copy and must read as new content.
|
|
25
|
+
const d = Object.getOwnPropertyDescriptor(node, "_nodeId");
|
|
26
|
+
return d !== undefined && !d.enumerable ? d.value : undefined;
|
|
57
27
|
}
|
|
28
|
+
const requireNid = makeRequireNid(nid);
|
|
58
29
|
export class MdastVisitorContext {
|
|
59
30
|
#commandBuffer = new CommandBuffer();
|
|
60
31
|
#diagnostics = [];
|
|
61
32
|
#handle;
|
|
62
33
|
#getSource;
|
|
34
|
+
#resolver;
|
|
35
|
+
/** One canonical object per parent id, so visitors can dedupe by identity.
|
|
36
|
+
* Null until the first `parent()` call; most passes never make one. */
|
|
37
|
+
#parentsById = null;
|
|
63
38
|
/**
|
|
64
39
|
* The URL of the document being processed (the compile `fileURL` option),
|
|
65
40
|
* or `undefined` when none was given. Use `fileURLToPath(ctx.fileURL)` for a
|
|
66
41
|
* decoded filesystem path.
|
|
67
42
|
*/
|
|
68
43
|
fileURL;
|
|
69
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Document-level data bag, shared across every plugin in the compile and
|
|
46
|
+
* across the mdast→hast phase boundary. Mutate keys directly
|
|
47
|
+
* (`ctx.data.foo = x`); the bag itself isn't reassignable. Values are kept
|
|
48
|
+
* on the JS side, so any value is allowed, including functions and class
|
|
49
|
+
* instances. Returned to the caller as `result.data`.
|
|
50
|
+
*/
|
|
51
|
+
data;
|
|
52
|
+
constructor(handle, getSource, fileURL, resolver, data) {
|
|
70
53
|
this.#handle = handle;
|
|
71
54
|
this.#getSource = getSource;
|
|
72
55
|
this.fileURL = fileURL;
|
|
56
|
+
this.#resolver = resolver;
|
|
57
|
+
this.data = data;
|
|
73
58
|
}
|
|
74
59
|
get source() {
|
|
75
60
|
const value = this.#getSource();
|
|
@@ -77,41 +62,99 @@ export class MdastVisitorContext {
|
|
|
77
62
|
return value;
|
|
78
63
|
}
|
|
79
64
|
removeNode(node) {
|
|
80
|
-
this.#commandBuffer.removeNode(
|
|
65
|
+
this.#commandBuffer.removeNode(requireNid(node, "removeNode"));
|
|
81
66
|
}
|
|
82
67
|
insertBefore(node, newNode) {
|
|
83
|
-
|
|
68
|
+
const id = requireNid(node, "insertBefore");
|
|
69
|
+
for (const n of asArray(newNode))
|
|
70
|
+
emitMdastTree(this.#commandBuffer, "insertBefore", id, n);
|
|
84
71
|
}
|
|
85
72
|
insertAfter(node, newNode) {
|
|
86
|
-
|
|
73
|
+
const id = requireNid(node, "insertAfter");
|
|
74
|
+
for (const n of asArray(newNode))
|
|
75
|
+
emitMdastTree(this.#commandBuffer, "insertAfter", id, n);
|
|
87
76
|
}
|
|
88
77
|
/**
|
|
89
78
|
* Wrap `node` in `parentNode`, making it `parentNode`'s first child. Any
|
|
90
79
|
* children `parentNode` declares are kept after it.
|
|
91
80
|
*/
|
|
92
81
|
wrapNode(node, parentNode) {
|
|
93
|
-
|
|
82
|
+
const id = requireNid(node, "wrapNode");
|
|
83
|
+
emitMdastTree(this.#commandBuffer, "wrapNode", id, parentNode);
|
|
94
84
|
}
|
|
95
85
|
prependChild(node, childNode) {
|
|
96
|
-
|
|
86
|
+
const id = requireNid(node, "prependChild");
|
|
87
|
+
for (const n of asArray(childNode))
|
|
88
|
+
emitMdastTree(this.#commandBuffer, "prependChild", id, n);
|
|
97
89
|
}
|
|
98
90
|
appendChild(node, childNode) {
|
|
99
|
-
|
|
91
|
+
const id = requireNid(node, "appendChild");
|
|
92
|
+
for (const n of asArray(childNode))
|
|
93
|
+
emitMdastTree(this.#commandBuffer, "appendChild", id, n);
|
|
94
|
+
}
|
|
95
|
+
/** Insert one node or an array at `index`; clamps (`0` or less prepends, past the end appends). */
|
|
96
|
+
insertChildAt(node, index, childNode) {
|
|
97
|
+
const children = "children" in node ? node.children : [];
|
|
98
|
+
if (index <= 0 || children.length === 0) {
|
|
99
|
+
this.prependChild(node, childNode);
|
|
100
|
+
}
|
|
101
|
+
else if (index >= children.length) {
|
|
102
|
+
this.appendChild(node, childNode);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.insertBefore(children[index], childNode);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Remove the `index`-th child of `node`; a no-op when there is no such child. */
|
|
109
|
+
removeChildAt(node, index) {
|
|
110
|
+
const child = "children" in node ? node.children[index] : undefined;
|
|
111
|
+
if (child)
|
|
112
|
+
this.removeNode(child);
|
|
100
113
|
}
|
|
101
114
|
replaceNode(node, newNode) {
|
|
102
|
-
|
|
115
|
+
const id = requireNid(node, "replaceNode");
|
|
116
|
+
emitMdastTree(this.#commandBuffer, "replace", id, newNode, true);
|
|
103
117
|
}
|
|
104
118
|
setProperty(node, key, value) {
|
|
119
|
+
if (key === "children") {
|
|
120
|
+
// children is structural: set-children keeps the node and swaps only its
|
|
121
|
+
// child list (reused children keep their id).
|
|
122
|
+
const id = requireNid(node, "setProperty");
|
|
123
|
+
const ops = compileMdastChildrenToOpstream(value);
|
|
124
|
+
if (!ops)
|
|
125
|
+
throw unencodableContentError(value);
|
|
126
|
+
this.#commandBuffer.setChildrenOpstream(id, ops);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
105
129
|
if (key === "data") {
|
|
106
130
|
// data is stored as JSON in the arena, serialize it for the command buffer
|
|
107
|
-
this.#commandBuffer.setProperty(
|
|
131
|
+
this.#commandBuffer.setProperty(requireNid(node, "setProperty"), key, value != null ? JSON.stringify(value) : null);
|
|
108
132
|
return;
|
|
109
133
|
}
|
|
110
|
-
this.#commandBuffer.setProperty(
|
|
134
|
+
this.#commandBuffer.setProperty(requireNid(node, "setProperty"), key, value);
|
|
111
135
|
}
|
|
112
136
|
/** Collect the concatenated text of all descendant text nodes (like mdast-util-to-string). */
|
|
113
137
|
textContent(node, options) {
|
|
114
|
-
return mdastTextContentHandle(this.#handle,
|
|
138
|
+
return mdastTextContentHandle(this.#handle, requireNid(node, "textContent"), options);
|
|
139
|
+
}
|
|
140
|
+
parent(node) {
|
|
141
|
+
const parentId = this.#resolver.parentIdOf(requireNid(node, "parent"));
|
|
142
|
+
if (parentId === undefined)
|
|
143
|
+
return undefined;
|
|
144
|
+
const byId = (this.#parentsById ??= new Map());
|
|
145
|
+
let parent = byId.get(parentId);
|
|
146
|
+
if (parent === undefined) {
|
|
147
|
+
parent = this.#resolver.materializeOne(parentId);
|
|
148
|
+
byId.set(parentId, parent);
|
|
149
|
+
}
|
|
150
|
+
return parent;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Index of `node` within its parent's children, or `undefined` at the root.
|
|
154
|
+
* Use this rather than `parent.children.indexOf(node)`, which won't find it.
|
|
155
|
+
*/
|
|
156
|
+
indexOf(node) {
|
|
157
|
+
return this.#resolver.indexInParent(requireNid(node, "indexOf"));
|
|
115
158
|
}
|
|
116
159
|
report({ message, node, severity = "error", }) {
|
|
117
160
|
this.#diagnostics.push({
|
|
@@ -129,32 +172,6 @@ export class MdastVisitorContext {
|
|
|
129
172
|
return this.#diagnostics;
|
|
130
173
|
}
|
|
131
174
|
}
|
|
132
|
-
/** Merge return-value + context command buffers and release internals. */
|
|
133
|
-
function mergeAndReset(returnBuffer, ctx) {
|
|
134
|
-
const ctxCmdBuf = ctx.getCommandBuffer();
|
|
135
|
-
const ctxBuf = ctxCmdBuf.getBuffer();
|
|
136
|
-
const retBuf = returnBuffer.getBuffer();
|
|
137
|
-
const totalLen = retBuf.length + ctxBuf.length;
|
|
138
|
-
let merged;
|
|
139
|
-
if (totalLen === 0) {
|
|
140
|
-
merged = new Uint8Array(0);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
merged = new Uint8Array(totalLen);
|
|
144
|
-
merged.set(retBuf, 0);
|
|
145
|
-
merged.set(ctxBuf, retBuf.length);
|
|
146
|
-
}
|
|
147
|
-
returnBuffer.reset();
|
|
148
|
-
ctxCmdBuf.reset();
|
|
149
|
-
return { merged, hasMutations: totalLen > 0 };
|
|
150
|
-
}
|
|
151
|
-
const textDecoder = new TextDecoder("utf-8");
|
|
152
|
-
/** Build name→nodeType map from TYPE_NAMES (reverse of TYPE_NAMES). */
|
|
153
|
-
const NAME_TO_TYPE = {};
|
|
154
|
-
for (const [num, name] of Object.entries(TYPE_NAMES)) {
|
|
155
|
-
NAME_TO_TYPE[name] = Number(num);
|
|
156
|
-
}
|
|
157
|
-
/** Resolve subscriptions from a plugin instance. */
|
|
158
175
|
export function resolveMdastSubscriptions(plugin) {
|
|
159
176
|
const subs = [];
|
|
160
177
|
for (const [name, fn] of Object.entries(plugin)) {
|
|
@@ -170,69 +187,61 @@ export function resolveMdastSubscriptions(plugin) {
|
|
|
170
187
|
}
|
|
171
188
|
return subs;
|
|
172
189
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
class MdastLazyChildResolver extends LazyChildResolver {
|
|
191
|
+
createReader(wire) {
|
|
192
|
+
return new MdastReader(wire);
|
|
193
|
+
}
|
|
194
|
+
materializeNode(reader, nodeId) {
|
|
195
|
+
return materializeNode(reader, nodeId);
|
|
196
|
+
}
|
|
197
|
+
readParentId(reader, nodeId) {
|
|
198
|
+
return reader.getParentId(nodeId);
|
|
199
|
+
}
|
|
200
|
+
readChildIds(reader, nodeId) {
|
|
201
|
+
return reader.getChildIds(nodeId);
|
|
202
|
+
}
|
|
184
203
|
}
|
|
185
|
-
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this.#handle = handle;
|
|
204
|
+
/** Build the child-stub list for a matched node from the wire's `[child_ids]
|
|
205
|
+
* [child_types]` blocks — no arena snapshot. The seal check still applies:
|
|
206
|
+
* post-pass ids are stale, and a stub built from them could later splice the
|
|
207
|
+
* wrong node as a ref. */
|
|
208
|
+
function readMdastChildStubs(view, buf, idsPos, typesPos, count, resolver) {
|
|
209
|
+
resolver.assertUnsealed();
|
|
210
|
+
const stubs = new Array(count);
|
|
211
|
+
for (let i = 0; i < count; i++) {
|
|
212
|
+
stubs[i] = new MdastChildStub(resolver, ru32(view, idsPos + i * 4), buf[typesPos + i]);
|
|
195
213
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const json = napiGetNodeData(handle, id);
|
|
210
|
-
const val = json ? JSON.parse(json) : null;
|
|
211
|
-
Object.defineProperty(this, "data", {
|
|
212
|
-
value: val,
|
|
213
|
-
writable: true,
|
|
214
|
-
enumerable: true,
|
|
215
|
-
configurable: true,
|
|
216
|
-
});
|
|
217
|
-
return val;
|
|
218
|
-
},
|
|
219
|
-
configurable: true,
|
|
214
|
+
return stubs;
|
|
215
|
+
}
|
|
216
|
+
/** Install `children` as an own enumerable getter (spread must carry it),
|
|
217
|
+
* self-replacing with the one stable stub array on first read. One closure
|
|
218
|
+
* and one define per node — installing the wire locals as hidden slots
|
|
219
|
+
* instead measurably regressed every matching pipeline. */
|
|
220
|
+
function makeLazyChildren(node, view, buf, childIdsPos, childTypesPos, childCount, resolver) {
|
|
221
|
+
Object.defineProperty(node, "children", {
|
|
222
|
+
get() {
|
|
223
|
+
const val = readMdastChildStubs(view, buf, childIdsPos, childTypesPos, childCount, resolver);
|
|
224
|
+
Object.defineProperty(this, "children", {
|
|
225
|
+
value: val,
|
|
226
|
+
writable: true,
|
|
220
227
|
enumerable: true,
|
|
228
|
+
configurable: true,
|
|
221
229
|
});
|
|
222
|
-
return
|
|
223
|
-
}
|
|
224
|
-
|
|
230
|
+
return val;
|
|
231
|
+
},
|
|
232
|
+
enumerable: true,
|
|
233
|
+
configurable: true,
|
|
234
|
+
});
|
|
225
235
|
}
|
|
226
236
|
/**
|
|
227
237
|
* Read an MDAST node from the inline data in a match buffer entry.
|
|
228
238
|
*
|
|
229
239
|
* Inline format (from Rust serialize_mdast_node_inline):
|
|
230
|
-
* [node_data: u32+bytes][position: 6×u32 = 24B][child_count:
|
|
240
|
+
* [node_data: u32+bytes][position: 6×u32 = 24B][child_count: u32][child_ids: N×u32]
|
|
241
|
+
* [child_types: N×u8][type-specific data]
|
|
231
242
|
*/
|
|
232
|
-
const encoder = new TextEncoder();
|
|
233
243
|
function readMdastMatchedNode(view, buf, dataOffset, nodeId, nodeType, resolver) {
|
|
234
244
|
let pos = dataOffset;
|
|
235
|
-
// Node data (JSON bytes), always first
|
|
236
245
|
const dataJsonLen = ru32(view, pos);
|
|
237
246
|
pos += 4;
|
|
238
247
|
let initialData = null;
|
|
@@ -241,265 +250,54 @@ function readMdastMatchedNode(view, buf, dataOffset, nodeId, nodeType, resolver)
|
|
|
241
250
|
try {
|
|
242
251
|
initialData = JSON.parse(jsonStr);
|
|
243
252
|
}
|
|
244
|
-
catch {
|
|
245
|
-
|
|
253
|
+
catch (err) {
|
|
254
|
+
if (process.env.NODE_ENV !== "production") {
|
|
255
|
+
console.warn(`readMdastMatchedNode: malformed node_data for nodeId=${nodeId}`, err);
|
|
256
|
+
}
|
|
246
257
|
}
|
|
247
258
|
pos += dataJsonLen;
|
|
248
259
|
}
|
|
249
|
-
|
|
250
|
-
const position = {
|
|
251
|
-
start: { offset: ru32(view, pos), line: ru32(view, pos + 8), column: ru32(view, pos + 12) },
|
|
252
|
-
end: { offset: ru32(view, pos + 4), line: ru32(view, pos + 16), column: ru32(view, pos + 20) },
|
|
253
|
-
};
|
|
260
|
+
const position = readPosition(view, pos);
|
|
254
261
|
pos += 24;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
262
|
+
const childCount = ru32(view, pos);
|
|
263
|
+
pos += 4;
|
|
264
|
+
// Ids/types decode lazily with `.children` — most matched nodes never read them.
|
|
265
|
+
const childIdsPos = pos;
|
|
266
|
+
pos += childCount * 4;
|
|
267
|
+
const childTypesPos = pos;
|
|
268
|
+
pos += childCount;
|
|
263
269
|
const typeName = TYPE_NAMES[nodeType] ?? `unknown(${nodeType})`;
|
|
264
|
-
|
|
265
|
-
|
|
270
|
+
const node = { type: typeName };
|
|
271
|
+
if (position !== undefined)
|
|
272
|
+
node.position = position;
|
|
266
273
|
if (childCount > 0) {
|
|
267
|
-
|
|
268
|
-
get() {
|
|
269
|
-
const val = resolver.materializeChildren(childIds);
|
|
270
|
-
Object.defineProperty(this, "children", {
|
|
271
|
-
value: val,
|
|
272
|
-
writable: true,
|
|
273
|
-
enumerable: true,
|
|
274
|
-
configurable: true,
|
|
275
|
-
});
|
|
276
|
-
return val;
|
|
277
|
-
},
|
|
278
|
-
configurable: true,
|
|
279
|
-
enumerable: true,
|
|
280
|
-
});
|
|
274
|
+
makeLazyChildren(node, view, buf, childIdsPos, childTypesPos, childCount, resolver);
|
|
281
275
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const vlen = ru32(view, pos);
|
|
295
|
-
node.value = rstr(buf, pos + 4, vlen);
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
case 8: {
|
|
299
|
-
// code
|
|
300
|
-
const langLen = ru16(view, pos);
|
|
301
|
-
pos += 2;
|
|
302
|
-
node.lang = langLen > 0 ? rstr(buf, pos, langLen) : null;
|
|
303
|
-
pos += langLen;
|
|
304
|
-
const metaLen = ru16(view, pos);
|
|
305
|
-
pos += 2;
|
|
306
|
-
node.meta = metaLen > 0 ? rstr(buf, pos, metaLen) : null;
|
|
307
|
-
pos += metaLen;
|
|
308
|
-
const valLen = ru32(view, pos);
|
|
309
|
-
pos += 4;
|
|
310
|
-
node.value = rstr(buf, pos, valLen);
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
case 27:
|
|
314
|
-
case 28: {
|
|
315
|
-
// math, inlineMath
|
|
316
|
-
const metaLen = ru16(view, pos);
|
|
317
|
-
pos += 2;
|
|
318
|
-
node.meta = metaLen > 0 ? rstr(buf, pos, metaLen) : null;
|
|
319
|
-
pos += metaLen;
|
|
320
|
-
const valLen = ru32(view, pos);
|
|
321
|
-
pos += 4;
|
|
322
|
-
node.value = rstr(buf, pos, valLen);
|
|
323
|
-
break;
|
|
324
|
-
}
|
|
325
|
-
case 15: {
|
|
326
|
-
// link
|
|
327
|
-
const urlLen = ru16(view, pos);
|
|
328
|
-
pos += 2;
|
|
329
|
-
node.url = rstr(buf, pos, urlLen);
|
|
330
|
-
pos += urlLen;
|
|
331
|
-
const titleLen = ru16(view, pos);
|
|
332
|
-
pos += 2;
|
|
333
|
-
node.title = titleLen > 0 ? rstr(buf, pos, titleLen) : null;
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
case 16: {
|
|
337
|
-
// image
|
|
338
|
-
const urlLen = ru16(view, pos);
|
|
339
|
-
pos += 2;
|
|
340
|
-
node.url = rstr(buf, pos, urlLen);
|
|
341
|
-
pos += urlLen;
|
|
342
|
-
const altLen = ru16(view, pos);
|
|
343
|
-
pos += 2;
|
|
344
|
-
node.alt = rstr(buf, pos, altLen);
|
|
345
|
-
pos += altLen;
|
|
346
|
-
const titleLen = ru16(view, pos);
|
|
347
|
-
pos += 2;
|
|
348
|
-
node.title = titleLen > 0 ? rstr(buf, pos, titleLen) : null;
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
case 9: {
|
|
352
|
-
// definition
|
|
353
|
-
const urlLen = ru16(view, pos);
|
|
354
|
-
pos += 2;
|
|
355
|
-
node.url = rstr(buf, pos, urlLen);
|
|
356
|
-
pos += urlLen;
|
|
357
|
-
const titleLen = ru16(view, pos);
|
|
358
|
-
pos += 2;
|
|
359
|
-
node.title = titleLen > 0 ? rstr(buf, pos, titleLen) : null;
|
|
360
|
-
pos += titleLen;
|
|
361
|
-
const idLen = ru16(view, pos);
|
|
362
|
-
pos += 2;
|
|
363
|
-
node.identifier = rstr(buf, pos, idLen);
|
|
364
|
-
pos += idLen;
|
|
365
|
-
const labelLen = ru16(view, pos);
|
|
366
|
-
pos += 2;
|
|
367
|
-
node.label = rstr(buf, pos, labelLen);
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
case 5: {
|
|
371
|
-
// list
|
|
372
|
-
node.start = ru32(view, pos);
|
|
373
|
-
node.ordered = buf[pos + 4] !== 0;
|
|
374
|
-
node.spread = buf[pos + 5] !== 0;
|
|
375
|
-
if (!node.ordered)
|
|
376
|
-
node.start = null;
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
case 6: {
|
|
380
|
-
// listItem
|
|
381
|
-
const checked = buf[pos];
|
|
382
|
-
node.checked = checked === 2 ? null : checked === 1;
|
|
383
|
-
node.spread = buf[pos + 1] !== 0;
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
case 17:
|
|
387
|
-
case 18:
|
|
388
|
-
case 20: {
|
|
389
|
-
// linkReference, imageReference, footnoteReference
|
|
390
|
-
const idLen = ru16(view, pos);
|
|
391
|
-
pos += 2;
|
|
392
|
-
node.identifier = rstr(buf, pos, idLen);
|
|
393
|
-
pos += idLen;
|
|
394
|
-
const labelLen = ru16(view, pos);
|
|
395
|
-
pos += 2;
|
|
396
|
-
node.label = rstr(buf, pos, labelLen);
|
|
397
|
-
pos += labelLen;
|
|
398
|
-
const kind = buf[pos];
|
|
399
|
-
// Only link/image references carry `referenceType`; the mdast spec
|
|
400
|
-
// defines it for those two, not for `footnoteReference`.
|
|
401
|
-
if (nodeType !== 20) {
|
|
402
|
-
node.referenceType = ["shortcut", "collapsed", "full"][kind] ?? "shortcut";
|
|
403
|
-
}
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
case 19: {
|
|
407
|
-
// footnoteDefinition
|
|
408
|
-
const idLen = ru16(view, pos);
|
|
409
|
-
pos += 2;
|
|
410
|
-
node.identifier = rstr(buf, pos, idLen);
|
|
411
|
-
pos += idLen;
|
|
412
|
-
const labelLen = ru16(view, pos);
|
|
413
|
-
pos += 2;
|
|
414
|
-
node.label = rstr(buf, pos, labelLen);
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
case 21: {
|
|
418
|
-
// table
|
|
419
|
-
const count = ru16(view, pos);
|
|
420
|
-
pos += 2;
|
|
421
|
-
const alignNames = [null, "left", "right", "center"];
|
|
422
|
-
node.align = Array.from({ length: count }, (_, i) => alignNames[buf[pos + i]] ?? null);
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
case 30:
|
|
426
|
-
case 31:
|
|
427
|
-
case 32: {
|
|
428
|
-
// containerDirective, leafDirective, textDirective
|
|
429
|
-
const nameLen = ru16(view, pos);
|
|
430
|
-
pos += 2;
|
|
431
|
-
node.name = rstr(buf, pos, nameLen);
|
|
432
|
-
pos += nameLen;
|
|
433
|
-
const attrCount = ru16(view, pos);
|
|
434
|
-
pos += 2;
|
|
435
|
-
const attributes = {};
|
|
436
|
-
for (let i = 0; i < attrCount; i++) {
|
|
437
|
-
const keyLen = ru16(view, pos);
|
|
438
|
-
pos += 2;
|
|
439
|
-
const key = rstr(buf, pos, keyLen);
|
|
440
|
-
pos += keyLen;
|
|
441
|
-
const valLen = ru16(view, pos);
|
|
442
|
-
pos += 2;
|
|
443
|
-
const val = rstr(buf, pos, valLen);
|
|
444
|
-
pos += valLen;
|
|
445
|
-
attributes[key] = val;
|
|
276
|
+
// Fixed-field types decode from the generated layout table; the rest
|
|
277
|
+
// (variable-length / cross-field) stay in the hand-written switch.
|
|
278
|
+
if (!decodeMdastTypeData(view, buf, pos, nodeType, node)) {
|
|
279
|
+
switch (nodeType) {
|
|
280
|
+
case 5: {
|
|
281
|
+
// list
|
|
282
|
+
node.start = ru32(view, pos);
|
|
283
|
+
node.ordered = buf[pos + 4] !== 0;
|
|
284
|
+
node.spread = buf[pos + 5] !== 0;
|
|
285
|
+
if (!node.ordered)
|
|
286
|
+
node.start = null;
|
|
287
|
+
break;
|
|
446
288
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const nameLen = ru16(view, pos);
|
|
454
|
-
pos += 2;
|
|
455
|
-
node.name = nameLen > 0 ? rstr(buf, pos, nameLen) : null;
|
|
456
|
-
pos += nameLen;
|
|
457
|
-
const attrCount = ru16(view, pos);
|
|
458
|
-
pos += 2;
|
|
459
|
-
const attributes = [];
|
|
460
|
-
for (let i = 0; i < attrCount; i++) {
|
|
461
|
-
const kind = buf[pos];
|
|
462
|
-
pos += 1;
|
|
463
|
-
const anLen = ru16(view, pos);
|
|
464
|
-
pos += 2;
|
|
465
|
-
const an = rstr(buf, pos, anLen);
|
|
466
|
-
pos += anLen;
|
|
467
|
-
const avLen = ru32(view, pos);
|
|
468
|
-
pos += 4;
|
|
469
|
-
const av = rstr(buf, pos, avLen);
|
|
470
|
-
pos += avLen;
|
|
471
|
-
switch (kind) {
|
|
472
|
-
case 0:
|
|
473
|
-
attributes.push({ type: "mdxJsxAttribute", name: an, value: null });
|
|
474
|
-
break;
|
|
475
|
-
case 1:
|
|
476
|
-
attributes.push({ type: "mdxJsxAttribute", name: an, value: av });
|
|
477
|
-
break;
|
|
478
|
-
case 2:
|
|
479
|
-
attributes.push({
|
|
480
|
-
type: "mdxJsxAttribute",
|
|
481
|
-
name: an,
|
|
482
|
-
value: { type: "mdxJsxAttributeValueExpression", value: av },
|
|
483
|
-
});
|
|
484
|
-
break;
|
|
485
|
-
case 3:
|
|
486
|
-
attributes.push({ type: "mdxJsxExpressionAttribute", value: av });
|
|
487
|
-
break;
|
|
488
|
-
}
|
|
289
|
+
case 6: {
|
|
290
|
+
// listItem
|
|
291
|
+
const checked = buf[pos];
|
|
292
|
+
node.checked = checked === 2 ? null : checked === 1;
|
|
293
|
+
node.spread = buf[pos + 1] !== 0;
|
|
294
|
+
break;
|
|
489
295
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
case 103:
|
|
495
|
-
case 104: {
|
|
496
|
-
// mdxFlowExpression, mdxTextExpression, mdxjsEsm
|
|
497
|
-
const vlen = ru32(view, pos);
|
|
498
|
-
node.value = rstr(buf, pos + 4, vlen);
|
|
499
|
-
break;
|
|
296
|
+
// table (21), directives (30/31/32) and mdxJsx elements (100/101) are
|
|
297
|
+
// decoded by the generated `decodeMdastTypeData` from their tails.
|
|
298
|
+
// root(0), paragraph(1), thematicBreak(3), blockquote(4), emphasis(11),
|
|
299
|
+
// strong(12), break(14), tableRow(22), tableCell(23), delete(24): no extra data
|
|
500
300
|
}
|
|
501
|
-
// root(0), paragraph(1), thematicBreak(3), blockquote(4), emphasis(11),
|
|
502
|
-
// strong(12), break(14), tableRow(22), tableCell(23), delete(24): no extra data
|
|
503
301
|
}
|
|
504
302
|
mdastNodeIdMap.set(node, nodeId);
|
|
505
303
|
if (initialData) {
|
|
@@ -507,9 +305,6 @@ function readMdastMatchedNode(view, buf, dataOffset, nodeId, nodeType, resolver)
|
|
|
507
305
|
}
|
|
508
306
|
return node;
|
|
509
307
|
}
|
|
510
|
-
/** Apply a sync visitor result to the return buffer.
|
|
511
|
-
* If the result is the same object as the input node, treat it as a no-op
|
|
512
|
-
* so that context mutations (e.g. setProperty) are not clobbered. */
|
|
513
308
|
/** The arena id of a node if it is an existing (materialized) node, else
|
|
514
309
|
* undefined for a freshly-built one. */
|
|
515
310
|
function reusedId(node) {
|
|
@@ -518,28 +313,193 @@ function reusedId(node) {
|
|
|
518
313
|
const id = nid(node);
|
|
519
314
|
return typeof id === "number" ? id : undefined;
|
|
520
315
|
}
|
|
316
|
+
// Reused across every replacement in a pass: compile is synchronous and its
|
|
317
|
+
// result is copied into the command buffer before the next call, so a single
|
|
318
|
+
// writer is safe and avoids a 512-byte allocation per built node.
|
|
319
|
+
const mdastWriter = new OpWriter();
|
|
521
320
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
* rebuild splices
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
* it is the new shape replacing the visited node.
|
|
321
|
+
* Compile a declarative MDAST replacement tree to the op-stream — the only
|
|
322
|
+
* structural encoding. Reused nodes (those still carrying an arena id) become
|
|
323
|
+
* `ref`s so the rebuild splices the original back in place. Returns null when
|
|
324
|
+
* the replay can't reproduce the tree identically (unsupported node type or
|
|
325
|
+
* out-of-range numeric field); the caller turns that into a hard error.
|
|
528
326
|
*/
|
|
529
|
-
function
|
|
327
|
+
function compileMdastToOpstream(root, forReplace = false) {
|
|
328
|
+
mdastWriter.begin();
|
|
329
|
+
try {
|
|
330
|
+
if (!emitMdastOp(mdastWriter, root, true, forReplace))
|
|
331
|
+
return null;
|
|
332
|
+
return mdastWriter.take();
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
mdastWriter.end();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/** Compile a set-children payload: a root-wrapped child list, the shape
|
|
339
|
+
* `Patch::SetChildren` splices in. Reused children become refs. */
|
|
340
|
+
function compileMdastChildrenToOpstream(children) {
|
|
341
|
+
if (!Array.isArray(children))
|
|
342
|
+
return null;
|
|
343
|
+
mdastWriter.begin();
|
|
344
|
+
try {
|
|
345
|
+
mdastWriter.open(NAME_TO_TYPE.root);
|
|
346
|
+
for (const c of children) {
|
|
347
|
+
if (!emitMdastOp(mdastWriter, c, false, false))
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
mdastWriter.close();
|
|
351
|
+
return mdastWriter.take();
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
mdastWriter.end();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function emitMdastOp(w, node, isRoot, forReplace) {
|
|
530
358
|
if (node === null || typeof node !== "object")
|
|
531
|
-
return
|
|
359
|
+
return false;
|
|
532
360
|
if (!isRoot) {
|
|
533
361
|
const id = reusedId(node);
|
|
534
|
-
if (id !== undefined)
|
|
535
|
-
|
|
362
|
+
if (id !== undefined) {
|
|
363
|
+
w.ref(id);
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
536
366
|
}
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
367
|
+
const n = node;
|
|
368
|
+
const type = MDAST_OPSTREAM_TYPES[n.type];
|
|
369
|
+
if (type === undefined)
|
|
370
|
+
return false;
|
|
371
|
+
w.open(type);
|
|
372
|
+
if (typeof n.value === "string")
|
|
373
|
+
w.str(OF_VALUE, n.value);
|
|
374
|
+
if (typeof n.url === "string")
|
|
375
|
+
w.str(OF_URL, n.url);
|
|
376
|
+
if (typeof n.title === "string")
|
|
377
|
+
w.str(OF_TITLE, n.title);
|
|
378
|
+
if (typeof n.alt === "string")
|
|
379
|
+
w.str(OF_ALT, n.alt);
|
|
380
|
+
if (typeof n.lang === "string")
|
|
381
|
+
w.str(OF_LANG, n.lang);
|
|
382
|
+
if (typeof n.meta === "string")
|
|
383
|
+
w.str(OF_META, n.meta);
|
|
384
|
+
if (typeof n.identifier === "string")
|
|
385
|
+
w.str(OF_IDENTIFIER, n.identifier);
|
|
386
|
+
if (typeof n.label === "string")
|
|
387
|
+
w.str(OF_LABEL, n.label);
|
|
388
|
+
if (typeof n.referenceType === "string")
|
|
389
|
+
w.str(OF_REFERENCE_TYPE, n.referenceType);
|
|
390
|
+
// Out-of-range numbers compile to null and the caller throws — a visible
|
|
391
|
+
// error instead of silently masking the bits.
|
|
392
|
+
if (typeof n.depth === "number") {
|
|
393
|
+
if (!Number.isInteger(n.depth) || n.depth < 0 || n.depth > 255)
|
|
394
|
+
return false;
|
|
395
|
+
w.u8(OF_DEPTH, n.depth);
|
|
540
396
|
}
|
|
541
|
-
|
|
397
|
+
if (typeof n.checked === "boolean")
|
|
398
|
+
w.u8(OF_CHECKED, n.checked ? 1 : 0);
|
|
399
|
+
if (typeof n.start === "number") {
|
|
400
|
+
if (!Number.isInteger(n.start) || n.start < 0 || n.start > 4294967295)
|
|
401
|
+
return false;
|
|
402
|
+
w.u32(OF_START, n.start);
|
|
403
|
+
}
|
|
404
|
+
if (typeof n.ordered === "boolean")
|
|
405
|
+
w.bool(OF_ORDERED, n.ordered);
|
|
406
|
+
if (typeof n.spread === "boolean")
|
|
407
|
+
w.bool(OF_SPREAD, n.spread);
|
|
408
|
+
if (typeof n.name === "string")
|
|
409
|
+
w.str(OF_NAME, n.name);
|
|
410
|
+
const attrs = n.attributes;
|
|
411
|
+
if (Array.isArray(attrs)) {
|
|
412
|
+
for (const a of attrs)
|
|
413
|
+
emitMdxAttr(w, a);
|
|
414
|
+
}
|
|
415
|
+
else if (attrs !== null && typeof attrs === "object") {
|
|
416
|
+
// Directive attributes: a string→string map; non-string values are
|
|
417
|
+
// dropped, since the stored form holds only strings.
|
|
418
|
+
for (const key in attrs) {
|
|
419
|
+
const v = attrs[key];
|
|
420
|
+
if (typeof v === "string")
|
|
421
|
+
w.prop(key, PROP_STRING, v);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (Array.isArray(n.align))
|
|
425
|
+
w.align(n.align.map(alignCode));
|
|
426
|
+
if (n.data?._mdxExplicitJsx === true) {
|
|
427
|
+
w.bool(OF_EXPLICIT, true);
|
|
428
|
+
}
|
|
429
|
+
if (n.data != null)
|
|
430
|
+
w.data(n.data);
|
|
431
|
+
if (isRoot && forReplace && n._keepChildren === true) {
|
|
432
|
+
// Replace splices the target's original children, discarding any the
|
|
433
|
+
// replacement declares.
|
|
434
|
+
w.keepChildren();
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
// `_keepChildren` only applies to replace; other ops ignore the marker
|
|
438
|
+
// and emit the declared children.
|
|
439
|
+
const children = n.children;
|
|
440
|
+
if (Array.isArray(children)) {
|
|
441
|
+
for (const c of children)
|
|
442
|
+
if (!emitMdastOp(w, c, false, forReplace))
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
w.close();
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
/** Map a table `align` entry to its arena code (none=0). */
|
|
450
|
+
function alignCode(a) {
|
|
451
|
+
return a === "left" ? 1 : a === "right" ? 2 : a === "center" ? 3 : 0;
|
|
452
|
+
}
|
|
453
|
+
/** True for the `{raw}` / `{rawHtml}` escape hatches — re-parsed by Rust rather
|
|
454
|
+
* than compiled to an op-stream, so they ride the RAW_MARKDOWN / RAW_HTML
|
|
455
|
+
* payloads instead of the declarative encoder. */
|
|
456
|
+
function isRawMdastContent(content) {
|
|
457
|
+
const c = content;
|
|
458
|
+
return typeof c.raw === "string" || typeof c.rawHtml === "string";
|
|
542
459
|
}
|
|
460
|
+
/** Encode `content` as the `op` structural command. Declarative nodes compile
|
|
461
|
+
* to the op-stream; the `{raw}`/`{rawHtml}` escape hatches ride the raw
|
|
462
|
+
* re-parse payloads. Anything that compiles to neither is a hard error — the
|
|
463
|
+
* op-stream is the only declarative encoding. The switches stay inline so the
|
|
464
|
+
* buffer calls are monomorphic (computed method names defeat inline caches on
|
|
465
|
+
* this warm path). */
|
|
466
|
+
function emitMdastTree(buffer, op, id, content, forReplace = false) {
|
|
467
|
+
if (isRawMdastContent(content)) {
|
|
468
|
+
switch (op) {
|
|
469
|
+
case "replace":
|
|
470
|
+
return buffer.replace(id, content);
|
|
471
|
+
case "insertBefore":
|
|
472
|
+
return buffer.insertBefore(id, content);
|
|
473
|
+
case "insertAfter":
|
|
474
|
+
return buffer.insertAfter(id, content);
|
|
475
|
+
case "prependChild":
|
|
476
|
+
return buffer.prependChild(id, content);
|
|
477
|
+
case "appendChild":
|
|
478
|
+
return buffer.appendChild(id, content);
|
|
479
|
+
case "wrapNode":
|
|
480
|
+
return buffer.wrapNode(id, content);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const ops = compileMdastToOpstream(content, forReplace);
|
|
484
|
+
if (ops === null)
|
|
485
|
+
throw unencodableContentError(content);
|
|
486
|
+
switch (op) {
|
|
487
|
+
case "replace":
|
|
488
|
+
return buffer.replaceOpstream(id, ops);
|
|
489
|
+
case "insertBefore":
|
|
490
|
+
return buffer.insertBeforeOpstream(id, ops);
|
|
491
|
+
case "insertAfter":
|
|
492
|
+
return buffer.insertAfterOpstream(id, ops);
|
|
493
|
+
case "prependChild":
|
|
494
|
+
return buffer.prependChildOpstream(id, ops);
|
|
495
|
+
case "appendChild":
|
|
496
|
+
return buffer.appendChildOpstream(id, ops);
|
|
497
|
+
case "wrapNode":
|
|
498
|
+
return buffer.wrapNodeOpstream(id, ops);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/** A result that is the same object as the input node is a no-op, so context
|
|
502
|
+
* mutations (e.g. setProperty) are not clobbered. */
|
|
543
503
|
function applyMdastVisitResult(result, nodeId, returnBuffer, originalNode) {
|
|
544
504
|
if (result === undefined || result === null)
|
|
545
505
|
return;
|
|
@@ -554,7 +514,7 @@ function applyMdastVisitResult(result, nodeId, returnBuffer, originalNode) {
|
|
|
554
514
|
returnBuffer.replace(nodeId, result);
|
|
555
515
|
break;
|
|
556
516
|
case "structured_node":
|
|
557
|
-
returnBuffer
|
|
517
|
+
emitMdastTree(returnBuffer, "replace", nodeId, result, true);
|
|
558
518
|
break;
|
|
559
519
|
}
|
|
560
520
|
}
|
|
@@ -565,18 +525,18 @@ function applyMdastVisitResult(result, nodeId, returnBuffer, originalNode) {
|
|
|
565
525
|
* Returns MdastVisitResult synchronously if all visitors are sync,
|
|
566
526
|
* or Promise<MdastVisitResult> if any visitor is async.
|
|
567
527
|
*/
|
|
568
|
-
export function visitMdastHandle(handle, plugin, subs, source, fileURL) {
|
|
528
|
+
export function visitMdastHandle(handle, plugin, subs, source, fileURL, data = {}) {
|
|
569
529
|
const getSource = typeof source === "function" ? source : () => source;
|
|
570
|
-
const context = new MdastVisitorContext(handle, getSource, fileURL);
|
|
571
|
-
const returnBuffer = new CommandBuffer();
|
|
572
530
|
const resolver = new MdastLazyChildResolver(handle);
|
|
531
|
+
const context = new MdastVisitorContext(handle, getSource, fileURL, resolver, data);
|
|
532
|
+
const returnBuffer = new CommandBuffer();
|
|
573
533
|
const rustSubs = subs.map((s) => ({ nodeType: s.nodeType, tagFilter: [] }));
|
|
574
534
|
const matchBuf = walkMdastHandle(handle, rustSubs);
|
|
575
535
|
const matchView = new DataView(matchBuf.buffer, matchBuf.byteOffset, matchBuf.byteLength);
|
|
576
536
|
const matchCount = ru32(matchView, 0);
|
|
577
537
|
let deferred = null;
|
|
578
538
|
for (let i = 0; i < matchCount; i++) {
|
|
579
|
-
const indexBase = 4 + i *
|
|
539
|
+
const indexBase = 4 + i * 10;
|
|
580
540
|
const nodeId = ru32(matchView, indexBase);
|
|
581
541
|
const subIndex = matchBuf[indexBase + 4];
|
|
582
542
|
const dataOffset = ru32(matchView, indexBase + 6);
|
|
@@ -596,9 +556,14 @@ export function visitMdastHandle(handle, plugin, subs, source, fileURL) {
|
|
|
596
556
|
for (const { nodeId, result, originalNode } of results) {
|
|
597
557
|
applyMdastVisitResult(result, nodeId, returnBuffer, originalNode);
|
|
598
558
|
}
|
|
559
|
+
// End of the pass — the caller applies the returned command buffer next,
|
|
560
|
+
// renumbering the arena, so later snapshots would resolve match-time
|
|
561
|
+
// child ids against wrong nodes. This is the last point we control.
|
|
562
|
+
resolver.seal();
|
|
599
563
|
return finalizeMdastVisit(handle, context, returnBuffer);
|
|
600
564
|
});
|
|
601
565
|
}
|
|
566
|
+
resolver.seal();
|
|
602
567
|
return finalizeMdastVisit(handle, context, returnBuffer);
|
|
603
568
|
}
|
|
604
569
|
function finalizeMdastVisit(handle, context, returnBuffer) {
|