satteri 0.9.4 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/binding.browser.d.ts +1 -1
- package/dist/binding.browser.js +1 -1
- package/dist/binding.d.ts +1 -1
- package/dist/binding.js +1 -1
- package/dist/byte-writer.d.ts +4 -0
- package/dist/byte-writer.js +13 -4
- package/dist/child-stub.d.ts +7 -3
- package/dist/child-stub.js +18 -7
- package/dist/command-buffer.d.ts +36 -50
- package/dist/command-buffer.js +102 -49
- package/dist/compile.d.ts +80 -32
- package/dist/compile.js +295 -70
- package/dist/freeze.d.ts +2 -0
- package/dist/freeze.js +10 -0
- package/dist/generated/wire-constants.d.ts +3 -2
- package/dist/generated/wire-constants.js +4 -2
- package/dist/hast/child-stub.d.ts +2 -3
- package/dist/hast/child-stub.js +18 -11
- package/dist/hast/element-props.d.ts +1 -1
- package/dist/hast/element-props.js +14 -6
- package/dist/hast/hast-materializer.d.ts +6 -2
- package/dist/hast/hast-materializer.js +48 -134
- package/dist/hast/hast-reader.d.ts +7 -13
- package/dist/hast/hast-reader.js +76 -21
- package/dist/hast/hast-visitor.d.ts +55 -5
- package/dist/hast/hast-visitor.js +320 -154
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1 -1
- package/dist/lazy-child-resolver.d.ts +20 -13
- package/dist/lazy-child-resolver.js +66 -38
- package/dist/materializer-cache.d.ts +42 -0
- package/dist/materializer-cache.js +192 -0
- package/dist/mdast/child-stub.d.ts +6 -3
- package/dist/mdast/child-stub.js +71 -8
- package/dist/mdast/generated/layout.d.ts +2 -3
- package/dist/mdast/generated/layout.js +24 -28
- package/dist/mdast/generated/node-types.d.ts +4 -0
- package/dist/mdast/generated/node-types.js +25 -0
- package/dist/mdast/mdast-materializer.d.ts +12 -3
- package/dist/mdast/mdast-materializer.js +83 -78
- package/dist/mdast/mdast-reader.d.ts +15 -0
- package/dist/mdast/mdast-reader.js +55 -10
- package/dist/mdast/mdast-visitor.d.ts +79 -32
- package/dist/mdast/mdast-visitor.js +305 -122
- package/dist/op-stream.d.ts +5 -12
- package/dist/op-stream.js +6 -21
- package/dist/plugin.d.ts +36 -10
- package/dist/plugin.js +50 -2
- package/dist/string-pool.d.ts +13 -0
- package/dist/string-pool.js +91 -0
- package/dist/types.d.ts +44 -1
- package/dist/visitor-shared.d.ts +27 -4
- package/dist/visitor-shared.js +36 -10
- package/dist/wire-read.js +3 -1
- package/index.d.ts +130 -7
- package/index.js +89 -68
- package/package.json +24 -19
- package/dist/lazy-props.d.ts +0 -10
- package/dist/lazy-props.js +0 -50
- package/satteri_napi.wasi-browser.js +0 -83
- package/satteri_napi.wasi.cjs +0 -134
- package/wasi-worker-browser.mjs +0 -36
- package/wasi-worker.mjs +0 -63
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Decode a HAST element property value from its wire `(kind, value)` — shared
|
|
2
2
|
* by the walk decoder and the snapshot reader so the kind dispatch lives once.
|
|
3
3
|
* The bool kinds carry no value string (callers pass `""`). */
|
|
4
|
-
import { PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_COMMA_SEP, PROP_INT, } from "../generated/wire-constants.js";
|
|
4
|
+
import { PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_COMMA_SEP, PROP_COMMA_SEP_NUM, PROP_INT, } from "../generated/wire-constants.js";
|
|
5
5
|
export function decodeElementProp(kind, value) {
|
|
6
6
|
switch (kind) {
|
|
7
7
|
case PROP_BOOL_TRUE:
|
|
@@ -10,11 +10,19 @@ export function decodeElementProp(kind, value) {
|
|
|
10
10
|
return false;
|
|
11
11
|
case PROP_SPACE_SEP:
|
|
12
12
|
return value.split(" ").filter((s) => s.length > 0);
|
|
13
|
-
case PROP_COMMA_SEP:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
13
|
+
case PROP_COMMA_SEP: {
|
|
14
|
+
// Interior empty items are kept; only a trailing empty is dropped.
|
|
15
|
+
const items = value.split(",").map((s) => s.trim());
|
|
16
|
+
if (items[items.length - 1] === "")
|
|
17
|
+
items.pop();
|
|
18
|
+
return items;
|
|
19
|
+
}
|
|
20
|
+
case PROP_COMMA_SEP_NUM: {
|
|
21
|
+
const items = value.split(",").map((s) => s.trim());
|
|
22
|
+
if (items[items.length - 1] === "")
|
|
23
|
+
items.pop();
|
|
24
|
+
return items.map((s) => (s !== "" && !Number.isNaN(Number(s)) ? Number(s) : s));
|
|
25
|
+
}
|
|
18
26
|
case PROP_INT:
|
|
19
27
|
return Number(value);
|
|
20
28
|
default:
|
|
@@ -2,10 +2,14 @@ import { HastReader } from "./hast-reader.js";
|
|
|
2
2
|
import type { Root } from "hast";
|
|
3
3
|
import type { HastNode } from "../types.js";
|
|
4
4
|
export type { HastNode };
|
|
5
|
+
/** Container node types (the ones that carry `children`); everything else is a leaf. */
|
|
6
|
+
export declare const HAST_CONTAINER_TYPES: ReadonlySet<number>;
|
|
5
7
|
/**
|
|
6
|
-
* Materialize a single HAST node
|
|
8
|
+
* Materialize a single HAST node; scalars eager, `children` lazy, memoized per
|
|
9
|
+
* `(reader, id)`; `frozen` (the plugin walk path) deep-freezes so plugins
|
|
10
|
+
* cannot corrupt the shared cache.
|
|
7
11
|
*/
|
|
8
|
-
export declare
|
|
12
|
+
export declare const materializeHastNode: (reader: HastReader, nodeId: number, frozen?: boolean, refs?: import("../visitor-shared.js").NodeRefs) => import("hast").Nodes;
|
|
9
13
|
/**
|
|
10
14
|
* Materialize the full HAST tree from root (nodeId=0).
|
|
11
15
|
*/
|
|
@@ -1,143 +1,57 @@
|
|
|
1
|
-
import { HastReader, HAST_ROOT, HAST_ELEMENT, HAST_TEXT, HAST_COMMENT,
|
|
1
|
+
import { HastReader, HAST_ROOT, HAST_ELEMENT, HAST_TEXT, HAST_COMMENT, HAST_RAW, HAST_MDX_JSX_ELEMENT, HAST_MDX_JSX_TEXT_ELEMENT, HAST_MDX_FLOW_EXPRESSION, HAST_MDX_TEXT_EXPRESSION, HAST_MDX_ESM, } from "./hast-reader.js";
|
|
2
2
|
import { TYPE_NAMES } from "./generated/node-types.js";
|
|
3
|
-
import { lazyProp, lazyGroup } from "../lazy-props.js";
|
|
4
3
|
import { restorePhantomSpaces } from "../phantom.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// children: lazy getter
|
|
34
|
-
Object.defineProperty(node, "children", {
|
|
35
|
-
get() {
|
|
36
|
-
const childIds = reader.getChildIds(nodeId);
|
|
37
|
-
const children = childIds.map((id) => materializeHastNode(reader, id));
|
|
38
|
-
Object.defineProperty(this, "children", {
|
|
39
|
-
value: children,
|
|
40
|
-
writable: true,
|
|
41
|
-
configurable: true,
|
|
42
|
-
enumerable: true,
|
|
43
|
-
});
|
|
44
|
-
return children;
|
|
45
|
-
},
|
|
46
|
-
configurable: true,
|
|
47
|
-
enumerable: true,
|
|
48
|
-
});
|
|
49
|
-
break;
|
|
50
|
-
case HAST_ELEMENT: {
|
|
51
|
-
// tagName and properties: lazy, resolved together from one reader call
|
|
52
|
-
lazyGroup(node, ["tagName", "properties"], () => {
|
|
53
|
-
const { tagName, properties } = reader.getElementData(nodeId);
|
|
54
|
-
return { tagName, properties: propsToRecord(properties) };
|
|
55
|
-
});
|
|
56
|
-
// children: lazy
|
|
57
|
-
Object.defineProperty(node, "children", {
|
|
58
|
-
get() {
|
|
59
|
-
const childIds = reader.getChildIds(nodeId);
|
|
60
|
-
const children = childIds.map((id) => materializeHastNode(reader, id));
|
|
61
|
-
Object.defineProperty(this, "children", {
|
|
62
|
-
value: children,
|
|
63
|
-
writable: true,
|
|
64
|
-
configurable: true,
|
|
65
|
-
enumerable: true,
|
|
66
|
-
});
|
|
67
|
-
return children;
|
|
68
|
-
},
|
|
69
|
-
configurable: true,
|
|
70
|
-
enumerable: true,
|
|
71
|
-
});
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
case HAST_TEXT:
|
|
75
|
-
case HAST_COMMENT:
|
|
76
|
-
case HAST_RAW:
|
|
77
|
-
Object.defineProperties(node, {
|
|
78
|
-
value: lazyProp("value", () => reader.getTextValue(nodeId)),
|
|
79
|
-
});
|
|
80
|
-
break;
|
|
81
|
-
case HAST_DOCTYPE:
|
|
82
|
-
// No extra properties
|
|
83
|
-
break;
|
|
84
|
-
case HAST_MDX_JSX_ELEMENT:
|
|
85
|
-
case HAST_MDX_JSX_TEXT_ELEMENT:
|
|
86
|
-
lazyGroup(node, ["name", "attributes"], () => reader.getMdxJsxElementData(nodeId));
|
|
87
|
-
Object.defineProperty(node, "children", {
|
|
88
|
-
get() {
|
|
89
|
-
const childIds = reader.getChildIds(nodeId);
|
|
90
|
-
const children = childIds.map((id) => materializeHastNode(reader, id));
|
|
91
|
-
Object.defineProperty(this, "children", {
|
|
92
|
-
value: children,
|
|
93
|
-
writable: true,
|
|
94
|
-
configurable: true,
|
|
95
|
-
enumerable: true,
|
|
96
|
-
});
|
|
97
|
-
return children;
|
|
98
|
-
},
|
|
99
|
-
configurable: true,
|
|
100
|
-
enumerable: true,
|
|
101
|
-
});
|
|
102
|
-
break;
|
|
103
|
-
case HAST_MDX_FLOW_EXPRESSION:
|
|
104
|
-
case HAST_MDX_TEXT_EXPRESSION:
|
|
105
|
-
Object.defineProperties(node, {
|
|
106
|
-
value: lazyProp("value", () => restorePhantomSpaces(reader.getTextValue(nodeId))),
|
|
107
|
-
});
|
|
108
|
-
break;
|
|
109
|
-
case HAST_MDX_ESM:
|
|
110
|
-
Object.defineProperties(node, {
|
|
111
|
-
value: lazyProp("value", () => reader.getTextValue(nodeId)),
|
|
112
|
-
});
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
// Plugins can set `data` on any node type, so rehydrate generically
|
|
116
|
-
// (see website/content/docs/divergences.md for the code-block case).
|
|
117
|
-
const rawData = reader.getNodeData(nodeId);
|
|
118
|
-
if (rawData !== null) {
|
|
119
|
-
try {
|
|
120
|
-
const parsed = JSON.parse(rawData);
|
|
121
|
-
if (parsed && typeof parsed === "object" && Object.keys(parsed).length > 0) {
|
|
122
|
-
Object.defineProperty(node, "data", {
|
|
123
|
-
value: parsed,
|
|
124
|
-
writable: true,
|
|
125
|
-
configurable: true,
|
|
126
|
-
enumerable: true,
|
|
127
|
-
});
|
|
4
|
+
import { createMaterializer } from "../materializer-cache.js";
|
|
5
|
+
/** Container node types (the ones that carry `children`); everything else is a leaf. */
|
|
6
|
+
export const HAST_CONTAINER_TYPES = new Set([
|
|
7
|
+
HAST_ROOT,
|
|
8
|
+
HAST_ELEMENT,
|
|
9
|
+
HAST_MDX_JSX_ELEMENT,
|
|
10
|
+
HAST_MDX_JSX_TEXT_ELEMENT,
|
|
11
|
+
]);
|
|
12
|
+
const hastMaterializer = createMaterializer({
|
|
13
|
+
label: "materializeHastNode",
|
|
14
|
+
typeNames: TYPE_NAMES,
|
|
15
|
+
hasChildren: (nodeType) => HAST_CONTAINER_TYPES.has(nodeType),
|
|
16
|
+
populate(node, reader, nodeId, nodeType) {
|
|
17
|
+
switch (nodeType) {
|
|
18
|
+
case HAST_ELEMENT:
|
|
19
|
+
reader.readElementInto(nodeId, node);
|
|
20
|
+
break;
|
|
21
|
+
case HAST_TEXT:
|
|
22
|
+
case HAST_COMMENT:
|
|
23
|
+
case HAST_RAW:
|
|
24
|
+
node.value = reader.getTextValue(nodeId);
|
|
25
|
+
break;
|
|
26
|
+
case HAST_MDX_JSX_ELEMENT:
|
|
27
|
+
case HAST_MDX_JSX_TEXT_ELEMENT: {
|
|
28
|
+
const { name, attributes } = reader.getMdxJsxElementData(nodeId);
|
|
29
|
+
node.name = name;
|
|
30
|
+
node.attributes = attributes;
|
|
31
|
+
break;
|
|
128
32
|
}
|
|
33
|
+
case HAST_MDX_FLOW_EXPRESSION:
|
|
34
|
+
case HAST_MDX_TEXT_EXPRESSION:
|
|
35
|
+
node.value = restorePhantomSpaces(reader.getTextValue(nodeId));
|
|
36
|
+
break;
|
|
37
|
+
case HAST_MDX_ESM:
|
|
38
|
+
node.value = reader.getTextValue(nodeId);
|
|
39
|
+
break;
|
|
40
|
+
// HAST_ROOT / HAST_DOCTYPE: no extra properties
|
|
41
|
+
default:
|
|
42
|
+
break;
|
|
129
43
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Materialize a single HAST node; scalars eager, `children` lazy, memoized per
|
|
48
|
+
* `(reader, id)`; `frozen` (the plugin walk path) deep-freezes so plugins
|
|
49
|
+
* cannot corrupt the shared cache.
|
|
50
|
+
*/
|
|
51
|
+
export const materializeHastNode = hastMaterializer.node;
|
|
138
52
|
/**
|
|
139
53
|
* Materialize the full HAST tree from root (nodeId=0).
|
|
140
54
|
*/
|
|
141
55
|
export function materializeHastTree(reader) {
|
|
142
|
-
return
|
|
56
|
+
return hastMaterializer.tree(reader, 0);
|
|
143
57
|
}
|
|
@@ -6,7 +6,6 @@ export declare const HAST_ROOT: number;
|
|
|
6
6
|
export declare const HAST_ELEMENT: number;
|
|
7
7
|
export declare const HAST_TEXT: number;
|
|
8
8
|
export declare const HAST_COMMENT: number;
|
|
9
|
-
export declare const HAST_DOCTYPE: number;
|
|
10
9
|
export declare const HAST_RAW: number;
|
|
11
10
|
export declare const HAST_MDX_JSX_ELEMENT: number;
|
|
12
11
|
export declare const HAST_MDX_JSX_TEXT_ELEMENT: number;
|
|
@@ -15,7 +14,7 @@ export declare const HAST_MDX_ESM: number;
|
|
|
15
14
|
export declare const HAST_MDX_TEXT_EXPRESSION: number;
|
|
16
15
|
export interface HastProperty {
|
|
17
16
|
name: string;
|
|
18
|
-
value: string | number | boolean | string[];
|
|
17
|
+
value: string | number | boolean | (string | number)[];
|
|
19
18
|
}
|
|
20
19
|
export declare class HastReader {
|
|
21
20
|
#private;
|
|
@@ -36,9 +35,6 @@ export declare class HastReader {
|
|
|
36
35
|
getString(offset: number, len: number): string;
|
|
37
36
|
/** Get position data for a node. */
|
|
38
37
|
getPosition(nodeId: number): Position | undefined;
|
|
39
|
-
/** Whether the node carries a source position (line 0 + offset 0 is the
|
|
40
|
-
* synthesized-node sentinel), without decoding the three position objects. */
|
|
41
|
-
hasPosition(nodeId: number): boolean;
|
|
42
38
|
/** Get the node_type byte for a given node ID. */
|
|
43
39
|
getNodeType(nodeId: number): number;
|
|
44
40
|
/** Get the parent id for a given node (0xffffffff at the root). */
|
|
@@ -49,14 +45,12 @@ export declare class HastReader {
|
|
|
49
45
|
pushChildIds(nodeId: number, stack: number[]): void;
|
|
50
46
|
/** Get the raw type_data bytes for a node. */
|
|
51
47
|
getTypeData(nodeId: number): Uint8Array;
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* [name: StringRef(8B)][value_type: u8(1B)][_pad: [u8;3](3B)][value: StringRef(8B)]
|
|
59
|
-
*/
|
|
48
|
+
/** Element `tagName`, `properties` count, and the i-th property, read without
|
|
49
|
+
* building the intermediate views and records `getElementData` allocates. */
|
|
50
|
+
getElementTagName(nodeId: number): string;
|
|
51
|
+
getElementPropCount(nodeId: number): number;
|
|
52
|
+
getElementPropName(nodeId: number, index: number): string;
|
|
53
|
+
getElementPropValue(nodeId: number, index: number): HastProperty["value"];
|
|
60
54
|
getElementData(nodeId: number): {
|
|
61
55
|
tagName: string;
|
|
62
56
|
properties: HastProperty[];
|
package/dist/hast/hast-reader.js
CHANGED
|
@@ -2,13 +2,13 @@ import { restorePhantomSpaces } from "../phantom.js";
|
|
|
2
2
|
import { readPosition } from "../wire-read.js";
|
|
3
3
|
import { MDX_ATTR_BOOLEAN_PROP, MDX_ATTR_LITERAL_PROP, MDX_ATTR_EXPRESSION_PROP, MDX_ATTR_SPREAD, } from "../op-stream.js";
|
|
4
4
|
import { decodeElementProp } from "./element-props.js";
|
|
5
|
+
import { PoolOffsets } from "../string-pool.js";
|
|
5
6
|
import { NAME_TO_TYPE } from "./generated/node-types.js";
|
|
6
7
|
import { ARENA_MAGIC, KIND_HAST, FIELD, HEADER } from "../generated/arena-layout.js";
|
|
7
8
|
export const HAST_ROOT = NAME_TO_TYPE.root;
|
|
8
9
|
export const HAST_ELEMENT = NAME_TO_TYPE.element;
|
|
9
10
|
export const HAST_TEXT = NAME_TO_TYPE.text;
|
|
10
11
|
export const HAST_COMMENT = NAME_TO_TYPE.comment;
|
|
11
|
-
export const HAST_DOCTYPE = NAME_TO_TYPE.doctype;
|
|
12
12
|
export const HAST_RAW = NAME_TO_TYPE.raw;
|
|
13
13
|
export const HAST_MDX_JSX_ELEMENT = NAME_TO_TYPE.mdxJsxFlowElement;
|
|
14
14
|
export const HAST_MDX_JSX_TEXT_ELEMENT = NAME_TO_TYPE.mdxJsxTextElement;
|
|
@@ -20,6 +20,8 @@ export class HastReader {
|
|
|
20
20
|
#header;
|
|
21
21
|
#textDecoder;
|
|
22
22
|
#stringPoolCache = null;
|
|
23
|
+
#poolChecked = false;
|
|
24
|
+
#poolOffsets = null;
|
|
23
25
|
constructor(buffer) {
|
|
24
26
|
if (buffer instanceof Uint8Array) {
|
|
25
27
|
this.#view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
@@ -27,7 +29,7 @@ export class HastReader {
|
|
|
27
29
|
else {
|
|
28
30
|
this.#view = new DataView(buffer);
|
|
29
31
|
}
|
|
30
|
-
this.#textDecoder = new TextDecoder("utf-8");
|
|
32
|
+
this.#textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
31
33
|
this.#header = this.#readHeader();
|
|
32
34
|
}
|
|
33
35
|
#readHeader() {
|
|
@@ -101,23 +103,27 @@ export class HastReader {
|
|
|
101
103
|
getString(offset, len) {
|
|
102
104
|
if (len === 0)
|
|
103
105
|
return "";
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
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);
|
|
107
121
|
}
|
|
108
122
|
/** Get position data for a node. */
|
|
109
123
|
getPosition(nodeId) {
|
|
110
124
|
const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
|
|
111
125
|
return readPosition(this.#view, base + FIELD.start_offset);
|
|
112
126
|
}
|
|
113
|
-
/** Whether the node carries a source position (line 0 + offset 0 is the
|
|
114
|
-
* synthesized-node sentinel), without decoding the three position objects. */
|
|
115
|
-
hasPosition(nodeId) {
|
|
116
|
-
const base = this.#header.nodesOffset + nodeId * this.#header.nodeStructSize;
|
|
117
|
-
const v = this.#view;
|
|
118
|
-
return (v.getUint32(base + FIELD.start_line, true) !== 0 ||
|
|
119
|
-
v.getUint32(base + FIELD.start_offset, true) !== 0);
|
|
120
|
-
}
|
|
121
127
|
/** Get the node_type byte for a given node ID. */
|
|
122
128
|
getNodeType(nodeId) {
|
|
123
129
|
const { nodesOffset, nodeStructSize } = this.#header;
|
|
@@ -168,10 +174,10 @@ export class HastReader {
|
|
|
168
174
|
}
|
|
169
175
|
/** Read a StringRef (offset: u32 LE, len: u32 LE) from a byte array at byteOffset. */
|
|
170
176
|
#readStringRef(data, byteOffset) {
|
|
171
|
-
const
|
|
177
|
+
const at = data.byteOffset - this.#view.byteOffset + byteOffset;
|
|
172
178
|
return {
|
|
173
|
-
offset: view.getUint32(
|
|
174
|
-
len: view.getUint32(4, true),
|
|
179
|
+
offset: this.#view.getUint32(at, true),
|
|
180
|
+
len: this.#view.getUint32(at + 4, true),
|
|
175
181
|
};
|
|
176
182
|
}
|
|
177
183
|
/**
|
|
@@ -182,6 +188,58 @@ export class HastReader {
|
|
|
182
188
|
* then prop_count * 20 bytes:
|
|
183
189
|
* [name: StringRef(8B)][value_type: u8(1B)][_pad: [u8;3](3B)][value: StringRef(8B)]
|
|
184
190
|
*/
|
|
191
|
+
/** Byte position of a node's `type_data`, relative to this reader's view, or
|
|
192
|
+
* `-1` when the node stores fewer than `min` bytes. */
|
|
193
|
+
#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)
|
|
197
|
+
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));
|
|
202
|
+
}
|
|
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) {
|
|
206
|
+
const at = this.#typeDataAt(nodeId, 16);
|
|
207
|
+
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
|
+
}
|
|
185
243
|
getElementData(nodeId) {
|
|
186
244
|
const data = this.getTypeData(nodeId);
|
|
187
245
|
if (data.length < 16) {
|
|
@@ -266,10 +324,7 @@ export class HastReader {
|
|
|
266
324
|
* These store a single StringRef (8 bytes) as their type_data.
|
|
267
325
|
*/
|
|
268
326
|
getTextValue(nodeId) {
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
return "";
|
|
272
|
-
const ref = this.#readStringRef(data, 0);
|
|
273
|
-
return this.getString(ref.offset, ref.len);
|
|
327
|
+
const at = this.#typeDataAt(nodeId, 8);
|
|
328
|
+
return at === -1 ? "" : this.#stringAt(at);
|
|
274
329
|
}
|
|
275
330
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type HastNode } from "./hast-materializer.js";
|
|
2
|
-
import type { HastRaw, Data } from "../types.js";
|
|
2
|
+
import type { HastRaw, Data, SourceFormat } from "../types.js";
|
|
3
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
|
+
import { type PluginOptions } from "../visitor-shared.js";
|
|
8
9
|
import type { HastHandle } from "../handles.js";
|
|
9
10
|
export type { HastHandle };
|
|
10
11
|
/** ESTree-compatible Program node returned by `parseExpression()`. */
|
|
@@ -30,16 +31,30 @@ export interface HastVisitorContext {
|
|
|
30
31
|
* instances. Returned to the caller as `result.data`.
|
|
31
32
|
*/
|
|
32
33
|
readonly data: Data;
|
|
34
|
+
/**
|
|
35
|
+
* The source format this compile is processing: `"markdown"` for a plain
|
|
36
|
+
* Markdown compile, `"mdx"` for an MDX one. Lets a plugin shared between both
|
|
37
|
+
* pipelines branch on which it is handling.
|
|
38
|
+
*/
|
|
39
|
+
readonly sourceFormat: SourceFormat;
|
|
33
40
|
removeNode(node: Readonly<HastNode>): void;
|
|
34
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Swap `node` for one node, or for an array of nodes placed in order at its
|
|
43
|
+
* position. An empty array drops the node, the same as `removeNode`.
|
|
44
|
+
* The document root takes a `root` and nothing else: the one place a `root`
|
|
45
|
+
* is accepted as content.
|
|
46
|
+
*/
|
|
47
|
+
replaceNode(node: Readonly<HastNode>, newNode: HastContent | HastContent[]): void;
|
|
35
48
|
insertBefore(node: Readonly<HastNode>, newNode: HastContent | HastContent[]): void;
|
|
36
49
|
insertAfter(node: Readonly<HastNode>, newNode: HastContent | HastContent[]): void;
|
|
37
50
|
/**
|
|
38
51
|
* Wrap `node` in `parentNode`, making it `parentNode`'s first child. Any
|
|
39
52
|
* children `parentNode` declares are kept after it, so a `div` with an anchor
|
|
40
|
-
* child wraps a heading as `div > [heading, anchor]`.
|
|
53
|
+
* child wraps a heading as `div > [heading, anchor]`. `parentNode` is an
|
|
54
|
+
* element, an MDX JSX element, or `{ raw }` HTML parsing to exactly one
|
|
55
|
+
* element, never a void element, whose children would not render.
|
|
41
56
|
*/
|
|
42
|
-
wrapNode(node: Readonly<HastNode>, parentNode:
|
|
57
|
+
wrapNode(node: Readonly<HastNode>, parentNode: HastParentContent | RawHastContent | RawHtmlHastContent): void;
|
|
43
58
|
prependChild(node: Readonly<HastNode>, childNode: HastContent | HastContent[]): void;
|
|
44
59
|
appendChild(node: Readonly<HastNode>, childNode: HastContent | HastContent[]): void;
|
|
45
60
|
/** Insert one node or an array at `index`; clamps (`0` or less prepends, past the end appends). */
|
|
@@ -71,13 +86,37 @@ export interface HastVisitorContext {
|
|
|
71
86
|
/** New content for a HAST structural mutation. Unlike [`MdastContent`], HAST has
|
|
72
87
|
* a `raw` node type, so it needs no raw/rawHtml escape hatch. */
|
|
73
88
|
export type HastContent = HastNode;
|
|
89
|
+
/** A `wrapNode` wrapper: node types that can hold children. */
|
|
90
|
+
export type HastParentContent = Exclude<Extract<HastNode, {
|
|
91
|
+
children: unknown[];
|
|
92
|
+
}>, HastRoot>;
|
|
93
|
+
/** Raw `wrapNode` wrapper: the HTML is parsed at apply time (not call time)
|
|
94
|
+
* and must yield exactly one non-void element, which becomes the wrapper.
|
|
95
|
+
* `mdxExpressions` is accepted for parity with the MDAST phase and has no
|
|
96
|
+
* effect: braces in HTML text are always literal. */
|
|
97
|
+
export interface RawHastContent {
|
|
98
|
+
raw: string;
|
|
99
|
+
mdxExpressions?: boolean;
|
|
100
|
+
}
|
|
101
|
+
export interface RawHtmlHastContent {
|
|
102
|
+
/** @deprecated Use the equivalent `{ raw }`. */
|
|
103
|
+
rawHtml: string;
|
|
104
|
+
}
|
|
74
105
|
/** A filtered visitor: Rust filters by tag/component name, only matched nodes cross the boundary. */
|
|
75
106
|
export interface HastFilteredVisitor<N extends HastNode = HastNode> {
|
|
76
107
|
filter: string[];
|
|
77
108
|
visit(node: Readonly<N>, ctx: HastVisitorContext): HastNode | void | Promise<HastNode | void>;
|
|
78
109
|
}
|
|
79
110
|
type HastVisitorFn<N extends HastNode = HastNode> = (node: Readonly<N>, ctx: HastVisitorContext) => HastNode | void | Promise<HastNode | void>;
|
|
111
|
+
export type HastHookFn = (root: Readonly<HastRoot>, ctx: HastVisitorContext) => void | Promise<void>;
|
|
80
112
|
export interface HastVisitorInstance {
|
|
113
|
+
/** Plugin-level configuration (e.g. `{ position: true }` to read positions). */
|
|
114
|
+
options?: PluginOptions;
|
|
115
|
+
/** Runs once per document, before the plugin's visitors. Awaited when async. */
|
|
116
|
+
before?: HastHookFn;
|
|
117
|
+
/** Runs once per document, after the plugin's visitors have settled. Awaited
|
|
118
|
+
* when async. */
|
|
119
|
+
after?: HastHookFn;
|
|
81
120
|
element?: HastFilteredVisitor<Element> | HastFilteredVisitor<Element>[];
|
|
82
121
|
mdxJsxFlowElement?: HastFilteredVisitor<MdxJsxFlowElementHast> | HastFilteredVisitor<MdxJsxFlowElementHast>[];
|
|
83
122
|
mdxJsxTextElement?: HastFilteredVisitor<MdxJsxTextElementHast> | HastFilteredVisitor<MdxJsxTextElementHast>[];
|
|
@@ -109,4 +148,15 @@ export declare function resolveSubscriptions(plugin: HastVisitorInstance): Resol
|
|
|
109
148
|
* replaced earlier in the same pass (the caller warns when non-zero), or a
|
|
110
149
|
* Promise of that count if any visitor is async.
|
|
111
150
|
*/
|
|
112
|
-
export declare function visitHastHandle(handle: HastHandle, plugin: HastVisitorInstance, subs: ResolvedSubscription[], source: string | (() => string), fileURL: URL | undefined, data?: Data): number | Promise<number>;
|
|
151
|
+
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
|
+
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. */
|
|
161
|
+
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
|
+
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>;
|