@hyperframes/lint 0.8.199 → 0.8.201
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +53 -5
- package/dist/index.js +53 -5
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -1047,6 +1047,19 @@ function selectorAliasesRuntimeHiddenStyle(selector) {
|
|
|
1047
1047
|
}
|
|
1048
1048
|
return unsafe;
|
|
1049
1049
|
}
|
|
1050
|
+
function selectsOnInlineStyle(selector) {
|
|
1051
|
+
let found = false;
|
|
1052
|
+
try {
|
|
1053
|
+
selectorParser((root) => {
|
|
1054
|
+
root.walkAttributes((node) => {
|
|
1055
|
+
if (node.attribute.toLowerCase() === "style") found = true;
|
|
1056
|
+
});
|
|
1057
|
+
}).processSync(selector);
|
|
1058
|
+
} catch {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
return found;
|
|
1062
|
+
}
|
|
1050
1063
|
var POSITION_PROPERTIES = /* @__PURE__ */ new Set(["left", "top", "right", "bottom", "inset"]);
|
|
1051
1064
|
function weakIdOf(node) {
|
|
1052
1065
|
if (node.type === "attribute" && node.attribute.toLowerCase() === "id") {
|
|
@@ -1454,6 +1467,7 @@ var coreRules = [
|
|
|
1454
1467
|
const findings = [];
|
|
1455
1468
|
const reportedRepeatedIds = /* @__PURE__ */ new Set();
|
|
1456
1469
|
const reportedHiddenStyleSelectors = /* @__PURE__ */ new Set();
|
|
1470
|
+
const reportedInlineStyleSelectors = /* @__PURE__ */ new Set();
|
|
1457
1471
|
const reportedReducedIdSelectors = /* @__PURE__ */ new Set();
|
|
1458
1472
|
for (const style of styles) {
|
|
1459
1473
|
let root;
|
|
@@ -1486,6 +1500,17 @@ var coreRules = [
|
|
|
1486
1500
|
fixHint: `Remove the duplicate ancestor: change \`#${repeatedId} #${repeatedId}\` to \`#${repeatedId}\`.`
|
|
1487
1501
|
});
|
|
1488
1502
|
}
|
|
1503
|
+
if (!reportedInlineStyleSelectors.has(selector) && selectsOnInlineStyle(selector)) {
|
|
1504
|
+
reportedInlineStyleSelectors.add(selector);
|
|
1505
|
+
findings.push({
|
|
1506
|
+
code: "inline_style_attribute_selector",
|
|
1507
|
+
severity: "error",
|
|
1508
|
+
message: `Selector "${selector}" reads the inline \`style\` attribute. GSAP writes inline styles every frame, and each write then restyles far more than the animated element, so the preview slows to a crawl.`,
|
|
1509
|
+
selector,
|
|
1510
|
+
fixHint: "Let the runtime hide hidden clips; it already hides their contents. For a state of your own, toggle a class or data attribute from your script and select on that.",
|
|
1511
|
+
snippet: truncateSnippet(rule.toString())
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1489
1514
|
if (!forcesOpacityZero || reportedHiddenStyleSelectors.has(selector) || !selectorAliasesRuntimeHiddenStyle(selector)) {
|
|
1490
1515
|
continue;
|
|
1491
1516
|
}
|
|
@@ -1495,7 +1520,7 @@ var coreRules = [
|
|
|
1495
1520
|
severity: "error",
|
|
1496
1521
|
message: `Selector "${selector}" observes HyperFrames' runtime-owned hidden style and forces opacity to zero. The renderer hides each native video before copying its computed opacity to the visible replacement frame, so this rule makes both transparent.`,
|
|
1497
1522
|
selector,
|
|
1498
|
-
fixHint:
|
|
1523
|
+
fixHint: "Remove this rule; the runtime already hides a hidden clip and its contents. To fade or hide something yourself, animate it on the timeline, or toggle a class or data attribute and select on that.",
|
|
1499
1524
|
snippet: truncateSnippet(rule.toString())
|
|
1500
1525
|
});
|
|
1501
1526
|
}
|
|
@@ -2478,7 +2503,7 @@ var mediaRules = [
|
|
|
2478
2503
|
const source = { id: readAttr(tag.raw, "id") || void 0, raw: tag.raw };
|
|
2479
2504
|
if (tag.name === "video") {
|
|
2480
2505
|
if (isAudibleVideoTag(tag.raw)) videos.push([src, source]);
|
|
2481
|
-
} else if (tag.name === "audio") {
|
|
2506
|
+
} else if (tag.name === "audio" && readAttr(tag.raw, "data-timeline-role") !== "room-tone") {
|
|
2482
2507
|
audios.push([src, source]);
|
|
2483
2508
|
}
|
|
2484
2509
|
}
|
|
@@ -2514,6 +2539,8 @@ var mediaRules = [
|
|
|
2514
2539
|
findAudioGroupTimingAttrFindings,
|
|
2515
2540
|
// audio_group_carve_attr
|
|
2516
2541
|
findAudioGroupCarveAttrFindings,
|
|
2542
|
+
// audio_group_holds_elements
|
|
2543
|
+
findAudioGroupHoldsElementsFindings,
|
|
2517
2544
|
findLinkedClipFindings
|
|
2518
2545
|
];
|
|
2519
2546
|
function findVolumeTweenOverridesGainFindings(ctx) {
|
|
@@ -2651,6 +2678,26 @@ function findAudioGroupTimingAttrFindings(ctx) {
|
|
|
2651
2678
|
}
|
|
2652
2679
|
return findings;
|
|
2653
2680
|
}
|
|
2681
|
+
function findAudioGroupHoldsElementsFindings(ctx) {
|
|
2682
|
+
const findings = [];
|
|
2683
|
+
for (const group of ctx.tags) {
|
|
2684
|
+
if (group.name !== "hf-audio-group") continue;
|
|
2685
|
+
const end = group.closeIndex ?? Number.POSITIVE_INFINITY;
|
|
2686
|
+
const held = ctx.tags.filter((tag) => tag.index > group.index && tag.index < end);
|
|
2687
|
+
if (held.length === 0) continue;
|
|
2688
|
+
const elementId = readAttr(group.raw, "id") || void 0;
|
|
2689
|
+
const first = readAttr(held[0].raw, "id");
|
|
2690
|
+
findings.push({
|
|
2691
|
+
code: "audio_group_holds_elements",
|
|
2692
|
+
severity: "warning",
|
|
2693
|
+
message: `${elementId ? `#${elementId}` : "This audio group"} holds ${held.length} element(s), starting with <${held[0].name}${first ? `#${first}` : ""}>. Being inside it routes no audio through the bus (only \`data-audio-group\` does), and while the group has \`data-hidden\` they are hidden and silenced in preview and render.`,
|
|
2694
|
+
elementId,
|
|
2695
|
+
fixHint: "Close the group with `</hf-audio-group>` right after its opening tag (`/>` does not close a custom element), and give clips meant for this bus `data-audio-group`.",
|
|
2696
|
+
snippet: truncateSnippet(group.raw)
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
return findings;
|
|
2700
|
+
}
|
|
2654
2701
|
function findAudioGroupCarveAttrFindings(ctx) {
|
|
2655
2702
|
const findings = [];
|
|
2656
2703
|
for (const tag of ctx.tags) {
|
|
@@ -3174,13 +3221,14 @@ function variablesDeclarationFindings(tag, tags) {
|
|
|
3174
3221
|
code: "invalid_composition_variables_declaration",
|
|
3175
3222
|
severity: "error",
|
|
3176
3223
|
message: `data-composition-variables entry [${i}] has a default that does not match its type, or an enum without an options array.`,
|
|
3177
|
-
fixHint: "Use number defaults for number variables, boolean defaults for boolean variables, and string defaults for other types. Enum variables also require an options array.",
|
|
3224
|
+
fixHint: "Use number defaults for number variables, boolean defaults for boolean variables, and string defaults for other types. Font/image also accept font {name, source} or image {url, alt?, fit?} objects. Enum variables also require an options array.",
|
|
3178
3225
|
snippet: truncateSnippet(tag.raw)
|
|
3179
3226
|
});
|
|
3180
3227
|
continue;
|
|
3181
3228
|
}
|
|
3182
|
-
const id =
|
|
3183
|
-
|
|
3229
|
+
const id = e.id;
|
|
3230
|
+
const mediaUrl = e.type === "image" && typeof e.default !== "string" ? e.default.url : e.default;
|
|
3231
|
+
if ((e.type === "image" || varSrcIds.has(id)) && typeof mediaUrl === "string" && mediaUrl.length > 0 && !isSafeMediaUrl(mediaUrl)) {
|
|
3184
3232
|
findings.push({
|
|
3185
3233
|
code: "unloadable_media_variable_default",
|
|
3186
3234
|
severity: "error",
|
package/dist/index.js
CHANGED
|
@@ -1053,6 +1053,19 @@ function selectorAliasesRuntimeHiddenStyle(selector) {
|
|
|
1053
1053
|
}
|
|
1054
1054
|
return unsafe;
|
|
1055
1055
|
}
|
|
1056
|
+
function selectsOnInlineStyle(selector) {
|
|
1057
|
+
let found = false;
|
|
1058
|
+
try {
|
|
1059
|
+
selectorParser((root) => {
|
|
1060
|
+
root.walkAttributes((node) => {
|
|
1061
|
+
if (node.attribute.toLowerCase() === "style") found = true;
|
|
1062
|
+
});
|
|
1063
|
+
}).processSync(selector);
|
|
1064
|
+
} catch {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
return found;
|
|
1068
|
+
}
|
|
1056
1069
|
var POSITION_PROPERTIES = /* @__PURE__ */ new Set(["left", "top", "right", "bottom", "inset"]);
|
|
1057
1070
|
function weakIdOf(node) {
|
|
1058
1071
|
if (node.type === "attribute" && node.attribute.toLowerCase() === "id") {
|
|
@@ -1460,6 +1473,7 @@ var coreRules = [
|
|
|
1460
1473
|
const findings = [];
|
|
1461
1474
|
const reportedRepeatedIds = /* @__PURE__ */ new Set();
|
|
1462
1475
|
const reportedHiddenStyleSelectors = /* @__PURE__ */ new Set();
|
|
1476
|
+
const reportedInlineStyleSelectors = /* @__PURE__ */ new Set();
|
|
1463
1477
|
const reportedReducedIdSelectors = /* @__PURE__ */ new Set();
|
|
1464
1478
|
for (const style of styles) {
|
|
1465
1479
|
let root;
|
|
@@ -1492,6 +1506,17 @@ var coreRules = [
|
|
|
1492
1506
|
fixHint: `Remove the duplicate ancestor: change \`#${repeatedId} #${repeatedId}\` to \`#${repeatedId}\`.`
|
|
1493
1507
|
});
|
|
1494
1508
|
}
|
|
1509
|
+
if (!reportedInlineStyleSelectors.has(selector) && selectsOnInlineStyle(selector)) {
|
|
1510
|
+
reportedInlineStyleSelectors.add(selector);
|
|
1511
|
+
findings.push({
|
|
1512
|
+
code: "inline_style_attribute_selector",
|
|
1513
|
+
severity: "error",
|
|
1514
|
+
message: `Selector "${selector}" reads the inline \`style\` attribute. GSAP writes inline styles every frame, and each write then restyles far more than the animated element, so the preview slows to a crawl.`,
|
|
1515
|
+
selector,
|
|
1516
|
+
fixHint: "Let the runtime hide hidden clips; it already hides their contents. For a state of your own, toggle a class or data attribute from your script and select on that.",
|
|
1517
|
+
snippet: truncateSnippet(rule.toString())
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1495
1520
|
if (!forcesOpacityZero || reportedHiddenStyleSelectors.has(selector) || !selectorAliasesRuntimeHiddenStyle(selector)) {
|
|
1496
1521
|
continue;
|
|
1497
1522
|
}
|
|
@@ -1501,7 +1526,7 @@ var coreRules = [
|
|
|
1501
1526
|
severity: "error",
|
|
1502
1527
|
message: `Selector "${selector}" observes HyperFrames' runtime-owned hidden style and forces opacity to zero. The renderer hides each native video before copying its computed opacity to the visible replacement frame, so this rule makes both transparent.`,
|
|
1503
1528
|
selector,
|
|
1504
|
-
fixHint:
|
|
1529
|
+
fixHint: "Remove this rule; the runtime already hides a hidden clip and its contents. To fade or hide something yourself, animate it on the timeline, or toggle a class or data attribute and select on that.",
|
|
1505
1530
|
snippet: truncateSnippet(rule.toString())
|
|
1506
1531
|
});
|
|
1507
1532
|
}
|
|
@@ -2484,7 +2509,7 @@ var mediaRules = [
|
|
|
2484
2509
|
const source = { id: readAttr(tag.raw, "id") || void 0, raw: tag.raw };
|
|
2485
2510
|
if (tag.name === "video") {
|
|
2486
2511
|
if (isAudibleVideoTag(tag.raw)) videos.push([src, source]);
|
|
2487
|
-
} else if (tag.name === "audio") {
|
|
2512
|
+
} else if (tag.name === "audio" && readAttr(tag.raw, "data-timeline-role") !== "room-tone") {
|
|
2488
2513
|
audios.push([src, source]);
|
|
2489
2514
|
}
|
|
2490
2515
|
}
|
|
@@ -2520,6 +2545,8 @@ var mediaRules = [
|
|
|
2520
2545
|
findAudioGroupTimingAttrFindings,
|
|
2521
2546
|
// audio_group_carve_attr
|
|
2522
2547
|
findAudioGroupCarveAttrFindings,
|
|
2548
|
+
// audio_group_holds_elements
|
|
2549
|
+
findAudioGroupHoldsElementsFindings,
|
|
2523
2550
|
findLinkedClipFindings
|
|
2524
2551
|
];
|
|
2525
2552
|
function findVolumeTweenOverridesGainFindings(ctx) {
|
|
@@ -2657,6 +2684,26 @@ function findAudioGroupTimingAttrFindings(ctx) {
|
|
|
2657
2684
|
}
|
|
2658
2685
|
return findings;
|
|
2659
2686
|
}
|
|
2687
|
+
function findAudioGroupHoldsElementsFindings(ctx) {
|
|
2688
|
+
const findings = [];
|
|
2689
|
+
for (const group of ctx.tags) {
|
|
2690
|
+
if (group.name !== "hf-audio-group") continue;
|
|
2691
|
+
const end = group.closeIndex ?? Number.POSITIVE_INFINITY;
|
|
2692
|
+
const held = ctx.tags.filter((tag) => tag.index > group.index && tag.index < end);
|
|
2693
|
+
if (held.length === 0) continue;
|
|
2694
|
+
const elementId = readAttr(group.raw, "id") || void 0;
|
|
2695
|
+
const first = readAttr(held[0].raw, "id");
|
|
2696
|
+
findings.push({
|
|
2697
|
+
code: "audio_group_holds_elements",
|
|
2698
|
+
severity: "warning",
|
|
2699
|
+
message: `${elementId ? `#${elementId}` : "This audio group"} holds ${held.length} element(s), starting with <${held[0].name}${first ? `#${first}` : ""}>. Being inside it routes no audio through the bus (only \`data-audio-group\` does), and while the group has \`data-hidden\` they are hidden and silenced in preview and render.`,
|
|
2700
|
+
elementId,
|
|
2701
|
+
fixHint: "Close the group with `</hf-audio-group>` right after its opening tag (`/>` does not close a custom element), and give clips meant for this bus `data-audio-group`.",
|
|
2702
|
+
snippet: truncateSnippet(group.raw)
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
return findings;
|
|
2706
|
+
}
|
|
2660
2707
|
function findAudioGroupCarveAttrFindings(ctx) {
|
|
2661
2708
|
const findings = [];
|
|
2662
2709
|
for (const tag of ctx.tags) {
|
|
@@ -3180,13 +3227,14 @@ function variablesDeclarationFindings(tag, tags) {
|
|
|
3180
3227
|
code: "invalid_composition_variables_declaration",
|
|
3181
3228
|
severity: "error",
|
|
3182
3229
|
message: `data-composition-variables entry [${i}] has a default that does not match its type, or an enum without an options array.`,
|
|
3183
|
-
fixHint: "Use number defaults for number variables, boolean defaults for boolean variables, and string defaults for other types. Enum variables also require an options array.",
|
|
3230
|
+
fixHint: "Use number defaults for number variables, boolean defaults for boolean variables, and string defaults for other types. Font/image also accept font {name, source} or image {url, alt?, fit?} objects. Enum variables also require an options array.",
|
|
3184
3231
|
snippet: truncateSnippet(tag.raw)
|
|
3185
3232
|
});
|
|
3186
3233
|
continue;
|
|
3187
3234
|
}
|
|
3188
|
-
const id =
|
|
3189
|
-
|
|
3235
|
+
const id = e.id;
|
|
3236
|
+
const mediaUrl = e.type === "image" && typeof e.default !== "string" ? e.default.url : e.default;
|
|
3237
|
+
if ((e.type === "image" || varSrcIds.has(id)) && typeof mediaUrl === "string" && mediaUrl.length > 0 && !isSafeMediaUrl(mediaUrl)) {
|
|
3190
3238
|
findings.push({
|
|
3191
3239
|
code: "unloadable_media_variable_default",
|
|
3192
3240
|
severity: "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/lint",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.201",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"linkedom": "^0.18.12",
|
|
36
36
|
"postcss": "^8.5.8",
|
|
37
37
|
"postcss-selector-parser": "^7.1.4",
|
|
38
|
-
"@hyperframes/parsers": "0.8.
|
|
38
|
+
"@hyperframes/parsers": "0.8.201"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|