Skip to content

Commit 0c134a4

Browse files
committed
feat(lint): warn when composition is missing data-no-timeline
1 parent ef18613 commit 0c134a4

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

packages/core/src/lint/rules/composition.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,44 @@ describe("composition rules", () => {
591591
});
592592
});
593593

594+
describe("missing_data_no_timeline", () => {
595+
it("warns when root has no timeline registration and no data-no-timeline", async () => {
596+
const html = `<!DOCTYPE html><html><body>
597+
<div data-composition-id="c1" data-width="320" data-height="180" data-duration="5"></div>
598+
</body></html>`;
599+
const result = await lintHyperframeHtml(html);
600+
const finding = result.findings.find((f) => f.code === "missing_data_no_timeline");
601+
expect(finding).toBeDefined();
602+
expect(finding?.severity).toBe("warning");
603+
});
604+
605+
it("does not warn when data-no-timeline is present (boolean form)", async () => {
606+
const html = `<!DOCTYPE html><html><body>
607+
<div data-composition-id="c1" data-no-timeline data-width="320" data-height="180" data-duration="5"></div>
608+
</body></html>`;
609+
const result = await lintHyperframeHtml(html);
610+
expect(result.findings.find((f) => f.code === "missing_data_no_timeline")).toBeUndefined();
611+
});
612+
613+
it("does not warn when a script registers window.__timelines[id]", async () => {
614+
const html = `<!DOCTYPE html><html><body>
615+
<div data-composition-id="c1" data-width="320" data-height="180" data-duration="5"></div>
616+
<script>
617+
window.__timelines = window.__timelines || {};
618+
window.__timelines["c1"] = gsap.timeline({ paused: true });
619+
</script>
620+
</body></html>`;
621+
const result = await lintHyperframeHtml(html);
622+
expect(result.findings.find((f) => f.code === "missing_data_no_timeline")).toBeUndefined();
623+
});
624+
625+
it("does not warn when there is no root composition-id", async () => {
626+
const html = `<!DOCTYPE html><html><body><p>hello</p></body></html>`;
627+
const result = await lintHyperframeHtml(html);
628+
expect(result.findings.find((f) => f.code === "missing_data_no_timeline")).toBeUndefined();
629+
});
630+
});
631+
594632
describe("root_composition_missing_data_duration (removed)", () => {
595633
// The rule was a static proxy for the runtime's loop-inflation Infinity
596634
// emission, but lint cannot observe GSAP timeline duration statically and

packages/core/src/lint/rules/composition.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,31 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding
393393
return findings;
394394
},
395395

396+
// missing_data_no_timeline
397+
// The producer polls window.__timelines[id] with a 45-second timeout waiting
398+
// for GSAP timeline registration. Compositions that never call
399+
// window.__timelines[id] = tl stall for 45 s every render. Adding
400+
// data-no-timeline to the root element tells the producer to skip the poll.
401+
({ rootTag, rootCompositionId, scripts }) => {
402+
if (!rootCompositionId || !rootTag) return [];
403+
if (/\bdata-no-timeline\b/.test(rootTag.raw)) return [];
404+
const registersTimeline = scripts.some((s) => s.content.includes("window.__timelines["));
405+
if (registersTimeline) return [];
406+
return [
407+
{
408+
code: "missing_data_no_timeline",
409+
severity: "warning",
410+
message:
411+
"This composition has no `window.__timelines` registration but is missing `data-no-timeline`. " +
412+
"The producer polls for timeline registration for up to 45 seconds before timing out, " +
413+
"adding 45 s to every render.",
414+
fixHint:
415+
'Add `data-no-timeline` to the root element to skip the poll: `<div data-composition-id="..." data-no-timeline ...>`.',
416+
snippet: truncateSnippet(rootTag.raw),
417+
},
418+
];
419+
},
420+
396421
// requestanimationframe_in_composition
397422
({ scripts }) => {
398423
const findings: HyperframeLintFinding[] = [];

0 commit comments

Comments
 (0)