mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
refactor(core): unify composition contract (#2157)
* refactor(core): unify composition contract * fix(parsers): parse start expressions linearly
This commit is contained in:
@@ -3,6 +3,33 @@ import { describe, it, expect } from "vitest";
|
||||
import { lintHyperframeHtml } from "../hyperframeLinter.js";
|
||||
|
||||
describe("composition rules", () => {
|
||||
describe("canonical timing contract", () => {
|
||||
it("rejects deprecated attributes even when canonical attributes are also present", async () => {
|
||||
const result = await lintHyperframeHtml(`<!doctype html><html><body>
|
||||
<div data-composition-id="main" data-start="0" data-duration="5">
|
||||
<div class="clip" data-start="1" data-duration="2" data-end="9" data-track-index="1" data-layer="4"></div>
|
||||
</div>
|
||||
</body></html>`);
|
||||
|
||||
expect(result.findings.map(({ code }) => code)).toEqual(
|
||||
expect.arrayContaining(["deprecated_data_end", "deprecated_data_layer"]),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses canonical timing when deprecated attributes conflict", async () => {
|
||||
const result = await lintHyperframeHtml(`<!doctype html><html><body>
|
||||
<div data-composition-id="main" data-start="0" data-duration="5">
|
||||
<div id="a" class="clip" data-start="0" data-duration="2" data-end="9" data-track-index="1"></div>
|
||||
<div id="b" class="clip" data-start="2" data-duration="2" data-track-index="1"></div>
|
||||
</div>
|
||||
</body></html>`);
|
||||
|
||||
expect(
|
||||
result.findings.find(({ code }) => code === "overlapping_clips_same_track"),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("subcomposition guidance", () => {
|
||||
it("warns when any HTML composition file is over 300 lines", async () => {
|
||||
const html = Array.from({ length: 301 }, (_, i) =>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
WINDOW_TIMELINE_ASSIGN_PATTERN,
|
||||
} from "../utils";
|
||||
import { COMPOSITION_VARIABLE_TYPES } from "@hyperframes/parsers/composition";
|
||||
import { COMPOSITION_ATTRIBUTES, readClipTiming } from "@hyperframes/parsers/composition-contract";
|
||||
|
||||
// Agent guidance thresholds: warning-only nudges for files/tracks that become hard
|
||||
// to inspect and revise reliably in a single composition.
|
||||
@@ -66,6 +67,10 @@ const INLINE_STYLE_DISPLAY_NONE_PATTERN = /(?:^|;)\s*display\s*:\s*none\b/i;
|
||||
// below one 60fps frame (~16.67ms), so this only ever swallows float slop.
|
||||
const OVERLAP_EPSILON_SECONDS = 1e-6;
|
||||
|
||||
function readTagTiming(rawTag: string) {
|
||||
return readClipTiming({ getAttribute: (name) => readAttr(rawTag, name) });
|
||||
}
|
||||
|
||||
function countPhysicalLines(source: string): number {
|
||||
if (source.length === 0) return 0;
|
||||
|
||||
@@ -389,7 +394,7 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding
|
||||
if (isCompositionRootOrMount(tag.raw)) continue;
|
||||
if (!readAttr(tag.raw, "data-start")) continue;
|
||||
|
||||
const track = readAttr(tag.raw, "data-track-index");
|
||||
const track = readAttr(tag.raw, COMPOSITION_ATTRIBUTES.trackIndex);
|
||||
if (!track) continue;
|
||||
trackCounts.set(track, (trackCounts.get(track) ?? 0) + 1);
|
||||
}
|
||||
@@ -446,7 +451,8 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding
|
||||
({ tags }) => {
|
||||
const findings: HyperframeLintFinding[] = [];
|
||||
for (const tag of tags) {
|
||||
if (readAttr(tag.raw, "data-layer") && !readAttr(tag.raw, "data-track-index")) {
|
||||
const timing = readTagTiming(tag.raw);
|
||||
if (timing.diagnostics.some(({ code }) => code === "deprecated-layer")) {
|
||||
const elementId = readAttr(tag.raw, "id") || undefined;
|
||||
findings.push({
|
||||
code: "deprecated_data_layer",
|
||||
@@ -457,7 +463,7 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding
|
||||
snippet: truncateSnippet(tag.raw),
|
||||
});
|
||||
}
|
||||
if (readAttr(tag.raw, "data-end") && !readAttr(tag.raw, "data-duration")) {
|
||||
if (timing.diagnostics.some(({ code }) => code === "deprecated-end")) {
|
||||
const elementId = readAttr(tag.raw, "id") || undefined;
|
||||
findings.push({
|
||||
code: "deprecated_data_end",
|
||||
@@ -568,17 +574,14 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding
|
||||
const trackMap = new Map<string, ClipInfo[]>();
|
||||
|
||||
for (const tag of tags) {
|
||||
const startStr = readAttr(tag.raw, "data-start");
|
||||
const durationStr = readAttr(tag.raw, "data-duration");
|
||||
const trackStr = readAttr(tag.raw, "data-track-index");
|
||||
if (!startStr || !durationStr || !trackStr) continue;
|
||||
|
||||
const start = Number(startStr);
|
||||
const duration = Number(durationStr);
|
||||
const trackStr = readAttr(tag.raw, COMPOSITION_ATTRIBUTES.trackIndex);
|
||||
if (!trackStr) continue;
|
||||
const timing = readTagTiming(tag.raw);
|
||||
const { start, duration } = timing;
|
||||
const track = trackStr;
|
||||
|
||||
// Skip non-numeric (relative timing references like "intro-comp")
|
||||
if (Number.isNaN(start) || Number.isNaN(duration)) continue;
|
||||
if (start == null || duration == null) continue;
|
||||
|
||||
const clips = trackMap.get(track) || [];
|
||||
clips.push({
|
||||
|
||||
Reference in New Issue
Block a user