Skip to content

fix(lint): stop gsap_repeated_fromto_without_baseline flagging a load-time gsap.set() baseline - #3895

Open
miga-heygen wants to merge 1 commit into
mainfrom
fix/gsap-lint-baseline-hide-contradiction
Open

fix(lint): stop gsap_repeated_fromto_without_baseline flagging a load-time gsap.set() baseline#3895
miga-heygen wants to merge 1 commit into
mainfrom
fix/gsap-lint-baseline-hide-contradiction

Conversation

@miga-heygen

Copy link
Copy Markdown
Contributor

Summary

Two GSAP lint rules gave contradictory fix guidance for the common "hide the element until it's revealed" pattern:

  • gsap_repeated_fromto_without_baseline warns when ≥2 tl.fromTo() calls target the same element with no stable resting-state baseline established beforehand (GSAP applies a fromTo's "from" values at authoring time, so without a baseline, whichever fromTo was authored last silently becomes the resting state for any seek before the timeline actually starts). Its fixHint offered two options: immediateRender: false on each future fromTo, or an earlier in-timeline tl.set(sel, {...}, 0).
  • gsap_timeline_set_initial_hide independently warns on exactly that second shape — an in-timeline tl.set(...) at position 0 whose values hide the element (a zero-duration set at the exact position-0 playhead doesn't render on frame 0 in this engine, so the element pops hidden a frame later).

Following the first rule's own advice trips the second rule, since a "safe resting state" for a hide-until-reveal animation is, almost by definition, a hidden state.

Fix

gsap_timeline_set_initial_hide already treats a load-time gsap.set(...) (as opposed to an in-timeline tl.set(...)) as a reliable baseline — it exempts it from firing at all. gsap_repeated_fromto_without_baseline's own baseline check didn't recognize the same thing, rejecting any load-time set unconditionally. This aligns the two:

  • packages/lint/src/rules/gsap.ts: gsap_repeated_fromto_without_baseline's baseline check now also accepts a preceding load-time gsap.set(...) call as valid — gated on a new reliability scan (extractStandaloneSetSelectors, factored out of the pre-existing extractStandaloneHiddenSelectors) that excludes a gsap.set(...) deferred behind a callback or event handler. That distinction is needed because the GSAP parser flags any bare gsap.set(...) as load-time regardless of where it sits in the AST — a set inside a click handler only runs on user interaction, not at load, so it can't stand in for a resting state before the first tween.
  • The rule's fixHint now recommends the load-time gsap.set(...) route ahead of the in-timeline tl.set(...,0) route, and notes the latter can trip the other rule.

Known, accepted limitation (documented in a code comment at the check site): the reliability scan reports selectors, not specific occurrences, across the whole composition's scripts. A composition with two gsap.set(...) calls for the same selector — one deferred+earlier-in-source, one genuinely load-time+later — could false-accept the deferred one as a baseline. This requires an unusual duplicate-declaration shape and is a warning-severity, advice-quality rule (not a build-blocking correctness check), so it's left as a documented boundary rather than a blocker.

Test plan

  • packages/lint/src/rules/gsap.test.ts: updated the one existing test that encoded the old (now-corrected) behavior, and added new tests: a load-time gsap.set(...) baseline is now accepted; a load-time gsap.set(...) baseline satisfies both rules simultaneously (the actual contradiction, now resolved); a gsap.set(...) deferred behind a callback is still correctly rejected as a baseline. Full suite: 194/194 passing.
  • Verified against a real revert: reverted just gsap.ts (kept the tests), reran — the two new/changed tests failed exactly as expected against the old behavior; restored the fix, reran — all 194 pass again.
  • bunx tsc --noEmit -p packages/lint clean.
  • bunx oxlint / bunx oxfmt --check clean on both changed files.
  • Confirmed the refactor of extractStandaloneHiddenSelectors into a shared extractStandaloneSetSelectors base doesn't change its existing behavior (same regex, same match-group handling, only the value filter moved to a parameter) — full pre-existing suite still green.

…-time gsap.set() baseline

Its own fixHint recommended two fixes: `immediateRender: false`, or an
earlier in-timeline `tl.set(sel, {...}, 0)` baseline. That second option
collides with gsap_timeline_set_initial_hide, which independently warns on
exactly that shape whenever the values hide the element — the common
hide-until-reveal case both rules exist for. Following one rule's advice
trips the other.

gsap_timeline_set_initial_hide already treats a load-time `gsap.set(...)`
as a reliable baseline (it exempts `global` sets outright), but
gsap_repeated_fromto_without_baseline's own baseline check rejected any
`global` set unconditionally. Aligns the two: a preceding load-time
`gsap.set(...)` now satisfies the baseline check too, gated on a new
reliability scan (factored out of the existing hidden-selector scan) that
excludes a set deferred behind a callback or event handler, since the GSAP
parser flags any bare `gsap.set(...)` as load-time regardless of where it
sits in the AST. The fixHint now recommends the load-time route first and
cross-references the other rule.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant