mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
fix(lint): consolidate lint and audit correctness (#2413)
* fix(lint): stop CSS comments in <style> from manufacturing phantom root tags
extractOpenTags scans raw source text with a flat regex that has no
concept of <style>/<script> block boundaries, so a CSS comment like
`/* <g> wrapper */` inside a <style> block reads as a real open tag.
findRootTag consumes that flat tag list and only skips tags literally
named script/style/meta/link/title, so the phantom <g> tag (not in
that skip list) wins the "first non-ignored body tag" search and gets
returned as the composition root instead of the real one that follows.
This manufactured root_missing_composition_id and root_missing_dimensions
(the phantom tag has neither) plus head_leaked_text (the leaked-text
scan slices up to the phantom tag's position, landing inside the
<style> block before its real closing tag, so the raw CSS text reads
as leaked markup) on an otherwise valid sub-composition — reported
with an exact bisected repro: a <template>-wrapped SVG sub-composition
whose <style> block comments reference an inner <g> element.
Fix: compute <style>/<script> content spans up front (reusing the
existing extractBlocks + STYLE_BLOCK_PATTERN/SCRIPT_BLOCK_PATTERN) and
skip any TAG_PATTERN match that falls inside one, before it ever
reaches findRootTag or any other extractOpenTags consumer. Same shape
as the prior fix for a leading <svg> defs block being mistaken for the
root (8ee4b7df) — this closes a sibling gap in the same function.
Test: new regression case with a <style> block containing a `/* <g> */`
comment ahead of an <svg data-composition-id> root, asserting none of
the three findings fire. Full lint package suite (318 tests) passes.
* feat(lint): flag duplicate data-composition-id values
Declaring data-composition-id on more than one element (commonly the <meta>
tag from the quickstart template AND the root <div> added to satisfy
root_missing_composition_id) is a silent collision: `compositions --json`
returns two entries for the same id (one duration:0) and inspect/snapshot
crash with "Cannot read properties of undefined (reading totalDuration)".
Lint passed clean through all of it.
New rule `duplicate_composition_id`: group elements by data-composition-id
value and error on any value shared by 2+ elements, naming the id and calling
out the meta-vs-root collision in the fixHint. 3 tests: dup fires, single id
passes, two distinct ids don't collide. (Implemented via Codex; verified
independently: 111 lint tests pass, oxfmt/oxlint clean.)
* fix(audits): avoid caption false positives
* fix(lint): ignore proxy-label tween overlaps
* fix(cli): preserve the five-percent text audit floor
* fix(lint): preserve proxy identity across lexical scopes
* fix(cli): audit only directly painted text
* fix(lint): compare live composition ids canonically
* fix(lint): preserve expanded proxy identities
* fix(cli): measure directly painted text geometry
* fix(lint): preserve first duplicate attribute value
* fix(lint): keep shared proxy identity across helpers
* fix(parsers): preserve expanded proxy identity
* fix(lint): decode composition IDs consistently
This commit is contained in:
@@ -121,7 +121,7 @@ export function findRootTag(source: string, parsedTags?: readonly OpenTag[]): Op
|
||||
const bodyTag = tags.find((tag) => tag.name === "body");
|
||||
if (
|
||||
bodyTag &&
|
||||
(readAttr(bodyTag.raw, "data-composition-id") ||
|
||||
(readDecodedAttr(bodyTag.raw, "data-composition-id") ||
|
||||
readAttr(bodyTag.raw, "data-width") ||
|
||||
readAttr(bodyTag.raw, "data-height"))
|
||||
) {
|
||||
@@ -148,7 +148,7 @@ export function findRootTag(source: string, parsedTags?: readonly OpenTag[]): Op
|
||||
// still eligible as the root.
|
||||
if (
|
||||
tag.name === "svg" &&
|
||||
!readAttr(tag.raw, "data-composition-id") &&
|
||||
!readDecodedAttr(tag.raw, "data-composition-id") &&
|
||||
!readAttr(tag.raw, "data-width") &&
|
||||
!readAttr(tag.raw, "data-height")
|
||||
) {
|
||||
@@ -173,6 +173,22 @@ export function readAttr(tagSource: string, attr: string): string | null {
|
||||
return match?.[1] || null;
|
||||
}
|
||||
|
||||
/** Read an HTML attribute using browser-equivalent character-reference decoding. */
|
||||
export function readDecodedAttr(tagSource: string, attr: string): string | null {
|
||||
if (!tagSource) return null;
|
||||
let value: string | null = null;
|
||||
const parser = new Parser(
|
||||
{
|
||||
onattribute(name, decodedValue) {
|
||||
if (value === null && name.toLowerCase() === attr.toLowerCase()) value = decodedValue;
|
||||
},
|
||||
},
|
||||
{ decodeEntities: true, lowerCaseAttributeNames: false, lowerCaseTags: true },
|
||||
);
|
||||
parser.end(tagSource);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an attribute that may legitimately contain the opposite quote
|
||||
* character. `readAttr` truncates `data-variable-values='{"title":"Hello"}'`
|
||||
@@ -200,7 +216,7 @@ export function readJsonAttr(tagSource: string, attr: string): string | null {
|
||||
export function collectCompositionIds(tags: OpenTag[]): Set<string> {
|
||||
const ids = new Set<string>();
|
||||
for (const tag of tags) {
|
||||
const compId = readAttr(tag.raw, "data-composition-id");
|
||||
const compId = readDecodedAttr(tag.raw, "data-composition-id");
|
||||
if (compId) ids.add(compId);
|
||||
}
|
||||
return ids;
|
||||
|
||||
Reference in New Issue
Block a user