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); }