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

116 lines
4.4 KiB
JavaScript

import { lazyProp, lazyGroup } from "../lazy-props.js";
import { TYPE_NAMES } from "./generated/node-types.js";
import { materializeMdastFields } from "./generated/layout.js";
// Leaf node types that do NOT have children.
// Type 9 = `definition`; type 18 = `imageReference` — leaves per mdast spec
// (imageReference carries `alt` as a string, not children).
export const LEAF_TYPES = new Set([
9, 10, 13, 7, 8, 14, 3, 16, 18, 20, 25, 26, 27, 28, 102, 103, 104,
]);
/**
* Add type-specific lazy properties to a node object.
*/
function addTypeProperties(node, reader, nodeId, nodeType) {
// Fixed-field types materialize from the generated layout table; the rest
// (variable-length / cross-field) stay in the hand-written switch.
if (materializeMdastFields(reader, node, nodeId, nodeType))
return;
switch (nodeType) {
case 5: {
// list
const resolveList = () => {
const d = reader.getListData(nodeId);
return { ordered: d.ordered, start: d.ordered ? d.start : null, spread: d.spread };
};
lazyGroup(node, ["ordered", "start", "spread"], resolveList);
break;
}
case 6: // listItem
lazyGroup(node, ["spread", "checked"], () => reader.getListItemData(nodeId));
break;
case 21: // table
Object.defineProperties(node, {
align: lazyProp("align", () => reader.getTableAlign(nodeId)),
});
break;
case 30: // containerDirective
case 31: // leafDirective
case 32: // textDirective
lazyGroup(node, ["name", "attributes"], () => reader.getDirectiveData(nodeId));
break;
case 100: // mdxJsxFlowElement
case 101: // mdxJsxTextElement
lazyGroup(node, ["name", "attributes"], () => reader.getMdxJsxElementData(nodeId));
break;
// Nodes with no type-specific props:
// root(0), paragraph(1), thematicBreak(3), blockquote(4),
// emphasis(11), strong(12), break(14), tableRow(22), tableCell(23), delete(24)
default:
break;
}
}
/**
* Materialize a single MDAST node from a binary buffer as a lazy JS object.
*/
export function materializeNode(reader, nodeId) {
const rawNode = reader.getNode(nodeId);
const nodeType = rawNode.type;
const typeName = TYPE_NAMES[nodeType] ?? `unknown(${nodeType})`;
const node = { type: typeName };
if (rawNode.position !== undefined) {
node.position = rawNode.position;
}
// _nodeId: non-enumerable internal reference
Object.defineProperty(node, "_nodeId", {
value: nodeId,
writable: false,
configurable: true,
enumerable: false,
});
// Type-specific lazy properties
addTypeProperties(node, reader, nodeId, nodeType);
// Plugin-set `data` survives the visitor walk via its own getter but
// would be dropped when materialized from a serialized handle.
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(`materializeNode: malformed node_data for nodeId=${nodeId}`, err);
}
}
}
// children: lazy getter (only for non-leaf nodes)
if (!LEAF_TYPES.has(nodeType)) {
Object.defineProperty(node, "children", {
get() {
const childIds = reader.getChildIds(nodeId);
const children = childIds.map((id) => materializeNode(reader, id));
Object.defineProperty(this, "children", {
value: children,
writable: true,
configurable: true,
enumerable: true,
});
return children;
},
configurable: true,
enumerable: true,
});
}
return node;
}
/** Materialize the full tree from root (nodeId=0). */
export function materializeMdastTree(reader) {
return materializeNode(reader, 0);
}