UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

92 lines (89 loc) 3.53 kB
import { create } from 'jsondiffpatch'; import { toText } from 'hast-util-to-text'; import { decompressHastAsync } from "../hastUtils/index.mjs"; const differ = create({ omitRemovedValues: true, cloneDiffValues: true }); export async function transformSource(source, fileName, sourceTransformers, comments) { // Find applicable transformers up front so we can short-circuit before // doing any source decoding work. const applicableTransformers = sourceTransformers.filter(({ extensions }) => extensions.some(ext => fileName.endsWith(`.${ext}`))); if (applicableTransformers.length === 0) { return undefined; } // Decode the source string and split it once. Both are independent of // which transformer is running, so doing this per-transformer (as the // previous implementation did) wasted work proportional to the number // of registered transformers. let sourceString; if (typeof source === 'string') { sourceString = source; } else if ('hastJson' in source) { sourceString = toText(JSON.parse(source.hastJson)); } else if ('hastCompressed' in source) { sourceString = toText(JSON.parse(await decompressHastAsync(source.hastCompressed))); } else { sourceString = toText(source); } const splitSource = sourceString.split('\n'); const transforms = await Promise.all(applicableTransformers.map(async ({ transformer }) => { try { const transformed = await transformer(sourceString, fileName, comments); if (transformed) { const reduced = Object.keys(transformed).reduce((acc, key) => { const entry = transformed[key]; const delta = differ.diff(splitSource, entry.source.split('\n')); // Comments are 1-indexed everywhere, including transformer input and output // (keyed against the transformed source's 1-indexed lines). const transformedComments = entry.comments; const hasDelta = !!delta && typeof delta === 'object' && Object.keys(delta).length > 0; const renamed = !!entry.fileName && entry.fileName !== fileName; // Drop entries that neither change the source nor rename the // file — there's nothing for the runtime to apply. if (!hasDelta && !renamed) { return acc; } acc[key] = { ...(hasDelta && { delta, hasDelta: true }), ...(entry.fileName !== undefined && { fileName: entry.fileName }), ...(transformedComments && { comments: transformedComments }) }; return acc; }, {}); // If every entry was dropped (e.g. a transformer that only // produced noop entries), surface `undefined` so the caller // treats the variant as untransformed. return Object.keys(reduced).length > 0 ? reduced : undefined; } return undefined; } catch (error) { throw new Error(`Failed to transform source code (file: ${fileName}): ${error instanceof Error && error.message}`); } })); if (transforms.length === 0 || transforms.every(t => t === undefined)) { return undefined; } return transforms.reduce((acc, transform) => { if (transform) { Object.entries(transform).forEach(([key, value]) => { if (acc[key]) { throw new Error(`Duplicate key found in source transformations: ${key}`); } acc[key] = value; }); } return acc; }, {}); }