mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
refactor(core): swap studio-api read path from recast to acorn parser (T6e) (#1392)
* refactor(core): swap studio-api read path from recast to acorn parser (T6e)
* fix(core,sdk): code-review findings — 5 correctness bugs + 2 cleanup
- gsapParserAcorn: top-level variable targets now resolved via program-scope
null-key fallback in lookupBindingFromAncestors (const el = querySelector...)
- gsapParserAcorn: fromTo guard requires args.length >= 3, preventing undefined
args[2]/args[3] access when fewer args supplied
- gsapWriterAcorn: remove fuzzing fallback in removeAnimationFromScript that
silently deleted the wrong animation (from→to ID conversion)
- gsapWriterAcorn: valueToCode guards NaN → "0" to avoid broken tween props;
safeKey regex aligned to ASCII-only (matching gsapSerialize)
- mutate: handleSetGsapTween now includes stagger in extras (was in addGsapTween
but missing from setGsapTween)
- apply-patches: script case now mirrors stylesheet — op=remove calls
setGsapScript("") instead of silently ignoring the patch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(core): add trust-model header to T6d parity suite
Documents the recast-baseline trust relationship and clarifies that
motionPath parity tests live in the Phase 3b commit (PR #1379) since
the acorn motionPath parser is also added there.
Addresses #1370 R1-N1 (Rames).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6dcbb5530e
commit
b8fa4b5dd2
@@ -3,6 +3,13 @@
|
||||
* T6d: parse-parity suite — runs the full gsapParser.test.ts parse scenarios
|
||||
* against parseGsapScriptAcorn. Write-path tests are it.skip'd; those live
|
||||
* in gsapWriter.acorn.test.ts.
|
||||
*
|
||||
* Trust model: assertions here trust the recast-baseline outputs from
|
||||
* gsapParser.test.ts as ground truth. T6b (gsapParser.acorn.test.ts) carries
|
||||
* the real behavioral parity contract; this file widens coverage to the full
|
||||
* corpus without duplicating the contract commentary.
|
||||
* motionPath parity tests live in the Phase 3b commit (PR #1379) because that
|
||||
* commit adds the acorn motionPath parser itself.
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { parseGsapScriptAcorn } from "./gsapParserAcorn.js";
|
||||
|
||||
@@ -153,7 +153,9 @@ function lookupBindingFromAncestors(
|
||||
const selector = bindings.get(scopeNode)?.get(name);
|
||||
if (selector !== undefined) return selector;
|
||||
}
|
||||
return null;
|
||||
// Program-scope bindings are stored under null (enclosingScopeNodeFromAncestors
|
||||
// returns null when no function wrapper exists — the common case in HF scripts).
|
||||
return bindings.get(null)?.get(name) ?? null;
|
||||
}
|
||||
|
||||
function isFunctionNode(node: any): boolean {
|
||||
@@ -470,31 +472,31 @@ function findAllTweenCalls(
|
||||
) {
|
||||
const method = callee.property.name;
|
||||
const args = node.arguments;
|
||||
if (args.length >= 2) {
|
||||
const selectorValue =
|
||||
resolveTargetSelector(args[0], nodeAncestors, scope, targetBindings) ??
|
||||
"__unresolved__";
|
||||
const selectorValue =
|
||||
args.length >= 1
|
||||
? (resolveTargetSelector(args[0], nodeAncestors, scope, targetBindings) ??
|
||||
"__unresolved__")
|
||||
: "__unresolved__";
|
||||
|
||||
if (method === "fromTo") {
|
||||
results.push({
|
||||
node,
|
||||
ancestors: nodeAncestors,
|
||||
method: "fromTo",
|
||||
selector: selectorValue,
|
||||
fromArg: args[1],
|
||||
varsArg: args[2],
|
||||
positionArg: args[3],
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
node,
|
||||
ancestors: nodeAncestors,
|
||||
method: method as GsapMethod,
|
||||
selector: selectorValue,
|
||||
varsArg: args[1],
|
||||
positionArg: args[2],
|
||||
});
|
||||
}
|
||||
if (method === "fromTo" && args.length >= 3) {
|
||||
results.push({
|
||||
node,
|
||||
ancestors: nodeAncestors,
|
||||
method: "fromTo",
|
||||
selector: selectorValue,
|
||||
fromArg: args[1],
|
||||
varsArg: args[2],
|
||||
positionArg: args[3],
|
||||
});
|
||||
} else if (method !== "fromTo" && args.length >= 2) {
|
||||
results.push({
|
||||
node,
|
||||
ancestors: nodeAncestors,
|
||||
method: method as GsapMethod,
|
||||
selector: selectorValue,
|
||||
varsArg: args[1],
|
||||
positionArg: args[2],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ import * as acornWalk from "acorn-walk";
|
||||
function valueToCode(value: unknown): string {
|
||||
if (typeof value === "string" && value.startsWith("__raw:")) return value.slice(6);
|
||||
if (typeof value === "string") return JSON.stringify(value);
|
||||
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
||||
if (typeof value === "number") return Number.isNaN(value) ? "0" : String(value);
|
||||
if (typeof value === "boolean") return String(value);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function safeKey(key: string): string {
|
||||
return /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
|
||||
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
||||
}
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
@@ -241,11 +242,7 @@ export function addAnimationToScript(
|
||||
export function removeAnimationFromScript(script: string, animationId: string): string {
|
||||
const parsed = parseGsapScriptAcornForWrite(script);
|
||||
if (!parsed) return script;
|
||||
let target = parsed.located.find((l) => l.id === animationId);
|
||||
if (!target) {
|
||||
const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
|
||||
target = parsed.located.find((l) => l.id === convertedId);
|
||||
}
|
||||
const target = parsed.located.find((l) => l.id === animationId);
|
||||
if (!target) return script;
|
||||
|
||||
const ms = new MagicString(script);
|
||||
|
||||
Reference in New Issue
Block a user