// Pure source-rewrite pass for `` references in a concept // file. Lives in its own file (no JSX) so test code can import it // without pulling the React component shim through the test graph. // See setup-concept.tsx for the orchestrator that drives this pass. import fs from "fs"; import path from "path"; import { extractRegion, inferLanguage } from "./demo-code"; import { resolveWithinDir } from "./safe-fs"; /** * Match the value of a string-literal JSX attribute. Returns undefined * for expression-valued attrs (e.g. `file={x}`) so the rewrite pass can * leave those references intact for the runtime component shim. */ function matchAttr(attrs: string, name: string): string | undefined { const dq = new RegExp(`\\b${name}="([^"]*)"`).exec(attrs); if (dq) return dq[1]; const sq = new RegExp(`\\b${name}='([^']*)'`).exec(attrs); if (sq) return sq[1]; return undefined; } /** * Pre-expand `` * JSX references in a concept-file source into fenced markdown blocks * sourced from `packageRoot`. The rewritten fences flow through the * regular MDXRemote → rehypeCode pipeline so they pick up Shiki * highlighting + the MdxCodeBlock chrome (copy button + figcaption). * * Only string-literal props are handled here. References with * expression-valued props (e.g. `file={something}`) are left intact * for the runtime component shim to resolve. Same posture as * `inlineSnippets` in docs-render.tsx. * * A reference whose file or region can't be found is replaced with an * empty string (logged to the server console). The body shouldn't * silently surface a broken `` JSX tag — that would crash * the MDX compile. */ export interface RewriteDemoCodeOptions { onError?: "warn" | "throw"; } function handleRewriteError( message: string, options: RewriteDemoCodeOptions, ): string { if (options.onError === "throw") { throw new Error(message); } console.warn(message); return ""; } function formatFenceTitle(title: string): string { return JSON.stringify(title); } const DEMO_CODE_TAG_RX = /])*)\/>/g; export function rewriteDemoCode( source: string, packageRoot: string, options: RewriteDemoCodeOptions = {}, ): string { // Match `` with quoted attributes, including values // such as `title="A > B"`, without spanning into the next JSX tag. return source.replace(DEMO_CODE_TAG_RX, (match, attrs: string) => { const file = matchAttr(attrs, "file"); const region = matchAttr(attrs, "region"); if (!file || !region) { if (options.onError === "throw") { throw new Error( `[demo-code] DemoCode references must use static file and region props: ${match}`, ); } return match; } const language = matchAttr(attrs, "language"); const title = matchAttr(attrs, "title"); const resolved = resolveWithinDir(packageRoot, file); if (!resolved || !fs.existsSync(resolved)) { return handleRewriteError( `[demo-code] file not found ${file} in package root ${packageRoot}`, options, ); } let raw: string; try { raw = fs.readFileSync(resolved, "utf-8"); } catch (err) { return handleRewriteError( `[demo-code] failed to read ${resolved}: ${(err as Error).message}`, options, ); } const ext = file.includes(".") ? file.slice(file.lastIndexOf(".") + 1).toLowerCase() : ""; let body: string | null; try { body = extractRegion(raw, region, ext); } catch (err) { return handleRewriteError( `[demo-code] extraction failed ${file} ${region}: ${(err as Error).message}`, options, ); } if (body === null) { return handleRewriteError( `[demo-code] region not found ${region} in ${file}`, options, ); } const lang = language ?? inferLanguage(file); const fenceTitle = title ?? path.basename(file); // 4-tilde fence so the embedded body can safely contain triple // backticks without prematurely closing the fence. return [ "", `~~~~${lang} title=${formatFenceTitle(fenceTitle)}`, body, "~~~~", "", ].join("\n"); }); }