IOweb/node_modules/satteri/dist/hast/hast-materializer.js
2026-07-03 15:07:38 -05:00

143 lines
5.6 KiB
JavaScript

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";
import { TYPE_NAMES } from "./generated/node-types.js";
import { lazyProp, lazyGroup } from "../lazy-props.js";
import { restorePhantomSpaces } from "../phantom.js";
function propsToRecord(props) {
const result = {};
for (const p of props) {
result[p.name] = p.value;
}
return result;
}
/**
* Materialize a single HAST node from a binary buffer as a lazy JS object.
*/
export function materializeHastNode(reader, nodeId) {
const nodeType = reader.getNodeType(nodeId);
const typeName = TYPE_NAMES[nodeType] ?? `unknown(${nodeType})`;
const node = { type: typeName };
// Position decodes to three objects; defer it — passthrough children (e.g.
// replaceNode keeping `node.children`) never read it.
if (reader.hasPosition(nodeId)) {
Object.defineProperty(node, "position", lazyProp("position", () => reader.getPosition(nodeId)));
}
// _nodeId: non-enumerable internal reference
Object.defineProperty(node, "_nodeId", {
value: nodeId,
writable: false,
configurable: true,
enumerable: false,
});
switch (nodeType) {
case HAST_ROOT:
// children: lazy getter
Object.defineProperty(node, "children", {
get() {
const childIds = reader.getChildIds(nodeId);
const children = childIds.map((id) => materializeHastNode(reader, id));
Object.defineProperty(this, "children", {
value: children,
writable: true,
configurable: true,
enumerable: true,
});
return children;
},
configurable: true,
enumerable: true,
});
break;
case HAST_ELEMENT: {
// tagName and properties: lazy, resolved together from one reader call
lazyGroup(node, ["tagName", "properties"], () => {
const { tagName, properties } = reader.getElementData(nodeId);
return { tagName, properties: propsToRecord(properties) };
});
// children: lazy
Object.defineProperty(node, "children", {
get() {
const childIds = reader.getChildIds(nodeId);
const children = childIds.map((id) => materializeHastNode(reader, id));
Object.defineProperty(this, "children", {
value: children,
writable: true,
configurable: true,
enumerable: true,
});
return children;
},
configurable: true,
enumerable: true,
});
break;
}
case HAST_TEXT:
case HAST_COMMENT:
case HAST_RAW:
Object.defineProperties(node, {
value: lazyProp("value", () => reader.getTextValue(nodeId)),
});
break;
case HAST_DOCTYPE:
// No extra properties
break;
case HAST_MDX_JSX_ELEMENT:
case HAST_MDX_JSX_TEXT_ELEMENT:
lazyGroup(node, ["name", "attributes"], () => reader.getMdxJsxElementData(nodeId));
Object.defineProperty(node, "children", {
get() {
const childIds = reader.getChildIds(nodeId);
const children = childIds.map((id) => materializeHastNode(reader, id));
Object.defineProperty(this, "children", {
value: children,
writable: true,
configurable: true,
enumerable: true,
});
return children;
},
configurable: true,
enumerable: true,
});
break;
case HAST_MDX_FLOW_EXPRESSION:
case HAST_MDX_TEXT_EXPRESSION:
Object.defineProperties(node, {
value: lazyProp("value", () => restorePhantomSpaces(reader.getTextValue(nodeId))),
});
break;
case HAST_MDX_ESM:
Object.defineProperties(node, {
value: lazyProp("value", () => reader.getTextValue(nodeId)),
});
break;
}
// Plugins can set `data` on any node type, so rehydrate generically
// (see website/content/docs/divergences.md for the code-block case).
const rawData = reader.getNodeData(nodeId);
if (rawData !== null) {
try {
const parsed = JSON.parse(rawData);
if (parsed && typeof parsed === "object" && Object.keys(parsed).length > 0) {
Object.defineProperty(node, "data", {
value: parsed,
writable: true,
configurable: true,
enumerable: true,
});
}
}
catch (err) {
if (process.env.NODE_ENV !== "production") {
console.warn(`materializeHastNode: malformed node_data for nodeId=${nodeId}`, err);
}
}
}
return node;
}
/**
* Materialize the full HAST tree from root (nodeId=0).
*/
export function materializeHastTree(reader) {
return materializeHastNode(reader, 0);
}