fix(javascript): distinguish HTML script loads from data

This commit is contained in:
morluto committed 2026-10-27 05:57:26 +08:00
1 parent 8694dc30d9
commit 83b2d16ee9
6 files changed
+114 -5

No files matched your search

@@ -30,8 +30,13 @@ it.each([
'<svg><foreignObject><script src="actual.js"></script></foreignObject></svg>',
["actual.js"],
],
// The existing extraction contract does not classify script MIME types.
['<script type="application/json" src="actual.js"></script>', ["actual.js"]],
['<script type="importmap" src="actual.js"></script>', ["actual.js"]],
['<script type="speculationrules" src="actual.js"></script>', ["actual.js"]],
['<script language="vbscript" src="actual.js"></script>', ["actual.js"]],
['<script type=" Text/JavaScript " src="actual.js"></script>', ["actual.js"]],
['<script type="MODULE" src="actual.js"></script>', ["actual.js"]],
['<script type=" module " src="actual.js"></script>', ["actual.js"]],
])("extracts document references from %s", (text, expected) => {
expect(
htmlArtifactReferences(text).scripts.map(({ scriptPath }) => scriptPath),
@@ -49,6 +54,7 @@ it("retains exact opening-tag coordinates rather than the script body", () => {
scripts: [
{
scriptPath: "actual.js",
scriptType: "classic",
location: {
start: { line: 3, column: 0 },
end: { line: 4, column: ' title=">" src=actual.js>'.length },
@@ -143,3 +149,21 @@ it("counts every inline body but retains bounded locations", () => {
},
});
});
it.each([
['type=" Text/JavaScript "', "classic"],
['type="MODULE"', "module"],
['type="application/json"', null],
['type="importmap"', null],
['type="speculationrules"', null],
['language="vbscript"', null],
['type=" module "', null],
])(
"retains external src with parsed JavaScript type for %s",
(attributes, scriptType) => {
expect(
htmlArtifactReferences(`<script ${attributes} src="source.js"></script>`)
.scripts,
).toMatchObject([{ scriptPath: "source.js", scriptType }]);
},
);
@@ -11,6 +11,8 @@ import type {
interface HtmlScriptReference {
readonly scriptPath: string;
/** Null for data blocks and other non-JavaScript script types. */
readonly scriptType: HtmlInlineScript["scriptType"] | null;
/** The opening tag's range in parse5 coordinates. */
readonly location: JavaScriptSourceRange;
}
@@ -54,7 +56,7 @@ const JAVASCRIPT_MIME_TYPES = new Set([
* MIME check; `module` must match the type string exactly, ignoring ASCII
* case. Import maps, speculation rules and data blocks are not JavaScript.
*/
const inlineScriptType = (
const javaScriptType = (
attrs: readonly { readonly name: string; readonly value: string }[],
): HtmlInlineScript["scriptType"] | null => {
const type = attrs.find(({ name }) => name === "type");
@@ -110,11 +112,13 @@ export const htmlArtifactReferences = (
if (href !== undefined) baseHref = href.value;
}
if (node.tagName === "script") {
const scriptType = javaScriptType(node.attrs);
const src = node.attrs.find(({ name }) => name === "src");
const location = node.sourceCodeLocation?.startTag;
if (src !== undefined && src.value !== "" && location !== undefined) {
scripts.push({
scriptPath: src.value,
scriptType,
location: {
start: sourcePoint(location.startLine, location.startCol),
end: sourcePoint(location.endLine, location.endCol),
@@ -122,8 +126,6 @@ export const htmlArtifactReferences = (
});
}
const inline = src === undefined ? inlineScriptBody(node) : undefined;
const scriptType =
inline === undefined ? null : inlineScriptType(node.attrs);
if (inline !== undefined && scriptType !== null) {
inlineScriptCount += 1;
if (inlineScripts.length < HTML_INLINE_SCRIPT_LOCATION_LIMIT)