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.
- package/README.md +11 -0
- package/dist/arena-reader.d.ts +13 -0
- package/dist/arena-reader.js +104 -0
- 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 +0 -18
- package/dist/byte-writer.js +6 -30
- package/dist/child-stub.d.ts +0 -17
- package/dist/child-stub.js +6 -22
- package/dist/command-buffer.d.ts +5 -26
- package/dist/command-buffer.js +28 -39
- package/dist/compile-options.d.ts +11 -0
- package/dist/compile-options.js +99 -0
- package/dist/compile-types.d.ts +330 -0
- package/dist/compile-types.js +1 -0
- package/dist/compile.d.ts +22 -328
- package/dist/compile.js +129 -379
- package/dist/directive-types.d.ts +1 -0
- package/dist/directive-types.js +1 -6
- package/dist/generated/arena-layout.d.ts +6 -4
- package/dist/generated/arena-layout.js +6 -7
- package/dist/generated/fused-wire.d.ts +3 -0
- package/dist/generated/fused-wire.js +229 -0
- package/dist/generated/parse-options.js +0 -5
- package/dist/generated/wire-constants.d.ts +3 -0
- package/dist/generated/wire-constants.js +33 -42
- package/dist/handles.d.ts +0 -7
- package/dist/hast/child-stub.d.ts +1 -8
- package/dist/hast/child-stub.js +14 -13
- package/dist/hast/element-props.d.ts +13 -0
- package/dist/hast/element-props.js +52 -6
- package/dist/hast/generated/node-types.d.ts +1 -9
- package/dist/hast/generated/node-types.js +1 -9
- package/dist/hast/generated/walk-decode.d.ts +10 -0
- package/dist/hast/generated/walk-decode.js +61 -0
- package/dist/hast/hast-materializer.d.ts +3 -10
- package/dist/hast/hast-materializer.js +73 -47
- package/dist/hast/hast-reader.d.ts +9 -38
- package/dist/hast/hast-reader.js +82 -271
- package/dist/hast/hast-visitor.d.ts +29 -16
- package/dist/hast/hast-visitor.js +197 -508
- package/dist/hast/hast-walk.d.ts +18 -0
- package/dist/hast/hast-walk.js +264 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -12
- package/dist/lazy-child-resolver.d.ts +1 -21
- package/dist/lazy-child-resolver.js +11 -32
- package/dist/materializer-cache.d.ts +5 -29
- package/dist/materializer-cache.js +29 -101
- package/dist/mdast/child-stub.d.ts +1 -12
- package/dist/mdast/child-stub.js +18 -28
- package/dist/mdast/column-align.d.ts +1 -1
- package/dist/mdast/column-align.js +0 -2
- package/dist/mdast/generated/layout.d.ts +0 -16
- package/dist/mdast/generated/layout.js +6 -47
- package/dist/mdast/generated/node-types.d.ts +1 -9
- package/dist/mdast/generated/node-types.js +3 -9
- package/dist/mdast/mdast-materializer.d.ts +3 -11
- package/dist/mdast/mdast-materializer.js +91 -63
- package/dist/mdast/mdast-reader.d.ts +15 -63
- package/dist/mdast/mdast-reader.js +132 -269
- package/dist/mdast/mdast-visitor.d.ts +23 -15
- package/dist/mdast/mdast-visitor.js +188 -294
- package/dist/mdast/mdast-walk.d.ts +13 -0
- package/dist/mdast/mdast-walk.js +144 -0
- package/dist/mdx-attr.js +0 -3
- package/dist/mdx-types.js +1 -6
- package/dist/op-stream.d.ts +1 -15
- package/dist/op-stream.js +3 -19
- package/dist/phantom.d.ts +0 -1
- package/dist/phantom.js +1 -9
- package/dist/plugin-pipeline.d.ts +32 -0
- package/dist/plugin-pipeline.js +177 -0
- package/dist/plugin.d.ts +8 -7
- package/dist/plugin.js +3 -9
- package/dist/types.d.ts +0 -1
- package/dist/visitor-node.d.ts +12 -0
- package/dist/visitor-node.js +23 -0
- package/dist/visitor-shared.d.ts +26 -30
- package/dist/visitor-shared.js +34 -36
- package/dist/wire-read.d.ts +0 -22
- package/dist/wire-read.js +3 -24
- package/index.d.ts +19 -10
- package/index.js +56 -53
- package/package.json +3 -12
- package/dist/string-pool.d.ts +0 -13
- package/dist/string-pool.js +0 -91
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LazyChildResolver, type EpochCache } from "../lazy-child-resolver.js";
|
|
2
|
+
import type { MdastNode } from "../types.js";
|
|
3
|
+
import { type NodeRefs } from "../visitor-shared.js";
|
|
4
|
+
import { MdastReader } from "./mdast-reader.js";
|
|
5
|
+
export declare function getNodeId(node: MdastNode, refs: NodeRefs): number | undefined;
|
|
6
|
+
export declare class MdastLazyChildResolver extends LazyChildResolver<MdastReader, MdastNode> {
|
|
7
|
+
protected cacheSlot(): WeakMap<globalThis.AnyHandle, EpochCache<MdastReader>>;
|
|
8
|
+
protected createReader(wire: Uint8Array): MdastReader;
|
|
9
|
+
protected materializeNode(reader: MdastReader, nodeId: number, refs: NodeRefs): MdastNode;
|
|
10
|
+
protected readParentId(reader: MdastReader, nodeId: number): number;
|
|
11
|
+
protected readChildIds(reader: MdastReader, nodeId: number): number[];
|
|
12
|
+
}
|
|
13
|
+
export declare function readMdastMatchedNode(view: DataView, buf: Uint8Array, dataOffset: number, nodeId: number, nodeType: number, resolver: MdastLazyChildResolver): MdastNode;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { LazyChildResolver, registerEpochCacheSlot, } from "../lazy-child-resolver.js";
|
|
2
|
+
import { VisitorNode } from "../visitor-node.js";
|
|
3
|
+
import { crossPipelineForeign, FOREIGN_REF } from "../visitor-shared.js";
|
|
4
|
+
import { readPosition, rstr, ru32 } from "../wire-read.js";
|
|
5
|
+
import { MdastChildStub } from "./child-stub.js";
|
|
6
|
+
import { decodeMdastTypeData } from "./generated/layout.js";
|
|
7
|
+
import { NAME_TO_TYPE, TYPE_NAMES } from "./generated/node-types.js";
|
|
8
|
+
import { isCustomLeaf, LEAF_TYPES, materializeNode } from "./mdast-materializer.js";
|
|
9
|
+
import { MdastReader } from "./mdast-reader.js";
|
|
10
|
+
const MDAST_CUSTOM = NAME_TO_TYPE.custom;
|
|
11
|
+
export function getNodeId(node, refs) {
|
|
12
|
+
// Spread copies must be treated as new content, not as references to the original arena node.
|
|
13
|
+
if (node instanceof MdastChildStub)
|
|
14
|
+
return node._refs === refs ? node._id : FOREIGN_REF;
|
|
15
|
+
const id = VisitorNode.getNodeId(node, refs) ?? refs.get(node);
|
|
16
|
+
if (id !== undefined)
|
|
17
|
+
return id;
|
|
18
|
+
// An unregistered non-enumerable ID belongs to a different tree; enumerable copies carry no identity.
|
|
19
|
+
const d = Object.getOwnPropertyDescriptor(node, "_nodeId");
|
|
20
|
+
if (d !== undefined && !d.enumerable)
|
|
21
|
+
return FOREIGN_REF;
|
|
22
|
+
return crossPipelineForeign(node);
|
|
23
|
+
}
|
|
24
|
+
const MDAST_EPOCH_CACHE = registerEpochCacheSlot(new WeakMap());
|
|
25
|
+
export class MdastLazyChildResolver extends LazyChildResolver {
|
|
26
|
+
cacheSlot() {
|
|
27
|
+
return MDAST_EPOCH_CACHE;
|
|
28
|
+
}
|
|
29
|
+
createReader(wire) {
|
|
30
|
+
return new MdastReader(wire);
|
|
31
|
+
}
|
|
32
|
+
materializeNode(reader, nodeId, refs) {
|
|
33
|
+
return materializeNode(reader, nodeId, true, refs);
|
|
34
|
+
}
|
|
35
|
+
readParentId(reader, nodeId) {
|
|
36
|
+
return reader.getParentId(nodeId);
|
|
37
|
+
}
|
|
38
|
+
readChildIds(reader, nodeId) {
|
|
39
|
+
return reader.getChildIds(nodeId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function readMdastChildStubs(view, buf, idsPos, typesPos, count, resolver) {
|
|
43
|
+
// With a hot snapshot a stub's deferral buys nothing; real nodes skip its per-field getters.
|
|
44
|
+
if (resolver.hasHotSnapshot()) {
|
|
45
|
+
const nodes = new Array(count);
|
|
46
|
+
for (let i = 0; i < count; i++) {
|
|
47
|
+
nodes[i] = resolver.materializeOne(ru32(view, idsPos + i * 4));
|
|
48
|
+
}
|
|
49
|
+
return nodes;
|
|
50
|
+
}
|
|
51
|
+
const stubs = new Array(count);
|
|
52
|
+
for (let i = 0; i < count; i++) {
|
|
53
|
+
stubs[i] = new MdastChildStub(resolver, ru32(view, idsPos + i * 4), buf[typesPos + i]);
|
|
54
|
+
}
|
|
55
|
+
return stubs;
|
|
56
|
+
}
|
|
57
|
+
// Own enumerable getters preserve children in spreads; closures avoid costly per-node wire slots.
|
|
58
|
+
function makeLazyChildren(node, view, buf, childIdsPos, childTypesPos, childCount, resolver) {
|
|
59
|
+
Object.defineProperty(node, "children", {
|
|
60
|
+
get() {
|
|
61
|
+
const val = readMdastChildStubs(view, buf, childIdsPos, childTypesPos, childCount, resolver);
|
|
62
|
+
Object.defineProperty(this, "children", {
|
|
63
|
+
value: val,
|
|
64
|
+
writable: true,
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
});
|
|
68
|
+
return val;
|
|
69
|
+
},
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export function readMdastMatchedNode(view, buf, dataOffset, nodeId, nodeType, resolver) {
|
|
75
|
+
let pos = dataOffset;
|
|
76
|
+
const dataJsonLen = ru32(view, pos);
|
|
77
|
+
pos += 4;
|
|
78
|
+
let initialData = null;
|
|
79
|
+
if (dataJsonLen > 0) {
|
|
80
|
+
const jsonStr = rstr(buf, pos, dataJsonLen);
|
|
81
|
+
try {
|
|
82
|
+
initialData = JSON.parse(jsonStr);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (process.env.NODE_ENV !== "production") {
|
|
86
|
+
console.warn(`readMdastMatchedNode: malformed node_data for nodeId=${nodeId}`, err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
pos += dataJsonLen;
|
|
90
|
+
}
|
|
91
|
+
const position = readPosition(view, pos);
|
|
92
|
+
pos += 24;
|
|
93
|
+
const childCount = ru32(view, pos);
|
|
94
|
+
pos += 4;
|
|
95
|
+
// Most visitors never read children, so defer decoding their IDs and types.
|
|
96
|
+
const childIdsPos = pos;
|
|
97
|
+
pos += childCount * 4;
|
|
98
|
+
const childTypesPos = pos;
|
|
99
|
+
pos += childCount;
|
|
100
|
+
const typeName = TYPE_NAMES[nodeType] ?? `unknown(${nodeType})`;
|
|
101
|
+
const node = new VisitorNode(typeName, resolver.refs, nodeId);
|
|
102
|
+
if (position !== undefined)
|
|
103
|
+
node.position = position;
|
|
104
|
+
if (childCount > 0) {
|
|
105
|
+
makeLazyChildren(node, view, buf, childIdsPos, childTypesPos, childCount, resolver);
|
|
106
|
+
}
|
|
107
|
+
if (!decodeMdastTypeData(view, buf, pos, nodeType, node)) {
|
|
108
|
+
switch (nodeType) {
|
|
109
|
+
case 5: {
|
|
110
|
+
node.start = ru32(view, pos);
|
|
111
|
+
node.ordered = buf[pos + 4] !== 0;
|
|
112
|
+
node.spread = buf[pos + 5] !== 0;
|
|
113
|
+
if (!node.ordered)
|
|
114
|
+
node.start = null;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 6: {
|
|
118
|
+
const checked = buf[pos];
|
|
119
|
+
node.checked = checked === 2 ? null : checked === 1;
|
|
120
|
+
node.spread = buf[pos + 1] !== 0;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 37: {
|
|
124
|
+
node.spread = buf[pos] !== 0;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Custom nodes expose name as type; an empty value must not make a parent look like a text leaf.
|
|
130
|
+
if (nodeType === MDAST_CUSTOM) {
|
|
131
|
+
node.type = node.name;
|
|
132
|
+
delete node.name;
|
|
133
|
+
if (node.value === "")
|
|
134
|
+
delete node.value;
|
|
135
|
+
}
|
|
136
|
+
const leafCustom = nodeType === MDAST_CUSTOM && isCustomLeaf({ value: node.value, data: initialData }, childCount);
|
|
137
|
+
if (childCount === 0 && !LEAF_TYPES.has(nodeType) && !leafCustom) {
|
|
138
|
+
node.children = [];
|
|
139
|
+
}
|
|
140
|
+
if (initialData) {
|
|
141
|
+
node.data = initialData;
|
|
142
|
+
}
|
|
143
|
+
return node;
|
|
144
|
+
}
|
package/dist/mdx-attr.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/** Decode an MDX JSX attribute from its wire `(kind, name, value)` — shared by
|
|
2
|
-
* the generated mdast tail decoder and the hast path so the kind dispatch lives
|
|
3
|
-
* once. Expression/spread values carry phantom-space sentinels, restored here. */
|
|
4
1
|
import { restorePhantomSpaces } from "./phantom.js";
|
|
5
2
|
export function decodeMdxJsxAttr(kind, name, value) {
|
|
6
3
|
switch (kind) {
|
package/dist/mdx-types.js
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// These replicate the type definitions from mdast-util-mdx-jsx,
|
|
4
|
-
// mdast-util-mdx-expression, and mdast-util-mdxjs-esm so we can avoid
|
|
5
|
-
// pulling in those packages (and their massive transitive dependency trees)
|
|
6
|
-
// just for a handful of interfaces.
|
|
1
|
+
// Local MDX interfaces avoid dependencies on packages needed only for their type definitions.
|
|
7
2
|
export {};
|
package/dist/op-stream.d.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Low-level op-stream writer shared by the MDAST/HAST declarative compilers.
|
|
3
|
-
*
|
|
4
|
-
* Emits the compact OPEN/CLOSE/field/REF/KEEP_CHILDREN/PROP stream that Rust
|
|
5
|
-
* replays straight into the arena (`replay_opstream` in js_commands.rs; byte
|
|
6
|
-
* values in generated/wire-constants.ts) — the only structural encoding for
|
|
7
|
-
* plugin-built content. Strings ride ByteWriter's zero-alloc path (inline
|
|
8
|
-
* char codes when short ASCII, `encodeInto` otherwise). `CommandBuffer`
|
|
9
|
-
* extends this class so payloads are emitted directly into the command
|
|
10
|
-
* bytes, with no intermediate opstream buffer or copy.
|
|
11
|
-
*/
|
|
12
1
|
import { ByteWriter } from "./byte-writer.js";
|
|
13
|
-
export { OF_VALUE, OF_URL, OF_TITLE, OF_ALT, OF_LANG, OF_META, OF_IDENTIFIER, OF_LABEL, OF_NAME, OF_REFERENCE_TYPE, OF_DEPTH, OF_CHECKED, OF_START, OF_ORDERED, OF_SPREAD, OF_TAGNAME, OF_EXPLICIT, PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_INT, PROP_NULL,
|
|
2
|
+
export { OF_VALUE, OF_URL, OF_TITLE, OF_ALT, OF_LANG, OF_META, OF_IDENTIFIER, OF_LABEL, OF_NAME, OF_REFERENCE_TYPE, OF_DEPTH, OF_CHECKED, OF_START, OF_ORDERED, OF_SPREAD, OF_TAGNAME, OF_EXPLICIT, PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_INT, PROP_NULL, PROP_TOKEN_LIST, } from "./generated/wire-constants.js";
|
|
14
3
|
export declare class OpWriter extends ByteWriter {
|
|
15
4
|
constructor(initialSize?: number);
|
|
16
5
|
open(type: number): void;
|
|
@@ -22,10 +11,7 @@ export declare class OpWriter extends ByteWriter {
|
|
|
22
11
|
data(value: unknown): void;
|
|
23
12
|
prop(name: string, kind: number, value: string): void;
|
|
24
13
|
ref(id: number): void;
|
|
25
|
-
/** Table column-alignment codes (0=none, 1=left, 2=right, 3=center). */
|
|
26
14
|
align(codes: readonly number[]): void;
|
|
27
15
|
keepChildren(): void;
|
|
28
16
|
}
|
|
29
|
-
/** Emit one MDX JSX attribute as an OP_PROP, mirroring `encode_js_jsx_attrs`:
|
|
30
|
-
* null→boolean, string→literal, `{ value }` object→expression, else→boolean. */
|
|
31
17
|
export declare function emitMdxAttr(w: OpWriter, a: Record<string, unknown>): void;
|
package/dist/op-stream.js
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* Low-level op-stream writer shared by the MDAST/HAST declarative compilers.
|
|
3
|
-
*
|
|
4
|
-
* Emits the compact OPEN/CLOSE/field/REF/KEEP_CHILDREN/PROP stream that Rust
|
|
5
|
-
* replays straight into the arena (`replay_opstream` in js_commands.rs; byte
|
|
6
|
-
* values in generated/wire-constants.ts) — the only structural encoding for
|
|
7
|
-
* plugin-built content. Strings ride ByteWriter's zero-alloc path (inline
|
|
8
|
-
* char codes when short ASCII, `encodeInto` otherwise). `CommandBuffer`
|
|
9
|
-
* extends this class so payloads are emitted directly into the command
|
|
10
|
-
* bytes, with no intermediate opstream buffer or copy.
|
|
11
|
-
*/
|
|
1
|
+
// Writing directly into CommandBuffer avoids an intermediate op-stream allocation and copy.
|
|
12
2
|
import { ByteWriter } from "./byte-writer.js";
|
|
13
3
|
import { OP_OPEN, OP_CLOSE, OP_REF, OP_KEEP_CHILDREN, OP_STR, OP_U8, OP_U32, OP_BOOL, OP_DATA, OP_PROP, OP_ALIGN, MDX_ATTR_BOOLEAN_PROP, MDX_ATTR_LITERAL_PROP, MDX_ATTR_EXPRESSION_PROP, MDX_ATTR_SPREAD, } from "./generated/wire-constants.js";
|
|
14
|
-
|
|
15
|
-
export { OF_VALUE, OF_URL, OF_TITLE, OF_ALT, OF_LANG, OF_META, OF_IDENTIFIER, OF_LABEL, OF_NAME, OF_REFERENCE_TYPE, OF_DEPTH, OF_CHECKED, OF_START, OF_ORDERED, OF_SPREAD, OF_TAGNAME, OF_EXPLICIT, PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_INT, PROP_NULL, MDX_ATTR_BOOLEAN_PROP, MDX_ATTR_LITERAL_PROP, MDX_ATTR_EXPRESSION_PROP, MDX_ATTR_SPREAD, } from "./generated/wire-constants.js";
|
|
4
|
+
export { OF_VALUE, OF_URL, OF_TITLE, OF_ALT, OF_LANG, OF_META, OF_IDENTIFIER, OF_LABEL, OF_NAME, OF_REFERENCE_TYPE, OF_DEPTH, OF_CHECKED, OF_START, OF_ORDERED, OF_SPREAD, OF_TAGNAME, OF_EXPLICIT, PROP_STRING, PROP_BOOL_TRUE, PROP_BOOL_FALSE, PROP_SPACE_SEP, PROP_INT, PROP_NULL, PROP_TOKEN_LIST, } from "./generated/wire-constants.js";
|
|
16
5
|
export class OpWriter extends ByteWriter {
|
|
17
6
|
constructor(initialSize = 512) {
|
|
18
7
|
super(initialSize);
|
|
@@ -52,9 +41,7 @@ export class OpWriter extends ByteWriter {
|
|
|
52
41
|
}
|
|
53
42
|
data(value) {
|
|
54
43
|
const json = JSON.stringify(value);
|
|
55
|
-
//
|
|
56
|
-
// returning undefined; a clear error beats a TypeError deep in the
|
|
57
|
-
// string encoder.
|
|
44
|
+
// JSON.stringify can return undefined for functions and custom toJSON methods.
|
|
58
45
|
if (typeof json !== "string") {
|
|
59
46
|
throw new Error("node `data` is not JSON-serializable");
|
|
60
47
|
}
|
|
@@ -75,7 +62,6 @@ export class OpWriter extends ByteWriter {
|
|
|
75
62
|
this.buf[this.n++] = OP_REF;
|
|
76
63
|
this.writeU32(id);
|
|
77
64
|
}
|
|
78
|
-
/** Table column-alignment codes (0=none, 1=left, 2=right, 3=center). */
|
|
79
65
|
align(codes) {
|
|
80
66
|
this.ensure(5 + codes.length);
|
|
81
67
|
this.buf[this.n++] = OP_ALIGN;
|
|
@@ -88,8 +74,6 @@ export class OpWriter extends ByteWriter {
|
|
|
88
74
|
this.buf[this.n++] = OP_KEEP_CHILDREN;
|
|
89
75
|
}
|
|
90
76
|
}
|
|
91
|
-
/** Emit one MDX JSX attribute as an OP_PROP, mirroring `encode_js_jsx_attrs`:
|
|
92
|
-
* null→boolean, string→literal, `{ value }` object→expression, else→boolean. */
|
|
93
77
|
export function emitMdxAttr(w, a) {
|
|
94
78
|
if (a.type === "mdxJsxExpressionAttribute") {
|
|
95
79
|
w.prop("", MDX_ATTR_SPREAD, typeof a.value === "string" ? a.value : "");
|
package/dist/phantom.d.ts
CHANGED
package/dist/phantom.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
* Sentinel the MDX parser substitutes for spaces inside expression and JSX
|
|
3
|
-
* attribute-expression values (see `PHANTOM_SPACE` in
|
|
4
|
-
* satteri-pulldown-cmark/src/mdx.rs). mdast and hast keep the sentinel in the
|
|
5
|
-
* stored value; readers and visitors restore the real space on the way out.
|
|
6
|
-
* Both plugin-facing paths (direct walk match and materialized child) must
|
|
7
|
-
* restore it so a node's `value` reads the same regardless of how it's reached.
|
|
8
|
-
*/
|
|
1
|
+
// Rust uses this sentinel for spaces inside MDX expressions; every JS read path must restore them.
|
|
9
2
|
const PHANTOM_SPACE = "\uF002";
|
|
10
|
-
/** Restore phantom-space sentinels to real spaces, allocating only when present. */
|
|
11
3
|
export function restorePhantomSpaces(value) {
|
|
12
4
|
return value.includes(PHANTOM_SPACE) ? value.replaceAll(PHANTOM_SPACE, " ") : value;
|
|
13
5
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { NativeConvertOptions } from "./compile-options.js";
|
|
2
|
+
import type { Frontmatter } from "./compile-types.js";
|
|
3
|
+
import type { AnyHandle } from "./handles.js";
|
|
4
|
+
import { type HastHandle } from "./hast/hast-visitor.js";
|
|
5
|
+
import { type MdastHandle } from "./mdast/mdast-visitor.js";
|
|
6
|
+
import type { HastPluginDefinition, MdastPluginDefinition } from "./plugin.js";
|
|
7
|
+
import type { Data, SourceFormat } from "./types.js";
|
|
8
|
+
export type MdastPipelineResult = {
|
|
9
|
+
handle: MdastHandle;
|
|
10
|
+
pendingCommands?: Uint8Array;
|
|
11
|
+
lastPlugin?: {
|
|
12
|
+
name?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function releaseHandle(handle: AnyHandle, invalidateStubs: boolean): void;
|
|
16
|
+
export declare function warnIfDroppedTransforms(dropped: number | undefined, plugin: {
|
|
17
|
+
name?: string;
|
|
18
|
+
} | null | undefined, kind: "mdast" | "hast"): void;
|
|
19
|
+
export declare function runMdastPluginsOnHandle(handle: MdastHandle, plugins: MdastPluginDefinition[], fileURL: URL | undefined, data: Data, sourceFormat: SourceFormat, collectLast?: boolean): MdastPipelineResult | Promise<MdastPipelineResult>;
|
|
20
|
+
export declare const EMPTY_COMMAND_BUFFER: Uint8Array<ArrayBuffer>;
|
|
21
|
+
export type CollectedHastCommands = {
|
|
22
|
+
commands: Uint8Array;
|
|
23
|
+
lastPlugin: {
|
|
24
|
+
name?: string;
|
|
25
|
+
} | null;
|
|
26
|
+
};
|
|
27
|
+
export declare function runHastPluginsCollectLast(handle: HastHandle, plugins: HastPluginDefinition[], source: string, fileURL: URL | undefined, data: Data, sourceFormat: SourceFormat): CollectedHastCommands | Promise<CollectedHastCommands>;
|
|
28
|
+
export type HastWithFrontmatter = {
|
|
29
|
+
hastHandle: HastHandle;
|
|
30
|
+
frontmatter: Frontmatter | null;
|
|
31
|
+
};
|
|
32
|
+
export declare function createHastHandleFromMdast(source: string, mdastPlugins: MdastPluginDefinition[], mdx: boolean, fileURL: URL | undefined, parseOptions: number, nativeConvertOptions: NativeConvertOptions | undefined, data: Data, trackPositions: boolean): HastWithFrontmatter | Promise<HastWithFrontmatter>;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { applyCommandsToHandle, applyCommandsToMdastHandle, convertMdastToHastHandle, createHastHandleWithFrontmatter, createMdastHandle, createMdxHastHandleWithFrontmatter, createMdxMdastHandle, dropHandle, getHandleSource, getMdastFrontmatter, } from "#binding";
|
|
2
|
+
import { resolveSubscriptions, visitHastHandleCollect, visitHastHookCollect, } from "./hast/hast-visitor.js";
|
|
3
|
+
import { markHandleMutated, releaseHandleReferences } from "./lazy-child-resolver.js";
|
|
4
|
+
import { resolveMdastSubscriptions, visitMdastHandle, visitMdastHook, } from "./mdast/mdast-visitor.js";
|
|
5
|
+
// Invalidate retained child stubs before their arena is freed.
|
|
6
|
+
export function releaseHandle(handle, invalidateStubs) {
|
|
7
|
+
if (invalidateStubs)
|
|
8
|
+
releaseHandleReferences(handle);
|
|
9
|
+
dropHandle(handle);
|
|
10
|
+
}
|
|
11
|
+
export function warnIfDroppedTransforms(dropped, plugin, kind) {
|
|
12
|
+
if (!dropped || !plugin)
|
|
13
|
+
return;
|
|
14
|
+
const name = plugin.name ?? "<anonymous>";
|
|
15
|
+
const noun = dropped === 1 ? "transform" : "transforms";
|
|
16
|
+
console.warn(`satteri: plugin "${name}" queued ${dropped} ${kind} ${noun} on node(s) that a plugin had ` +
|
|
17
|
+
`already removed or replaced; ${dropped === 1 ? "it was" : "they were"} dropped.`);
|
|
18
|
+
}
|
|
19
|
+
function settle(value, fn) {
|
|
20
|
+
if (value instanceof Promise)
|
|
21
|
+
return value.then(fn);
|
|
22
|
+
fn(value);
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
// Keep synchronous pipelines synchronous even when later steps can return promises.
|
|
26
|
+
function sequence(steps) {
|
|
27
|
+
let pending;
|
|
28
|
+
for (const step of steps) {
|
|
29
|
+
if (step === undefined)
|
|
30
|
+
continue;
|
|
31
|
+
pending = pending === undefined ? step() : pending.then(step);
|
|
32
|
+
}
|
|
33
|
+
return pending;
|
|
34
|
+
}
|
|
35
|
+
export function runMdastPluginsOnHandle(handle, plugins, fileURL, data, sourceFormat, collectLast = false) {
|
|
36
|
+
const out = { handle };
|
|
37
|
+
const getSource = () => getHandleSource(handle);
|
|
38
|
+
const runPlugin = (plugin, isLastPlugin) => {
|
|
39
|
+
const subs = resolveMdastSubscriptions(plugin);
|
|
40
|
+
const apply = (r, isFinalPass) => {
|
|
41
|
+
if (!r.hasMutations)
|
|
42
|
+
return;
|
|
43
|
+
if (collectLast && isLastPlugin && isFinalPass) {
|
|
44
|
+
out.pendingCommands = r.commandBuffer;
|
|
45
|
+
out.lastPlugin = plugin;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
markHandleMutated(handle);
|
|
49
|
+
const dropped = applyCommandsToMdastHandle(handle, r.commandBuffer);
|
|
50
|
+
warnIfDroppedTransforms(dropped, plugin, "mdast");
|
|
51
|
+
};
|
|
52
|
+
const before = typeof plugin.before === "function" ? plugin.before : undefined;
|
|
53
|
+
const after = typeof plugin.after === "function" ? plugin.after : undefined;
|
|
54
|
+
const hasVisitors = subs.length > 0;
|
|
55
|
+
// Resolvers are epoch-bound, so each pass needs its own context.
|
|
56
|
+
const diagnostics = [];
|
|
57
|
+
const runHook = (hook, isFinalPass) => () => settle(visitMdastHook(handle, plugin, hook, getSource, fileURL, data, sourceFormat, diagnostics), (r) => apply(r, isFinalPass));
|
|
58
|
+
const runVisitors = (isFinalPass) => () => settle(visitMdastHandle(handle, plugin, subs, getSource, fileURL, data, sourceFormat, diagnostics), (r) => apply(r, isFinalPass));
|
|
59
|
+
return sequence([
|
|
60
|
+
before ? runHook(before, !hasVisitors && after === undefined) : undefined,
|
|
61
|
+
hasVisitors ? runVisitors(after === undefined) : undefined,
|
|
62
|
+
after ? runHook(after, true) : undefined,
|
|
63
|
+
]);
|
|
64
|
+
};
|
|
65
|
+
let i = 0;
|
|
66
|
+
const runNext = () => {
|
|
67
|
+
for (;;) {
|
|
68
|
+
const plugin = plugins[i];
|
|
69
|
+
if (plugin === undefined)
|
|
70
|
+
break;
|
|
71
|
+
i++;
|
|
72
|
+
const r = runPlugin(plugin, i === plugins.length);
|
|
73
|
+
if (r instanceof Promise)
|
|
74
|
+
return r.then(runNext);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
};
|
|
78
|
+
return runNext();
|
|
79
|
+
}
|
|
80
|
+
export const EMPTY_COMMAND_BUFFER = new Uint8Array(0);
|
|
81
|
+
const NO_HAST_COMMANDS = {
|
|
82
|
+
commands: EMPTY_COMMAND_BUFFER,
|
|
83
|
+
lastPlugin: null,
|
|
84
|
+
};
|
|
85
|
+
// Fuse the last apply with rendering or compilation to save a NAPI roundtrip.
|
|
86
|
+
export function runHastPluginsCollectLast(handle, plugins, source, fileURL, data, sourceFormat) {
|
|
87
|
+
let i = 0;
|
|
88
|
+
const runNext = () => {
|
|
89
|
+
for (;;) {
|
|
90
|
+
const plugin = plugins[i];
|
|
91
|
+
if (plugin === undefined)
|
|
92
|
+
break;
|
|
93
|
+
const isLastPlugin = i === plugins.length - 1;
|
|
94
|
+
i++;
|
|
95
|
+
const subs = resolveSubscriptions(plugin);
|
|
96
|
+
const { before, after } = plugin;
|
|
97
|
+
const passes = [];
|
|
98
|
+
if (typeof before === "function")
|
|
99
|
+
passes.push(before);
|
|
100
|
+
if (subs.length > 0)
|
|
101
|
+
passes.push("visitors");
|
|
102
|
+
if (typeof after === "function")
|
|
103
|
+
passes.push(after);
|
|
104
|
+
if (passes.length === 0)
|
|
105
|
+
continue;
|
|
106
|
+
let collected = NO_HAST_COMMANDS;
|
|
107
|
+
// Resolvers are epoch-bound, so each pass needs its own context.
|
|
108
|
+
const diagnostics = [];
|
|
109
|
+
const runPass = (pass, passIndex) => () => settle(pass === "visitors"
|
|
110
|
+
? visitHastHandleCollect(handle, plugin, subs, source, fileURL, data, sourceFormat, diagnostics)
|
|
111
|
+
: visitHastHookCollect(handle, plugin, pass, source, fileURL, data, sourceFormat, diagnostics), (commands) => {
|
|
112
|
+
if (isLastPlugin && passIndex === passes.length - 1) {
|
|
113
|
+
collected = { commands, lastPlugin: plugin };
|
|
114
|
+
}
|
|
115
|
+
else if (commands.length > 0) {
|
|
116
|
+
markHandleMutated(handle);
|
|
117
|
+
const dropped = applyCommandsToHandle(handle, commands);
|
|
118
|
+
warnIfDroppedTransforms(dropped, plugin, "hast");
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const pending = sequence(passes.map(runPass));
|
|
122
|
+
if (isLastPlugin)
|
|
123
|
+
return pending instanceof Promise ? pending.then(() => collected) : collected;
|
|
124
|
+
if (pending instanceof Promise)
|
|
125
|
+
return pending.then(runNext);
|
|
126
|
+
}
|
|
127
|
+
return NO_HAST_COMMANDS;
|
|
128
|
+
};
|
|
129
|
+
return runNext();
|
|
130
|
+
}
|
|
131
|
+
function readFrontmatter(handle) {
|
|
132
|
+
const raw = getMdastFrontmatter(handle);
|
|
133
|
+
return raw ? { kind: raw.kind === "toml" ? "toml" : "yaml", value: raw.value } : null;
|
|
134
|
+
}
|
|
135
|
+
// Read frontmatter after plugins so their YAML/TOML edits reach the result.
|
|
136
|
+
export function createHastHandleFromMdast(source, mdastPlugins, mdx, fileURL, parseOptions, nativeConvertOptions, data, trackPositions) {
|
|
137
|
+
if (mdastPlugins.length === 0) {
|
|
138
|
+
const [hastHandle, raw] = mdx
|
|
139
|
+
? createMdxHastHandleWithFrontmatter(source, parseOptions, nativeConvertOptions, trackPositions)
|
|
140
|
+
: createHastHandleWithFrontmatter(source, parseOptions, nativeConvertOptions, trackPositions);
|
|
141
|
+
return {
|
|
142
|
+
hastHandle,
|
|
143
|
+
frontmatter: raw ? { kind: raw.kind === "toml" ? "toml" : "yaml", value: raw.value } : null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const mdastHandle = mdx
|
|
147
|
+
? createMdxMdastHandle(source, parseOptions, trackPositions)
|
|
148
|
+
: createMdastHandle(source, parseOptions, trackPositions);
|
|
149
|
+
const sourceFormat = mdx ? "mdx" : "markdown";
|
|
150
|
+
// Conversion empties the arena on success; finally also releases it on failure.
|
|
151
|
+
const finalize = (r) => {
|
|
152
|
+
try {
|
|
153
|
+
const frontmatter = readFrontmatter(r.handle);
|
|
154
|
+
// Conversion empties the MDAST arena, invalidating retained child stubs.
|
|
155
|
+
markHandleMutated(r.handle);
|
|
156
|
+
const hastHandle = convertMdastToHastHandle(r.handle, nativeConvertOptions);
|
|
157
|
+
return { hastHandle, frontmatter };
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
releaseHandle(r.handle, true);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
try {
|
|
164
|
+
const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, fileURL, data, sourceFormat);
|
|
165
|
+
if (mdastResult instanceof Promise) {
|
|
166
|
+
return mdastResult.then(finalize, (err) => {
|
|
167
|
+
releaseHandle(mdastHandle, true);
|
|
168
|
+
throw err;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return finalize(mdastResult);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
releaseHandle(mdastHandle, true);
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -18,10 +18,13 @@ export interface PluginFactoryContext {
|
|
|
18
18
|
/** The document-level data bag, before any plugin has run. */
|
|
19
19
|
readonly data: Data;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
type RequireAtLeastOne<T, K extends keyof T> = Omit<T, K> & {
|
|
22
|
+
[P in K]-?: Required<Pick<T, P>> & Partial<Pick<T, Exclude<K, P>>>;
|
|
23
|
+
}[K];
|
|
24
|
+
export type MdastPluginDefinition = RequireAtLeastOne<MdastPluginInstance, Exclude<keyof MdastPluginInstance, "options">> & {
|
|
22
25
|
name: string;
|
|
23
26
|
};
|
|
24
|
-
export type HastPluginDefinition = HastVisitorInstance & {
|
|
27
|
+
export type HastPluginDefinition = RequireAtLeastOne<HastVisitorInstance, Exclude<keyof HastVisitorInstance, "options">> & {
|
|
25
28
|
name: string;
|
|
26
29
|
};
|
|
27
30
|
type PluginEntry<D> = D | ((ctx: PluginFactoryContext) => PluginEntry<D>) | readonly PluginEntry<D>[] | null | undefined | false;
|
|
@@ -33,13 +36,11 @@ export type HastPluginEntry = PluginEntry<HastPluginDefinition>;
|
|
|
33
36
|
export type MdastPluginList = readonly MdastPluginEntry[];
|
|
34
37
|
/** Value accepted by the `hastPlugins` option. */
|
|
35
38
|
export type HastPluginList = readonly HastPluginEntry[];
|
|
36
|
-
/**
|
|
39
|
+
/** Alias for {@link MdastPluginEntry}. */
|
|
37
40
|
export type MdastPluginInput = MdastPluginEntry;
|
|
38
|
-
/**
|
|
41
|
+
/** Alias for {@link HastPluginEntry}. */
|
|
39
42
|
export type HastPluginInput = HastPluginEntry;
|
|
40
|
-
/**
|
|
41
|
-
* runs. Factories resolve here and nowhere else, so each is called once per
|
|
42
|
-
* compile no matter how deeply it is nested. */
|
|
43
|
+
/** Resolve nested entries and invoke each factory once per compile. */
|
|
43
44
|
export declare function normalizePlugins<D>(entries: readonly PluginEntry<D>[], option: string, source: string, fileURL: URL | undefined, sourceFormat: SourceFormat, data: Data): D[];
|
|
44
45
|
export declare function defineMdastPlugin<P extends MdastPluginDefinition>(definition: P): P;
|
|
45
46
|
export declare function defineHastPlugin<P extends HastPluginDefinition>(definition: P): P;
|
package/dist/plugin.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* deeper is a factory that leads back to itself, which would otherwise recurse
|
|
3
|
-
* until the stack overflows. */
|
|
1
|
+
// Bound recursive factories so a self-referential preset cannot overflow the stack.
|
|
4
2
|
const MAX_FACTORY_DEPTH = 10;
|
|
5
|
-
/**
|
|
6
|
-
* runs. Factories resolve here and nowhere else, so each is called once per
|
|
7
|
-
* compile no matter how deeply it is nested. */
|
|
3
|
+
/** Resolve nested entries and invoke each factory once per compile. */
|
|
8
4
|
export function normalizePlugins(entries, option, source, fileURL, sourceFormat, data) {
|
|
9
5
|
const out = [];
|
|
10
6
|
// Built lazily so a list with no factories allocates no context.
|
|
@@ -46,9 +42,7 @@ export function normalizePlugins(entries, option, source, fileURL, sourceFormat,
|
|
|
46
42
|
walk(entry, MAX_FACTORY_DEPTH);
|
|
47
43
|
return out;
|
|
48
44
|
}
|
|
49
|
-
//
|
|
50
|
-
// type. That lets the compile entry points distinguish sync plugins from async
|
|
51
|
-
// ones in their conditional return type.
|
|
45
|
+
// Preserve each visitor’s inferred return type so compile results can distinguish sync from async plugins.
|
|
52
46
|
export function defineMdastPlugin(definition) {
|
|
53
47
|
if (!definition.name) {
|
|
54
48
|
throw new Error("Plugin definition must have a name");
|
package/dist/types.d.ts
CHANGED
|
@@ -154,7 +154,6 @@ export interface BufferHeader {
|
|
|
154
154
|
typeDataOffset: number;
|
|
155
155
|
stringPoolLen: number;
|
|
156
156
|
stringPoolOffset: number;
|
|
157
|
-
/** Number of nodes that carry an extra JSON `data` blob. */
|
|
158
157
|
nodeDataCount: number;
|
|
159
158
|
/** Offset of the node-data section: `[id u32][len u32][bytes...]` repeated. */
|
|
160
159
|
nodeDataOffset: number;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type NodeRefs } from "./visitor-shared.js";
|
|
2
|
+
declare class PlainNode {
|
|
3
|
+
type: string;
|
|
4
|
+
constructor(type: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class VisitorNode extends PlainNode {
|
|
7
|
+
#private;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
constructor(type: string, refs: NodeRefs, id: number);
|
|
10
|
+
static getNodeId(node: object, refs: NodeRefs): number | undefined;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FOREIGN_REF } from "./visitor-shared.js";
|
|
2
|
+
class PlainNode {
|
|
3
|
+
constructor(type) {
|
|
4
|
+
return { type };
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
// Private identity avoids a WeakMap insertion for every matched node. A derived constructor
|
|
8
|
+
// installs its fields on the plain object returned by super(), preserving plain-node consumers
|
|
9
|
+
// without a costly prototype change. Spreads and structured clones never copy this identity.
|
|
10
|
+
export class VisitorNode extends PlainNode {
|
|
11
|
+
#refs;
|
|
12
|
+
#id;
|
|
13
|
+
constructor(type, refs, id) {
|
|
14
|
+
super(type);
|
|
15
|
+
this.#refs = refs;
|
|
16
|
+
this.#id = id;
|
|
17
|
+
}
|
|
18
|
+
static getNodeId(node, refs) {
|
|
19
|
+
if (!(#refs in node))
|
|
20
|
+
return undefined;
|
|
21
|
+
return node.#refs === refs ? node.#id : FOREIGN_REF;
|
|
22
|
+
}
|
|
23
|
+
}
|