satteri 0.10.5 → 0.11.3

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.
Files changed (90) hide show
  1. package/README.md +11 -0
  2. package/dist/arena-reader.d.ts +13 -0
  3. package/dist/arena-reader.js +104 -0
  4. package/dist/binding.browser.d.ts +1 -1
  5. package/dist/binding.browser.js +1 -1
  6. package/dist/binding.d.ts +1 -1
  7. package/dist/binding.js +1 -1
  8. package/dist/byte-writer.d.ts +0 -18
  9. package/dist/byte-writer.js +6 -30
  10. package/dist/child-stub.d.ts +0 -17
  11. package/dist/child-stub.js +6 -22
  12. package/dist/command-buffer.d.ts +5 -26
  13. package/dist/command-buffer.js +28 -39
  14. package/dist/compile-options.d.ts +11 -0
  15. package/dist/compile-options.js +101 -0
  16. package/dist/compile-types.d.ts +342 -0
  17. package/dist/compile-types.js +1 -0
  18. package/dist/compile.d.ts +22 -328
  19. package/dist/compile.js +133 -383
  20. package/dist/directive-types.d.ts +1 -0
  21. package/dist/directive-types.js +1 -6
  22. package/dist/generated/arena-layout.d.ts +6 -4
  23. package/dist/generated/arena-layout.js +6 -7
  24. package/dist/generated/fused-wire.d.ts +3 -0
  25. package/dist/generated/fused-wire.js +229 -0
  26. package/dist/generated/parse-options.d.ts +1 -0
  27. package/dist/generated/parse-options.js +1 -5
  28. package/dist/generated/wire-constants.d.ts +4 -0
  29. package/dist/generated/wire-constants.js +34 -42
  30. package/dist/handles.d.ts +0 -7
  31. package/dist/hast/child-stub.d.ts +1 -8
  32. package/dist/hast/child-stub.js +14 -13
  33. package/dist/hast/element-props.d.ts +13 -0
  34. package/dist/hast/element-props.js +52 -6
  35. package/dist/hast/generated/node-types.d.ts +1 -9
  36. package/dist/hast/generated/node-types.js +1 -9
  37. package/dist/hast/generated/walk-decode.d.ts +10 -0
  38. package/dist/hast/generated/walk-decode.js +61 -0
  39. package/dist/hast/hast-materializer.d.ts +3 -10
  40. package/dist/hast/hast-materializer.js +73 -47
  41. package/dist/hast/hast-reader.d.ts +9 -38
  42. package/dist/hast/hast-reader.js +82 -271
  43. package/dist/hast/hast-visitor.d.ts +29 -16
  44. package/dist/hast/hast-visitor.js +200 -508
  45. package/dist/hast/hast-walk.d.ts +18 -0
  46. package/dist/hast/hast-walk.js +264 -0
  47. package/dist/index.d.ts +5 -5
  48. package/dist/index.js +4 -12
  49. package/dist/lazy-child-resolver.d.ts +1 -21
  50. package/dist/lazy-child-resolver.js +11 -32
  51. package/dist/materializer-cache.d.ts +5 -29
  52. package/dist/materializer-cache.js +29 -101
  53. package/dist/mdast/child-stub.d.ts +1 -12
  54. package/dist/mdast/child-stub.js +18 -28
  55. package/dist/mdast/column-align.d.ts +1 -1
  56. package/dist/mdast/column-align.js +0 -2
  57. package/dist/mdast/generated/layout.d.ts +0 -16
  58. package/dist/mdast/generated/layout.js +6 -47
  59. package/dist/mdast/generated/node-types.d.ts +1 -9
  60. package/dist/mdast/generated/node-types.js +3 -9
  61. package/dist/mdast/mdast-materializer.d.ts +3 -11
  62. package/dist/mdast/mdast-materializer.js +91 -63
  63. package/dist/mdast/mdast-reader.d.ts +15 -63
  64. package/dist/mdast/mdast-reader.js +132 -269
  65. package/dist/mdast/mdast-visitor.d.ts +23 -15
  66. package/dist/mdast/mdast-visitor.js +189 -294
  67. package/dist/mdast/mdast-walk.d.ts +13 -0
  68. package/dist/mdast/mdast-walk.js +144 -0
  69. package/dist/mdx-attr.js +0 -3
  70. package/dist/mdx-types.js +1 -6
  71. package/dist/op-stream.d.ts +4 -15
  72. package/dist/op-stream.js +31 -20
  73. package/dist/phantom.d.ts +0 -1
  74. package/dist/phantom.js +1 -9
  75. package/dist/plugin-pipeline.d.ts +32 -0
  76. package/dist/plugin-pipeline.js +177 -0
  77. package/dist/plugin.d.ts +14 -8
  78. package/dist/plugin.js +5 -10
  79. package/dist/types.d.ts +0 -1
  80. package/dist/visitor-node.d.ts +12 -0
  81. package/dist/visitor-node.js +23 -0
  82. package/dist/visitor-shared.d.ts +26 -30
  83. package/dist/visitor-shared.js +34 -36
  84. package/dist/wire-read.d.ts +0 -22
  85. package/dist/wire-read.js +3 -24
  86. package/index.d.ts +19 -10
  87. package/index.js +56 -53
  88. package/package.json +6 -12
  89. package/dist/string-pool.d.ts +0 -13
  90. package/dist/string-pool.js +0 -91
@@ -1,10 +1,7 @@
1
- import { restorePhantomSpaces } from "../phantom.js";
2
- import { readPosition } from "../wire-read.js";
3
- import { MDX_ATTR_BOOLEAN_PROP, MDX_ATTR_LITERAL_PROP, MDX_ATTR_EXPRESSION_PROP, MDX_ATTR_SPREAD, } from "../op-stream.js";
4
- import { decodeElementProp } from "./element-props.js";
5
- import { PoolOffsets } from "../string-pool.js";
1
+ import { ArenaReader } from "../arena-reader.js";
2
+ import { FIELD, KIND_HAST, W_CHILDREN_COUNT, W_CHILDREN_START, W_DATA_LEN, W_DATA_OFFSET, W_PARENT, W_START_OFFSET, } from "../generated/arena-layout.js";
3
+ import { decodeMdxJsxAttr } from "../mdx-attr.js";
6
4
  import { NAME_TO_TYPE } from "./generated/node-types.js";
7
- import { ARENA_MAGIC, KIND_HAST, FIELD, HEADER } from "../generated/arena-layout.js";
8
5
  export const HAST_ROOT = NAME_TO_TYPE.root;
9
6
  export const HAST_ELEMENT = NAME_TO_TYPE.element;
10
7
  export const HAST_TEXT = NAME_TO_TYPE.text;
@@ -16,315 +13,129 @@ export const HAST_MDX_FLOW_EXPRESSION = NAME_TO_TYPE.mdxFlowExpression;
16
13
  export const HAST_MDX_ESM = NAME_TO_TYPE.mdxjsEsm;
17
14
  export const HAST_MDX_TEXT_EXPRESSION = NAME_TO_TYPE.mdxTextExpression;
18
15
  export class HastReader {
19
- #view;
20
- #header;
21
- #textDecoder;
22
- #stringPoolCache = null;
23
- #poolChecked = false;
24
- #poolOffsets = null;
16
+ #arena;
17
+ // Typed arrays avoid DataView getter overhead in unoptimized V8 tiers.
18
+ #u8;
19
+ #u32;
20
+ #nodesB;
21
+ #nodesW;
22
+ #strideB;
23
+ #strideW;
24
+ #childrenW;
25
+ #typeDataB;
25
26
  constructor(buffer) {
26
- if (buffer instanceof Uint8Array) {
27
- this.#view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
28
- }
29
- else {
30
- this.#view = new DataView(buffer);
31
- }
32
- this.#textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
33
- this.#header = this.#readHeader();
34
- }
35
- #readHeader() {
36
- const v = this.#view;
37
- const magic = v.getUint32(HEADER.magic, true);
38
- if (magic !== ARENA_MAGIC) {
39
- throw new Error(`Invalid HAST buffer: bad magic 0x${magic.toString(16)}`);
40
- }
41
- const kind = v.getUint32(HEADER.kind, true);
42
- if (kind !== KIND_HAST) {
43
- throw new Error(`HastReader was handed a buffer of kind ${kind} (expected ${KIND_HAST}). ` +
44
- `MDAST and HAST node types overlap; reading the wrong kind decodes garbage.`);
45
- }
46
- return {
47
- nodeStructSize: v.getUint32(HEADER.node_struct_size, true),
48
- nodeCount: v.getUint32(HEADER.node_count, true),
49
- nodesOffset: v.getUint32(HEADER.nodes_offset, true),
50
- childrenCount: v.getUint32(HEADER.children_count, true),
51
- childrenOffset: v.getUint32(HEADER.children_offset, true),
52
- typeDataLen: v.getUint32(HEADER.type_data_len, true),
53
- typeDataOffset: v.getUint32(HEADER.type_data_offset, true),
54
- stringPoolLen: v.getUint32(HEADER.string_pool_len, true),
55
- stringPoolOffset: v.getUint32(HEADER.string_pool_offset, true),
56
- nodeDataCount: v.getUint32(HEADER.node_data_count, true),
57
- nodeDataOffset: v.getUint32(HEADER.node_data_offset, true),
58
- };
59
- }
60
- #nodeDataTable = null;
61
- /**
62
- * Per-node JSON `data` blob (set via `Arena::set_node_data` on the Rust
63
- * side). Returns `null` when the node has no entry. Lazy-builds a
64
- * `Map<id, string>` on first call so a materialization pass on a
65
- * data-heavy tree stays O(nodes) rather than O(nodes × entries).
66
- */
27
+ const arena = new ArenaReader(buffer, KIND_HAST);
28
+ this.#arena = arena;
29
+ this.#u8 = arena.u8;
30
+ this.#u32 = arena.u32;
31
+ const header = arena.header;
32
+ this.#nodesB = header.nodesOffset;
33
+ this.#nodesW = header.nodesOffset >> 2;
34
+ this.#strideB = header.nodeStructSize;
35
+ this.#strideW = header.nodeStructSize >> 2;
36
+ this.#childrenW = header.childrenOffset >> 2;
37
+ this.#typeDataB = header.typeDataOffset;
38
+ }
39
+ /** @internal Memoized wire view for the fused decoder. */
40
+ getWire() {
41
+ return this.#arena.getWire();
42
+ }
43
+ /** Per-node JSON data, or null when absent. */
67
44
  getNodeData(nodeId) {
68
- if (this.#header.nodeDataCount === 0)
69
- return null;
70
- if (this.#nodeDataTable === null) {
71
- this.#nodeDataTable = new Map();
72
- const v = this.#view;
73
- let pos = this.#header.nodeDataOffset;
74
- for (let i = 0; i < this.#header.nodeDataCount; i++) {
75
- const id = v.getUint32(pos, true);
76
- pos += 4;
77
- const len = v.getUint32(pos, true);
78
- pos += 4;
79
- const slice = new Uint8Array(this.#view.buffer, this.#view.byteOffset + pos, len);
80
- this.#nodeDataTable.set(id, this.#textDecoder.decode(slice));
81
- pos += len;
82
- }
83
- }
84
- return this.#nodeDataTable.get(nodeId) ?? null;
45
+ return this.#arena.getNodeData(nodeId);
46
+ }
47
+ /** @internal null when no node carries data. */
48
+ getNodeDataTable() {
49
+ return this.#arena.getNodeDataTable();
85
50
  }
86
51
  get nodeCount() {
87
- return this.#header.nodeCount;
52
+ return this.#arena.header.nodeCount;
88
53
  }
89
54
  get header() {
90
- return { ...this.#header };
55
+ return { ...this.#arena.header };
91
56
  }
92
- /** The full string pool (original input + interning heap). Not the document
93
- * source as written; for that, read `ctx.source` from a plugin. */
57
+ /** Full string pool, including interned strings; use ctx.source for the original document. */
94
58
  getStringPool() {
95
- if (this.#stringPoolCache === null) {
96
- const { stringPoolOffset, stringPoolLen } = this.#header;
97
- const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset, stringPoolLen);
98
- this.#stringPoolCache = this.#textDecoder.decode(bytes);
99
- }
100
- return this.#stringPoolCache;
59
+ return this.#arena.getStringPool();
101
60
  }
102
- /** Read a substring from the string pool by byte offset and length. */
61
+ /** Wire string refs use UTF-16 units, so substring preserves their offsets. */
103
62
  getString(offset, len) {
104
- if (len === 0)
105
- return "";
106
- const pool = this.getStringPool();
107
- if (!this.#poolChecked) {
108
- this.#poolChecked = true;
109
- const { stringPoolOffset, stringPoolLen } = this.#header;
110
- const extraBytes = stringPoolLen - pool.length;
111
- if (extraBytes > 0) {
112
- const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset, stringPoolLen);
113
- this.#poolOffsets = new PoolOffsets(bytes, extraBytes);
114
- }
115
- }
116
- // An all-ASCII pool has byte offsets equal to its UTF-16 indices.
117
- const offsets = this.#poolOffsets;
118
- return offsets === null
119
- ? pool.substring(offset, offset + len)
120
- : offsets.slice(pool, offset, len);
63
+ return this.#arena.getString(offset, len);
121
64
  }
122
- /** Get position data for a node. */
65
+ /** A zero start line marks a synthesized node with no source range (unist
66
+ * lines are 1-based), surfaced as `undefined`. */
123
67
  getPosition(nodeId) {
124
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
125
- return readPosition(this.#view, base + FIELD.start_offset);
68
+ const u32 = this.#u32;
69
+ const b = this.#nodesW + nodeId * this.#strideW + W_START_OFFSET;
70
+ const startLine = u32[b + 2] ?? 0;
71
+ if (startLine === 0)
72
+ return undefined;
73
+ return {
74
+ start: { offset: u32[b] ?? 0, line: startLine, column: u32[b + 3] ?? 0 },
75
+ end: { offset: u32[b + 1] ?? 0, line: u32[b + 4] ?? 0, column: u32[b + 5] ?? 0 },
76
+ };
126
77
  }
127
78
  /** Get the node_type byte for a given node ID. */
128
79
  getNodeType(nodeId) {
129
- const { nodesOffset, nodeStructSize } = this.#header;
130
- return this.#view.getUint8(nodesOffset + nodeId * nodeStructSize + FIELD.node_type);
80
+ return this.#u8[this.#nodesB + nodeId * this.#strideB + FIELD.node_type] ?? 0;
131
81
  }
132
82
  /** Get the parent id for a given node (0xffffffff at the root). */
133
83
  getParentId(nodeId) {
134
- const { nodesOffset, nodeStructSize } = this.#header;
135
- return this.#view.getUint32(nodesOffset + nodeId * nodeStructSize + FIELD.parent, true);
84
+ return this.#u32[this.#nodesW + nodeId * this.#strideW + W_PARENT] ?? 0;
85
+ }
86
+ getChildrenCount(nodeId) {
87
+ return this.#u32[this.#nodesW + nodeId * this.#strideW + W_CHILDREN_COUNT] ?? 0;
136
88
  }
137
- /** Get child node IDs for a given node. */
138
89
  getChildIds(nodeId) {
139
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
140
- const v = this.#view;
141
- const childrenStart = v.getUint32(base + FIELD.children_start, true);
142
- const childrenCount = v.getUint32(base + FIELD.children_count, true);
90
+ const u32 = this.#u32;
91
+ const w = this.#nodesW + nodeId * this.#strideW;
92
+ const childrenStart = u32[w + W_CHILDREN_START] ?? 0;
93
+ const childrenCount = u32[w + W_CHILDREN_COUNT] ?? 0;
143
94
  if (childrenCount === 0)
144
95
  return [];
145
- const { childrenOffset } = this.#header;
96
+ const base = this.#childrenW + childrenStart;
146
97
  const ids = [];
147
98
  for (let i = 0; i < childrenCount; i++) {
148
- ids.push(v.getUint32(childrenOffset + (childrenStart + i) * 4, true));
99
+ ids.push(u32[base + i] ?? 0);
149
100
  }
150
101
  return ids;
151
102
  }
152
- /** Push child node IDs directly onto a stack array (reverse order for depth-first). */
153
- pushChildIds(nodeId, stack) {
154
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
155
- const v = this.#view;
156
- const childrenStart = v.getUint32(base + FIELD.children_start, true);
157
- const childrenCount = v.getUint32(base + FIELD.children_count, true);
158
- if (childrenCount === 0)
159
- return;
160
- const { childrenOffset } = this.#header;
161
- for (let i = childrenCount - 1; i >= 0; i--) {
162
- stack.push(v.getUint32(childrenOffset + (childrenStart + i) * 4, true));
163
- }
164
- }
165
103
  /** Get the raw type_data bytes for a node. */
166
104
  getTypeData(nodeId) {
167
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
168
- const v = this.#view;
169
- const dataOffset = v.getUint32(base + FIELD.data_offset, true);
170
- const dataLen = v.getUint32(base + FIELD.data_len, true);
105
+ const u32 = this.#u32;
106
+ const w = this.#nodesW + nodeId * this.#strideW;
107
+ const dataOffset = u32[w + W_DATA_OFFSET] ?? 0;
108
+ const dataLen = u32[w + W_DATA_LEN] ?? 0;
171
109
  if (dataLen === 0)
172
110
  return new Uint8Array(0);
173
- return new Uint8Array(this.#view.buffer, this.#view.byteOffset + this.#header.typeDataOffset + dataOffset, dataLen);
111
+ const start = this.#typeDataB + dataOffset;
112
+ return this.#u8.subarray(start, start + dataLen);
174
113
  }
175
- /** Read a StringRef (offset: u32 LE, len: u32 LE) from a byte array at byteOffset. */
176
- #readStringRef(data, byteOffset) {
177
- const at = data.byteOffset - this.#view.byteOffset + byteOffset;
178
- return {
179
- offset: this.#view.getUint32(at, true),
180
- len: this.#view.getUint32(at + 4, true),
181
- };
182
- }
183
- /**
184
- * Get element data for a HAST_ELEMENT node.
185
- *
186
- * Element type_data layout:
187
- * [tag_name: StringRef(8B)][prop_count: u32(4B)][_pad: u32(4B)] = 16-byte header
188
- * then prop_count * 20 bytes:
189
- * [name: StringRef(8B)][value_type: u8(1B)][_pad: [u8;3](3B)][value: StringRef(8B)]
190
- */
191
114
  /** Byte position of a node's `type_data`, relative to this reader's view, or
192
115
  * `-1` when the node stores fewer than `min` bytes. */
193
116
  #typeDataAt(nodeId, min) {
194
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
195
- const v = this.#view;
196
- if (v.getUint32(base + FIELD.data_len, true) < min)
117
+ const u32 = this.#u32;
118
+ const w = this.#nodesW + nodeId * this.#strideW;
119
+ if ((u32[w + W_DATA_LEN] ?? 0) < min)
197
120
  return -1;
198
- return this.#header.typeDataOffset + v.getUint32(base + FIELD.data_offset, true);
199
- }
200
- #stringAt(at) {
201
- return this.getString(this.#view.getUint32(at, true), this.#view.getUint32(at + 4, true));
121
+ return this.#typeDataB + (u32[w + W_DATA_OFFSET] ?? 0);
202
122
  }
203
- /** @internal Both element fields in one pass; the per-field accessors below
204
- * each re-resolve the node's `type_data` position. */
205
- readElementInto(nodeId, node) {
123
+ /** A zero-length element name represents an MDX fragment. */
124
+ getMdxJsxElementData(nodeId) {
206
125
  const at = this.#typeDataAt(nodeId, 16);
207
126
  if (at === -1) {
208
- node.tagName = "";
209
- node.properties = {};
210
- return;
211
- }
212
- const v = this.#view;
213
- node.tagName = this.#stringAt(at);
214
- const count = v.getUint32(at + 8, true);
215
- const properties = {};
216
- for (let i = 0; i < count; i++) {
217
- const base = at + 16 + i * 20;
218
- properties[this.#stringAt(base)] = decodeElementProp(v.getUint8(base + 8), this.#stringAt(base + 12));
219
- }
220
- node.properties = properties;
221
- }
222
- /** Element `tagName`, `properties` count, and the i-th property, read without
223
- * building the intermediate views and records `getElementData` allocates. */
224
- getElementTagName(nodeId) {
225
- const at = this.#typeDataAt(nodeId, 16);
226
- return at === -1 ? "" : this.#stringAt(at);
227
- }
228
- getElementPropCount(nodeId) {
229
- const at = this.#typeDataAt(nodeId, 16);
230
- return at === -1 ? 0 : this.#view.getUint32(at + 8, true);
231
- }
232
- getElementPropName(nodeId, index) {
233
- const at = this.#typeDataAt(nodeId, 16);
234
- return at === -1 ? "" : this.#stringAt(at + 16 + index * 20);
235
- }
236
- getElementPropValue(nodeId, index) {
237
- const at = this.#typeDataAt(nodeId, 16);
238
- if (at === -1)
239
- return "";
240
- const base = at + 16 + index * 20;
241
- return decodeElementProp(this.#view.getUint8(base + 8), this.#stringAt(base + 12));
242
- }
243
- getElementData(nodeId) {
244
- const data = this.getTypeData(nodeId);
245
- if (data.length < 16) {
246
- return { tagName: "", properties: [] };
247
- }
248
- const tagRef = this.#readStringRef(data, 0);
249
- const tagName = this.getString(tagRef.offset, tagRef.len);
250
- const view = new DataView(data.buffer, data.byteOffset + 8);
251
- const propCount = view.getUint32(0, true);
252
- const properties = [];
253
- for (let i = 0; i < propCount; i++) {
254
- const base = 16 + i * 20;
255
- const nameRef = this.#readStringRef(data, base);
256
- const name = this.getString(nameRef.offset, nameRef.len);
257
- const valueType = data[base + 8];
258
- const valueRef = this.#readStringRef(data, base + 12);
259
- const valueStr = this.getString(valueRef.offset, valueRef.len);
260
- properties.push({ name, value: decodeElementProp(valueType, valueStr) });
261
- }
262
- return { tagName, properties };
263
- }
264
- /**
265
- * Get MDX JSX element data: name and attributes.
266
- *
267
- * MDX JSX element type_data layout:
268
- * [name: StringRef(8B)][attr_count: u32(4B)][_pad: u32(4B)] = 16-byte header
269
- * then attr_count * 20 bytes:
270
- * [kind: u8(1B)][_pad: [u8;3](3B)][name: StringRef(8B)][value: StringRef(8B)]
271
- */
272
- getMdxJsxElementData(nodeId) {
273
- const data = this.getTypeData(nodeId);
274
- if (data.length < 16) {
275
127
  return { name: null, attributes: [] };
276
128
  }
277
- const nameRef = this.#readStringRef(data, 0);
278
- const name = nameRef.len > 0 ? this.getString(nameRef.offset, nameRef.len) : null;
279
- const view = new DataView(data.buffer, data.byteOffset + 8);
280
- const attrCount = view.getUint32(0, true);
129
+ const u32 = this.#u32;
130
+ const w = at >> 2;
131
+ const nameLen = u32[w + 1] ?? 0;
132
+ const name = nameLen > 0 ? this.getString(u32[w] ?? 0, nameLen) : null;
133
+ const attrCount = u32[w + 2] ?? 0;
281
134
  const attributes = [];
282
135
  for (let i = 0; i < attrCount; i++) {
283
- const base = 16 + i * 20;
284
- const kind = data[base];
285
- const attrNameRef = this.#readStringRef(data, base + 4);
286
- const attrValueRef = this.#readStringRef(data, base + 12);
287
- switch (kind) {
288
- case MDX_ATTR_BOOLEAN_PROP:
289
- attributes.push({
290
- type: "mdxJsxAttribute",
291
- name: this.getString(attrNameRef.offset, attrNameRef.len),
292
- value: null,
293
- });
294
- break;
295
- case MDX_ATTR_LITERAL_PROP:
296
- attributes.push({
297
- type: "mdxJsxAttribute",
298
- name: this.getString(attrNameRef.offset, attrNameRef.len),
299
- value: this.getString(attrValueRef.offset, attrValueRef.len),
300
- });
301
- break;
302
- case MDX_ATTR_EXPRESSION_PROP:
303
- attributes.push({
304
- type: "mdxJsxAttribute",
305
- name: this.getString(attrNameRef.offset, attrNameRef.len),
306
- value: {
307
- type: "mdxJsxAttributeValueExpression",
308
- value: restorePhantomSpaces(this.getString(attrValueRef.offset, attrValueRef.len)),
309
- },
310
- });
311
- break;
312
- case MDX_ATTR_SPREAD:
313
- attributes.push({
314
- type: "mdxJsxExpressionAttribute",
315
- value: restorePhantomSpaces(this.getString(attrValueRef.offset, attrValueRef.len)),
316
- });
317
- break;
318
- }
136
+ const base = w + 4 + i * 5;
137
+ attributes.push(decodeMdxJsxAttr(this.#u8[base << 2] ?? 0, this.getString(u32[base + 1] ?? 0, u32[base + 2] ?? 0), this.getString(u32[base + 3] ?? 0, u32[base + 4] ?? 0)));
319
138
  }
320
139
  return { name, attributes };
321
140
  }
322
- /**
323
- * Get the string value for HAST_TEXT, HAST_COMMENT, or HAST_RAW nodes.
324
- * These store a single StringRef (8 bytes) as their type_data.
325
- */
326
- getTextValue(nodeId) {
327
- const at = this.#typeDataAt(nodeId, 8);
328
- return at === -1 ? "" : this.#stringAt(at);
329
- }
330
141
  }
@@ -1,18 +1,17 @@
1
- import { type HastNode } from "./hast-materializer.js";
2
- import type { HastRaw, Data, SourceFormat } from "../types.js";
3
- import type { Element, Text, Comment, Doctype, Parents as HastParents, Root as HastRoot } from "hast";
4
1
  import type { Program } from "estree-jsx";
5
- import type { MdxJsxFlowElementHast, MdxJsxTextElementHast } from "../mdx-types.js";
6
- import type { MdxFlowExpressionHast, MdxTextExpressionHast } from "../mdx-types.js";
7
- import type { MdxjsEsmHast } from "../mdx-types.js";
8
- import { type PluginOptions } from "../visitor-shared.js";
2
+ import type { Comment, Doctype, Element, Parents as HastParents, Root as HastRoot, Text } from "hast";
3
+ import type { MdxFlowExpressionHast, MdxjsEsmHast, MdxJsxFlowElementHast, MdxJsxTextElementHast, MdxTextExpressionHast } from "../mdx-types.js";
4
+ import type { Data, HastRaw, SourceFormat } from "../types.js";
5
+ import { type HastNode } from "./hast-materializer.js";
9
6
  import type { HastHandle } from "../handles.js";
7
+ import { type PluginOptions, type SettableFieldKey } from "../visitor-shared.js";
10
8
  export type { HastHandle };
11
9
  /** ESTree-compatible Program node returned by `parseExpression()`. */
12
10
  export type EstreeProgram = Program;
13
11
  export interface HastDiagnostic {
14
12
  message: string;
15
13
  nodeId?: number | undefined;
14
+ position?: HastNode["position"] | undefined;
16
15
  severity: "error" | "warning" | "info";
17
16
  }
18
17
  export interface HastVisitorContext {
@@ -61,6 +60,20 @@ export interface HastVisitorContext {
61
60
  insertChildAt(node: Readonly<HastNode>, index: number, childNode: HastContent | HastContent[]): void;
62
61
  /** Remove the `index`-th child of `node`; a no-op when there is no such child. */
63
62
  removeChildAt(node: Readonly<HastNode>, index: number): void;
63
+ /** Replace a supported field on the node, including scalars and structured fields. */
64
+ setField<N extends HastNode, K extends SettableFieldKey<N>>(node: Readonly<N>, key: K, value: Exclude<N[K], undefined>): void;
65
+ /** `children` is structural and every parent accepts it, including node-type unions. */
66
+ setField(node: Readonly<HastNode>, key: "children", value: readonly HastContent[]): void;
67
+ /** `data` is an open per-node bag serialized to JSON. `null` clears it. */
68
+ setField(node: Readonly<HastNode>, key: "data", value: Record<string, unknown> | null): void;
69
+ /** Set one entry in an MDX JSX element's `attributes`. */
70
+ setAttribute(node: Readonly<HastNode>, name: string, value: unknown): void;
71
+ /**
72
+ * Set one entry in a hast element's `properties`.
73
+ *
74
+ * Using this for node fields or MDX JSX attributes is deprecated; use
75
+ * `setField` or `setAttribute` respectively.
76
+ */
64
77
  setProperty(node: Readonly<HastNode>, key: string, value: unknown): void;
65
78
  /** Collect the concatenated text of all descendant text nodes (like DOM textContent). */
66
79
  textContent(node: Readonly<HastNode>): string;
@@ -102,12 +115,19 @@ export interface RawHtmlHastContent {
102
115
  /** @deprecated Use the equivalent `{ raw }`. */
103
116
  rawHtml: string;
104
117
  }
118
+ /**
119
+ * Encode a standalone HAST tree as an op-stream document: one `root` wrapping
120
+ * `nodes`, which Rust replays into an arena of its own and serializes. The
121
+ * bytes are handed to `use` because they are a view into a pooled buffer,
122
+ * valid only until it is released.
123
+ */
124
+ export declare function encodeHastDocument<T>(nodes: readonly HastNode[], use: (ops: Uint8Array) => T): T;
105
125
  /** A filtered visitor: Rust filters by tag/component name, only matched nodes cross the boundary. */
106
126
  export interface HastFilteredVisitor<N extends HastNode = HastNode> {
107
127
  filter: string[];
108
128
  visit(node: Readonly<N>, ctx: HastVisitorContext): HastNode | void | Promise<HastNode | void>;
109
129
  }
110
- type HastVisitorFn<N extends HastNode = HastNode> = (node: Readonly<N>, ctx: HastVisitorContext) => HastNode | void | Promise<HastNode | void>;
130
+ export type HastVisitorFn<N extends HastNode = HastNode> = (node: Readonly<N>, ctx: HastVisitorContext) => HastNode | void | Promise<HastNode | void>;
111
131
  export type HastHookFn = (root: Readonly<HastRoot>, ctx: HastVisitorContext) => void | Promise<void>;
112
132
  export interface HastVisitorInstance {
113
133
  /** Plugin-level configuration (e.g. `{ position: true }` to read positions). */
@@ -149,14 +169,7 @@ export declare function resolveSubscriptions(plugin: HastVisitorInstance): Resol
149
169
  * Promise of that count if any visitor is async.
150
170
  */
151
171
  export declare function visitHastHandle(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string), fileURL: URL | undefined, data?: Data, sourceFormat?: SourceFormat, diagnostics?: HastDiagnostic[]): number | Promise<number>;
152
- /** Run a HAST visitor, build the command buffer, but do NOT apply it. Returns
153
- * the merged commands so the caller can choose how to dispatch: either via
154
- * `applyCommandsToHandle` (intermediate plugins in a chain) or via a fused
155
- * NAPI call like `applyCommandsAndRenderHandle` (final plugin, saves one
156
- * apply + one render + one drop crossing). Empty result means no mutations.
157
- */
158
172
  export declare function visitHastHandleCollect(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string), fileURL: URL | undefined, data?: Data, sourceFormat?: SourceFormat, diagnostics?: HastDiagnostic[]): Uint8Array | Promise<Uint8Array>;
159
- /** Its own pass so the caller can apply what `before` queued before the
160
- * visitors walk, and the visitors' mutations before `after` reads the tree. */
173
+ /** Hooks run in separate passes so each sees mutations from the preceding pass. */
161
174
  export declare function visitHastHookCollect(handle: HastHandle, plugin: HastVisitorInstance, hook: HastHookFn, source: string | (() => string), fileURL: URL | undefined, data?: Data, sourceFormat?: SourceFormat, diagnostics?: HastDiagnostic[]): Uint8Array | Promise<Uint8Array>;
162
175
  export declare function visitHastHook(handle: HastHandle, plugin: HastVisitorInstance, hook: HastHookFn, source: string | (() => string), fileURL: URL | undefined, data?: Data, sourceFormat?: SourceFormat, diagnostics?: HastDiagnostic[]): number | Promise<number>;