Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions packages/lint/src/rules/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,45 @@ function portraitCompositionWithScaffold(bodyCss: string, viewportContent: strin
</html>`;
}

describe("inline script syntax attribute semantics", () => {
it.each([
["type=module", `import value from "pkg"; await value;`],
['TYPE="module"', `import value from "pkg"; await value;`],
['type=" MODULE "', `import value from "pkg"; await value;`],
['type="mod&#117;le"', `import value from "pkg"; await value;`],
["type=application/json", `{"value":1}`],
["type=importmap", `{"imports":{"pkg":"./pkg.js"}}`],
["type=application/hyperframes-slideshow+json", `{"slides":[]}`],
['type="module" type="text/javascript"', `import value from "pkg"; await value;`],
])("recognizes the exempt script type in %s", async (attrs, content) => {
const result = await lintHyperframeHtml(`<script ${attrs}>${content}</script>`);
expect(result.findings.filter((f) => f.code === "invalid_inline_script_syntax")).toEqual([]);
});

it.each([
'data-type="module"',
'data-src="external.js"',
`data-note='type="module"'`,
`data-note='src="external.js"'`,
`data-note=">" data-src="external.js"`,
'type="text/javascript" type="module"',
'type=""',
])("checks classic inline syntax despite metadata in %s", async (attrs) => {
const result = await lintHyperframeHtml(`<script ${attrs}>const = broken;</script>`);
expect(result.findings.filter((f) => f.code === "invalid_inline_script_syntax")).toHaveLength(
1,
);
});

it.each(['src="external.js"', 'SRC="external.js"', "src"])(
"skips inline text when a real external src attribute is present: %s",
async (attrs) => {
const result = await lintHyperframeHtml(`<script ${attrs}>const = broken;</script>`);
expect(result.findings.filter((f) => f.code === "invalid_inline_script_syntax")).toEqual([]);
},
);
});

describe("core rules", () => {
it("does not lint scripts embedded inside an iframe srcdoc attribute", async () => {
const html = `
Expand Down
16 changes: 9 additions & 7 deletions packages/lint/src/rules/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ import {
INVALID_SCRIPT_CLOSE_PATTERN,
} from "../utils";

const SCRIPT_SYNTAX_EXEMPT_TYPES = new Set([
"application/json",
"application/hyperframes-slideshow+json",
"importmap",
"module",
]);

function repeatedDescendantId(selector: string): string | null {
let repeated: string | null = null;

Expand Down Expand Up @@ -791,13 +798,8 @@ export const coreRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> = [
({ scripts, locate }) => {
const findings: HyperframeLintFinding[] = [];
for (const script of scripts) {
const attrs = script.attrs || "";
if (
/\bsrc\s*=/.test(attrs) ||
/\btype\s*=\s*["'](?:application\/json|application\/hyperframes-slideshow\+json|importmap|module)["']/.test(
attrs,
)
)
const type = readDecodedAttr(script.raw, "type")?.trim().toLowerCase() ?? "";
if (readDecodedAttr(script.raw, "src") !== null || SCRIPT_SYNTAX_EXEMPT_TYPES.has(type))
continue;
const syntaxError = getInlineScriptSyntaxError(script.content);
if (!syntaxError) continue;
Expand Down
Loading