Repository navigation
fix(lint): check no longer fails projects that inline the GSAP library - #6018
miguel-heygen wants to merge 9 commits into
Conversation
non_deterministic_code skips a script that is GSAP itself (producer inlining comment or GSAP banner), using the detector missing_gsap_script already had, now one shared helper. The author's own Math.random/Date.now calls are still flagged.
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed exact 6383f7e9fb1c807afe5e685c9b5f88731a28dac2 with an independent adversarial pass. Sharing the predicate preserves the old missing_gsap_script signals, and the new separate-script test checks author code beside the library.
[P1 blocker] Raw markers disable checking for arbitrary author code. packages/lint/src/utils.ts:563–568 matches GreenSock or _gsScope anywhere, including comments and string literals; packages/lint/src/rules/core.ts:943–946 then skips the whole script before stripping either. const label = "GreenSock"; const x = Math.random(); const now = Date.now(); produces zero non_deterministic_code findings; removing the label produces two. A spoofed producer banner also suppresses them. Genuine GSAP followed by author calls in the same script is unchecked too, as the body acknowledges. This affects ordinary author scripts without any library present, beyond that stated mixed-bundle limit.
Exempt only a verified library body and retain checks on author statements before/after it, or normalize known assets without giving arbitrary markers a whole-script exemption. Add negative regressions for innocent strings/comments, spoofed banners and real GSAP plus author calls in one tag.
Validation: parent and independent reviewer executed the exact-source extracted core callback and utility code with TypeScript stripped (unused parser imports removed). Plain random/time code produces two findings; string/comment/banner variants and real local GSAP 3.14.2 plus author calls produce none. Genuine GSAP alone is exempted. Read the four changed files/relevant context; no full lint suite, CLI render or exhaustive version/plugin matrix run.
Need: yes, avoid library false positives · Size: focused · Reuse: presence detection is reused, but does not establish a library exemption.
Verdict: REQUEST CHANGES
Reasoning: The whole-script exemption silently suppresses real author nondeterminism based only on an incidental marker.
— hf-review
Exempt the parsed GSAP UMD statement that carries its banner, not every script that mentions GreenSock. Strings, comments, spoofed banners and author code before or after the library in the same script are checked again.
|
Fixed at 11c37c3. You were right: a marker shows GSAP is present, but it cannot prove which code is GSAP. The exemption is now a parsed span. Only a top-level UMD statement ( I added the regressions you asked for. All 5 fail on 6383f7e and pass here: author code before and after real GSAP in one tag, a "GreenSock" string, a comment, a spoofed banner, a spoofed inlining comment. The spoofs use expression statements, so the UMD check is the only thing that rejects them; dropping it fails exactly those 2 tests. With full real GSAP 3.14.2 plus |
Edit accuracy: accurate 271 (base branch 271), smooth 182 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (1)
|
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed exact 7713b03aac6228a71cce9d06ecc817223ffd1d81, including the helper extraction pushed during this review; actual-rule probes rerun at this head. The earlier incidental string/comment cases and semicolon-separated author code before/after real GSAP are fixed. Acorn now provides statement boundaries, and missing_gsap_script is restored to main.
[P1 blocker] The parsed statement is still not a verified GSAP body. packages/lint/src/utils.ts:592–601 only requires an ExpressionStatement whose raw text contains define.amd, then exempts that whole statement. This contains no GSAP or UMD wrapper, yet produces zero nondeterminism findings:
/*! GreenSock */
window.seed = ("define.amd", Math.random());The substring can also occur solely in a comment. A fabricated UMD wrapper with an author random call is exempt too. Thus the original spoofing failure remains after adding the second marker.
An independent pass also reproduced this with an ordinary outer IIFE containing genuine GSAP followed by window.seed = Math.random(): the nested banner and define.amd exempt the whole outer wrapper, including author code. This reproduced for 12 real GSAP 3.14.2 core/plugin files (minified and unminified).
The same boundary also swallows legitimate author code joined to genuine GSAP with a comma: replace the trailing semicolon of real gsap.min.js with , window.seed = Math.random();. Acorn makes both a single SequenceExpression statement, so the returned span includes and suppresses the author's call. Keeping a semicolon reports it correctly.
Please recognize the actual library expression/body rather than an entire marker-bearing statement, keep neighboring sequence-expression operands checked, and avoid granting arbitrary banners/wrappers a library identity (for example by verifying supported asset contents). Add wrapper and sequence-expression negatives above; checking raw define.amd is not structural UMD verification.
Validation: executed the exact-source core callback and utility code with TypeScript stripped and cached Acorn 8.17.0. Ordinary string/comment/banner-only cases and real GSAP plus a separate author statement report the author call; real GSAP alone is exempt. String/comment spoof variants, a fake wrapper and comma-joined real GSAP+author code all suppress it. Independent adversarial pass performed. No full lint suite, CLI render or exhaustive GSAP version/plugin matrix claimed.
Verdict: REQUEST CHANGES
Reasoning: The prior finding is partly repaired, but author nondeterminism can still be mistaken for GSAP internals through raw markers or a shared expression statement.
— hf-review
…ism check Recognise GSAP's UMD wrapper by structure (factory IIFE called with this and a function, with a real define.amd member) anywhere in the tree, and blank only that call. Author code joined by a comma or wrapped in an outer IIFE is checked; define.amd text is not enough.
|
Fixed at 0830d41. Each probe you listed now reports the author's call:
One point I am not taking, with the reason in the PR body under "Known limits": a deliberately hand-built copy of GSAP's wrapper and banner around author code is still exempt. Verifying asset contents means pinning bytes per GSAP version and plugin. That breaks on every release and on any re-minified copy, which brings back the false positive this PR removes. The rule is already text-level and easy to bypass on purpose ( |
jrusso1020
left a comment
There was a problem hiding this comment.
Re-reviewed exact 0830d41948ca82e4feb4026028745a1ee3f5671e, with a fresh independent adversarial pass. The prior incidental string/comment markers, comma-joined author calls, and no-argument outer IIFE cases now report the author call correctly. CallExpression ranges are a useful improvement over whole-statement exemption.
[P1 blocker] A containing bundle still inherits the nested GSAP call's identity. packages/lint/src/utils.ts:582–593 searches recursively through nested function bodies for define.amd, while :620–626 accepts any GSAP banner anywhere inside the candidate call. Consequently this ordinary containing wrapper is blanked in full:
(function (root, factory) {
// Paste the genuine, unchanged gsap.min.js here, including its banner.
window.seed = Math.random();
})(this, function () {});With real GSAP 3.14.2, the actual helper returns both the inner library span and the entire outer call span; the actual nondeterminism callback reports no findings. Removing the outer arguments makes it report the author call. The outer function need not contain its own AMD logic: recursive traversal borrows it from GSAP. A real outer UMD bundle with its own AMD dispatch also reproduces via the nested banner. This is accidental exemption of mixed bundled author code, separate from the disclosed deliberate forgery of GSAP's complete banner/wrapper.
Bind the banner and AMD structure to the same library wrapper, excluding markers inherited solely from nested libraries, and avoid selecting enclosing mixed bundles. Add this two-argument enclosing-wrapper regression alongside the existing no-argument IIFE case.
Validation: read current helper/rule/tests, previous reviews and replies. Parent and independent actual-source probes reproduce the containing-wrapper failure. Twelve real minified/unminified GSAP core/plugin files remain exempt on their own; ordinary separate author code, plain outer IIFE and the former string/comment spoofs are checked. No full lint suite, CLI render or exhaustive version/plugin matrix run. Complete intentional forgery is acknowledged as a text-lint limitation and is not the basis of this blocker.
Verdict: REQUEST CHANGES
Reasoning: The earlier cases are repaired, but nested library markers can still grant an enclosing mixed bundle a whole-call exemption and hide real author nondeterminism.
— hf-review
…exemption The define.amd check reads only the wrapper's own body, and each GSAP banner belongs to the wrapper right after it, else the innermost one containing it. An outer wrapper or UMD bundle around pasted GSAP keeps its author code checked.
|
Fixed at 6354982 by tying the banner and the AMD check to one wrapper:
The new regressions all fail on 0830d41 and pass here:
Each rule has its own mutation: letting the walk enter nested functions fails the borrowed- |
jrusso1020
left a comment
There was a problem hiding this comment.
Re-reviewed exact 6354982c8d82739f79fce9310c876de3db88ee9e, including a fresh independent adversarial pass. Own-body AMD traversal fixes borrowed nested define.amd; the previous unmodified minified/unminified nested-bundle witnesses now report the author call.
[P1 blocker] The banner-owner fallback still exempts a containing author bundle. packages/lint/src/utils.ts:614–617 falls back to the smallest enclosing UMD whenever adjacency lookup fails, even when the banner actually introduces a nested library. This happens with an unchanged, supported asset:
(function (root, factory) {
if (typeof define === "function" && define.amd) define([], factory);
else factory();
})(this, function () {
/* inlined: https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.js */
// Paste the genuine, unchanged unminified dist/gsap.js here.
window.seed = Math.random();
});With actual gsap@3.14.2 bytes, the helper returns both the inner library span and the entire outer call, and the actual nondeterminism callback returns zero findings. Without the producer comment, the author's call is correctly reported. The producer comment precedes (, while Acorn's inner call start is at function; failed adjacency assigns that comment to the outer UMD. The library's own internal banner separately identifies the inner call. No invented GSAP wrapper/banner or modification to the library is required.
The same fallback also swallows author code when the real minified library's wrapper receives an ordinary concatenation separator (;!function instead of !function) after its banner. Please distinguish a banner introducing a nested call from a banner inside that call's factory; do not grant the enclosing mixed bundle ownership merely because adjacency matching failed. Add the unchanged producer-inlined unminified asset case and a separator variant as negative controls.
Validation: parent and independent reviewer reproduced this with exact-source utils and core callback, TypeScript stripped, cached Acorn 8.17.0/acorn-walk. Twelve real minified/unminified core/plugin files remain exempt alone; separate author statements, ordinary outer IIFEs and the previous two-argument wrapper, string/comment and comma cases are checked. Read current helper/rule/tests, existing reviews and author reply. No full authored lint suite, CLI render or exhaustive version/plugin matrix claimed. Intentional complete wrapper forgery remains the disclosed text-lint limitation and is not this finding.
Verdict: REQUEST CHANGES
Reasoning: Earlier witnesses are repaired, but a normal producer comment around an unchanged library still assigns its enclosing mixed bundle a whole-call exemption.
— hf-review
…nd GSAP A banner introduces the wrapper after it across whitespace, ! ; ( and comments, so producer comments and statement separators bind to GSAP itself. The fallback owner for a banner inside a wrapper must wrap no other wrapper, so a bundle around GSAP never inherits it.
|
Fixed at 87570ed. You were right that the fallback was the hole. Two changes in
Your negative controls are in the tests and fail on 6354982: producer-inlined unminified GSAP in a UMD bundle, Your probes with the real, unchanged gsap@3.14.2 bytes:
The 51-file matrix is unchanged at 150/153 (the bannerless unminified |
jrusso1020
left a comment
There was a problem hiding this comment.
Re-reviewed exact 87570ed431cc54a3fc666bea329750ca7e9f5331. The prior unchanged-UMD producer-comment and separator witnesses now report the author call; rejecting enclosing wrappers with another nested wrapper repairs those cases.
[P1 blocker] An ordinary flat Rollup bundle still receives a whole-bundle exemption. packages/lint/src/utils.ts:615–621 assumes that an enclosing UMD with no nested qualifying UMD wrapper is the library. Standard ESM bundling does exactly that to mixed library/author code. Reproduction, using genuine gsap@3.14.2:
import { gsap } from "gsap";
export { gsap };
window.authorSeed = Math.random();
window.authorTime = Date.now();Build with Rollup 4.64.5, --format umd --name Composition, and inline the generated file. Rollup retains GSAP's genuine license inside its sole UMD factory, with both author assignments at the end. There is no inner UMD because GSAP entered as ESM. wrapsAnother is false, so the helper exempts the entire generated call. Parent independently regenerated a 246,319-byte bundle: exemption span [0,246318], zero nondeterminism findings, including neither author assignment. The same Rollup build of author-only code produces both findings. Fresh independent reviewer reproduced the mixed build too. No invented banner/wrapper or edited library is needed; this is separate from the disclosed deliberate forgery limit.
Do not infer library ownership from the absence of another UMD wrapper. Bound the exemption to demonstrable library code, or conservatively leave ambiguous flattened mixed bundles checked. Add an actual Rollup ESM-GSAP-plus-author fixture/regression, preserving the existing raw UMD positives and author-only control.
Validation: parent reran the current-source helper and actual core-rule callback with cached Acorn/acorn-walk and TS stripped. Twelve real minified/unminified core/plugin assets, their comma/IIFE neighbors and the earlier producer/separator cases were checked. The source and new regressions were read; no full authored lint suite, CLI render, exhaustive version/plugin matrix or author-reported perf matrix run claimed.
Verdict: REQUEST CHANGES
Reasoning: Earlier witnesses are repaired, but normal ESM-to-UMD bundling still turns GSAP's authentic license into an exemption for neighboring author nondeterminism.
— hf-review
A GSAP banner now exempts only the UMD wrapper call that starts right after it (skipping whitespace, comments, `!`, `;` and `(`). The fallback that gave a banner to the innermost enclosing wrapper is gone: Rollup's UMD output for an ESM import of GSAP keeps the banner inside the one factory it shares with author code, so that fallback exempted the whole bundle, author Math.random() and Date.now() included. Tradeoff: unminified dist/gsap.js pasted inline has its banner inside its own factory, so it is flagged again, as on main. The minified gsap.min.js and producer-inlined copies stay exempt.
|
Fixed at the root in 9b8f358. A GSAP banner now belongs only to the UMD wrapper call that starts right after it (skipping whitespace, comments,
|
jrusso1020
left a comment
There was a problem hiding this comment.
Re-reviewed exact 458aafe2357022a4bf07f735f36140172c5f00f5 with a fresh independent adversarial pass. Removing the enclosing-wrapper fallback fixes the previous raw Rollup witness, and the earlier comma, outer-wrapper, separator and incidental string/comment cases remain checked. Conservatively checking raw unminified GSAP is now documented and covered.
[P1 blocker] The producer source-name marker still grants an ordinary mixed bundle a whole-call exemption. packages/lint/src/utils.ts:567 accepts any inlined: source containing gsap; :616 then assigns its adjacent UMD call as library code. Reproduction with genuine GSAP ESM:
import { gsap } from "gsap";
export { gsap };
window.authorSeed = Math.random();
window.authorTime = Date.now();Build with Rollup UMD, then ordinary default Terser minification. Prefix the result with the producer's normal /* inlined: gsap-composition.js */ comment. The current helper exempts [36,70323], the whole generated call, and the actual rule reports zero findings. Identical minified bytes prefixed /* inlined: composition.js */ have no exemption and report both APIs. scene.js?vendor=gsap and https://example.com/gsap-demo/scene.js also suppress them. No wrapper/banner is forged: Rollup/Terser supply the ordinary UMD and the producer automatically supplies /* inlined: ${src} */ for external scripts (packages/producer/src/services/htmlCompiler.ts:1280). Parent independently reran the witness and controls.
Please stop treating a GSAP substring anywhere in a script URL/name as library identity. Restrict producer-marker recognition to supported library artifacts, or leave ambiguous author bundles checked, and add the real minified mixed-bundle + producer-comment regression with the otherwise-identical filename control. This remains accidental mixed-code exemption, separate from the disclosed deliberate reconstruction of a library wrapper/banner.
Validation: all 24 extracted added regression cases pass through the exact rule/helper with lightweight script extraction; previous-utils control fails the expected three changed cases. Independent controls passed 156 cases including all 25 real GSAP 3.14.2 minified core/plugin assets and surrounding author code, plus fresh Rollup/five Terser variants without the misleading source-name prefix. Read the current helper, rule integration, added tests, prior reviews/replies and actual producer prefix path. No full lint suite, CLI render, exhaustive version or performance matrix claimed.
Verdict: REQUEST CHANGES
Reasoning: The internal-license fallback is repaired, but an ordinary GSAP-containing filename or URL still hides author nondeterminism in producer-inlined minified bundles.
— hf-review
…dle it heads The producer's inlined-source comment now marks library code only when its URL path is an npm gsap dist file (gsap or gsap@<version>, then /dist/<file>.js, query and hash ignored). A Rollup/Terser bundle of GSAP plus author code inlined from gsap-composition.js, scene.js?vendor=gsap or another domain's gsap-demo/scene.js keeps its own Math.random() and Date.now() checked.

What
hyperframes checkfailed any project that pastes the GSAP library inline (for example GSAP 3.14.2 in a caption template layer).non_deterministic_codescanned GSAP's own source and reported its internals:Math.random()inutils.shuffle/utils.randomandDate.now()in its ticker. Because the finding is an error,checkalso skipped its render pass on those projects, and the author had no way to clear it short of removing GSAP.Fix
blankInlinedGsapLibrary(packages/lint/src/utils.ts) parses the script with acorn and blanks GSAP's own code with spaces, so offsets and reported lines stay correct.non_deterministic_codethen scans what is left, as before.GSAP's code is a call anywhere in the tree that has GSAP's UMD wrapper structure: a function expression that reads the real member
define.amdin its own body (not inside a nested function), called with exactly(this, function …). Each GSAP banner (/*! … GreenSock … */) or producer/* inlined: <src> */comment whose<src>URL path is an npm gsap dist file (gsap[@version]/dist/<file>.js; a name that only containsgsapdoes not count) belongs to at most one wrapper. First choice is the wrapper that starts right after it, skipping only whitespace, comments,!,;and((minified builds, producer comments, statement separators). A banner anywhere else owns nothing. Only wrappers that own a banner are blanked, and only their own call range.What stays checked:
define.amdonly inside a nested library;separator or a stray banner sits inside itdefine.amdthat appears only as text, in a string or a commentKnown limits:
Math["random"]()); the aim is that nothing is exempted by accident.main: their banner sits inside the Rollup factory, which is exactly where a bundler also puts author code that imports GSAP, so the two cannot be told apart without pinning file contents. The minified builds (gsap.min.jsand every plugin's.min.js), and any copy the producer inlines with its/* inlined: */comment, are exempt. Caption templates inlinegsap.min.js.missing_gsap_scriptis unchanged. It answers a different question (is GSAP loaded anywhere), where a loose marker match is the right call.Proof
23 tests in
packages/lint/src/rules/core.test.tsuse GSAP 3.14.2's real UMD wrapper, banner and internals. Proof runs:main's rule, the 4 library tests fail (pasted, producer-inlined, author script beside GSAP, author code before and after GSAP in one tag)define.amdtext check over a whole top-level statement), the 4 regressions from the second review fail: comma-joined author code, author code in an outer IIFE, anddefine.amdin a string and in a commentdefine.amdfound in nested functions), the 4 regressions from the third review fail: a two-argument outer wrapper, an outer UMD bundle around minified or unminified GSAP, and an outer wrapper that borrowsdefine.amd!), the 4 regressions from the fourth review fail: GSAP after a;separator, producer-inlined unminified GSAP in a bundle, separated GSAP in a bundle, a stray banner in a bundleMath.random()/Date.now()reports nothinginlined:source containinggsapcounts), the 3 regressions from the sixth review fail: a Rollup/Terser bundle inlined fromgsap-composition.js,scene.js?vendor=gsapand another domain'sgsap-demo/scene.js!fails the 2 separator tests; letting the fallback own a wrapper that wraps another fails the stray-banner testdefine.amdsearch enter nested functions fails only the borrowed-define.amdtest; choosing the outermost containing wrapper fails only the unminified-in-bundle test; dropping the adjacent-banner owner fails the 8 tests that use minified GSAPAll 25 minified
dist/*.min.jsfiles of gsap@3.14.2 lint clean. The full 246 KB Rollup bundle of GSAP plus author code reports both author calls (none on the previous head); the same bundle of author code alone reports both too. Lint time for the whole matrix, back to back on one box: 13.96–14.84 s here vs 13.82–13.89 s onmain.The fourth review's probes with the real, unchanged gsap@3.14.2 files inside an outer UMD bundle (producer-inlined
gsap.js+ author call;;-separatedgsap.min.js+ author call): this branch reports only the author's line; GSAP alone in the bundle reports nothing.mainreports GSAP's own lines in every case.Real files linted at
mainvs this branch:maingsap.min.js3.14.2 inlineMath.random(),Date.now()gsap.js3.14.2 inlineMath.random(),Date.now()Math.random(),Date.now()(known limit)gsap.min.jspluswindow.seed = Math.random()in the same tagMath.random(),Date.now()Lint time moved by 10–20 ms on these inputs, within run-to-run noise.
Whole lint suite: 983 passed.
coreandutilstest files: 3 runs in a row green.tsc, oxlint, oxfmt and fallow are clean.