@hyperframes/lint 0.8.186 → 0.8.189
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 +60 -47
- package/dist/index.js +60 -47
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -2023,6 +2023,53 @@ function findRuntimeMediaSrcMutationFindings(ctx) {
|
|
|
2023
2023
|
}
|
|
2024
2024
|
return findings;
|
|
2025
2025
|
}
|
|
2026
|
+
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2027
|
+
"area",
|
|
2028
|
+
"base",
|
|
2029
|
+
"br",
|
|
2030
|
+
"col",
|
|
2031
|
+
"embed",
|
|
2032
|
+
"hr",
|
|
2033
|
+
"img",
|
|
2034
|
+
"input",
|
|
2035
|
+
"link",
|
|
2036
|
+
"meta",
|
|
2037
|
+
"source",
|
|
2038
|
+
"track",
|
|
2039
|
+
"wbr"
|
|
2040
|
+
]);
|
|
2041
|
+
var closeTag = (name) => new RegExp(`</${name}>`, "gi");
|
|
2042
|
+
function innermostTimedWrapper(source, tags, media) {
|
|
2043
|
+
let found = null;
|
|
2044
|
+
for (const parent of tags) {
|
|
2045
|
+
if (parent.index >= media.index) break;
|
|
2046
|
+
if (parent.name === "video" || parent.name === "audio" || VOID_ELEMENTS.has(parent.name))
|
|
2047
|
+
continue;
|
|
2048
|
+
if (readDecodedAttr(parent.raw, "data-composition-id")) continue;
|
|
2049
|
+
const start = readAttr(parent.raw, "data-start");
|
|
2050
|
+
if (!start) continue;
|
|
2051
|
+
const events = [
|
|
2052
|
+
...tags.filter((t) => t.name === parent.name && t.index > parent.index && t.index < media.index).map((t) => [t.index, 1]),
|
|
2053
|
+
...[...source.substring(parent.index, media.index).matchAll(closeTag(parent.name))].map(
|
|
2054
|
+
(m) => [parent.index + m.index, -1]
|
|
2055
|
+
)
|
|
2056
|
+
];
|
|
2057
|
+
let depth = 1;
|
|
2058
|
+
for (const [, delta] of events.sort((x, y) => x[0] - y[0])) {
|
|
2059
|
+
depth += delta;
|
|
2060
|
+
if (depth === 0) break;
|
|
2061
|
+
}
|
|
2062
|
+
if (depth > 0) {
|
|
2063
|
+
found = {
|
|
2064
|
+
name: parent.name,
|
|
2065
|
+
id: readAttr(parent.raw, "id") || void 0,
|
|
2066
|
+
start,
|
|
2067
|
+
duration: readAttr(parent.raw, "data-duration")
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return found;
|
|
2072
|
+
}
|
|
2026
2073
|
var mediaRules = [
|
|
2027
2074
|
findNestedMediaStartBasisFindings,
|
|
2028
2075
|
findSpeedRampOnNonMediaFindings,
|
|
@@ -2159,54 +2206,19 @@ var mediaRules = [
|
|
|
2159
2206
|
// video_nested_in_timed_element
|
|
2160
2207
|
({ source, tags }) => {
|
|
2161
2208
|
const findings = [];
|
|
2162
|
-
const voidElements = /* @__PURE__ */ new Set([
|
|
2163
|
-
"area",
|
|
2164
|
-
"base",
|
|
2165
|
-
"br",
|
|
2166
|
-
"col",
|
|
2167
|
-
"embed",
|
|
2168
|
-
"hr",
|
|
2169
|
-
"img",
|
|
2170
|
-
"input",
|
|
2171
|
-
"link",
|
|
2172
|
-
"meta",
|
|
2173
|
-
"source",
|
|
2174
|
-
"track",
|
|
2175
|
-
"wbr"
|
|
2176
|
-
]);
|
|
2177
|
-
const timedTagPositions = [];
|
|
2178
|
-
for (const tag of tags) {
|
|
2179
|
-
if (tag.name === "video" || tag.name === "audio") continue;
|
|
2180
|
-
if (voidElements.has(tag.name)) continue;
|
|
2181
|
-
if (readDecodedAttr(tag.raw, "data-composition-id")) continue;
|
|
2182
|
-
if (readAttr(tag.raw, "data-start")) {
|
|
2183
|
-
timedTagPositions.push({
|
|
2184
|
-
name: tag.name,
|
|
2185
|
-
start: tag.index,
|
|
2186
|
-
id: readAttr(tag.raw, "id") || void 0
|
|
2187
|
-
});
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
2209
|
for (const tag of tags) {
|
|
2191
2210
|
if (tag.name !== "video") continue;
|
|
2192
2211
|
if (!readAttr(tag.raw, "data-start")) continue;
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
fixHint: "Time the wrapper OR the video, never both: remove data-start from the wrapper (use it as a non-timed visual container), or move the <video> up to be a direct child of the stage.",
|
|
2204
|
-
snippet: truncateSnippet(tag.raw)
|
|
2205
|
-
});
|
|
2206
|
-
break;
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2212
|
+
const parent = innermostTimedWrapper(source, tags, tag);
|
|
2213
|
+
if (!parent) continue;
|
|
2214
|
+
findings.push({
|
|
2215
|
+
code: "video_nested_in_timed_element",
|
|
2216
|
+
severity: "error",
|
|
2217
|
+
message: `<video> with data-start is nested inside <${parent.name}${parent.id ? ` id="${parent.id}"` : ""}> which also has data-start. The frame extractor resolves the video's start from its own data-start without the wrapper's offset, while visibility uses the wrapper's window, so the two disagree: the clip shows the wrong source frames and then disappears partway through its slot.`,
|
|
2218
|
+
elementId: readAttr(tag.raw, "id") || void 0,
|
|
2219
|
+
fixHint: "Time the wrapper OR the video, never both: remove data-start from the wrapper (use it as a non-timed visual container), or move the <video> up to be a direct child of the stage.",
|
|
2220
|
+
snippet: truncateSnippet(tag.raw)
|
|
2221
|
+
});
|
|
2210
2222
|
}
|
|
2211
2223
|
return findings;
|
|
2212
2224
|
},
|
|
@@ -2274,7 +2286,7 @@ var mediaRules = [
|
|
|
2274
2286
|
return findings;
|
|
2275
2287
|
},
|
|
2276
2288
|
// media_missing_data_start + media_missing_id + media_missing_src + media_preload_none
|
|
2277
|
-
({ tags }) => {
|
|
2289
|
+
({ source, tags }) => {
|
|
2278
2290
|
const findings = [];
|
|
2279
2291
|
for (const tag of tags) {
|
|
2280
2292
|
if (tag.name !== "video" && tag.name !== "audio") continue;
|
|
@@ -2282,12 +2294,13 @@ var mediaRules = [
|
|
|
2282
2294
|
const hasId = readAttr(tag.raw, "id");
|
|
2283
2295
|
const hasSrc = mediaHasResolvableSrc(tag, tags);
|
|
2284
2296
|
if (hasSrc && !hasDataStart) {
|
|
2297
|
+
const wrapper = tag.name === "video" ? innermostTimedWrapper(source, tags, tag) : null;
|
|
2285
2298
|
findings.push({
|
|
2286
2299
|
code: "media_missing_data_start",
|
|
2287
2300
|
severity: "error",
|
|
2288
2301
|
message: `<${tag.name}${hasId ? ` id="${hasId}"` : ""}> has src but no data-start. HyperFrames cannot own playback for untimed media, so preview and render behavior can diverge.`,
|
|
2289
2302
|
elementId: hasId || void 0,
|
|
2290
|
-
fixHint: `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
|
|
2303
|
+
fixHint: wrapper ? `This <${tag.name}> sits inside the timed <${wrapper.name}${wrapper.id ? ` id="${wrapper.id}"` : ""}> (data-start="${wrapper.start}"), and the wrapper's data-start would then clash. Move the <${tag.name}> out as a direct child of the stage and give it that wrapper's data-start="${wrapper.start}"${wrapper.duration ? ` and data-duration="${wrapper.duration}"` : ""}.` : `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
|
|
2291
2304
|
snippet: truncateSnippet(tag.raw)
|
|
2292
2305
|
});
|
|
2293
2306
|
}
|
package/dist/index.js
CHANGED
|
@@ -2029,6 +2029,53 @@ function findRuntimeMediaSrcMutationFindings(ctx) {
|
|
|
2029
2029
|
}
|
|
2030
2030
|
return findings;
|
|
2031
2031
|
}
|
|
2032
|
+
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
2033
|
+
"area",
|
|
2034
|
+
"base",
|
|
2035
|
+
"br",
|
|
2036
|
+
"col",
|
|
2037
|
+
"embed",
|
|
2038
|
+
"hr",
|
|
2039
|
+
"img",
|
|
2040
|
+
"input",
|
|
2041
|
+
"link",
|
|
2042
|
+
"meta",
|
|
2043
|
+
"source",
|
|
2044
|
+
"track",
|
|
2045
|
+
"wbr"
|
|
2046
|
+
]);
|
|
2047
|
+
var closeTag = (name) => new RegExp(`</${name}>`, "gi");
|
|
2048
|
+
function innermostTimedWrapper(source, tags, media) {
|
|
2049
|
+
let found = null;
|
|
2050
|
+
for (const parent of tags) {
|
|
2051
|
+
if (parent.index >= media.index) break;
|
|
2052
|
+
if (parent.name === "video" || parent.name === "audio" || VOID_ELEMENTS.has(parent.name))
|
|
2053
|
+
continue;
|
|
2054
|
+
if (readDecodedAttr(parent.raw, "data-composition-id")) continue;
|
|
2055
|
+
const start = readAttr(parent.raw, "data-start");
|
|
2056
|
+
if (!start) continue;
|
|
2057
|
+
const events = [
|
|
2058
|
+
...tags.filter((t) => t.name === parent.name && t.index > parent.index && t.index < media.index).map((t) => [t.index, 1]),
|
|
2059
|
+
...[...source.substring(parent.index, media.index).matchAll(closeTag(parent.name))].map(
|
|
2060
|
+
(m) => [parent.index + m.index, -1]
|
|
2061
|
+
)
|
|
2062
|
+
];
|
|
2063
|
+
let depth = 1;
|
|
2064
|
+
for (const [, delta] of events.sort((x, y) => x[0] - y[0])) {
|
|
2065
|
+
depth += delta;
|
|
2066
|
+
if (depth === 0) break;
|
|
2067
|
+
}
|
|
2068
|
+
if (depth > 0) {
|
|
2069
|
+
found = {
|
|
2070
|
+
name: parent.name,
|
|
2071
|
+
id: readAttr(parent.raw, "id") || void 0,
|
|
2072
|
+
start,
|
|
2073
|
+
duration: readAttr(parent.raw, "data-duration")
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
return found;
|
|
2078
|
+
}
|
|
2032
2079
|
var mediaRules = [
|
|
2033
2080
|
findNestedMediaStartBasisFindings,
|
|
2034
2081
|
findSpeedRampOnNonMediaFindings,
|
|
@@ -2165,54 +2212,19 @@ var mediaRules = [
|
|
|
2165
2212
|
// video_nested_in_timed_element
|
|
2166
2213
|
({ source, tags }) => {
|
|
2167
2214
|
const findings = [];
|
|
2168
|
-
const voidElements = /* @__PURE__ */ new Set([
|
|
2169
|
-
"area",
|
|
2170
|
-
"base",
|
|
2171
|
-
"br",
|
|
2172
|
-
"col",
|
|
2173
|
-
"embed",
|
|
2174
|
-
"hr",
|
|
2175
|
-
"img",
|
|
2176
|
-
"input",
|
|
2177
|
-
"link",
|
|
2178
|
-
"meta",
|
|
2179
|
-
"source",
|
|
2180
|
-
"track",
|
|
2181
|
-
"wbr"
|
|
2182
|
-
]);
|
|
2183
|
-
const timedTagPositions = [];
|
|
2184
|
-
for (const tag of tags) {
|
|
2185
|
-
if (tag.name === "video" || tag.name === "audio") continue;
|
|
2186
|
-
if (voidElements.has(tag.name)) continue;
|
|
2187
|
-
if (readDecodedAttr(tag.raw, "data-composition-id")) continue;
|
|
2188
|
-
if (readAttr(tag.raw, "data-start")) {
|
|
2189
|
-
timedTagPositions.push({
|
|
2190
|
-
name: tag.name,
|
|
2191
|
-
start: tag.index,
|
|
2192
|
-
id: readAttr(tag.raw, "id") || void 0
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
2215
|
for (const tag of tags) {
|
|
2197
2216
|
if (tag.name !== "video") continue;
|
|
2198
2217
|
if (!readAttr(tag.raw, "data-start")) continue;
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
fixHint: "Time the wrapper OR the video, never both: remove data-start from the wrapper (use it as a non-timed visual container), or move the <video> up to be a direct child of the stage.",
|
|
2210
|
-
snippet: truncateSnippet(tag.raw)
|
|
2211
|
-
});
|
|
2212
|
-
break;
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2218
|
+
const parent = innermostTimedWrapper(source, tags, tag);
|
|
2219
|
+
if (!parent) continue;
|
|
2220
|
+
findings.push({
|
|
2221
|
+
code: "video_nested_in_timed_element",
|
|
2222
|
+
severity: "error",
|
|
2223
|
+
message: `<video> with data-start is nested inside <${parent.name}${parent.id ? ` id="${parent.id}"` : ""}> which also has data-start. The frame extractor resolves the video's start from its own data-start without the wrapper's offset, while visibility uses the wrapper's window, so the two disagree: the clip shows the wrong source frames and then disappears partway through its slot.`,
|
|
2224
|
+
elementId: readAttr(tag.raw, "id") || void 0,
|
|
2225
|
+
fixHint: "Time the wrapper OR the video, never both: remove data-start from the wrapper (use it as a non-timed visual container), or move the <video> up to be a direct child of the stage.",
|
|
2226
|
+
snippet: truncateSnippet(tag.raw)
|
|
2227
|
+
});
|
|
2216
2228
|
}
|
|
2217
2229
|
return findings;
|
|
2218
2230
|
},
|
|
@@ -2280,7 +2292,7 @@ var mediaRules = [
|
|
|
2280
2292
|
return findings;
|
|
2281
2293
|
},
|
|
2282
2294
|
// media_missing_data_start + media_missing_id + media_missing_src + media_preload_none
|
|
2283
|
-
({ tags }) => {
|
|
2295
|
+
({ source, tags }) => {
|
|
2284
2296
|
const findings = [];
|
|
2285
2297
|
for (const tag of tags) {
|
|
2286
2298
|
if (tag.name !== "video" && tag.name !== "audio") continue;
|
|
@@ -2288,12 +2300,13 @@ var mediaRules = [
|
|
|
2288
2300
|
const hasId = readAttr(tag.raw, "id");
|
|
2289
2301
|
const hasSrc = mediaHasResolvableSrc(tag, tags);
|
|
2290
2302
|
if (hasSrc && !hasDataStart) {
|
|
2303
|
+
const wrapper = tag.name === "video" ? innermostTimedWrapper(source, tags, tag) : null;
|
|
2291
2304
|
findings.push({
|
|
2292
2305
|
code: "media_missing_data_start",
|
|
2293
2306
|
severity: "error",
|
|
2294
2307
|
message: `<${tag.name}${hasId ? ` id="${hasId}"` : ""}> has src but no data-start. HyperFrames cannot own playback for untimed media, so preview and render behavior can diverge.`,
|
|
2295
2308
|
elementId: hasId || void 0,
|
|
2296
|
-
fixHint: `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
|
|
2309
|
+
fixHint: wrapper ? `This <${tag.name}> sits inside the timed <${wrapper.name}${wrapper.id ? ` id="${wrapper.id}"` : ""}> (data-start="${wrapper.start}"), and the wrapper's data-start would then clash. Move the <${tag.name}> out as a direct child of the stage and give it that wrapper's data-start="${wrapper.start}"${wrapper.duration ? ` and data-duration="${wrapper.duration}"` : ""}.` : `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
|
|
2297
2310
|
snippet: truncateSnippet(tag.raw)
|
|
2298
2311
|
});
|
|
2299
2312
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/lint",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.189",
|
|
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.189"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.10",
|