@hyperframes/lint 0.8.22 → 0.8.23

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/index.js CHANGED
@@ -2941,6 +2941,47 @@ ${other.raw}`)
2941
2941
  }
2942
2942
  return findings;
2943
2943
  },
2944
+ // gsap_timeline_return_used_as_tween — `tl.to()` returns the TIMELINE, not the tween it just
2945
+ // created. `gsap.to()` returns a Tween, so the two read identically and behave nothing alike.
2946
+ // Capturing the timeline's return and later treating it as an individual animation aims a
2947
+ // tween-scoped call at the whole composition: `.kill()` on it interrupts the master timeline
2948
+ // and detaches it from its parent, which stops a parent-driven seek dead while leaving an
2949
+ // explicit `tl.progress()` still working -- so a render of the packaged defaults looks fine
2950
+ // and only live playback breaks. A rebuild that collected these to discard them also leaves
2951
+ // every previous keyframe in place and stacks the new ones on top.
2952
+ ({ scripts }) => {
2953
+ const findings = [];
2954
+ for (const script of scripts) {
2955
+ const source = stripJsComments(script.content);
2956
+ const timelineVars = collectTimelineVarNames(source);
2957
+ if (timelineVars.length === 0) continue;
2958
+ const receivers = timelineVars.map(escapeRegExp2).join("|");
2959
+ const pattern = new RegExp(
2960
+ String.raw`(?:\.push\s*\(\s*|(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*)(?:${receivers})\s*\.\s*(to|from|fromTo|set|call|add)\s*\(`,
2961
+ "g"
2962
+ );
2963
+ let match;
2964
+ while ((match = pattern.exec(source)) !== null) {
2965
+ const boundName = match[1];
2966
+ const method = match[2];
2967
+ if (boundName) {
2968
+ const treatedAsTween = new RegExp(
2969
+ String.raw`\b${escapeRegExp2(boundName)}\s*\.\s*(?:kill|revert|invalidate|pause|resume|restart|seek|progress|timeScale)\s*\(`
2970
+ );
2971
+ if (!treatedAsTween.test(source)) continue;
2972
+ }
2973
+ const contextStart = Math.max(0, match.index - 40);
2974
+ findings.push({
2975
+ code: "gsap_timeline_return_used_as_tween",
2976
+ severity: "error",
2977
+ message: `\`${match[0].includes(".push") ? "push" : boundName}\` captures the return of \`.${method}()\` on timeline \`${timelineVars[0]}\`, but a timeline's \`.to()\`/\`.from()\`/\`.set()\` returns THE TIMELINE ITSELF, not the tween it created. Every captured value is the same master timeline, so a tween-scoped call on it \u2014 \`.kill()\` above all \u2014 hits the whole composition: it interrupts the timeline and detaches it from its parent, which freezes a parent-driven seek while an explicit \`tl.progress()\` keeps working. Only \`gsap.to()\` returns a tween.`,
2978
+ fixHint: "To build a group of keyframes you can later discard, put them in a nested timeline: `const nested = gsap.timeline(); nested.to(...); tl.add(nested, 0);` \u2014 then `nested.kill()` replaces exactly those keyframes and cannot reach `tl`. Children keep their absolute times when the nest is added at 0. To hold a single real tween, create it with `gsap.to(...)` and place it with `tl.add(tween, at)`.",
2979
+ snippet: truncateSnippet(source.slice(contextStart, match.index + match[0].length + 60))
2980
+ });
2981
+ }
2982
+ }
2983
+ return findings;
2984
+ },
2944
2985
  // gsap_group_selector_keyframes
2945
2986
  ({ scripts }) => {
2946
2987
  const findings = [];