Files
hyperframes/packages/core/src/tokenSlug.ts
T
Vance IngallsandClaude Fable 5 d9368ec051 fix(core): address PR feedback — ReDoS-safe slug trim, getVariables cleanups
- slugify: replace the anchored alternated trim regex (/^-+|-+$/g) with a
  character-scan trim — CodeQL js/polynomial-redos blocker.
- readRenderOverrides: fold the readOverrides wrapper into the exported
  function (one name, no pass-through).
- getVariables: deduplicate declarers with a Set, matching
  injectCompositionCssVariables.
- Move the tokenSlug import to the top of the file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 02:34:18 -07:00

41 lines
1.5 KiB
TypeScript

/**
* THE slug for composition-variable ids → CSS custom-property names. Shared
* by the figma importer (emits `var(--<slug>, literal)`) and the runtime
* (defines `--<slug>` from declared variables) — one function so the two
* sides can never drift. The slug is lossy (case-folded, symbol runs
* collapse to "-"), so distinct ids CAN collide; callers surface
* detectSlugCollisions() as a warning rather than silently merging.
*/
export function slugify(name: string): string {
const collapsed = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
// Character-scan trim of leading/trailing "-" instead of /^-+|-+$/:
// CodeQL flags the alternated anchored regex as polynomial ReDoS on
// adversarial inputs (js/polynomial-redos).
let start = 0;
let end = collapsed.length;
while (start < end && collapsed[start] === "-") start++;
while (end > start && collapsed[end - 1] === "-") end--;
const slug = collapsed.slice(start, end);
return slug.length > 0 ? slug : "node";
}
export function cssVariableName(id: string): string {
return `--${slugify(id)}`;
}
/** Groups of distinct ids that collapse to the same CSS variable name. */
export function detectSlugCollisions(ids: Iterable<string>): string[][] {
const bySlug = new Map<string, string[]>();
for (const id of ids) {
const slug = cssVariableName(id);
const group = bySlug.get(slug);
if (group) {
if (!group.includes(id)) group.push(id);
} else {
bySlug.set(slug, [id]);
}
}
return [...bySlug.values()].filter((g) => g.length > 1);
}