mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(lint): detect optional managed media calls (#5210)
This commit is contained in:
@@ -449,6 +449,55 @@ describe("media rules", () => {
|
||||
expect(finding?.elementId).toBe("demo-video");
|
||||
});
|
||||
|
||||
function optionalMediaScene(script: string): string {
|
||||
return `<html><body>
|
||||
<div id="root" data-composition-id="main" data-width="640" data-height="360">
|
||||
<video id="demo-video" class="demo-video" src="clip.mp4" muted data-start="0" data-duration="1"></video>
|
||||
<div id="panel"></div>
|
||||
</div>
|
||||
<script>${script}</script>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
it.each([
|
||||
'const video = document.getElementById("demo-video"); video?.play();',
|
||||
'const video = document.getElementById("demo-video"); video.play?.();',
|
||||
'const video = document.getElementById("demo-video"); video?.play?.();',
|
||||
'const video = document.querySelector("#demo-video"); video?.pause();',
|
||||
'const video = document.querySelector(".demo-video"); video.pause?.();',
|
||||
'document.getElementById("demo-video")?.play();',
|
||||
'document.getElementById("demo-video").pause?.();',
|
||||
'document.querySelector("#demo-video")?.play?.();',
|
||||
'window.document.querySelector("video")?.pause();',
|
||||
'const video = document.getElementById("demo-video"); const label = `playing ${video?.play()}`;',
|
||||
])("reports an executed optional media call: %s", async (script) => {
|
||||
const result = await lintHyperframeHtml(optionalMediaScene(script));
|
||||
expect(
|
||||
result.findings.find((finding) => finding.code === "imperative_media_control"),
|
||||
).toMatchObject({
|
||||
severity: "error",
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
'const video = document.getElementById("demo-video"); const example = "video.play()";',
|
||||
'const video = document.getElementById("demo-video"); // video.play()\n',
|
||||
'const video = document.getElementById("demo-video"); const example = `video.play()`;',
|
||||
'const video = document.getElementById("demo-video"); const example = /video.play()/;',
|
||||
'const video = document.getElementById("demo-video"); const example = "video?.play?.()";',
|
||||
'const video = document.getElementById("demo-video"); /* video?.play() */',
|
||||
'const video = document.getElementById("demo-video"); const example = `video.pause?.()`;',
|
||||
`const example = 'document.getElementById("demo-video").play()';`,
|
||||
`const example = 'document.querySelector("video")?.play()';`,
|
||||
'const panel = document.getElementById("panel"); panel?.play?.();',
|
||||
'document.querySelector("#panel")?.pause();',
|
||||
])("does not report a quoted example or non-media optional call: %s", async (script) => {
|
||||
const result = await lintHyperframeHtml(optionalMediaScene(script));
|
||||
expect(result.findings.map((finding) => finding.code)).not.toContain(
|
||||
"imperative_media_control",
|
||||
);
|
||||
});
|
||||
|
||||
it("reports imperative currentTime writes on query-selected managed media", async () => {
|
||||
const html = `
|
||||
<html><body>
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
readAttr,
|
||||
readDecodedAttr,
|
||||
stripJsComments,
|
||||
stripJsCode,
|
||||
truncateSnippet,
|
||||
isMediaTag,
|
||||
hasAttrName,
|
||||
@@ -230,6 +231,7 @@ function findImperativeMediaControlFindings(ctx: LintContext): HyperframeLintFin
|
||||
if (mediaTags.length === 0 || ctx.scripts.length === 0) return findings;
|
||||
|
||||
for (const script of ctx.scripts) {
|
||||
const code = stripJsCode(script.content);
|
||||
const mediaVars = new Map<string, string | undefined>();
|
||||
const assignmentPatterns = [
|
||||
{
|
||||
@@ -249,6 +251,7 @@ function findImperativeMediaControlFindings(ctx: LintContext): HyperframeLintFin
|
||||
for (const { pattern, variableIndex, targetIndex } of assignmentPatterns) {
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(script.content)) !== null) {
|
||||
if (code[match.index] !== script.content[match.index]) continue;
|
||||
const variableName = match[variableIndex];
|
||||
const target = match[targetIndex];
|
||||
if (!variableName || !target) continue;
|
||||
@@ -261,13 +264,13 @@ function findImperativeMediaControlFindings(ctx: LintContext): HyperframeLintFin
|
||||
const directIdPatterns = [
|
||||
{
|
||||
pattern:
|
||||
/\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.play\s*\(/g,
|
||||
/\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\s*(?:\?\.|\.)\s*play\s*(?:\?\.\s*)?\(/g,
|
||||
kind: "play()",
|
||||
targetIndex: 1,
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
/\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.pause\s*\(/g,
|
||||
/\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\s*(?:\?\.|\.)\s*pause\s*(?:\?\.\s*)?\(/g,
|
||||
kind: "pause()",
|
||||
targetIndex: 1,
|
||||
},
|
||||
@@ -285,13 +288,13 @@ function findImperativeMediaControlFindings(ctx: LintContext): HyperframeLintFin
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
/\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.play\s*\(/g,
|
||||
/\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\s*(?:\?\.|\.)\s*play\s*(?:\?\.\s*)?\(/g,
|
||||
kind: "play()",
|
||||
targetIndex: 2,
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
/\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.pause\s*\(/g,
|
||||
/\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\s*(?:\?\.|\.)\s*pause\s*(?:\?\.\s*)?\(/g,
|
||||
kind: "pause()",
|
||||
targetIndex: 2,
|
||||
},
|
||||
@@ -312,6 +315,7 @@ function findImperativeMediaControlFindings(ctx: LintContext): HyperframeLintFin
|
||||
for (const { pattern, kind, targetIndex } of directIdPatterns) {
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(script.content)) !== null) {
|
||||
if (code[match.index] !== script.content[match.index]) continue;
|
||||
const target = match[targetIndex];
|
||||
if (!target) continue;
|
||||
const elementId = mediaIndex.ids.has(target)
|
||||
@@ -335,8 +339,20 @@ function findImperativeMediaControlFindings(ctx: LintContext): HyperframeLintFin
|
||||
for (const [variableName, elementId] of mediaVars) {
|
||||
const escapedVar = escapeRegExp(variableName);
|
||||
const variablePatterns = [
|
||||
{ pattern: new RegExp(`\\b${escapedVar}\\.play\\s*\\(`, "g"), kind: "play()" },
|
||||
{ pattern: new RegExp(`\\b${escapedVar}\\.pause\\s*\\(`, "g"), kind: "pause()" },
|
||||
{
|
||||
pattern: new RegExp(
|
||||
`\\b${escapedVar}\\s*(?:\\?\\.|\\.)\\s*play\\s*(?:\\?\\.\\s*)?\\(`,
|
||||
"g",
|
||||
),
|
||||
kind: "play()",
|
||||
},
|
||||
{
|
||||
pattern: new RegExp(
|
||||
`\\b${escapedVar}\\s*(?:\\?\\.|\\.)\\s*pause\\s*(?:\\?\\.\\s*)?\\(`,
|
||||
"g",
|
||||
),
|
||||
kind: "pause()",
|
||||
},
|
||||
{ pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" },
|
||||
{
|
||||
pattern: new RegExp(`\\b${escapedVar}\\.muted\\s*=`, "g"),
|
||||
@@ -346,6 +362,7 @@ function findImperativeMediaControlFindings(ctx: LintContext): HyperframeLintFin
|
||||
for (const { pattern, kind } of variablePatterns) {
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(script.content)) !== null) {
|
||||
if (code[match.index] !== script.content[match.index]) continue;
|
||||
findings.push({
|
||||
code: "imperative_media_control",
|
||||
severity: "error",
|
||||
|
||||
Reference in New Issue
Block a user