fix(javascript): resolve bare source-map and worker URLs next to the script (#890)
* 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>
This commit is contained in:
1 parent
aa8082ea6c
commit
45fcd28b40
4 files changed
+143
-20
No files matched your search
@@ -268,11 +268,12 @@ const addSourceMapEdge = (
|
||||
context: JavaScriptArtifactGraphContext,
|
||||
input: FindingInput<StaticSourceMap>,
|
||||
): void => {
|
||||
const resolvedPath = resolveArtifactPath(
|
||||
input.value.declared_url,
|
||||
input.file.path,
|
||||
context.filesByPath,
|
||||
);
|
||||
const resolvedPath = resolveArtifactPathByContext({
|
||||
declaredPath: input.value.declared_url,
|
||||
sourcePath: input.file.path,
|
||||
context: "url-reference",
|
||||
files: context.filesByPath,
|
||||
}).resolved_path;
|
||||
const resolvedFile =
|
||||
resolvedPath === null ? undefined : context.filesByPath.get(resolvedPath);
|
||||
const resolvedNode =
|
||||
@@ -341,17 +342,26 @@ const resolveReference = (
|
||||
? null
|
||||
: { path: `${sourcePath}#module:${specifier}`, file, node: module };
|
||||
}
|
||||
const path = resolveArtifactPath(
|
||||
specifier,
|
||||
sourcePath,
|
||||
context.filesByPath,
|
||||
reference.kind === "require"
|
||||
? "require"
|
||||
: reference.kind === "static-import" ||
|
||||
reference.kind === "dynamic-import"
|
||||
? "import"
|
||||
: undefined,
|
||||
);
|
||||
// Worker scripts are URLs relative to the script, not module specifiers.
|
||||
const path =
|
||||
reference.kind === "worker" || reference.kind === "service-worker"
|
||||
? resolveArtifactPathByContext({
|
||||
declaredPath: specifier,
|
||||
sourcePath,
|
||||
context: "url-reference",
|
||||
files: context.filesByPath,
|
||||
}).resolved_path
|
||||
: resolveArtifactPath(
|
||||
specifier,
|
||||
sourcePath,
|
||||
context.filesByPath,
|
||||
reference.kind === "require"
|
||||
? "require"
|
||||
: reference.kind === "static-import" ||
|
||||
reference.kind === "dynamic-import"
|
||||
? "import"
|
||||
: undefined,
|
||||
);
|
||||
if (path === null) return null;
|
||||
const file = context.filesByPath.get(path);
|
||||
const node = context.fileNodes.get(path);
|
||||
|
||||
@@ -562,6 +562,61 @@ describe("contextual JavaScript module identity", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("url-reference JavaScript artifact path resolution", () => {
|
||||
it("resolves sibling paths and rejects external URL forms", () => {
|
||||
const files = fileMap([
|
||||
file("dist/app.js", "root"),
|
||||
file("dist/app.js.map", "root"),
|
||||
file("dist/worker.js", "root"),
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolve({
|
||||
declaredPath: "app.js.map",
|
||||
sourcePath: "dist/app.js",
|
||||
context: "url-reference",
|
||||
files,
|
||||
}),
|
||||
).toMatchObject({
|
||||
resolution_status: "resolved",
|
||||
resolved_path: "dist/app.js.map",
|
||||
});
|
||||
expect(
|
||||
resolve({
|
||||
declaredPath: "app.js.map?v=1#section",
|
||||
sourcePath: "dist/app.js",
|
||||
context: "url-reference",
|
||||
files,
|
||||
}),
|
||||
).toMatchObject({
|
||||
resolution_status: "resolved",
|
||||
resolved_path: "dist/app.js.map",
|
||||
});
|
||||
expect(
|
||||
resolve({
|
||||
declaredPath: "//cdn.example.com/app.js.map",
|
||||
sourcePath: "dist/app.js",
|
||||
context: "url-reference",
|
||||
files,
|
||||
}),
|
||||
).toMatchObject({
|
||||
resolution_status: "external",
|
||||
resolved_path: null,
|
||||
});
|
||||
expect(
|
||||
resolve({
|
||||
declaredPath: "https://cdn.example.com/app.js.map",
|
||||
sourcePath: "dist/app.js",
|
||||
context: "url-reference",
|
||||
files,
|
||||
}),
|
||||
).toMatchObject({
|
||||
resolution_status: "external",
|
||||
resolved_path: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const resolve = (input: ResolveArtifactPathInput) =>
|
||||
resolveArtifactPathByContext(input);
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ type ArtifactPathResolutionContext =
|
||||
| "package-entrypoint"
|
||||
| "filesystem-expression"
|
||||
| "module-specifier"
|
||||
| "html-reference";
|
||||
| "html-reference"
|
||||
| "url-reference";
|
||||
|
||||
type UnresolvedArtifactPathStatus =
|
||||
| "not-found"
|
||||
@@ -127,6 +128,7 @@ const contextualCandidate = (
|
||||
const { context } = input;
|
||||
const declared =
|
||||
context === "html-reference" ||
|
||||
context === "url-reference" ||
|
||||
(context === "module-specifier" && input.moduleKind !== "require")
|
||||
? stripQueryAndFragment(input.declaredPath)
|
||||
: input.declaredPath;
|
||||
@@ -140,9 +142,9 @@ const contextualCandidate = (
|
||||
]);
|
||||
if (!declared.startsWith(".") && !declared.startsWith("/"))
|
||||
return bareModuleCandidate(input, declared);
|
||||
} else if (hasScheme(declared))
|
||||
} else if (looksExternal(declared))
|
||||
return unresolvedOutcome(input, "external", [
|
||||
"URL schemes are outside this local artifact path context.",
|
||||
"URL schemes and protocol-relative URLs are outside this local artifact path context.",
|
||||
]);
|
||||
const relative = declared.startsWith("/") ? declared.slice(1) : declared;
|
||||
return declared.startsWith("/")
|
||||
|
||||
Reference in new issue
Block a user