143 lines
5.6 KiB
JavaScript
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);
|
|
}
|