| 1201 | // ── Public API ────────────────────────────────────────────────────────────── |
| 1202 | |
| 1203 | export function parseGsapScript(script: string): ParsedGsap { |
| 1204 | try { |
| 1205 | const { detection, timelineVar, located } = parseGsapAst(script); |
| 1206 | const ref: TimelineRef = detection.ref ?? { kind: "identifier", name: "tl" }; |
| 1207 | const animations = located.map((l) => l.animation); |
| 1208 | |
| 1209 | const declPattern = |
| 1210 | ref.kind === "identifier" |
| 1211 | ? `(?:const|let|var)\\s+${timelineVar}\\s*=\\s*gsap\\.timeline\\s*\\([^)]*\\)\\s*;?` |
| 1212 | : `${escapeRegExp(timelineVar)}\\s*=\\s*gsap\\.timeline\\s*\\([^)]*\\)\\s*;?`; |
| 1213 | const timelineMatch = script.match(new RegExp(`^[\\s\\S]*?${declPattern}`)); |
| 1214 | const fallbackPreamble = |
| 1215 | ref.kind === "identifier" |
| 1216 | ? `const ${timelineVar} = gsap.timeline({ paused: true });` |
| 1217 | : `${timelineVar} = gsap.timeline({ paused: true });`; |
| 1218 | const preamble = timelineMatch?.[0] ?? fallbackPreamble; |
| 1219 | |
| 1220 | const lastCallIdx = script.lastIndexOf(`${timelineVar}.`); |
| 1221 | let postamble = ""; |
| 1222 | if (lastCallIdx !== -1) { |
| 1223 | const afterLast = script.slice(lastCallIdx); |
| 1224 | const endOfCall = afterLast.indexOf(";"); |
| 1225 | if (endOfCall !== -1) { |
| 1226 | postamble = script.slice(lastCallIdx + endOfCall + 1).trim(); |
| 1227 | } |
| 1228 | } |
| 1229 | |
| 1230 | const result: ParsedGsap = { animations, timelineVar, preamble, postamble }; |
| 1231 | if (detection.timelineCount > 1) result.multipleTimelines = true; |
| 1232 | if (detection.timelineCount > 0 && detection.ref === null) |
| 1233 | result.unsupportedTimelinePattern = true; |
| 1234 | return result; |
| 1235 | } catch { |
| 1236 | return { animations: [], timelineVar: "tl", preamble: "", postamble: "" }; |
| 1237 | } |
| 1238 | } |
| 1239 | |
| 1240 | // ── In-place AST mutation helpers ─────────────────────────────────────────── |
| 1241 | // |