@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
161 lines (158 loc) • 4.73 kB
JavaScript
/**
* Sentinel substrings a transformer puts into its returned `comments`
* map to mark newly-added lines that should animate in (when the
* transform is applied) or out (when it is reverted). The markers are
* metadata only — they never appear in the rendered source text.
* Detection is substring-based so callers can decorate them however
* reads best alongside any neighbouring comments
* (e.g. `'// @expanding-start (api key)'`).
*
* Two flavours:
* - `@expanding-start` / `@expanding-end` delimit a contiguous,
* multi-line range (inclusive on both ends).
* - `@expanding` on its own marks a single added line — equivalent
* to a same-line start+end pair, but easier to write when the
* addition is just one line.
*/
export const EXPANDING_START_MARKER = '@expanding-start';
export const EXPANDING_END_MARKER = '@expanding-end';
export const EXPANDING_SINGLE_MARKER = '@expanding';
// `@expanding` not followed by `-` — distinguishes the single-line
// marker from the `-start` / `-end` variants without false matches.
const SINGLE_MARKER_PATTERN = /@expanding(?!-)/;
function classifyEntries(entries) {
let hasStart = false;
let hasEnd = false;
let hasSingle = false;
if (!entries) {
return {
hasStart,
hasEnd,
hasSingle
};
}
for (const entry of entries) {
if (typeof entry !== 'string') {
continue;
}
if (entry.includes(EXPANDING_START_MARKER)) {
hasStart = true;
}
if (entry.includes(EXPANDING_END_MARKER)) {
hasEnd = true;
}
if (SINGLE_MARKER_PATTERN.test(entry)) {
hasSingle = true;
}
}
return {
hasStart,
hasEnd,
hasSingle
};
}
function collectLineNumbers(comments) {
const lineNumbers = [];
for (const key of Object.keys(comments)) {
const line = Number(key);
if (Number.isFinite(line) && line > 0) {
lineNumbers.push(line);
}
}
lineNumbers.sort((a, b) => a - b);
return lineNumbers;
}
/**
* Scans a `SourceComments` map for `@expanding`, `@expanding-start`,
* and `@expanding-end` markers and returns the inclusive 1-indexed
* line ranges they delimit.
*
* Pairing rule: walk lines in ascending order. A standalone
* `@expanding` immediately produces a single-line `[line, line]`
* range. For ranges, the first `@expanding-start` opens a range and
* the next `@expanding-end` closes it. Unpaired range markers (a
* start with no matching end, or an end with no preceding start) are
* silently dropped — the most likely cause is a transformer
* mid-iteration and the safe behaviour is "no animation for that
* fragment" rather than either crashing or animating an unbounded
* region. Nested or overlapping ranges are not supported; a second
* `@expanding-start` before the previous one is closed replaces the
* open range's start.
*
* @param comments - The remapped 1-indexed comments map.
* @returns Sorted `[startLine, endLine]` pairs (inclusive on both
* ends). Returns an empty array when `comments` is `undefined`,
* empty, or contains no markers.
*/
export function findExpandingRanges(comments) {
if (!comments) {
return [];
}
const lineNumbers = collectLineNumbers(comments);
if (lineNumbers.length === 0) {
return [];
}
const ranges = [];
let openStart = null;
for (const line of lineNumbers) {
const {
hasStart,
hasEnd,
hasSingle
} = classifyEntries(comments[line]);
if (hasSingle) {
ranges.push([line, line]);
}
// Same-line start+end (multi-line addition collapsed to one line)
// is a valid range.
if (hasStart && hasEnd) {
ranges.push([line, line]);
openStart = null;
continue;
}
if (hasStart) {
openStart = line;
continue;
}
if (hasEnd && openStart !== null) {
ranges.push([openStart, line]);
openStart = null;
}
}
return ranges;
}
/**
* Fast yes/no check used by the pipeline's layout-shift classifier so
* it can avoid materialising the full `findExpandingRanges` array when
* all it needs is a boolean. Equivalent to
* `findExpandingRanges(...).length > 0` but short-circuits on the first
* matched marker.
*/
export function hasExpandingRanges(comments) {
if (!comments) {
return false;
}
const lineNumbers = collectLineNumbers(comments);
let openStart = false;
for (const line of lineNumbers) {
const {
hasStart,
hasEnd,
hasSingle
} = classifyEntries(comments[line]);
if (hasSingle) {
return true;
}
if (hasStart && hasEnd) {
return true;
}
if (hasStart) {
openStart = true;
continue;
}
if (hasEnd && openStart) {
return true;
}
}
return false;
}