mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(lint): isolate IIFE timing constants (#2378)
This commit is contained in:
@@ -928,6 +928,26 @@ describe("GSAP rules", () => {
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does NOT report overlapping_gsap_tweens for same-named constants in separate IIFEs", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
<div data-composition-id="c1" data-width="1920" data-height="1080">
|
||||
<div id="x"></div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
(() => { const T = 0; tl.to("#x", { opacity: 1, duration: 1 }, T + 0); })();
|
||||
(() => { const T = 10; tl.to("#x", { opacity: 0, duration: 1 }, T + 0); })();
|
||||
window.__timelines["c1"] = tl;
|
||||
</script>
|
||||
</body></html>`;
|
||||
const result = await lintHyperframeHtml(html);
|
||||
const finding = result.findings.find((f) => f.code === "overlapping_gsap_tweens");
|
||||
expect(finding).toBeUndefined();
|
||||
});
|
||||
|
||||
it("detects overlapping_gsap_tweens between variable-target tweens", async () => {
|
||||
// Both tweens target the same element via a querySelector variable and their
|
||||
// windows overlap on `opacity`. The structure-driven window builder must see
|
||||
|
||||
@@ -685,6 +685,19 @@ describe("variable-target resolution (querySelector pattern)", () => {
|
||||
expect(result.animations[2].extras?.stagger).toBe("__raw:0.1");
|
||||
});
|
||||
|
||||
it("does not leak same-named constants across IIFE scopes", () => {
|
||||
const script = `
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
(() => { const T = 0; tl.to("#x", { opacity: 1, duration: 1 }, T + 0); })();
|
||||
(() => { const T = 10; tl.to("#x", { opacity: 0, duration: 1 }, T + 0); })();
|
||||
`;
|
||||
const result = parseGsapScript(script);
|
||||
expect(result.animations.map((animation) => animation.position)).toEqual([
|
||||
"__raw:T + 0",
|
||||
"__raw:T + 0",
|
||||
]);
|
||||
});
|
||||
|
||||
it("marks unresolvable variable targets with __unresolved__ and hasUnresolvedSelector", () => {
|
||||
const script = `
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
@@ -296,6 +296,12 @@ function resolveCollectionSelector(
|
||||
|
||||
function collectScopeBindings(ast: any): ScopeBindings {
|
||||
const bindings = new Map<string, number | string | boolean>();
|
||||
// This compact resolver is intentionally conservative: it does not carry a
|
||||
// full lexical environment. If the same identifier has different constant
|
||||
// values in separate function/IIFE scopes, treating either value as global
|
||||
// corrupts every tween in the other scope. Mark that name ambiguous so its
|
||||
// expressions stay __raw and timing-sensitive lint rules skip them.
|
||||
const ambiguousBindings = new Set<string>();
|
||||
// Const ARRAY/OBJECT literals are kept as AST nodes for member/index folding
|
||||
// (resolveMemberNode), exposed to resolveNode via the CONST_NODES side-table.
|
||||
const constNodes: ConstNodes = new Map();
|
||||
@@ -310,7 +316,14 @@ function collectScopeBindings(ast: any): ScopeBindings {
|
||||
return;
|
||||
}
|
||||
const val = resolveNode(init, bindings);
|
||||
if (val !== undefined) bindings.set(name, val);
|
||||
if (val === undefined || ambiguousBindings.has(name)) return;
|
||||
const existing = bindings.get(name);
|
||||
if (existing !== undefined && existing !== val) {
|
||||
bindings.delete(name);
|
||||
ambiguousBindings.add(name);
|
||||
} else if (existing === undefined) {
|
||||
bindings.set(name, val);
|
||||
}
|
||||
},
|
||||
});
|
||||
return bindings;
|
||||
@@ -1171,7 +1184,13 @@ function tweenCallToAnimation(
|
||||
const hasPositionArg = !!call.positionArg;
|
||||
const posVal = hasPositionArg ? extractLiteralValue(call.positionArg, scope) : 0;
|
||||
const position: number | string =
|
||||
typeof posVal === "number" ? posVal : typeof posVal === "string" ? posVal : 0;
|
||||
typeof posVal === "number"
|
||||
? posVal
|
||||
: typeof posVal === "string"
|
||||
? posVal
|
||||
: hasPositionArg
|
||||
? `__raw:${source.slice(call.positionArg.start, call.positionArg.end)}`
|
||||
: 0;
|
||||
let duration = typeof vars.duration === "number" ? vars.duration : undefined;
|
||||
const ease = typeof vars.ease === "string" ? vars.ease : undefined;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user