satteri 0.10.4 → 0.11.1

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 (89) 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 +99 -0
  16. package/dist/compile-types.d.ts +330 -0
  17. package/dist/compile-types.js +1 -0
  18. package/dist/compile.d.ts +22 -328
  19. package/dist/compile.js +129 -379
  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.js +0 -5
  27. package/dist/generated/wire-constants.d.ts +3 -0
  28. package/dist/generated/wire-constants.js +33 -42
  29. package/dist/handles.d.ts +0 -7
  30. package/dist/hast/child-stub.d.ts +1 -8
  31. package/dist/hast/child-stub.js +14 -13
  32. package/dist/hast/element-props.d.ts +13 -0
  33. package/dist/hast/element-props.js +52 -6
  34. package/dist/hast/generated/node-types.d.ts +1 -9
  35. package/dist/hast/generated/node-types.js +1 -9
  36. package/dist/hast/generated/walk-decode.d.ts +10 -0
  37. package/dist/hast/generated/walk-decode.js +61 -0
  38. package/dist/hast/hast-materializer.d.ts +3 -10
  39. package/dist/hast/hast-materializer.js +73 -47
  40. package/dist/hast/hast-reader.d.ts +9 -38
  41. package/dist/hast/hast-reader.js +82 -271
  42. package/dist/hast/hast-visitor.d.ts +29 -16
  43. package/dist/hast/hast-visitor.js +197 -508
  44. package/dist/hast/hast-walk.d.ts +18 -0
  45. package/dist/hast/hast-walk.js +264 -0
  46. package/dist/index.d.ts +5 -5
  47. package/dist/index.js +4 -12
  48. package/dist/lazy-child-resolver.d.ts +1 -21
  49. package/dist/lazy-child-resolver.js +11 -32
  50. package/dist/materializer-cache.d.ts +5 -29
  51. package/dist/materializer-cache.js +29 -101
  52. package/dist/mdast/child-stub.d.ts +1 -12
  53. package/dist/mdast/child-stub.js +18 -28
  54. package/dist/mdast/column-align.d.ts +1 -1
  55. package/dist/mdast/column-align.js +0 -2
  56. package/dist/mdast/generated/layout.d.ts +0 -16
  57. package/dist/mdast/generated/layout.js +6 -47
  58. package/dist/mdast/generated/node-types.d.ts +1 -9
  59. package/dist/mdast/generated/node-types.js +3 -9
  60. package/dist/mdast/mdast-materializer.d.ts +3 -11
  61. package/dist/mdast/mdast-materializer.js +91 -63
  62. package/dist/mdast/mdast-reader.d.ts +15 -63
  63. package/dist/mdast/mdast-reader.js +132 -269
  64. package/dist/mdast/mdast-visitor.d.ts +23 -15
  65. package/dist/mdast/mdast-visitor.js +188 -294
  66. package/dist/mdast/mdast-walk.d.ts +13 -0
  67. package/dist/mdast/mdast-walk.js +144 -0
  68. package/dist/mdx-attr.js +0 -3
  69. package/dist/mdx-types.js +1 -6
  70. package/dist/op-stream.d.ts +1 -15
  71. package/dist/op-stream.js +3 -19
  72. package/dist/phantom.d.ts +0 -1
  73. package/dist/phantom.js +1 -9
  74. package/dist/plugin-pipeline.d.ts +32 -0
  75. package/dist/plugin-pipeline.js +177 -0
  76. package/dist/plugin.d.ts +8 -7
  77. package/dist/plugin.js +3 -9
  78. package/dist/types.d.ts +0 -1
  79. package/dist/visitor-node.d.ts +12 -0
  80. package/dist/visitor-node.js +23 -0
  81. package/dist/visitor-shared.d.ts +26 -30
  82. package/dist/visitor-shared.js +34 -36
  83. package/dist/wire-read.d.ts +0 -22
  84. package/dist/wire-read.js +3 -24
  85. package/index.d.ts +19 -10
  86. package/index.js +56 -53
  87. package/package.json +3 -12
  88. package/dist/string-pool.d.ts +0 -13
  89. package/dist/string-pool.js +0 -91
@@ -1,356 +1,222 @@
1
- import { restorePhantomSpaces } from "../phantom.js";
2
- import { readPosition } from "../wire-read.js";
1
+ import { ArenaReader } from "../arena-reader.js";
2
+ import { FIELD, KIND_MDAST, 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";
3
4
  import { decodeColumnAlign } from "./column-align.js";
4
- import { PoolOffsets } from "../string-pool.js";
5
5
  import { NodeTypeName } from "./generated/node-types.js";
6
- import { ARENA_MAGIC, KIND_MDAST, FIELD, HEADER } from "../generated/arena-layout.js";
7
6
  export { NodeType, NodeTypeName } from "./generated/node-types.js";
8
7
  export class MdastReader {
9
- #view;
10
- #header;
11
- #textDecoder;
12
- #stringPoolCache = null;
13
- #poolChecked = false;
14
- #poolOffsets = null;
8
+ #arena;
9
+ // Typed arrays avoid DataView getter overhead in unoptimized V8 tiers.
10
+ #u8;
11
+ #u32;
12
+ #nodesB;
13
+ #nodesW;
14
+ #strideB;
15
+ #strideW;
16
+ #childrenW;
17
+ #typeDataB;
15
18
  constructor(buffer) {
16
- if (buffer instanceof Uint8Array) {
17
- this.#view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
18
- }
19
- else {
20
- this.#view = new DataView(buffer);
21
- }
22
- this.#textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
23
- this.#header = this.#readHeader();
24
- }
25
- #readHeader() {
26
- const v = this.#view;
27
- const magic = v.getUint32(HEADER.magic, true);
28
- if (magic !== ARENA_MAGIC) {
29
- throw new Error(`Invalid buffer: bad magic 0x${magic.toString(16)}, expected 0x${ARENA_MAGIC.toString(16)}`);
30
- }
31
- const kind = v.getUint32(HEADER.kind, true);
32
- if (kind !== KIND_MDAST) {
33
- throw new Error(`MdastReader was handed a buffer of kind ${kind} (expected ${KIND_MDAST}). ` +
34
- `MDAST and HAST node types overlap; reading the wrong kind decodes garbage.`);
35
- }
36
- return {
37
- nodeStructSize: v.getUint32(HEADER.node_struct_size, true),
38
- nodeCount: v.getUint32(HEADER.node_count, true),
39
- nodesOffset: v.getUint32(HEADER.nodes_offset, true),
40
- childrenCount: v.getUint32(HEADER.children_count, true),
41
- childrenOffset: v.getUint32(HEADER.children_offset, true),
42
- typeDataLen: v.getUint32(HEADER.type_data_len, true),
43
- typeDataOffset: v.getUint32(HEADER.type_data_offset, true),
44
- stringPoolLen: v.getUint32(HEADER.string_pool_len, true),
45
- stringPoolOffset: v.getUint32(HEADER.string_pool_offset, true),
46
- nodeDataCount: v.getUint32(HEADER.node_data_count, true),
47
- nodeDataOffset: v.getUint32(HEADER.node_data_offset, true),
48
- };
49
- }
50
- #nodeDataTable = null;
51
- /** Per-node JSON `data` blob (set via `Arena::set_node_data` on the Rust
52
- * side). Lazy-builds a `Map<id, string>` on first call so materialization
53
- * of a data-heavy tree stays O(nodes) rather than O(nodes × entries). */
19
+ const arena = new ArenaReader(buffer, KIND_MDAST);
20
+ this.#arena = arena;
21
+ this.#u8 = arena.u8;
22
+ this.#u32 = arena.u32;
23
+ const header = arena.header;
24
+ this.#nodesB = header.nodesOffset;
25
+ this.#nodesW = header.nodesOffset >> 2;
26
+ this.#strideB = header.nodeStructSize;
27
+ this.#strideW = header.nodeStructSize >> 2;
28
+ this.#childrenW = header.childrenOffset >> 2;
29
+ this.#typeDataB = header.typeDataOffset;
30
+ }
31
+ /** @internal Memoized wire view for the fused decoder. */
32
+ getWire() {
33
+ return this.#arena.getWire();
34
+ }
35
+ /** Per-node JSON data, or null when absent. */
54
36
  getNodeData(nodeId) {
55
- if (this.#header.nodeDataCount === 0)
56
- return null;
57
- if (this.#nodeDataTable === null) {
58
- this.#nodeDataTable = new Map();
59
- const v = this.#view;
60
- let pos = this.#header.nodeDataOffset;
61
- for (let i = 0; i < this.#header.nodeDataCount; i++) {
62
- const id = v.getUint32(pos, true);
63
- pos += 4;
64
- const len = v.getUint32(pos, true);
65
- pos += 4;
66
- const slice = new Uint8Array(this.#view.buffer, this.#view.byteOffset + pos, len);
67
- this.#nodeDataTable.set(id, this.#textDecoder.decode(slice));
68
- pos += len;
69
- }
70
- }
71
- return this.#nodeDataTable.get(nodeId) ?? null;
37
+ return this.#arena.getNodeData(nodeId);
38
+ }
39
+ /** @internal null when no node carries data. */
40
+ getNodeDataTable() {
41
+ return this.#arena.getNodeDataTable();
72
42
  }
73
43
  get nodeCount() {
74
- return this.#header.nodeCount;
44
+ return this.#arena.header.nodeCount;
75
45
  }
76
46
  get header() {
77
- return { ...this.#header };
47
+ return { ...this.#arena.header };
78
48
  }
79
- /** The full string pool (original input + interning heap). Not the document
80
- * source as written; for that, read `ctx.source` from a plugin. */
49
+ /** Full string pool, including interned strings; use ctx.source for the original document. */
81
50
  getStringPool() {
82
- if (this.#stringPoolCache === null) {
83
- const { stringPoolOffset, stringPoolLen } = this.#header;
84
- const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset, stringPoolLen);
85
- this.#stringPoolCache = this.#textDecoder.decode(bytes);
86
- }
87
- return this.#stringPoolCache;
51
+ return this.#arena.getStringPool();
88
52
  }
53
+ /** Wire string refs use UTF-16 units, so substring preserves their offsets. */
89
54
  getString(offset, len) {
90
- if (len === 0)
91
- return "";
92
- const pool = this.getStringPool();
93
- if (!this.#poolChecked) {
94
- this.#poolChecked = true;
95
- const { stringPoolOffset, stringPoolLen } = this.#header;
96
- const extraBytes = stringPoolLen - pool.length;
97
- if (extraBytes > 0) {
98
- const bytes = new Uint8Array(this.#view.buffer, this.#view.byteOffset + stringPoolOffset, stringPoolLen);
99
- this.#poolOffsets = new PoolOffsets(bytes, extraBytes);
100
- }
101
- }
102
- // An all-ASCII pool has byte offsets equal to its UTF-16 indices.
103
- const offsets = this.#poolOffsets;
104
- return offsets === null
105
- ? pool.substring(offset, offset + len)
106
- : offsets.slice(pool, offset, len);
55
+ return this.#arena.getString(offset, len);
107
56
  }
108
57
  getNode(nodeId) {
109
- const { nodesOffset, nodeStructSize, nodeCount } = this.#header;
58
+ const nodeCount = this.nodeCount;
110
59
  if (nodeId >= nodeCount) {
111
60
  throw new RangeError(`Node ID ${nodeId} out of range (count: ${nodeCount})`);
112
61
  }
113
- const base = nodesOffset + nodeId * nodeStructSize;
114
- const v = this.#view;
115
- const type = v.getUint8(base + FIELD.node_type);
116
- const position = readPosition(v, base + FIELD.start_offset);
62
+ const u32 = this.#u32;
63
+ const w = this.#nodesW + nodeId * this.#strideW;
64
+ const type = this.#u8[this.#nodesB + nodeId * this.#strideB + FIELD.node_type] ?? 0;
117
65
  return {
118
- id: v.getUint32(base + FIELD.id, true),
66
+ id: u32[w] ?? 0,
119
67
  type,
120
68
  typeName: NodeTypeName[type] ?? `Unknown(${type})`,
121
- parent: v.getUint32(base + FIELD.parent, true),
122
- position,
123
- childrenStart: v.getUint32(base + FIELD.children_start, true),
124
- childrenCount: v.getUint32(base + FIELD.children_count, true),
125
- dataOffset: v.getUint32(base + FIELD.data_offset, true),
126
- dataLen: v.getUint32(base + FIELD.data_len, true),
69
+ parent: u32[w + W_PARENT] ?? 0,
70
+ position: this.getPosition(nodeId),
71
+ childrenStart: u32[w + W_CHILDREN_START] ?? 0,
72
+ childrenCount: u32[w + W_CHILDREN_COUNT] ?? 0,
73
+ dataOffset: u32[w + W_DATA_OFFSET] ?? 0,
74
+ dataLen: u32[w + W_DATA_LEN] ?? 0,
127
75
  };
128
76
  }
129
77
  /** Fast path: read only the type byte for a node. */
130
78
  getNodeType(nodeId) {
131
- const { nodesOffset, nodeStructSize } = this.#header;
132
- return this.#view.getUint8(nodesOffset + nodeId * nodeStructSize + FIELD.node_type);
79
+ return this.#u8[this.#nodesB + nodeId * this.#strideB + FIELD.node_type] ?? 0;
133
80
  }
81
+ /** A zero start line marks a synthesized node with no source range (unist
82
+ * lines are 1-based), surfaced as `undefined`. */
134
83
  getPosition(nodeId) {
135
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
136
- return readPosition(this.#view, base + FIELD.start_offset);
84
+ const u32 = this.#u32;
85
+ const b = this.#nodesW + nodeId * this.#strideW + W_START_OFFSET;
86
+ const startLine = u32[b + 2] ?? 0;
87
+ if (startLine === 0)
88
+ return undefined;
89
+ return {
90
+ start: { offset: u32[b] ?? 0, line: startLine, column: u32[b + 3] ?? 0 },
91
+ end: { offset: u32[b + 1] ?? 0, line: u32[b + 4] ?? 0, column: u32[b + 5] ?? 0 },
92
+ };
137
93
  }
138
94
  /** Fast path: read only the parent id for a node (0xffffffff at the root). */
139
95
  getParentId(nodeId) {
140
- const { nodesOffset, nodeStructSize } = this.#header;
141
- return this.#view.getUint32(nodesOffset + nodeId * nodeStructSize + FIELD.parent, true);
96
+ return this.#u32[this.#nodesW + nodeId * this.#strideW + W_PARENT] ?? 0;
97
+ }
98
+ getChildrenCount(nodeId) {
99
+ return this.#u32[this.#nodesW + nodeId * this.#strideW + W_CHILDREN_COUNT] ?? 0;
142
100
  }
143
101
  getChildIds(nodeId) {
144
- const { nodesOffset, nodeStructSize, childrenOffset } = this.#header;
145
- const base = nodesOffset + nodeId * nodeStructSize;
146
- const v = this.#view;
147
- const childrenStart = v.getUint32(base + FIELD.children_start, true);
148
- const childrenCount = v.getUint32(base + FIELD.children_count, true);
102
+ const u32 = this.#u32;
103
+ const w = this.#nodesW + nodeId * this.#strideW;
104
+ const childrenStart = u32[w + W_CHILDREN_START] ?? 0;
105
+ const childrenCount = u32[w + W_CHILDREN_COUNT] ?? 0;
149
106
  if (childrenCount === 0)
150
107
  return [];
108
+ const base = this.#childrenW + childrenStart;
151
109
  const ids = [];
152
110
  for (let i = 0; i < childrenCount; i++) {
153
- ids.push(v.getUint32(childrenOffset + (childrenStart + i) * 4, true));
111
+ ids.push(u32[base + i] ?? 0);
154
112
  }
155
113
  return ids;
156
114
  }
157
115
  /** Push child node IDs directly onto a stack array (reverse order for depth-first). */
158
116
  pushChildIds(nodeId, stack) {
159
- const { nodesOffset, nodeStructSize, childrenOffset } = this.#header;
160
- const base = nodesOffset + nodeId * nodeStructSize;
161
- const v = this.#view;
162
- const childrenStart = v.getUint32(base + FIELD.children_start, true);
163
- const childrenCount = v.getUint32(base + FIELD.children_count, true);
164
- if (childrenCount === 0)
165
- return;
117
+ const u32 = this.#u32;
118
+ const w = this.#nodesW + nodeId * this.#strideW;
119
+ const childrenStart = u32[w + W_CHILDREN_START] ?? 0;
120
+ const childrenCount = u32[w + W_CHILDREN_COUNT] ?? 0;
121
+ const base = this.#childrenW + childrenStart;
166
122
  for (let i = childrenCount - 1; i >= 0; i--) {
167
- stack.push(v.getUint32(childrenOffset + (childrenStart + i) * 4, true));
123
+ stack.push(u32[base + i] ?? 0);
168
124
  }
169
125
  }
170
- /** Fixed-layout field reads that touch the buffer directly. The generated
171
- * decoder runs once per node, so an intermediate view or ref object here
172
- * costs an allocation per field. */
126
+ // Read fields directly to avoid allocating an intermediate view or reference per field.
173
127
  fieldU8(nodeId, offset, fallback) {
174
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
175
- const v = this.#view;
176
- if (offset >= v.getUint32(base + FIELD.data_len, true))
128
+ const u32 = this.#u32;
129
+ const w = this.#nodesW + nodeId * this.#strideW;
130
+ if (offset >= (u32[w + W_DATA_LEN] ?? 0))
131
+ return fallback;
132
+ return this.#u8[this.#typeDataB + (u32[w + W_DATA_OFFSET] ?? 0) + offset] ?? fallback;
133
+ }
134
+ fieldU32(nodeId, offset, fallback) {
135
+ const u32 = this.#u32;
136
+ const w = this.#nodesW + nodeId * this.#strideW;
137
+ if (offset + 4 > (u32[w + W_DATA_LEN] ?? 0))
177
138
  return fallback;
178
- const at = this.#header.typeDataOffset + v.getUint32(base + FIELD.data_offset, true);
179
- return v.getUint8(at + offset);
139
+ return u32[(this.#typeDataB + (u32[w + W_DATA_OFFSET] ?? 0) + offset) >> 2] ?? fallback;
180
140
  }
181
141
  /** `""` when the field is absent or empty; callers map that to `null` for
182
142
  * nullable fields, matching the Rust decoders' bounds checks. */
183
143
  fieldString(nodeId, offset) {
184
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
185
- const v = this.#view;
186
- if (offset + 8 > v.getUint32(base + FIELD.data_len, true))
144
+ const u32 = this.#u32;
145
+ const w = this.#nodesW + nodeId * this.#strideW;
146
+ if (offset + 8 > (u32[w + W_DATA_LEN] ?? 0))
187
147
  return "";
188
- const at = this.#header.typeDataOffset + v.getUint32(base + FIELD.data_offset, true) + offset;
189
- return this.getString(v.getUint32(at, true), v.getUint32(at + 4, true));
148
+ const at = (this.#typeDataB + (u32[w + W_DATA_OFFSET] ?? 0) + offset) >> 2;
149
+ return this.getString(u32[at] ?? 0, u32[at + 1] ?? 0);
190
150
  }
191
151
  getTypeData(nodeId) {
192
- const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
193
- const v = this.#view;
194
- const dataOffset = v.getUint32(base + FIELD.data_offset, true);
195
- const dataLen = v.getUint32(base + FIELD.data_len, true);
152
+ const u32 = this.#u32;
153
+ const w = this.#nodesW + nodeId * this.#strideW;
154
+ const dataOffset = u32[w + W_DATA_OFFSET] ?? 0;
155
+ const dataLen = u32[w + W_DATA_LEN] ?? 0;
196
156
  if (dataLen === 0)
197
157
  return new Uint8Array(0);
198
- return new Uint8Array(v.buffer, v.byteOffset + this.#header.typeDataOffset + dataOffset, dataLen);
158
+ const start = this.#typeDataB + dataOffset;
159
+ return this.#u8.subarray(start, start + dataLen);
199
160
  }
200
161
  /** Read a StringRef (offset: u32 LE, len: u32 LE) from type data. */
201
162
  readStringRef(typeData, byteOffset = 0) {
202
- const at = typeData.byteOffset - this.#view.byteOffset + byteOffset;
163
+ const at = (typeData.byteOffset - this.#u8.byteOffset + byteOffset) >> 2;
203
164
  return {
204
- offset: this.#view.getUint32(at, true),
205
- len: this.#view.getUint32(at + 4, true),
165
+ offset: this.#u32[at] ?? 0,
166
+ len: this.#u32[at + 1] ?? 0,
206
167
  };
207
168
  }
208
- /**
209
- * StringRef value. Valid for Text, InlineCode, Html, Yaml, Toml nodes.
210
- * These store a single StringRef as their type data.
211
- */
212
- getTextValue(nodeId) {
213
- return this.fieldString(nodeId, 0);
214
- }
215
- /**
216
- * ListData #[repr(C)]: start(0..4), ordered(4), spread(5), _pad(6..8).
217
- * Valid for List nodes.
218
- */
219
- getListData(nodeId) {
220
- const data = this.getTypeData(nodeId);
221
- const view = new DataView(data.buffer, data.byteOffset);
222
- return {
223
- start: view.getUint32(0, true),
224
- ordered: data[4] !== 0,
225
- spread: data[5] !== 0,
226
- };
227
- }
228
- /**
229
- * ListItemData #[repr(C)]: checked(0), spread(1).
230
- * checked: 0=unchecked, 1=checked, 2=not-a-task-item.
231
- */
232
- getListItemData(nodeId) {
233
- const data = this.getTypeData(nodeId);
234
- const checkedByte = data[0];
235
- return {
236
- checked: checkedByte === 2 ? null : checkedByte === 1,
237
- spread: data[1] !== 0,
238
- };
239
- }
240
- /**
241
- * DescriptionDetailsData #[repr(C)]: spread(0). Valid for descriptionDetails.
242
- */
243
- getDescriptionDetailsData(nodeId) {
244
- const data = this.getTypeData(nodeId);
245
- return { spread: data.length > 0 && data[0] !== 0 };
246
- }
247
- /**
248
- * TableData #[repr(C)]: align_count(0..4), then align_count bytes.
249
- * Alignment bytes: 0=none, 1=left, 2=right, 3=center.
250
- */
169
+ /** Alignment bytes follow a u32 count: 0=none, 1=left, 2=right, 3=center. */
251
170
  getTableAlign(nodeId) {
252
171
  const data = this.getTypeData(nodeId);
253
172
  if (data.length < 4)
254
173
  return [];
255
- const view = new DataView(data.buffer, data.byteOffset);
256
- const count = view.getUint32(0, true);
174
+ const count = this.#u32[(data.byteOffset - this.#u8.byteOffset) >> 2] ?? 0;
257
175
  const result = [];
258
176
  for (let i = 0; i < count; i++) {
259
- result.push(decodeColumnAlign(data[4 + i]));
177
+ result.push(decodeColumnAlign(data[4 + i] ?? 0));
260
178
  }
261
179
  return result;
262
180
  }
263
- /**
264
- * MdxJsxElementData: name StringRef (0..8). len===0 means fragment.
265
- */
181
+ /** A zero-length element name represents an MDX fragment. */
266
182
  getMdxJsxElementName(nodeId) {
267
183
  const data = this.getTypeData(nodeId);
268
184
  const nameRef = this.readStringRef(data, 0);
269
185
  return nameRef.len > 0 ? this.getString(nameRef.offset, nameRef.len) : null;
270
186
  }
271
- /**
272
- * MDX JSX element data: name + attributes.
273
- *
274
- * Layout:
275
- * [name: StringRef(8B)][attr_count: u32(4B)][_pad: u32(4B)] = 16-byte header
276
- * then attr_count * 20 bytes:
277
- * [kind: u8(1B)][_pad: [u8;3](3B)][name: StringRef(8B)][value: StringRef(8B)]
278
- *
279
- * Attribute kinds: 0=boolean, 1=literal, 2=expression, 3=spread
280
- */
187
+ /** Attribute kinds: 0=boolean, 1=literal, 2=expression, 3=spread. */
281
188
  getMdxJsxElementData(nodeId) {
282
- const data = this.getTypeData(nodeId);
283
- if (data.length < 16) {
189
+ const u32 = this.#u32;
190
+ const nw = this.#nodesW + nodeId * this.#strideW;
191
+ const dataLen = u32[nw + W_DATA_LEN] ?? 0;
192
+ if (dataLen < 16) {
284
193
  return { name: this.getMdxJsxElementName(nodeId), attributes: [] };
285
194
  }
286
- const nameRef = this.readStringRef(data, 0);
287
- const name = nameRef.len > 0 ? this.getString(nameRef.offset, nameRef.len) : null;
288
- const view = new DataView(data.buffer, data.byteOffset + 8);
289
- const attrCount = view.getUint32(0, true);
195
+ const w = (this.#typeDataB + (u32[nw + W_DATA_OFFSET] ?? 0)) >> 2;
196
+ const nameLen = u32[w + 1] ?? 0;
197
+ const name = nameLen > 0 ? this.getString(u32[w] ?? 0, nameLen) : null;
198
+ const attrCount = u32[w + 2] ?? 0;
290
199
  const attributes = [];
291
200
  for (let i = 0; i < attrCount; i++) {
292
- const base = 16 + i * 20;
293
- const kind = data[base];
294
- const attrNameRef = this.readStringRef(data, base + 4);
295
- const attrValueRef = this.readStringRef(data, base + 12);
296
- switch (kind) {
297
- case 0: // BooleanProp
298
- attributes.push({
299
- type: "mdxJsxAttribute",
300
- name: this.getString(attrNameRef.offset, attrNameRef.len),
301
- value: null,
302
- });
303
- break;
304
- case 1: // LiteralProp
305
- attributes.push({
306
- type: "mdxJsxAttribute",
307
- name: this.getString(attrNameRef.offset, attrNameRef.len),
308
- value: this.getString(attrValueRef.offset, attrValueRef.len),
309
- });
310
- break;
311
- case 2: // ExpressionProp
312
- attributes.push({
313
- type: "mdxJsxAttribute",
314
- name: this.getString(attrNameRef.offset, attrNameRef.len),
315
- value: {
316
- type: "mdxJsxAttributeValueExpression",
317
- value: restorePhantomSpaces(this.getString(attrValueRef.offset, attrValueRef.len)),
318
- },
319
- });
320
- break;
321
- case 3: // Spread
322
- attributes.push({
323
- type: "mdxJsxExpressionAttribute",
324
- value: restorePhantomSpaces(this.getString(attrValueRef.offset, attrValueRef.len)),
325
- });
326
- break;
327
- }
201
+ const base = w + 4 + i * 5;
202
+ 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)));
328
203
  }
329
204
  return { name, attributes };
330
205
  }
331
- /**
332
- * DirectiveData layout:
333
- * [name: StringRef(8B)][attr_count: u32(4B)][_pad: u32(4B)] = 16-byte header
334
- * then attr_count × 16 bytes:
335
- * [key: StringRef(8B)][value: StringRef(8B)]
336
- */
337
206
  getDirectiveData(nodeId) {
338
- const data = this.getTypeData(nodeId);
339
- if (data.length < 16) {
207
+ const u32 = this.#u32;
208
+ const nw = this.#nodesW + nodeId * this.#strideW;
209
+ if ((u32[nw + W_DATA_LEN] ?? 0) < 16) {
340
210
  return { name: "", attributes: {} };
341
211
  }
342
- const nameRef = this.readStringRef(data, 0);
343
- const name = this.getString(nameRef.offset, nameRef.len);
344
- const view = new DataView(data.buffer, data.byteOffset + 8);
345
- const attrCount = view.getUint32(0, true);
212
+ const w = (this.#typeDataB + (u32[nw + W_DATA_OFFSET] ?? 0)) >> 2;
213
+ const name = this.getString(u32[w] ?? 0, u32[w + 1] ?? 0);
214
+ const attrCount = u32[w + 2] ?? 0;
346
215
  const attributes = {};
347
216
  for (let i = 0; i < attrCount; i++) {
348
- const base = 16 + i * 16;
349
- const keyRef = this.readStringRef(data, base);
350
- const valRef = this.readStringRef(data, base + 8);
351
- const key = this.getString(keyRef.offset, keyRef.len);
352
- const val = this.getString(valRef.offset, valRef.len);
353
- attributes[key] = val;
217
+ const base = w + 4 + i * 4;
218
+ const key = this.getString(u32[base] ?? 0, u32[base + 1] ?? 0);
219
+ attributes[key] = this.getString(u32[base + 2] ?? 0, u32[base + 3] ?? 0);
354
220
  }
355
221
  return { name, attributes };
356
222
  }
@@ -360,14 +226,11 @@ export class MdastReader {
360
226
  walk(visitor, rootId = 0) {
361
227
  const stack = [rootId];
362
228
  while (stack.length > 0) {
363
- const nodeId = stack.pop();
229
+ const nodeId = stack.pop() ?? 0;
364
230
  const nodeType = this.getNodeType(nodeId);
365
231
  const result = visitor(nodeId, nodeType);
366
232
  if (result !== false) {
367
- const childIds = this.getChildIds(nodeId);
368
- for (let i = childIds.length - 1; i >= 0; i--) {
369
- stack.push(childIds[i]);
370
- }
233
+ this.pushChildIds(nodeId, stack);
371
234
  }
372
235
  }
373
236
  }
@@ -1,14 +1,12 @@
1
- import { MdastReader } from "./mdast-reader.js";
1
+ import type { Blockquote, Break, Code, Definition, Delete, Emphasis, FootnoteDefinition, FootnoteReference, Heading, Html, Image, ImageReference, InlineCode, Link, LinkReference, List, ListItem, Parents as MdastParents, Root as MdastRoot, Paragraph, Strong, Table, TableCell, TableRow, Text, ThematicBreak, Yaml } from "mdast";
2
2
  import { CommandBuffer } from "../command-buffer.js";
3
- import type { MdastNode, Toml, MathNode, InlineMath, Superscript, Subscript, DescriptionList, DescriptionTerm, DescriptionDetails, Custom, Data, SourceFormat } from "../types.js";
4
- import { type PluginOptions } from "../visitor-shared.js";
5
- import { LazyChildResolver } from "../lazy-child-resolver.js";
6
- import type { MdastHandle } from "../handles.js";
7
- import type { Blockquote, Break, Code, Definition, Delete, Emphasis, FootnoteDefinition, FootnoteReference, Heading, Html, Image, ImageReference, InlineCode, Link, LinkReference, List, ListItem, Paragraph, Strong, Table, TableRow, TableCell, Text, ThematicBreak, Yaml, Parents as MdastParents, Root as MdastRoot } from "mdast";
8
- import type { MdxJsxFlowElement, MdxJsxTextElement } from "../mdx-types.js";
9
- import type { MdxFlowExpression, MdxTextExpression } from "../mdx-types.js";
10
- import type { MdxjsEsm } from "../mdx-types.js";
11
3
  import type { ContainerDirective, LeafDirective, TextDirective } from "../directive-types.js";
4
+ import type { MdastHandle } from "../handles.js";
5
+ import { LazyChildResolver } from "../lazy-child-resolver.js";
6
+ import type { MdxFlowExpression, MdxjsEsm, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression } from "../mdx-types.js";
7
+ import type { Custom, Data, DescriptionDetails, DescriptionList, DescriptionTerm, InlineMath, MathNode, MdastNode, SourceFormat, Subscript, Superscript, Toml } from "../types.js";
8
+ import { type PluginOptions, type SettableFieldKey } from "../visitor-shared.js";
9
+ import { MdastReader } from "./mdast-reader.js";
12
10
  /** A string spliced into the tree, re-parsed as Markdown. Set `mdxExpressions:
13
11
  * false` to keep MDX `{…}` literal, needed when injecting generated HTML
14
12
  * (KaTeX, highlighters, diagrams) whose braces aren't expressions. Default true. */
@@ -89,6 +87,18 @@ export declare class MdastVisitorContext {
89
87
  * content, or a raw string, which parses to a root of its own.
90
88
  */
91
89
  replaceNode(node: Readonly<MdastTarget>, newNode: MdastContent | MdastContent[]): void;
90
+ setField<N extends MdastTarget, K extends SettableFieldKey<N, true>>(node: Readonly<N>, key: K, value: Exclude<N[K], undefined>): void;
91
+ /** Table alignment is an array stored in the table's typed data. */
92
+ setField(node: Readonly<Table>, key: "align", value: Table["align"]): void;
93
+ /** `children` is structural and every parent accepts it, including node-type unions. */
94
+ setField(node: Readonly<MdastTarget>, key: "children", value: readonly MdastTarget[]): void;
95
+ /** `data` is an open per-node bag serialized to JSON. `null` clears it. */
96
+ setField(node: Readonly<MdastTarget>, key: "data", value: Record<string, unknown> | null): void;
97
+ /** Set one string entry in a directive's `attributes`. */
98
+ setAttribute(node: Readonly<ContainerDirective | LeafDirective | TextDirective>, name: string, value: string): void;
99
+ /** Set one entry in an MDX JSX element's `attributes`. */
100
+ setAttribute(node: Readonly<MdxJsxFlowElement | MdxJsxTextElement>, name: string, value: unknown): void;
101
+ /** @deprecated MDAST has no property container; use `setField`. */
92
102
  setProperty<N extends MdastTarget, K extends keyof N & string>(node: Readonly<N>, key: K, value: N[K]): void;
93
103
  /** `children` is structural and every parent accepts it, so the key also
94
104
  * works on node-type unions (e.g. a node returned by `parent()`). */
@@ -119,12 +129,11 @@ export declare class MdastVisitorContext {
119
129
  node?: Readonly<MdastTarget>;
120
130
  severity?: "error" | "warning" | "info";
121
131
  }): void;
122
- /** Get the binary command buffer for all mutations recorded via context methods. */
123
132
  getCommandBuffer(): CommandBuffer;
124
133
  getDiagnostics(): MdastDiagnostic[];
125
134
  }
126
135
  type MdastVisitorResult = MdastNode | RawMdastContent | RawHtmlMdastContent | undefined | null | void;
127
- type MdastVisitorFn<N extends MdastNode | Custom = MdastNode> = (node: Readonly<N>, context: MdastVisitorContext) => MdastVisitorResult | Promise<MdastVisitorResult>;
136
+ export type MdastVisitorFn<N extends MdastNode | Custom = MdastNode> = (node: Readonly<N>, context: MdastVisitorContext) => MdastVisitorResult | Promise<MdastVisitorResult>;
128
137
  export type MdastHookFn = (root: Readonly<MdastRoot>, context: MdastVisitorContext) => void | Promise<void>;
129
138
  export interface MdastPluginInstance {
130
139
  /** Plugin-level configuration (e.g. `{ position: true }` to read positions). */
@@ -188,17 +197,16 @@ interface MdastVisitResult {
188
197
  export type { MdastHandle };
189
198
  interface MdastSubscription {
190
199
  nodeType: number;
191
- visitFn: (node: MdastNode, context: MdastVisitorContext) => unknown;
200
+ visitFn: MdastVisitorFn;
192
201
  }
193
202
  export declare function resolveMdastSubscriptions(plugin: MdastPluginInstance): MdastSubscription[];
194
203
  /**
195
204
  * Walk an MDAST handle in Rust, dispatch matched nodes to JS visitor functions,
196
- * and apply mutations back to the handle. No arena buffers cross NAPI.
205
+ * and collect mutations for the caller to apply. No arena buffers cross NAPI.
197
206
  *
198
207
  * Returns MdastVisitResult synchronously if all visitors are sync,
199
208
  * or Promise<MdastVisitResult> if any visitor is async.
200
209
  */
201
210
  export declare function visitMdastHandle(handle: MdastHandle, plugin: MdastPluginInstance, subs: MdastSubscription[], source: string | (() => string), fileURL: URL | undefined, data?: Data, sourceFormat?: SourceFormat, diagnostics?: MdastDiagnostic[]): MdastVisitResult | Promise<MdastVisitResult>;
202
- /** Its own pass so the caller can apply what `before` queued before the
203
- * visitors walk, and the visitors' mutations before `after` reads the tree. */
211
+ /** Hooks run in separate passes so each sees mutations from the preceding pass. */
204
212
  export declare function visitMdastHook(handle: MdastHandle, plugin: MdastPluginInstance, hook: MdastHookFn, source: string | (() => string), fileURL: URL | undefined, data?: Data, sourceFormat?: SourceFormat, diagnostics?: MdastDiagnostic[]): MdastVisitResult | Promise<MdastVisitResult>;