satteri 0.8.1 → 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.
Files changed (60) hide show
  1. package/README.md +35 -0
  2. package/dist/byte-writer.d.ts +31 -0
  3. package/dist/byte-writer.js +106 -0
  4. package/dist/child-stub.d.ts +17 -0
  5. package/dist/child-stub.js +57 -0
  6. package/dist/command-buffer.d.ts +23 -26
  7. package/dist/command-buffer.js +64 -132
  8. package/dist/compile.d.ts +5 -1
  9. package/dist/compile.js +40 -26
  10. package/dist/directive-types.d.ts +4 -2
  11. package/dist/generated/arena-layout.d.ts +37 -0
  12. package/dist/generated/arena-layout.js +41 -0
  13. package/dist/generated/wire-constants.d.ts +51 -0
  14. package/dist/generated/wire-constants.js +63 -0
  15. package/dist/handles.d.ts +21 -0
  16. package/dist/handles.js +1 -0
  17. package/dist/hast/child-stub.d.ts +18 -0
  18. package/dist/hast/child-stub.js +43 -0
  19. package/dist/hast/element-props.d.ts +2 -0
  20. package/dist/hast/element-props.js +23 -0
  21. package/dist/hast/generated/node-types.d.ts +14 -0
  22. package/dist/hast/generated/node-types.js +64 -0
  23. package/dist/hast/hast-materializer.d.ts +2 -1
  24. package/dist/hast/hast-materializer.js +9 -45
  25. package/dist/hast/hast-reader.d.ts +18 -23
  26. package/dist/hast/hast-reader.js +48 -115
  27. package/dist/hast/hast-visitor.d.ts +35 -13
  28. package/dist/hast/hast-visitor.js +395 -302
  29. package/dist/index.d.ts +10 -4
  30. package/dist/index.js +23 -1
  31. package/dist/lazy-child-resolver.d.ts +36 -0
  32. package/dist/lazy-child-resolver.js +95 -0
  33. package/dist/lazy-props.d.ts +1 -1
  34. package/dist/mdast/child-stub.d.ts +18 -0
  35. package/dist/mdast/child-stub.js +46 -0
  36. package/dist/mdast/column-align.d.ts +1 -0
  37. package/dist/mdast/column-align.js +6 -0
  38. package/dist/mdast/generated/layout.d.ts +19 -0
  39. package/dist/mdast/generated/layout.js +272 -0
  40. package/dist/mdast/generated/node-types.d.ts +58 -0
  41. package/dist/mdast/generated/node-types.js +266 -0
  42. package/dist/mdast/mdast-materializer.d.ts +3 -2
  43. package/dist/mdast/mdast-materializer.js +9 -93
  44. package/dist/mdast/mdast-reader.d.ts +4 -120
  45. package/dist/mdast/mdast-reader.js +45 -272
  46. package/dist/mdast/mdast-visitor.d.ts +52 -14
  47. package/dist/mdast/mdast-visitor.js +348 -427
  48. package/dist/mdx-attr.d.ts +2 -0
  49. package/dist/mdx-attr.js +20 -0
  50. package/dist/op-stream.d.ts +38 -0
  51. package/dist/op-stream.js +125 -0
  52. package/dist/phantom.d.ts +2 -0
  53. package/dist/phantom.js +13 -0
  54. package/dist/types.d.ts +34 -4
  55. package/dist/visitor-shared.d.ts +28 -0
  56. package/dist/visitor-shared.js +52 -0
  57. package/dist/wire-read.d.ts +27 -0
  58. package/dist/wire-read.js +59 -0
  59. package/index.js +52 -52
  60. package/package.json +6 -6
@@ -1,5 +1,7 @@
1
1
  import { HastReader, HAST_ROOT, HAST_ELEMENT, HAST_TEXT, HAST_COMMENT, HAST_DOCTYPE, HAST_RAW, HAST_MDX_JSX_ELEMENT, HAST_MDX_JSX_TEXT_ELEMENT, HAST_MDX_FLOW_EXPRESSION, HAST_MDX_TEXT_EXPRESSION, HAST_MDX_ESM, } from "./hast-reader.js";
2
+ import { TYPE_NAMES } from "./generated/node-types.js";
2
3
  import { lazyProp, lazyGroup } from "../lazy-props.js";
4
+ import { restorePhantomSpaces } from "../phantom.js";
3
5
  function propsToRecord(props) {
4
6
  const result = {};
5
7
  for (const p of props) {
@@ -12,47 +14,13 @@ function propsToRecord(props) {
12
14
  */
13
15
  export function materializeHastNode(reader, nodeId) {
14
16
  const nodeType = reader.getNodeType(nodeId);
15
- let typeName;
16
- switch (nodeType) {
17
- case HAST_ROOT:
18
- typeName = "root";
19
- break;
20
- case HAST_ELEMENT:
21
- typeName = "element";
22
- break;
23
- case HAST_TEXT:
24
- typeName = "text";
25
- break;
26
- case HAST_COMMENT:
27
- typeName = "comment";
28
- break;
29
- case HAST_DOCTYPE:
30
- typeName = "doctype";
31
- break;
32
- case HAST_RAW:
33
- typeName = "raw";
34
- break;
35
- case HAST_MDX_JSX_ELEMENT:
36
- typeName = "mdxJsxFlowElement";
37
- break;
38
- case HAST_MDX_JSX_TEXT_ELEMENT:
39
- typeName = "mdxJsxTextElement";
40
- break;
41
- case HAST_MDX_FLOW_EXPRESSION:
42
- typeName = "mdxFlowExpression";
43
- break;
44
- case HAST_MDX_TEXT_EXPRESSION:
45
- typeName = "mdxTextExpression";
46
- break;
47
- case HAST_MDX_ESM:
48
- typeName = "mdxjsEsm";
49
- break;
50
- default:
51
- typeName = `unknown(${nodeType})`;
52
- break;
17
+ const typeName = TYPE_NAMES[nodeType] ?? `unknown(${nodeType})`;
18
+ const node = { type: typeName };
19
+ // Position decodes to three objects; defer it — passthrough children (e.g.
20
+ // replaceNode keeping `node.children`) never read it.
21
+ if (reader.hasPosition(nodeId)) {
22
+ Object.defineProperty(node, "position", lazyProp("position", () => reader.getPosition(nodeId)));
53
23
  }
54
- const position = reader.getPosition(nodeId);
55
- const node = (position ? { type: typeName, position } : { type: typeName });
56
24
  // _nodeId: non-enumerable internal reference
57
25
  Object.defineProperty(node, "_nodeId", {
58
26
  value: nodeId,
@@ -135,11 +103,7 @@ export function materializeHastNode(reader, nodeId) {
135
103
  case HAST_MDX_FLOW_EXPRESSION:
136
104
  case HAST_MDX_TEXT_EXPRESSION:
137
105
  Object.defineProperties(node, {
138
- // Substitute U+F002 phantom-space sentinels (see PHANTOM_SPACE in
139
- // satteri-pulldown-cmark/src/mdx.rs) back to regular spaces — mdast
140
- // and hast keep phantoms in the value field; only the compile path
141
- // drops them.
142
- value: lazyProp("value", () => reader.getTextValue(nodeId).replaceAll("", " ")),
106
+ value: lazyProp("value", () => restorePhantomSpaces(reader.getTextValue(nodeId))),
143
107
  });
144
108
  break;
145
109
  case HAST_MDX_ESM:
@@ -1,17 +1,18 @@
1
1
  import type { BufferHeader } from "../types.js";
2
2
  import type { MdxJsxAttribute, MdxJsxExpressionAttribute } from "../mdx-types.js";
3
+ import type { Position } from "unist";
3
4
  export type { MdxJsxAttribute, MdxJsxExpressionAttribute };
4
- export declare const HAST_ROOT = 0;
5
- export declare const HAST_ELEMENT = 1;
6
- export declare const HAST_TEXT = 2;
7
- export declare const HAST_COMMENT = 3;
8
- export declare const HAST_DOCTYPE = 4;
9
- export declare const HAST_RAW = 5;
10
- export declare const HAST_MDX_JSX_ELEMENT = 10;
11
- export declare const HAST_MDX_JSX_TEXT_ELEMENT = 11;
12
- export declare const HAST_MDX_FLOW_EXPRESSION = 12;
13
- export declare const HAST_MDX_ESM = 13;
14
- export declare const HAST_MDX_TEXT_EXPRESSION = 14;
5
+ export declare const HAST_ROOT: number;
6
+ export declare const HAST_ELEMENT: number;
7
+ export declare const HAST_TEXT: number;
8
+ export declare const HAST_COMMENT: number;
9
+ export declare const HAST_DOCTYPE: number;
10
+ export declare const HAST_RAW: number;
11
+ export declare const HAST_MDX_JSX_ELEMENT: number;
12
+ export declare const HAST_MDX_JSX_TEXT_ELEMENT: number;
13
+ export declare const HAST_MDX_FLOW_EXPRESSION: number;
14
+ export declare const HAST_MDX_ESM: number;
15
+ export declare const HAST_MDX_TEXT_EXPRESSION: number;
15
16
  export interface HastProperty {
16
17
  name: string;
17
18
  value: string | number | boolean | string[];
@@ -33,20 +34,14 @@ export declare class HastReader {
33
34
  /** Read a substring from the string pool by byte offset and length. */
34
35
  getString(offset: number, len: number): string;
35
36
  /** Get position data for a node. */
36
- getPosition(nodeId: number): {
37
- start: {
38
- offset: number;
39
- line: number;
40
- column: number;
41
- };
42
- end: {
43
- offset: number;
44
- line: number;
45
- column: number;
46
- };
47
- } | undefined;
37
+ getPosition(nodeId: number): Position | undefined;
38
+ /** Whether the node carries a source position (line 0 + offset 0 is the
39
+ * synthesized-node sentinel), without decoding the three position objects. */
40
+ hasPosition(nodeId: number): boolean;
48
41
  /** Get the node_type byte for a given node ID. */
49
42
  getNodeType(nodeId: number): number;
43
+ /** Get the parent id for a given node (0xffffffff at the root). */
44
+ getParentId(nodeId: number): number;
50
45
  /** Get child node IDs for a given node. */
51
46
  getChildIds(nodeId: number): number[];
52
47
  /** Push child node IDs directly onto a stack array (reverse order for depth-first). */
@@ -1,53 +1,20 @@
1
- // HAST node type constants (must match node_types.rs)
2
- export const HAST_ROOT = 0;
3
- export const HAST_ELEMENT = 1;
4
- export const HAST_TEXT = 2;
5
- export const HAST_COMMENT = 3;
6
- export const HAST_DOCTYPE = 4;
7
- export const HAST_RAW = 5;
8
- // MDX-specific HAST node types
9
- export const HAST_MDX_JSX_ELEMENT = 10;
10
- export const HAST_MDX_JSX_TEXT_ELEMENT = 11;
11
- export const HAST_MDX_FLOW_EXPRESSION = 12;
12
- export const HAST_MDX_ESM = 13;
13
- export const HAST_MDX_TEXT_EXPRESSION = 14;
14
- const PROP_STRING = 0;
15
- const PROP_BOOL_TRUE = 1;
16
- const PROP_BOOL_FALSE = 2;
17
- const PROP_SPACE_SEP = 3;
18
- const PROP_COMMA_SEP = 4;
19
- // MDX JSX attribute kinds (must match node_types.rs)
20
- const MDX_ATTR_BOOLEAN_PROP = 0;
21
- const MDX_ATTR_LITERAL_PROP = 1;
22
- const MDX_ATTR_EXPRESSION_PROP = 2;
23
- const MDX_ATTR_SPREAD = 3;
24
- // HastNode field offsets (same layout as MDAST, shared binary format)
25
- // id: u32 @ 0
26
- // node_type: u8 @ 4
27
- // _pad: [u8; 3] @ 5
28
- // parent: u32 @ 8
29
- // ...
30
- // children_start: u32 @ 36
31
- // children_count: u32 @ 40
32
- // data_offset: u32 @ 44
33
- // data_len: u32 @ 48
34
- // Total: 52 bytes
35
- const FIELD = {
36
- node_type: 4,
37
- start_offset: 12,
38
- end_offset: 16,
39
- start_line: 20,
40
- start_column: 24,
41
- end_line: 28,
42
- end_column: 32,
43
- children_start: 36,
44
- children_count: 40,
45
- data_offset: 44,
46
- data_len: 48,
47
- };
48
- // "MDAR" bytes: 4d 44 41 52; read as LE u32 = 0x5241444d
49
- const MAGIC = 0x5241444d;
50
- const KIND_HAST = 2;
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 { NAME_TO_TYPE } from "./generated/node-types.js";
6
+ import { ARENA_MAGIC, KIND_HAST, FIELD, HEADER } from "../generated/arena-layout.js";
7
+ export const HAST_ROOT = NAME_TO_TYPE.root;
8
+ export const HAST_ELEMENT = NAME_TO_TYPE.element;
9
+ export const HAST_TEXT = NAME_TO_TYPE.text;
10
+ export const HAST_COMMENT = NAME_TO_TYPE.comment;
11
+ export const HAST_DOCTYPE = NAME_TO_TYPE.doctype;
12
+ export const HAST_RAW = NAME_TO_TYPE.raw;
13
+ export const HAST_MDX_JSX_ELEMENT = NAME_TO_TYPE.mdxJsxFlowElement;
14
+ export const HAST_MDX_JSX_TEXT_ELEMENT = NAME_TO_TYPE.mdxJsxTextElement;
15
+ export const HAST_MDX_FLOW_EXPRESSION = NAME_TO_TYPE.mdxFlowExpression;
16
+ export const HAST_MDX_ESM = NAME_TO_TYPE.mdxjsEsm;
17
+ export const HAST_MDX_TEXT_EXPRESSION = NAME_TO_TYPE.mdxTextExpression;
51
18
  export class HastReader {
52
19
  #view;
53
20
  #header;
@@ -65,27 +32,27 @@ export class HastReader {
65
32
  }
66
33
  #readHeader() {
67
34
  const v = this.#view;
68
- const magic = v.getUint32(0, true);
69
- if (magic !== MAGIC) {
35
+ const magic = v.getUint32(HEADER.magic, true);
36
+ if (magic !== ARENA_MAGIC) {
70
37
  throw new Error(`Invalid HAST buffer: bad magic 0x${magic.toString(16)}`);
71
38
  }
72
- const kind = v.getUint32(4, true);
39
+ const kind = v.getUint32(HEADER.kind, true);
73
40
  if (kind !== KIND_HAST) {
74
41
  throw new Error(`HastReader was handed a buffer of kind ${kind} (expected ${KIND_HAST}). ` +
75
42
  `MDAST and HAST node types overlap; reading the wrong kind decodes garbage.`);
76
43
  }
77
44
  return {
78
- nodeStructSize: v.getUint32(8, true),
79
- nodeCount: v.getUint32(12, true),
80
- nodesOffset: v.getUint32(16, true),
81
- childrenCount: v.getUint32(20, true),
82
- childrenOffset: v.getUint32(24, true),
83
- typeDataLen: v.getUint32(28, true),
84
- typeDataOffset: v.getUint32(32, true),
85
- sourceLen: v.getUint32(36, true),
86
- sourceOffset: v.getUint32(40, true),
87
- nodeDataCount: v.getUint32(44, true),
88
- nodeDataOffset: v.getUint32(48, true),
45
+ nodeStructSize: v.getUint32(HEADER.node_struct_size, true),
46
+ nodeCount: v.getUint32(HEADER.node_count, true),
47
+ nodesOffset: v.getUint32(HEADER.nodes_offset, true),
48
+ childrenCount: v.getUint32(HEADER.children_count, true),
49
+ childrenOffset: v.getUint32(HEADER.children_offset, true),
50
+ typeDataLen: v.getUint32(HEADER.type_data_len, true),
51
+ typeDataOffset: v.getUint32(HEADER.type_data_offset, true),
52
+ sourceLen: v.getUint32(HEADER.source_len, true),
53
+ sourceOffset: v.getUint32(HEADER.source_offset, true),
54
+ nodeDataCount: v.getUint32(HEADER.node_data_count, true),
55
+ nodeDataOffset: v.getUint32(HEADER.node_data_offset, true),
89
56
  };
90
57
  }
91
58
  #nodeDataTable = null;
@@ -139,30 +106,27 @@ export class HastReader {
139
106
  }
140
107
  /** Get position data for a node. */
141
108
  getPosition(nodeId) {
109
+ const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
110
+ return readPosition(this.#view, base + FIELD.start_offset);
111
+ }
112
+ /** Whether the node carries a source position (line 0 + offset 0 is the
113
+ * synthesized-node sentinel), without decoding the three position objects. */
114
+ hasPosition(nodeId) {
142
115
  const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
143
116
  const v = this.#view;
144
- const startLine = v.getUint32(base + FIELD.start_line, true);
145
- const startOffset = v.getUint32(base + FIELD.start_offset, true);
146
- if (startLine === 0 && startOffset === 0)
147
- return undefined;
148
- return {
149
- start: {
150
- offset: startOffset,
151
- line: startLine,
152
- column: v.getUint32(base + FIELD.start_column, true),
153
- },
154
- end: {
155
- offset: v.getUint32(base + FIELD.end_offset, true),
156
- line: v.getUint32(base + FIELD.end_line, true),
157
- column: v.getUint32(base + FIELD.end_column, true),
158
- },
159
- };
117
+ return (v.getUint32(base + FIELD.start_line, true) !== 0 ||
118
+ v.getUint32(base + FIELD.start_offset, true) !== 0);
160
119
  }
161
120
  /** Get the node_type byte for a given node ID. */
162
121
  getNodeType(nodeId) {
163
122
  const { nodesOffset, nodeStructSize } = this.#header;
164
123
  return this.#view.getUint8(nodesOffset + nodeId * nodeStructSize + FIELD.node_type);
165
124
  }
125
+ /** Get the parent id for a given node (0xffffffff at the root). */
126
+ getParentId(nodeId) {
127
+ const { nodesOffset, nodeStructSize } = this.#header;
128
+ return this.#view.getUint32(nodesOffset + nodeId * nodeStructSize + FIELD.parent, true);
129
+ }
166
130
  /** Get child node IDs for a given node. */
167
131
  getChildIds(nodeId) {
168
132
  const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
@@ -233,39 +197,8 @@ export class HastReader {
233
197
  const name = this.getString(nameRef.offset, nameRef.len);
234
198
  const valueType = data[base + 8];
235
199
  const valueRef = this.#readStringRef(data, base + 12);
236
- switch (valueType) {
237
- case PROP_BOOL_TRUE:
238
- properties.push({ name, value: true });
239
- break;
240
- case PROP_BOOL_FALSE:
241
- properties.push({ name, value: false });
242
- break;
243
- case PROP_STRING:
244
- properties.push({ name, value: this.getString(valueRef.offset, valueRef.len) });
245
- break;
246
- case PROP_SPACE_SEP: {
247
- const raw = this.getString(valueRef.offset, valueRef.len);
248
- properties.push({ name, value: raw.split(" ").filter((s) => s.length > 0) });
249
- break;
250
- }
251
- case PROP_COMMA_SEP: {
252
- const raw = this.getString(valueRef.offset, valueRef.len);
253
- properties.push({
254
- name,
255
- value: raw
256
- .split(",")
257
- .map((s) => s.trim())
258
- .filter((s) => s.length > 0),
259
- });
260
- break;
261
- }
262
- case 5: {
263
- // PROP_INT
264
- const raw = this.getString(valueRef.offset, valueRef.len);
265
- properties.push({ name, value: Number(raw) });
266
- break;
267
- }
268
- }
200
+ const valueStr = this.getString(valueRef.offset, valueRef.len);
201
+ properties.push({ name, value: decodeElementProp(valueType, valueStr) });
269
202
  }
270
203
  return { tagName, properties };
271
204
  }
@@ -313,14 +246,14 @@ export class HastReader {
313
246
  name: this.getString(attrNameRef.offset, attrNameRef.len),
314
247
  value: {
315
248
  type: "mdxJsxAttributeValueExpression",
316
- value: this.getString(attrValueRef.offset, attrValueRef.len).replaceAll("", " "),
249
+ value: restorePhantomSpaces(this.getString(attrValueRef.offset, attrValueRef.len)),
317
250
  },
318
251
  });
319
252
  break;
320
253
  case MDX_ATTR_SPREAD:
321
254
  attributes.push({
322
255
  type: "mdxJsxExpressionAttribute",
323
- value: this.getString(attrValueRef.offset, attrValueRef.len).replaceAll("", " "),
256
+ value: restorePhantomSpaces(this.getString(attrValueRef.offset, attrValueRef.len)),
324
257
  });
325
258
  break;
326
259
  }
@@ -1,11 +1,12 @@
1
1
  import { type HastNode } from "./hast-materializer.js";
2
- import type { HastRaw } from "../types.js";
3
- import type { Element, Text, Comment, Doctype } from "hast";
2
+ import type { HastRaw, Data } from "../types.js";
3
+ import type { Element, Text, Comment, Doctype, Parents as HastParents, Root as HastRoot } from "hast";
4
4
  import type { Program } from "estree-jsx";
5
5
  import type { MdxJsxFlowElementHast, MdxJsxTextElementHast } from "../mdx-types.js";
6
6
  import type { MdxFlowExpressionHast, MdxTextExpressionHast } from "../mdx-types.js";
7
7
  import type { MdxjsEsmHast } from "../mdx-types.js";
8
- export type HastHandle = any;
8
+ import type { HastHandle } from "../handles.js";
9
+ export type { HastHandle };
9
10
  /** ESTree-compatible Program node returned by `parseExpression()`. */
10
11
  export type EstreeProgram = Program;
11
12
  export interface HastDiagnostic {
@@ -21,25 +22,45 @@ export interface HastVisitorContext {
21
22
  * decoded filesystem path.
22
23
  */
23
24
  readonly fileURL: URL | undefined;
25
+ /**
26
+ * Document-level data bag, shared across every plugin in the compile and
27
+ * across the mdast→hast phase boundary. Mutate keys directly
28
+ * (`ctx.data.foo = x`); the bag itself isn't reassignable. Values are kept
29
+ * on the JS side, so any value is allowed, including functions and class
30
+ * instances. Returned to the caller as `result.data`.
31
+ */
32
+ readonly data: Data;
24
33
  removeNode(node: Readonly<HastNode>): void;
25
- replaceNode(node: Readonly<HastNode>, newNode: HastNode): void;
26
- insertBefore(node: Readonly<HastNode>, newNode: HastNode | HastNode[]): void;
27
- insertAfter(node: Readonly<HastNode>, newNode: HastNode | HastNode[]): void;
34
+ replaceNode(node: Readonly<HastNode>, newNode: HastContent): void;
35
+ insertBefore(node: Readonly<HastNode>, newNode: HastContent | HastContent[]): void;
36
+ insertAfter(node: Readonly<HastNode>, newNode: HastContent | HastContent[]): void;
28
37
  /**
29
38
  * Wrap `node` in `parentNode`, making it `parentNode`'s first child. Any
30
39
  * children `parentNode` declares are kept after it, so a `div` with an anchor
31
40
  * child wraps a heading as `div > [heading, anchor]`.
32
41
  */
33
- wrapNode(node: Readonly<HastNode>, parentNode: HastNode): void;
34
- prependChild(node: Readonly<HastNode>, childNode: HastNode | HastNode[]): void;
35
- appendChild(node: Readonly<HastNode>, childNode: HastNode | HastNode[]): void;
42
+ wrapNode(node: Readonly<HastNode>, parentNode: HastContent): void;
43
+ prependChild(node: Readonly<HastNode>, childNode: HastContent | HastContent[]): void;
44
+ appendChild(node: Readonly<HastNode>, childNode: HastContent | HastContent[]): void;
36
45
  /** Insert one node or an array at `index`; clamps (`0` or less prepends, past the end appends). */
37
- insertChildAt(node: Readonly<HastNode>, index: number, childNode: HastNode | HastNode[]): void;
46
+ insertChildAt(node: Readonly<HastNode>, index: number, childNode: HastContent | HastContent[]): void;
38
47
  /** Remove the `index`-th child of `node`; a no-op when there is no such child. */
39
48
  removeChildAt(node: Readonly<HastNode>, index: number): void;
40
49
  setProperty(node: Readonly<HastNode>, key: string, value: unknown): void;
41
50
  /** Collect the concatenated text of all descendant text nodes (like DOM textContent). */
42
51
  textContent(node: Readonly<HastNode>): string;
52
+ /**
53
+ * The parent of a node, or `undefined` at the root. Within a pass the same
54
+ * parent is always the same object, so visitors on sibling nodes can dedupe
55
+ * by identity.
56
+ */
57
+ parent<N extends Exclude<HastNode, HastRoot>>(node: Readonly<N>): Readonly<HastParents>;
58
+ parent(node: Readonly<HastNode>): Readonly<HastParents> | undefined;
59
+ /**
60
+ * Index of `node` within its parent's children, or `undefined` at the root.
61
+ * Use this rather than `parent.children.indexOf(node)`, which won't find it.
62
+ */
63
+ indexOf(node: Readonly<HastNode>): number | undefined;
43
64
  report(opts: {
44
65
  message: string;
45
66
  node?: Readonly<HastNode>;
@@ -47,6 +68,9 @@ export interface HastVisitorContext {
47
68
  }): void;
48
69
  getDiagnostics(): HastDiagnostic[];
49
70
  }
71
+ /** New content for a HAST structural mutation. Unlike [`MdastContent`], HAST has
72
+ * a `raw` node type, so it needs no raw/rawHtml escape hatch. */
73
+ export type HastContent = HastNode;
50
74
  /** A filtered visitor: Rust filters by tag/component name, only matched nodes cross the boundary. */
51
75
  export interface HastFilteredVisitor<N extends HastNode = HastNode> {
52
76
  filter: string[];
@@ -76,7 +100,6 @@ interface ResolvedSubscription {
76
100
  tagFilter: string[];
77
101
  visitFn: (node: HastNode, ctx: HastVisitorContext) => HastNode | void;
78
102
  }
79
- /** Resolve subscriptions from a plugin instance. */
80
103
  export declare function resolveSubscriptions(plugin: HastVisitorInstance): ResolvedSubscription[];
81
104
  /**
82
105
  * Walk a handle's arena in Rust, dispatch matched nodes to JS visitor functions,
@@ -86,5 +109,4 @@ export declare function resolveSubscriptions(plugin: HastVisitorInstance): Resol
86
109
  * replaced earlier in the same pass (the caller warns when non-zero), or a
87
110
  * Promise of that count if any visitor is async.
88
111
  */
89
- export declare function visitHastHandle(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string), fileURL: URL | undefined): number | Promise<number>;
90
- export {};
112
+ export declare function visitHastHandle(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string), fileURL: URL | undefined, data?: Data): number | Promise<number>;