* fix(javascript): resolve bare source-map and worker URLs next to the script
A sourceMappingURL like `main.js.map` (the default of webpack, esbuild,
rollup, Vite and rolldown) and `new Worker("worker.js")` were resolved as
module specifiers, so the bare name was looked up as an npm package and the
map or worker script was never linked. Resolve them as URLs relative to the
declaring script.
* fix(javascript): tighten url-reference resolution edge cases
Use looksExternal for non-module contexts so protocol-relative URLs
cannot be misread as root-absolute paths, cover url-reference unit
cases, and assert bare service-worker script resolution.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { expect, it } from "vitest";
|
|
|
|
import { reconstructJavaScriptArtifact } from "../../../src/application/javascript/JavaScriptArtifactReconstruction.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
it.each(["\n", "\r\n", "\r", "\u2028", "\u2029"])(
|
|
"preserves source-map graph evidence coordinates after %j",
|
|
async (separator) => {
|
|
const root = await createTestTempDirectory("rea-source-map-locations-");
|
|
const directive = "//# sourceMappingURL=app.js.map";
|
|
const source = `const marker = "😀";${separator} ${directive}`;
|
|
await writeFile(join(root, "app.js"), source);
|
|
const result = await reconstructJavaScriptArtifact({ input_path: root });
|
|
expect(result.statistics.parse_failures).toBe(0);
|
|
const edges = result.graph.edges.filter(
|
|
({ relation, properties }) =>
|
|
relation === "maps_to" && properties.declared_url === "app.js.map",
|
|
);
|
|
expect(edges).toHaveLength(1);
|
|
expect(edges[0]?.evidence).toMatchObject({
|
|
state: "inferred",
|
|
artifact: {
|
|
sha256: createHash("sha256").update(source).digest("hex"),
|
|
},
|
|
location: {
|
|
available: true,
|
|
value: {
|
|
kind: "source-range",
|
|
source: "app.js",
|
|
start: { line: 2, column: 2 },
|
|
end: { line: 2, column: directive.length + 2 },
|
|
},
|
|
},
|
|
});
|
|
expect(edges[0]?.properties).toMatchObject({
|
|
declared_url: "app.js.map",
|
|
resolved_path: null,
|
|
});
|
|
},
|
|
);
|
|
|
|
it("resolves a bare source-map file name and worker URL next to the script", async () => {
|
|
const root = await createTestTempDirectory("rea-source-map-relative-");
|
|
await mkdir(join(root, "dist"));
|
|
await writeFile(
|
|
join(root, "dist", "app.js"),
|
|
[
|
|
'new Worker("worker.js");',
|
|
'navigator.serviceWorker.register("sw.js");',
|
|
"//# sourceMappingURL=app.js.map",
|
|
].join("\n"),
|
|
);
|
|
await writeFile(
|
|
join(root, "dist", "worker.js"),
|
|
"self.onmessage = () => {};",
|
|
);
|
|
await writeFile(join(root, "dist", "sw.js"), "self.onfetch = () => {};");
|
|
await writeFile(
|
|
join(root, "dist", "app.js.map"),
|
|
JSON.stringify({
|
|
version: 3,
|
|
sources: ["../src/app.js"],
|
|
sourcesContent: ['new Worker("worker.js");'],
|
|
names: [],
|
|
mappings: "AAAA",
|
|
}),
|
|
);
|
|
const result = await reconstructJavaScriptArtifact({ input_path: root });
|
|
const nodes = new Map(result.graph.nodes.map((node) => [node.node_id, node]));
|
|
const mapEdge = result.graph.edges.find(
|
|
({ relation, properties }) =>
|
|
relation === "maps_to" && properties.declared_url === "app.js.map",
|
|
);
|
|
expect(mapEdge?.properties).toMatchObject({
|
|
declared_url: "app.js.map",
|
|
resolved_path: "dist/app.js.map",
|
|
});
|
|
expect(nodes.get(mapEdge?.target_node_id ?? "")?.identity.strategy).toBe(
|
|
"content-digest",
|
|
);
|
|
const workerEdge = result.graph.edges.find(
|
|
({ relation, source_node_id }) =>
|
|
relation === "maps_to" && nodes.get(source_node_id)?.kind === "worker",
|
|
);
|
|
expect(workerEdge?.properties).toMatchObject({
|
|
resolved_path: "dist/worker.js",
|
|
});
|
|
const serviceWorkerEdge = result.graph.edges.find(
|
|
({ relation, source_node_id }) =>
|
|
relation === "maps_to" &&
|
|
nodes.get(source_node_id)?.kind === "service-worker",
|
|
);
|
|
expect(serviceWorkerEdge?.properties).toMatchObject({
|
|
resolved_path: "dist/sw.js",
|
|
});
|
|
});
|