@hyperframes/parsers 0.7.45 → 0.7.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composition.d.ts +59 -2
- package/dist/composition.js +152 -1
- package/dist/composition.js.map +1 -1
- package/dist/gsapWriterAcorn.js +36 -4
- package/dist/gsapWriterAcorn.js.map +1 -1
- package/dist/hfIds.d.ts +10 -6
- package/dist/hfIds.js +6 -1
- package/dist/hfIds.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +152 -39
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/hfIds.d.ts
CHANGED
|
@@ -14,12 +14,16 @@ declare const EXCLUDED_TAGS: Set<string>;
|
|
|
14
14
|
*/
|
|
15
15
|
declare function mintHfId(el: Element, assigned: Set<string>): string;
|
|
16
16
|
/**
|
|
17
|
-
* True for a
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* True for a sub-composition authoring template whose content the studio preview
|
|
18
|
+
* unwraps into the served body. Two accepted forms:
|
|
19
|
+
* A) `<template data-composition-id="X">…` — the id on the template itself.
|
|
20
|
+
* B) `<template id="X-template"><div data-composition-id="X">…` — the id on the
|
|
21
|
+
* wrapped root div (the form `hyperframes add` scaffolds and registry blocks use).
|
|
22
|
+
* Only these are treated as transparent containers for hf-id purposes. A plain
|
|
23
|
+
* `<template>` (runtime clone-source: list item, particle, etc.) must NOT get
|
|
24
|
+
* inner ids — its content is cloned N times into the live DOM, so a persisted
|
|
25
|
+
* inner id would be duplicated across every clone. Form B is distinguished from
|
|
26
|
+
* a clone-source by the presence of a direct `[data-composition-id]` child.
|
|
23
27
|
*/
|
|
24
28
|
declare function isCompositionTemplate(el: Element): boolean;
|
|
25
29
|
declare function ensureHfIds(html: string): string;
|
package/dist/hfIds.js
CHANGED
|
@@ -49,7 +49,12 @@ function mintHfId(el, assigned) {
|
|
|
49
49
|
return id;
|
|
50
50
|
}
|
|
51
51
|
function isCompositionTemplate(el) {
|
|
52
|
-
|
|
52
|
+
if (el.tagName.toLowerCase() !== "template") return false;
|
|
53
|
+
if (el.getAttribute("data-composition-id") !== null) return true;
|
|
54
|
+
for (const child of Array.from(el.children)) {
|
|
55
|
+
if (child.getAttribute("data-composition-id") !== null) return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
53
58
|
}
|
|
54
59
|
function walkElements(root, visit) {
|
|
55
60
|
for (const child of Array.from(root.children)) {
|
package/dist/hfIds.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hfIds.ts"],"sourcesContent":["/**\n * Stable hf- element id minting (R1). Node-safe (linkedom only, not browser DOM).\n *\n * Two surfaces share these helpers:\n * - ensureHfIds(html): node-id surface — mints data-hf-id on every element.\n * - mintHfId(el, assigned): shared by htmlParser for clip ids.\n *\n * Hash is CONTENT ONLY (tag + sorted attrs + own text) — no sibling position,\n * so inserting a non-identical sibling never shifts another element's id.\n */\nimport { parseHTML } from \"linkedom\";\n\n// Non-editable / non-visual elements that should never receive a stable id.\nexport const EXCLUDED_TAGS = new Set([\n \"script\",\n \"style\",\n \"template\",\n \"meta\",\n \"link\",\n \"noscript\",\n \"base\",\n]);\n\n// 32-bit FNV-1a. Pure, deterministic, no crypto, no Math.random.\nfunction fnv1a(str: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n// 4 base-36 chars · 36^4 ≈ 1.68M ids per document. Birthday-paradox collision\n// ≈ N²/(2·36^4): well under 1% per document after dup rehash at realistic\n// clip-model sizes (≤ a few hundred elements). The dup-rehash in mintHfId\n// resolves the rare collision; width is deliberately small for readable ids.\nfunction toHfId(hash: number): string {\n const s = (hash >>> 0).toString(36);\n // Use suffix (most-avalanched bits) for better distribution within the 4-char window.\n const four = s.length >= 4 ? s.slice(-4) : s.padStart(4, \"0\");\n return `hf-${four}`;\n}\n\n// Element's own direct text (TEXT_NODE children), not descendants'.\nfunction ownText(el: Element): string {\n let text = \"\";\n el.childNodes.forEach((n) => {\n if (n.nodeType === 3) text += (n as Text).nodeValue ?? \"\";\n });\n return text.trim();\n}\n\nfunction contentKey(el: Element): string {\n // Exclude all data-hf-* attrs (ids, studio state) — they must not influence the hash.\n // Use \\x00 / \\x01 separators (invalid in HTML attrs) to prevent ambiguous serialization.\n const attrs = Array.from(el.attributes)\n .filter((a) => !a.name.startsWith(\"data-hf-\"))\n .map((a) => `${a.name}\\x00${a.value}`)\n .sort()\n .join(\"\\x01\");\n return `${el.tagName.toLowerCase()}|${attrs}|${ownText(el)}`;\n}\n\n/**\n * Collision tiebreak for byte-identical siblings: document-order dup counter\n * (`hash(key#N)`). This IS order-dependent — two identical `<span></span>`\n * get different ids based on which comes first in the DOM. This is unavoidable:\n * unique ids for byte-identical elements require a positional signal.\n *\n * Why this is safe in practice: once `ensureHfIds` write-back persists\n * `data-hf-id` to source the attribute is physically bound to its element.\n * Reordering identical siblings carries the attribute along → zero\n * order-dependence post-persist. `ensureHfIds` skips pinned elements\n * (`if (el.getAttribute(\"data-hf-id\")) continue`), so normal operation\n * never re-exposes the ordering after first persist.\n */\n// WIRE CONTRACT: id minting is content-keyed (FNV1a of innerHTML + tag). R7's\n// preview route relies on mintHfId producing identical ids across mint contexts\n// (disk-persist pass vs. in-memory bundle pass) — see preview.test.ts\n// \"bundle returning untagged HTML gets same ids as disk\". Any change that adds\n// positional, session, or random input to the hash breaks that invariant and\n// makes hf- ids diverge between disk and served HTML, silently corrupting\n// drag-to-edit targeting.\nexport function mintHfId(el: Element, assigned: Set<string>): string {\n const key = contentKey(el);\n let id = toHfId(fnv1a(key));\n let dup = 0;\n while (assigned.has(id)) {\n dup += 1;\n // Graceful fallback instead of a hard throw: rehashing only fails to find a\n // free 4-char slot in a pathological document (~1.6M identical elements).\n // Rather than crash the whole parse, widen the id with the dup counter —\n // still deterministic and unique, just longer than the 4-char norm.\n if (dup > 10000) {\n id = `hf-${(fnv1a(key) >>> 0).toString(36)}-${dup}`;\n break;\n }\n id = toHfId(fnv1a(`${key}#${dup}`));\n }\n assigned.add(id);\n return id;\n}\n\n/**\n * True for a `<template data-composition-id
|
|
1
|
+
{"version":3,"sources":["../src/hfIds.ts"],"sourcesContent":["/**\n * Stable hf- element id minting (R1). Node-safe (linkedom only, not browser DOM).\n *\n * Two surfaces share these helpers:\n * - ensureHfIds(html): node-id surface — mints data-hf-id on every element.\n * - mintHfId(el, assigned): shared by htmlParser for clip ids.\n *\n * Hash is CONTENT ONLY (tag + sorted attrs + own text) — no sibling position,\n * so inserting a non-identical sibling never shifts another element's id.\n */\nimport { parseHTML } from \"linkedom\";\n\n// Non-editable / non-visual elements that should never receive a stable id.\nexport const EXCLUDED_TAGS = new Set([\n \"script\",\n \"style\",\n \"template\",\n \"meta\",\n \"link\",\n \"noscript\",\n \"base\",\n]);\n\n// 32-bit FNV-1a. Pure, deterministic, no crypto, no Math.random.\nfunction fnv1a(str: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n// 4 base-36 chars · 36^4 ≈ 1.68M ids per document. Birthday-paradox collision\n// ≈ N²/(2·36^4): well under 1% per document after dup rehash at realistic\n// clip-model sizes (≤ a few hundred elements). The dup-rehash in mintHfId\n// resolves the rare collision; width is deliberately small for readable ids.\nfunction toHfId(hash: number): string {\n const s = (hash >>> 0).toString(36);\n // Use suffix (most-avalanched bits) for better distribution within the 4-char window.\n const four = s.length >= 4 ? s.slice(-4) : s.padStart(4, \"0\");\n return `hf-${four}`;\n}\n\n// Element's own direct text (TEXT_NODE children), not descendants'.\nfunction ownText(el: Element): string {\n let text = \"\";\n el.childNodes.forEach((n) => {\n if (n.nodeType === 3) text += (n as Text).nodeValue ?? \"\";\n });\n return text.trim();\n}\n\nfunction contentKey(el: Element): string {\n // Exclude all data-hf-* attrs (ids, studio state) — they must not influence the hash.\n // Use \\x00 / \\x01 separators (invalid in HTML attrs) to prevent ambiguous serialization.\n const attrs = Array.from(el.attributes)\n .filter((a) => !a.name.startsWith(\"data-hf-\"))\n .map((a) => `${a.name}\\x00${a.value}`)\n .sort()\n .join(\"\\x01\");\n return `${el.tagName.toLowerCase()}|${attrs}|${ownText(el)}`;\n}\n\n/**\n * Collision tiebreak for byte-identical siblings: document-order dup counter\n * (`hash(key#N)`). This IS order-dependent — two identical `<span></span>`\n * get different ids based on which comes first in the DOM. This is unavoidable:\n * unique ids for byte-identical elements require a positional signal.\n *\n * Why this is safe in practice: once `ensureHfIds` write-back persists\n * `data-hf-id` to source the attribute is physically bound to its element.\n * Reordering identical siblings carries the attribute along → zero\n * order-dependence post-persist. `ensureHfIds` skips pinned elements\n * (`if (el.getAttribute(\"data-hf-id\")) continue`), so normal operation\n * never re-exposes the ordering after first persist.\n */\n// WIRE CONTRACT: id minting is content-keyed (FNV1a of innerHTML + tag). R7's\n// preview route relies on mintHfId producing identical ids across mint contexts\n// (disk-persist pass vs. in-memory bundle pass) — see preview.test.ts\n// \"bundle returning untagged HTML gets same ids as disk\". Any change that adds\n// positional, session, or random input to the hash breaks that invariant and\n// makes hf- ids diverge between disk and served HTML, silently corrupting\n// drag-to-edit targeting.\nexport function mintHfId(el: Element, assigned: Set<string>): string {\n const key = contentKey(el);\n let id = toHfId(fnv1a(key));\n let dup = 0;\n while (assigned.has(id)) {\n dup += 1;\n // Graceful fallback instead of a hard throw: rehashing only fails to find a\n // free 4-char slot in a pathological document (~1.6M identical elements).\n // Rather than crash the whole parse, widen the id with the dup counter —\n // still deterministic and unique, just longer than the 4-char norm.\n if (dup > 10000) {\n id = `hf-${(fnv1a(key) >>> 0).toString(36)}-${dup}`;\n break;\n }\n id = toHfId(fnv1a(`${key}#${dup}`));\n }\n assigned.add(id);\n return id;\n}\n\n/**\n * True for a sub-composition authoring template whose content the studio preview\n * unwraps into the served body. Two accepted forms:\n * A) `<template data-composition-id=\"X\">…` — the id on the template itself.\n * B) `<template id=\"X-template\"><div data-composition-id=\"X\">…` — the id on the\n * wrapped root div (the form `hyperframes add` scaffolds and registry blocks use).\n * Only these are treated as transparent containers for hf-id purposes. A plain\n * `<template>` (runtime clone-source: list item, particle, etc.) must NOT get\n * inner ids — its content is cloned N times into the live DOM, so a persisted\n * inner id would be duplicated across every clone. Form B is distinguished from\n * a clone-source by the presence of a direct `[data-composition-id]` child.\n */\nexport function isCompositionTemplate(el: Element): boolean {\n if (el.tagName.toLowerCase() !== \"template\") return false;\n if (el.getAttribute(\"data-composition-id\") !== null) return true;\n for (const child of Array.from(el.children)) {\n if (child.getAttribute(\"data-composition-id\") !== null) return true;\n }\n return false;\n}\n\n/**\n * Document-order walk of every element under `root`, descending into\n * composition `<template>` subtrees — linkedom's querySelectorAll does not, so\n * template-based sub-comps would otherwise never get inner ids (the preview\n * unwraps the template and stamps the SAME content, so skipping here splits\n * the id space between the served DOM and the raw file). Plain templates are\n * skipped entirely (see isCompositionTemplate).\n */\nfunction walkElements(root: Element, visit: (el: Element) => void): void {\n for (const child of Array.from(root.children)) {\n const isTemplate = child.tagName.toLowerCase() === \"template\";\n if (isTemplate && !isCompositionTemplate(child)) continue;\n visit(child);\n walkElements(child, visit);\n }\n}\n\nexport function ensureHfIds(html: string): string {\n // Mirror parseSourceDocument's fragment-wrapping so bare fragments don't land\n // outside <body> in linkedom, which would cause body.querySelectorAll to return [].\n const hasDocumentShell = /<!doctype|<html[\\s>]/i.test(html);\n const wrapped = !hasDocumentShell;\n const { document } = wrapped\n ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`)\n : parseHTML(html);\n const body = document.body;\n if (!body) return html;\n\n const assigned = new Set<string>();\n // Seed with already-present ids (pin) so fresh mints never collide with them.\n // Scope to <body> to match the mint walk below — a stray data-hf-id in <head>\n // must not pin an id into the set that a body element would then be bumped off.\n walkElements(body, (el) => {\n const existing = el.getAttribute(\"data-hf-id\");\n if (existing) assigned.add(existing);\n });\n\n walkElements(body, (el) => {\n if (EXCLUDED_TAGS.has(el.tagName.toLowerCase())) return;\n if (el.getAttribute(\"data-hf-id\")) return; // pinned\n el.setAttribute(\"data-hf-id\", mintHfId(el, assigned));\n });\n\n return wrapped ? document.body.innerHTML || \"\" : document.toString();\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { SPRING_PRESETS, SpringPreset, generateSpringEaseData } from './springEa
|
|
|
7
7
|
export { parseGsapScriptAcorn as parseGsapScript } from './gsapParserAcorn.js';
|
|
8
8
|
export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId } from './hfIds.js';
|
|
9
9
|
export { ParsableDocumentLike, SubCompositionValidity, SubCompositionValidityReason, checkSubCompositionUsability } from './subCompositionValidity.js';
|
|
10
|
-
export { CANONICAL_FONT_DISPLAY_NAMES, FONT_ALIAS_KEYS, FONT_ALIAS_MAP, decodeUrlPathVariants, resolveAliasDisplayName } from './composition.js';
|
|
10
|
+
export { CANONICAL_FONT_DISPLAY_NAMES, FONT_ALIAS_KEYS, FONT_ALIAS_MAP, VariableUsageScan, decodeUrlPathVariants, parseCompositionVariables, resolveAliasDisplayName, scanVariableUsage } from './composition.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Thrown by htmlParser functions when the input HTML is empty or does not
|
|
@@ -44,6 +44,7 @@ interface CompositionMetadata {
|
|
|
44
44
|
variables: CompositionVariable[];
|
|
45
45
|
}
|
|
46
46
|
declare function extractCompositionMetadata(html: string): CompositionMetadata;
|
|
47
|
+
|
|
47
48
|
declare function validateCompositionHtml(html: string): ValidationResult;
|
|
48
49
|
|
|
49
50
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1936,6 +1936,46 @@ function parseGsapScriptAcorn(script) {
|
|
|
1936
1936
|
}
|
|
1937
1937
|
}
|
|
1938
1938
|
|
|
1939
|
+
// src/compositionVariables.ts
|
|
1940
|
+
var DEFAULT_TYPEOF = {
|
|
1941
|
+
string: "string",
|
|
1942
|
+
number: "number",
|
|
1943
|
+
color: "string",
|
|
1944
|
+
boolean: "boolean",
|
|
1945
|
+
enum: "string",
|
|
1946
|
+
font: "string",
|
|
1947
|
+
image: "string"
|
|
1948
|
+
};
|
|
1949
|
+
function isRecord(v) {
|
|
1950
|
+
return typeof v === "object" && v !== null;
|
|
1951
|
+
}
|
|
1952
|
+
function isVariableType(t) {
|
|
1953
|
+
return typeof t === "string" && t in DEFAULT_TYPEOF;
|
|
1954
|
+
}
|
|
1955
|
+
function isCompositionVariable(v) {
|
|
1956
|
+
if (!isRecord(v)) return false;
|
|
1957
|
+
if (typeof v.id !== "string" || typeof v.label !== "string") return false;
|
|
1958
|
+
if (!isVariableType(v.type)) return false;
|
|
1959
|
+
if (typeof v.default !== DEFAULT_TYPEOF[v.type]) return false;
|
|
1960
|
+
if (v.type === "enum" && !Array.isArray(v.options)) return false;
|
|
1961
|
+
return true;
|
|
1962
|
+
}
|
|
1963
|
+
function parseCompositionVariables(htmlEl) {
|
|
1964
|
+
const variablesAttr = htmlEl.getAttribute("data-composition-variables");
|
|
1965
|
+
if (!variablesAttr) {
|
|
1966
|
+
return [];
|
|
1967
|
+
}
|
|
1968
|
+
try {
|
|
1969
|
+
const parsed = JSON.parse(variablesAttr);
|
|
1970
|
+
if (!Array.isArray(parsed)) {
|
|
1971
|
+
return [];
|
|
1972
|
+
}
|
|
1973
|
+
return parsed.filter(isCompositionVariable);
|
|
1974
|
+
} catch {
|
|
1975
|
+
return [];
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1939
1979
|
// src/hfIds.ts
|
|
1940
1980
|
import { parseHTML } from "linkedom";
|
|
1941
1981
|
var EXCLUDED_TAGS = /* @__PURE__ */ new Set([
|
|
@@ -1987,7 +2027,12 @@ function mintHfId(el, assigned) {
|
|
|
1987
2027
|
return id;
|
|
1988
2028
|
}
|
|
1989
2029
|
function isCompositionTemplate(el) {
|
|
1990
|
-
|
|
2030
|
+
if (el.tagName.toLowerCase() !== "template") return false;
|
|
2031
|
+
if (el.getAttribute("data-composition-id") !== null) return true;
|
|
2032
|
+
for (const child of Array.from(el.children)) {
|
|
2033
|
+
if (child.getAttribute("data-composition-id") !== null) return true;
|
|
2034
|
+
}
|
|
2035
|
+
return false;
|
|
1991
2036
|
}
|
|
1992
2037
|
function walkElements(root, visit) {
|
|
1993
2038
|
for (const child of Array.from(root.children)) {
|
|
@@ -2615,44 +2660,6 @@ function extractCompositionMetadata(html) {
|
|
|
2615
2660
|
variables
|
|
2616
2661
|
};
|
|
2617
2662
|
}
|
|
2618
|
-
function parseCompositionVariables(htmlEl) {
|
|
2619
|
-
const variablesAttr = htmlEl.getAttribute("data-composition-variables");
|
|
2620
|
-
if (!variablesAttr) {
|
|
2621
|
-
return [];
|
|
2622
|
-
}
|
|
2623
|
-
try {
|
|
2624
|
-
const parsed = JSON.parse(variablesAttr);
|
|
2625
|
-
if (!Array.isArray(parsed)) {
|
|
2626
|
-
return [];
|
|
2627
|
-
}
|
|
2628
|
-
return parsed.filter((v) => {
|
|
2629
|
-
if (typeof v !== "object" || v === null) return false;
|
|
2630
|
-
if (typeof v.id !== "string" || typeof v.label !== "string") return false;
|
|
2631
|
-
if (!["string", "number", "color", "boolean", "enum", "font", "image"].includes(v.type))
|
|
2632
|
-
return false;
|
|
2633
|
-
switch (v.type) {
|
|
2634
|
-
case "string":
|
|
2635
|
-
return typeof v.default === "string";
|
|
2636
|
-
case "number":
|
|
2637
|
-
return typeof v.default === "number";
|
|
2638
|
-
case "color":
|
|
2639
|
-
return typeof v.default === "string";
|
|
2640
|
-
case "boolean":
|
|
2641
|
-
return typeof v.default === "boolean";
|
|
2642
|
-
case "enum":
|
|
2643
|
-
return typeof v.default === "string" && Array.isArray(v.options);
|
|
2644
|
-
case "font":
|
|
2645
|
-
return typeof v.default === "string";
|
|
2646
|
-
case "image":
|
|
2647
|
-
return typeof v.default === "string";
|
|
2648
|
-
default:
|
|
2649
|
-
return false;
|
|
2650
|
-
}
|
|
2651
|
-
});
|
|
2652
|
-
} catch {
|
|
2653
|
-
return [];
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
2663
|
function validateCompositionHtml(html) {
|
|
2657
2664
|
const errors = [];
|
|
2658
2665
|
const warnings = [];
|
|
@@ -2936,6 +2943,110 @@ function decodeUrlPathVariants(path) {
|
|
|
2936
2943
|
return variants;
|
|
2937
2944
|
}
|
|
2938
2945
|
|
|
2946
|
+
// src/variableUsage.ts
|
|
2947
|
+
import * as acorn3 from "acorn";
|
|
2948
|
+
import * as acornWalk3 from "acorn-walk";
|
|
2949
|
+
function isGetVariablesCallee(callee) {
|
|
2950
|
+
if (callee?.type === "Identifier") return callee.name === "getVariables";
|
|
2951
|
+
if (callee?.type === "MemberExpression" && !callee.computed) {
|
|
2952
|
+
return callee.property?.type === "Identifier" && callee.property.name === "getVariables";
|
|
2953
|
+
}
|
|
2954
|
+
return false;
|
|
2955
|
+
}
|
|
2956
|
+
function collectFromObjectPattern(pattern, out) {
|
|
2957
|
+
for (const prop of pattern.properties ?? []) {
|
|
2958
|
+
if (prop?.type === "RestElement") {
|
|
2959
|
+
out.incomplete();
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2962
|
+
if (prop?.type !== "Property") continue;
|
|
2963
|
+
if (prop.computed === true) {
|
|
2964
|
+
out.incomplete();
|
|
2965
|
+
} else if (prop.key?.type === "Identifier") {
|
|
2966
|
+
out.use(String(prop.key.name));
|
|
2967
|
+
} else if (prop.key?.type === "Literal" && typeof prop.key.value === "string") {
|
|
2968
|
+
out.use(prop.key.value);
|
|
2969
|
+
} else {
|
|
2970
|
+
out.incomplete();
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
function collectFromMemberAccess(member, out) {
|
|
2975
|
+
if (member.computed !== true && member.property?.type === "Identifier") {
|
|
2976
|
+
out.use(String(member.property.name));
|
|
2977
|
+
} else if (member.computed === true && member.property?.type === "Literal" && typeof member.property.value === "string") {
|
|
2978
|
+
out.use(member.property.value);
|
|
2979
|
+
} else {
|
|
2980
|
+
out.incomplete();
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
function classifyValueRead(parent, valueNode, out) {
|
|
2984
|
+
if (!parent || parent.type === "ExpressionStatement") {
|
|
2985
|
+
return null;
|
|
2986
|
+
}
|
|
2987
|
+
if (parent.type === "MemberExpression" && parent.object === valueNode) {
|
|
2988
|
+
collectFromMemberAccess(parent, out);
|
|
2989
|
+
return null;
|
|
2990
|
+
}
|
|
2991
|
+
if (parent.type === "VariableDeclarator" && parent.init === valueNode) {
|
|
2992
|
+
if (parent.id?.type === "ObjectPattern") {
|
|
2993
|
+
collectFromObjectPattern(parent.id, out);
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
if (parent.id?.type === "Identifier") return String(parent.id.name);
|
|
2997
|
+
out.incomplete();
|
|
2998
|
+
return null;
|
|
2999
|
+
}
|
|
3000
|
+
out.incomplete();
|
|
3001
|
+
return null;
|
|
3002
|
+
}
|
|
3003
|
+
function scanVariableUsage(scriptText) {
|
|
3004
|
+
const usedIds = [];
|
|
3005
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3006
|
+
let scanIncomplete = false;
|
|
3007
|
+
const sink = {
|
|
3008
|
+
use(id) {
|
|
3009
|
+
if (!seen.has(id)) {
|
|
3010
|
+
seen.add(id);
|
|
3011
|
+
usedIds.push(id);
|
|
3012
|
+
}
|
|
3013
|
+
},
|
|
3014
|
+
incomplete() {
|
|
3015
|
+
scanIncomplete = true;
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
let ast;
|
|
3019
|
+
try {
|
|
3020
|
+
ast = acorn3.parse(scriptText, { ecmaVersion: "latest", sourceType: "script" });
|
|
3021
|
+
} catch {
|
|
3022
|
+
return { usedIds: [], scanIncomplete: true };
|
|
3023
|
+
}
|
|
3024
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
3025
|
+
acornWalk3.ancestor(ast, {
|
|
3026
|
+
CallExpression(node, _, ancestors) {
|
|
3027
|
+
if (!isGetVariablesCallee(node.callee)) return;
|
|
3028
|
+
const parent = ancestors[ancestors.length - 2];
|
|
3029
|
+
const alias = classifyValueRead(parent, node, sink);
|
|
3030
|
+
if (alias) aliases.add(alias);
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
if (aliases.size > 0) {
|
|
3034
|
+
acornWalk3.ancestor(ast, {
|
|
3035
|
+
// fallow-ignore-next-line complexity
|
|
3036
|
+
Identifier(node, _, ancestors) {
|
|
3037
|
+
if (!aliases.has(String(node.name))) return;
|
|
3038
|
+
const parent = ancestors[ancestors.length - 2];
|
|
3039
|
+
if (!parent) return;
|
|
3040
|
+
if (parent.type === "VariableDeclarator" && parent.id === node) return;
|
|
3041
|
+
if (parent.type === "MemberExpression" && parent.property === node) return;
|
|
3042
|
+
if (parent.type === "Property" && parent.key === node && parent.computed !== true) return;
|
|
3043
|
+
if (classifyValueRead(parent, node, sink)) sink.incomplete();
|
|
3044
|
+
}
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
return { usedIds, scanIncomplete };
|
|
3048
|
+
}
|
|
3049
|
+
|
|
2939
3050
|
// src/fontAliases.ts
|
|
2940
3051
|
var FONT_ALIAS_MAP = {
|
|
2941
3052
|
// ── Canonical bundled fonts (self-referencing) ────────────────────────
|
|
@@ -3077,11 +3188,13 @@ export {
|
|
|
3077
3188
|
keyframesToGsapAnimations,
|
|
3078
3189
|
mintHfId,
|
|
3079
3190
|
normalizeResolutionFlag,
|
|
3191
|
+
parseCompositionVariables,
|
|
3080
3192
|
parseGsapScriptAcorn as parseGsapScript,
|
|
3081
3193
|
parseHtml,
|
|
3082
3194
|
queryByAttr,
|
|
3083
3195
|
removeElementFromHtml,
|
|
3084
3196
|
resolveAliasDisplayName,
|
|
3197
|
+
scanVariableUsage,
|
|
3085
3198
|
serializeGsapAnimations,
|
|
3086
3199
|
unrollComputedTimeline,
|
|
3087
3200
|
updateElementInHtml,
|