fix(javascript): match source-map originals that name parent directories (#1215)

* fix(javascript): match source-map originals that name parent directories

* fix(javascript): resolve parent segments against the source map's directory

* fix(javascript): resolve source originals against their map location

* fix(javascript): preserve URI and empty source map path semantics

* fix(javascript): separate source URL paths from opaque URI payloads

* fix(javascript): preserve filesystem names during URI classification

* style(javascript): format source URI presence guard

* fix(javascript): resolve source map references with URL semantics

* fix(javascript): preserve literal UNC source map paths

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
Jay Hemnaniandmorluto authored and GitHub committed 2026-10-09 18:04:25 +08:00
1 parent 24a3aa08d1
commit 889fb374df
4 files changed
+713 -75

No files matched your search

@@ -0,0 +1,69 @@
import { posix } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
interface SourceMapPath {
readonly value: string;
readonly scope: "artifact-relative" | "suffix";
}
/** Resolve a source-map URL reference without changing its recorded identity. */
export const resolveJavaScriptSourceMapPath = (
source: string,
mapPath?: string,
): SourceMapPath | null => {
const portable = source.replaceAll("\\", "/");
const windowsPath = /^[a-z]:\//iu.test(portable) || source.startsWith("\\\\");
// Preserve parent traversal rather than letting URL resolution clamp at '/'.
const anchor = `/${"source/".repeat(portable.split("/").length + 1)}`;
const directory = posix.join(
anchor,
posix.dirname(mapPath?.replaceAll("\\", "/") ?? "."),
);
const base = pathToFileURL(`${directory}/`, { windows: false });
const absolute = windowsPath ? windowsFileUrl(source) : URL.parse(source);
const resolved = windowsPath ? absolute : URL.parse(source, base);
if (
resolved === null ||
!resolved.pathname.startsWith("/") ||
resolved.pathname.endsWith("/")
)
return null;
const relative =
absolute === null
? !portable.startsWith("/")
: absolute.href !== resolved.href;
const path = decodedUrlPath(resolved);
if (path === null) return null;
const value = relative
? posix.relative(anchor, path)
: path.replace(/^\/+/, "");
if (value === "" || value === ".." || value.startsWith("../")) return null;
return {
value,
scope: relative && mapPath !== undefined ? "artifact-relative" : "suffix",
};
};
const windowsFileUrl = (path: string): URL | null => {
try {
return pathToFileURL(path, { windows: true });
} catch (cause: unknown) {
if (cause instanceof TypeError) return null;
throw cause;
}
};
const decodedUrlPath = (url: URL): string | null => {
// Encoded separators must not manufacture a different filesystem hierarchy.
if (/%(?:2f|5c)/iu.test(url.pathname)) return null;
try {
if (url.protocol === "file:")
return fileURLToPath(url, {
windows: url.hostname !== "" && url.hostname !== "localhost",
}).replaceAll("\\", "/");
return `${url.host}${decodeURIComponent(url.pathname)}`;
} catch (cause: unknown) {
if (cause instanceof URIError || cause instanceof TypeError) return null;
throw cause;
}
};
@@ -5,6 +5,7 @@ import {
createJavaScriptApplicationNode,
} from "./javascriptApplicationGraph.js";
import { createHistoricalSourceGraph } from "../referenceSourceGraph.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareSourceToBundle } from "./sourceToBundleComparison.js";
import {
SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS,
@@ -187,44 +188,47 @@ describe("source to bundle digest changes", () => {
});
describe("source to bundle path syntax", () => {
it.each(["src/modified#part.ts", "src/modified?part.ts"])(
"preserves literal filesystem punctuation in %s",
(path) => {
const node = moduleNode("punctuated", { path });
const graph = createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
root_node_ids: [node.node_id],
nodes: [node],
edges: [],
coverage: {
status: "complete",
truncated: false,
omitted_count: 0,
limits: [],
},
limitations: [],
});
const result = compareSourceToBundle({
reference: historicalGraph("complete", [path]),
application: {
evidenceId: EVIDENCE_ID,
rootArtifactSha256: HASH.artifact,
graph,
},
});
expect(item(result, path)).toMatchObject({
status: "unknown",
current_node_ids: [node.node_id],
candidates: [
expect.objectContaining({
signals: expect.arrayContaining([
expect.objectContaining({ kind: "current-path-exact" }),
]),
}),
],
});
},
);
it.each([
"src/modified#part.ts",
"src/modified?part.ts",
"pkg:app/src/modified#part.ts",
"pkg:app/src/modified?part.ts",
"data:/src/modified.ts",
])("preserves literal filesystem punctuation in %s", (path) => {
const node = moduleNode("punctuated", { path });
const graph = createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
root_node_ids: [node.node_id],
nodes: [node],
edges: [],
coverage: {
status: "complete",
truncated: false,
omitted_count: 0,
limits: [],
},
limitations: [],
});
const result = compareSourceToBundle({
reference: historicalGraph("complete", [path]),
application: {
evidenceId: EVIDENCE_ID,
rootArtifactSha256: HASH.artifact,
graph,
},
});
expect(item(result, path)).toMatchObject({
status: "unknown",
current_node_ids: [node.node_id],
candidates: [
expect.objectContaining({
signals: expect.arrayContaining([
expect.objectContaining({ kind: "current-path-exact" }),
]),
}),
],
});
});
it.each([
"https://example.test/src/modified.ts?revision=2#section",
@@ -347,7 +351,7 @@ const historicalGraph = (
content_state: "hashed" as const,
limitations: [],
})),
],
].sort((left, right) => compareCodePoints(left.path, right.path)),
relationships: [],
parse_failures: [],
exclusions: [],
+76 -18
View File
@@ -9,6 +9,7 @@ import {
type SourceToBundleSignal,
} from "./sourceToBundleComparisonSchemas.js";
import { isDigest } from "../digests.js";
import { resolveJavaScriptSourceMapPath } from "./javascriptSourceMapPaths.js";
type SourceFile = Extract<
HistoricalSourceGraph["entries"][number],
@@ -23,6 +24,8 @@ type CurrentPathKind =
interface CurrentPath {
readonly kind: CurrentPathKind;
readonly value: string;
/** Artifact-relative map resolution identifies one path, not its suffixes. */
readonly allowSuffix: boolean;
}
interface CurrentProjection {
@@ -93,7 +96,9 @@ export const buildSourceToBundleCandidateIndex = (
for (const digest of projection.digests)
addIndexValue(byDigest, digest, projection.node.node_id);
for (const path of projection.paths) {
for (const suffix of pathSuffixes(path.value))
for (const suffix of path.allowSuffix
? pathSuffixes(path.value)
: [path.value])
addIndexValue(byPathSuffix, suffix, projection.node.node_id);
addIndexValue(
byBasename,
@@ -169,7 +174,22 @@ const projectCurrentNode = (node: ApplicationNode): CurrentProjection => {
if (node.identity.strategy === "source-map-original") {
if (node.identity.source_sha256 !== null)
digests.add(node.identity.source_sha256);
addPath(paths, "source-map-original", node.identity.original_source);
const mapLocations = node.observations.flatMap(({ evidence }) =>
evidence.extractor.operation === "parse-local-source-map" &&
evidence.location.available &&
evidence.location.value.kind === "artifact-path"
? [evidence.location.value.path]
: [],
);
if (mapLocations.length === 0)
addPath(paths, "source-map-original", node.identity.original_source);
for (const mapPath of mapLocations)
addPath(
paths,
"source-map-original",
node.identity.original_source,
mapPath,
);
}
if (node.identity.strategy === "canonical-path")
addPath(paths, "canonical-path", node.identity.path);
@@ -177,8 +197,18 @@ const projectCurrentNode = (node: ApplicationNode): CurrentProjection => {
const sourceDigest = observation.properties.source_sha256;
if (typeof sourceDigest === "string" && isDigest(sourceDigest))
digests.add(sourceDigest);
for (const key of PATH_PROPERTIES)
addJsonPath(paths, observation.properties[key]);
for (const key of PATH_PROPERTIES) {
const location = observation.evidence.location;
const mapPath =
node.identity.strategy === "source-map-original" &&
observation.evidence.extractor.operation === "parse-local-source-map" &&
(key === "source" || key === "original_source") &&
location.available &&
location.value.kind === "artifact-path"
? location.value.path
: undefined;
addJsonPath(paths, observation.properties[key], mapPath);
}
}
return {
node,
@@ -212,7 +242,10 @@ const candidateSignals = (
signals.push(signal("current-path-exact", source.path, exactPaths));
const suffixPaths = otherPaths
.filter(
({ value }) => value !== source.path && value.endsWith(`/${source.path}`),
({ value, allowSuffix }) =>
allowSuffix &&
value !== source.path &&
value.endsWith(`/${source.path}`),
)
.map(({ value }) => value);
if (suffixPaths.length > 0)
@@ -259,45 +292,70 @@ const matchingPaths = (
): string[] =>
currentPaths
.filter(
({ value }) => value === sourcePath || value.endsWith(`/${sourcePath}`),
({ value, allowSuffix }) =>
value === sourcePath ||
(allowSuffix && value.endsWith(`/${sourcePath}`)),
)
.map(({ value }) => value);
const addJsonPath = (paths: CurrentPath[], value: unknown): void => {
if (typeof value === "string") addPath(paths, "observation-path", value);
const addJsonPath = (
paths: CurrentPath[],
value: unknown,
mapPath?: string,
): void => {
if (typeof value === "string")
addPath(paths, "observation-path", value, mapPath);
};
const addPath = (
paths: CurrentPath[],
kind: CurrentPathKind,
raw: string,
mapPath?: string,
): void => {
if (kind === "source-map-original" || mapPath !== undefined) {
const resolved = resolveJavaScriptSourceMapPath(raw, mapPath);
if (resolved !== null)
paths.push({
kind,
value: resolved.value,
allowSuffix: resolved.scope === "suffix",
});
return;
}
const value = normalizeCurrentPath(raw);
if (value !== null) paths.push({ kind, value });
if (value !== null) paths.push({ kind, value, allowSuffix: true });
};
/** Matches a `scheme://...` URL prefix; bare filesystem paths keep `?`/`#`. */
const SCHEME_URL = /^[a-z][a-z0-9+.-]*:\/\//iu;
/** Matches a `scheme://...` URL prefix with any run of slashes, for stripping. */
const SCHEME_URL_PREFIX = /^[a-z][a-z0-9+.-]*:\/\/+/iu;
const normalizeCurrentPath = (raw: string): string | null => {
const withoutQuery = SCHEME_URL.test(raw)
? (raw.split(/[?#]/u, 1)[0] ?? "")
: raw;
const withoutScheme = withoutQuery.replace(SCHEME_URL_PREFIX, "");
const parts = withoutScheme
.replaceAll("\\", "/")
.split("/")
.filter((part) => part !== "" && part !== ".");
if (parts.length === 0 || parts.includes("..")) return null;
const portable = withoutQuery
.replace(SCHEME_URL_PREFIX, "")
.replaceAll("\\", "/");
const parts: string[] = [];
for (const part of portable.split("/")) {
if (part === "" || part === ".") continue;
if (part === "..") {
if (parts.length === 0) return null;
parts.pop();
} else parts.push(part);
}
if (parts.length === 0) return null;
return parts.join("/");
};
const uniquePaths = (paths: readonly CurrentPath[]): CurrentPath[] =>
[
...new Map(
paths.map((path) => [`${path.kind}\0${path.value}`, path]),
paths.map((path) => [
`${path.kind}\0${path.value}\0${String(path.allowSuffix)}`,
path,
]),
).values(),
].sort((left, right) =>
compareCodePoints(
@@ -6,6 +6,7 @@ import { analyzeJavaScriptApplication } from "../../../src/application/javascrip
import { parseApplicationGraphEvidence } from "../../../src/application/javascript/JavaScriptApplicationEvidenceGraph.js";
import { importReferenceSource } from "../../../src/application/ReferenceSourceImport.js";
import { compareSourceToBundle } from "../../../src/domain/javascript/sourceToBundleComparison.js";
import { resolveSourceMapSource } from "../../../src/javascript/sourceMaps/DecodedSourceMap.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it("keeps literal hash characters in historical/current filesystem paths", async () => {
@@ -17,25 +18,9 @@ it("keeps literal hash characters in historical/current filesystem paths", async
writeFile(join(previous, "worker#main.js"), "export const value = 1;"),
writeFile(join(current, "worker#main.js"), "export const value = 2;"),
]);
const reference = await importReferenceSource({
root: previous,
caller: "source-to-bundle-path-test",
policy: { secretPatterns: [] },
});
if (!reference.ok) throw new Error(reference.error.message);
const analyzed = await analyzeJavaScriptApplication({ input_path: current });
if (!analyzed.ok) throw analyzed.error;
const application = parseApplicationGraphEvidence(analyzed.value);
const comparison = compareSourceToBundle({
reference: reference.value,
application: {
evidenceId: application.evidence.evidence_id,
rootArtifactSha256: application.rootArtifactSha256,
graph: application.graph,
},
});
const { reference, comparison } = await compareTrees(previous, current);
expect(reference.value.entries).toContainEqual(
expect(reference.entries).toContainEqual(
expect.objectContaining({
kind: "file",
path: "worker#main.js",
@@ -59,6 +44,507 @@ it("keeps literal hash characters in historical/current filesystem paths", async
);
});
it("matches source-map originals named relative to the map", async () => {
const root = await createTestTempDirectory("rea-source-map-parent-");
const previous = join(root, "previous");
const current = join(root, "current");
await Promise.all([
mkdir(join(previous, "src"), { recursive: true }),
mkdir(join(current, "dist"), { recursive: true }),
]);
await Promise.all([
writeFile(join(previous, "src", "a.js"), "export const value = 1;\n"),
writeFile(
join(current, "dist", "main.js"),
"var value = 2;\n//# sourceMappingURL=main.js.map\n",
),
writeFile(
join(current, "dist", "main.js.map"),
JSON.stringify({
version: 3,
file: "main.js",
sources: ["../src/a.js"],
sourcesContent: ["export const value = 2;\n"],
names: [],
mappings: "AAAA",
}),
),
]);
const { comparison } = await compareTrees(previous, current);
expect(comparison.items).toContainEqual(
expect.objectContaining({
source_path: "src/a.js",
candidates: expect.arrayContaining([
expect.objectContaining({
current_node_kind: "source-module",
signals: expect.arrayContaining([
expect.objectContaining({
kind: "source-map-original-path",
current_values: ["src/a.js"],
}),
]),
}),
]),
}),
);
});
it.each([
{
mapPath: "pkg/dist/main.js.map",
source: "..\\src\\a.js",
expected: "pkg/src/a.js",
},
{
mapPath: "pkg/dist/main.js.map",
source: "../src/a.js",
expected: "pkg/src/a.js",
},
{
mapPath: "apps/pkg/dist/maps/main.js.map",
source: "../../src/a.js",
expected: "apps/pkg/src/a.js",
},
{
mapPath: "pkg/dist/main.js.map",
sourceRoot: "../src",
source: "a.js",
expected: "pkg/src/a.js",
},
{
mapPath: "pkg/maps/main.map",
sourceRoot: "../src",
source: "/vendor/a.js",
expected: "pkg/src/vendor/a.js",
decoy: "vendor/a.js",
},
{
mapPath: "pkg/maps/main.js.map",
source: "modules/a.js",
expected: "pkg/maps/modules/a.js",
},
{
mapPath: "pkg/maps/main.js.map",
source: "generated/../src/a.js",
expected: "pkg/maps/src/a.js",
},
{
mapPath: "pkg/maps/main.js.map",
sourceRoot: "generated/..",
source: "src/a.js",
expected: "pkg/maps/src/a.js",
},
{
mapPath: "pkg/maps/main.js.map",
source: "../actual/%2e%2e/src/a.js",
expected: "pkg/src/a.js",
decoy: "pkg/actual/%2e%2e/src/a.js",
},
{
mapPath: "pkg/maps/main.js.map",
sourceRoot: "../actual/.%2E",
source: "src/a.js",
expected: "pkg/src/a.js",
decoy: "pkg/actual/.%2E/src/a.js",
},
{
mapPath: "pkg#name?/maps/main.js.map",
source: "../src/a.js",
expected: "pkg#name?/src/a.js",
},
{
mapPath: "pkg/maps/main.js.map",
source: "modules/a%20b.js",
expected: "pkg/maps/modules/a b.js",
},
{
mapPath: "pkg/maps/main.js.map",
source: "modules/a%23b.js",
expected: "pkg/maps/modules/a#b.js",
},
{
mapPath: "pkg/maps/main.js.map",
source: "../actual/%252e%252e/src/a.js",
expected: "pkg/actual/%2e%2e/src/a.js",
decoy: "pkg/src/a.js",
},
])(
"resolves $source against $mapPath without selecting a same-basename decoy",
async ({
mapPath,
source,
sourceRoot,
expected,
decoy: decoyPath = "src/a.js",
}) => {
if (sourceRoot !== undefined) {
const resolved = resolveSourceMapSource(
source,
sourceRoot,
`https://example.test/${mapPath}`,
);
expect(decodeURIComponent(new URL(resolved).pathname)).toBe(
`/${expected}`,
);
}
const root = await createTestTempDirectory("rea-nested-map-");
const previous = join(root, "previous");
const current = join(root, "current");
await Promise.all([
mkdir(join(previous, expected, ".."), { recursive: true }),
mkdir(join(previous, decoyPath, ".."), { recursive: true }),
mkdir(join(current, mapPath, ".."), { recursive: true }),
]);
await Promise.all([
writeFile(join(previous, expected), "export const value = 1;"),
writeFile(join(previous, decoyPath), "export const decoy = 99;"),
writeFile(
join(current, mapPath),
JSON.stringify({
version: 3,
sources: [source],
...(sourceRoot === undefined ? {} : { sourceRoot }),
sourcesContent: ["export const value = 2;"],
names: [],
mappings: "AAAA",
}),
),
]);
const { comparison } = await compareTrees(previous, current);
const correct = comparison.items.find(
({ source_path }) => source_path === expected,
);
expect(correct?.current_node_ids).toHaveLength(1);
expect(correct?.candidates).toContainEqual(
expect.objectContaining({
confidence: "high",
signals: expect.arrayContaining([
expect.objectContaining({
kind: "source-map-original-path",
current_values: [expected],
}),
]),
}),
);
const decoy = comparison.items.find(
({ source_path }) => source_path === decoyPath,
);
expect(decoy?.current_node_ids).toEqual([]);
expect(
decoy?.candidates
.flatMap(({ signals }) => signals)
.some(({ kind }) => kind === "current-path-exact"),
).toBe(false);
// A suffix alone must not claim a second map-relative original identity.
expect(
decoy?.candidates
.flatMap(({ signals }) => signals)
.some(({ kind }) => kind === "source-map-original-path"),
).toBe(false);
},
);
it("retains every map location when identical map artifacts merge", async () => {
const root = await createTestTempDirectory("rea-source-map-merged-");
const previous = join(root, "previous");
const current = join(root, "current");
const map = JSON.stringify({
version: 3,
sources: ["../src/x.js"],
sourcesContent: ["export const value = 2;"],
names: [],
mappings: "AAAA",
});
for (const prefix of ["a", "b"]) {
await Promise.all([
mkdir(join(previous, prefix, "src"), { recursive: true }),
mkdir(join(current, prefix, "maps"), { recursive: true }),
]);
await Promise.all([
writeFile(join(previous, prefix, "src/x.js"), "export const value = 1;"),
writeFile(join(current, prefix, "maps/main.js.map"), map),
]);
}
const { comparison } = await compareTrees(previous, current);
for (const prefix of ["a", "b"]) {
const path = `${prefix}/src/x.js`;
const item = comparison.items.find(
({ source_path }) => source_path === path,
);
expect(item?.current_node_ids).toHaveLength(1);
expect(item?.candidates).toContainEqual(
expect.objectContaining({
confidence: "high",
signals: expect.arrayContaining([
expect.objectContaining({
kind: "source-map-original-path",
current_values: [path],
}),
]),
}),
);
}
});
it.each([
{ source: "file:/repo/src/a.js", hasPath: true },
{ source: "file:C:/repo/src/a.js", hasPath: true },
{ source: "file:C:\\repo\\src\\a.js", hasPath: true },
{ source: "C:\\repo\\src\\a.js", hasPath: true },
{ source: "file://server/share/src/a.js", hasPath: true },
{ source: "file:/repo/src/a.js?version=2#original", hasPath: true },
{ source: "webpack:///src/a.js", hasPath: true },
{ source: "https://example.com/src/a.js#original", hasPath: true },
{ source: "node:internal/src/a.js", hasPath: false },
{ source: "data:text/javascript,virtual/src/a.js", hasPath: false },
{ source: "data:text/javascript,virtual/../src/a.js", hasPath: false },
{ source: "data:\\src\\a.js", hasPath: false },
])(
"projects source references from $source only when they name a file",
async ({ source, hasPath }) => {
const root = await createTestTempDirectory("rea-source-map-uri-");
const previous = join(root, "previous");
const current = join(root, "current");
await Promise.all([
mkdir(join(previous, "src"), { recursive: true }),
mkdir(join(current, "maps"), { recursive: true }),
]);
await Promise.all([
writeFile(join(previous, "src/a.js"), "export const value = 1;"),
writeFile(
join(current, "maps/main.js.map"),
JSON.stringify({
version: 3,
sources: [source],
sourcesContent: ["export const value = 2;"],
names: [],
mappings: "AAAA",
}),
),
]);
const { comparison, application } = await compareTrees(previous, current);
expect(application.graph.nodes).toContainEqual(
expect.objectContaining({
identity: expect.objectContaining({ original_source: source }),
}),
);
if (hasPath)
expect(comparison.items[0]?.candidates).toContainEqual(
expect.objectContaining({
confidence: "high",
signals: expect.arrayContaining([
expect.objectContaining({ kind: "source-map-original-path" }),
]),
}),
);
else expect(comparison.items[0]?.candidates).toEqual([]);
},
);
it.each(["#original.js", "?version.js", "?version.js#original.js"])(
"uses the relative URL pathname rather than a literal %s filename",
async (suffix) => {
const root = await createTestTempDirectory("rea-source-map-suffix-");
const previous = join(root, "previous");
const current = join(root, "current");
await Promise.all([
mkdir(join(previous, "src"), { recursive: true }),
mkdir(join(current, "maps"), { recursive: true }),
]);
const source = `../src/a.js${suffix}`;
await Promise.all([
writeFile(join(previous, "src/a.js"), "export const value = 1;"),
writeFile(join(previous, `src/a.js${suffix}`), "export const decoy = 3;"),
writeFile(
join(current, "maps/main.js.map"),
JSON.stringify({
version: 3,
sources: [source],
sourcesContent: ["export const value = 2;"],
names: [],
mappings: "AAAA",
}),
),
]);
const { comparison, application } = await compareTrees(previous, current);
const actual = comparison.items.find(
({ source_path }) => source_path === "src/a.js",
);
expect(actual?.current_node_ids).toHaveLength(1);
expect(actual?.candidates).toContainEqual(
expect.objectContaining({
confidence: "high",
signals: expect.arrayContaining([
expect.objectContaining({
kind: "source-map-original-path",
current_values: ["src/a.js"],
}),
]),
}),
);
const decoy = comparison.items.find(
({ source_path }) => source_path === `src/a.js${suffix}`,
);
expect(decoy?.current_node_ids).toEqual([]);
expect(decoy?.candidates).toEqual([]);
expect(application.graph.nodes).toContainEqual(
expect.objectContaining({
identity: expect.objectContaining({ original_source: source }),
}),
);
},
);
it.each([
{ filename: "a#b.js", decoyFilename: "a" },
{ filename: "a?b.js", decoyFilename: "a" },
{ filename: "a%23b.js", decoyFilename: "a#b.js" },
])(
"preserves the literal UNC filesystem name $filename without selecting a URL decoy",
async ({ filename, decoyFilename }) => {
const root = await createTestTempDirectory("rea-source-map-unc-");
const previous = join(root, "previous");
const current = join(root, "current");
const source = `\\\\server\\share\\src\\${filename}`;
const expected = `src/${filename}`;
const decoy = `src/${decoyFilename}`;
await Promise.all([
mkdir(join(previous, "src"), { recursive: true }),
mkdir(join(current, "maps"), { recursive: true }),
]);
await Promise.all([
writeFile(join(previous, expected), "export const value = 1;"),
writeFile(join(previous, decoy), "export const decoy = 3;"),
writeFile(
join(current, "maps/main.map"),
JSON.stringify({
version: 3,
sources: [source],
sourcesContent: ["export const value = 2;"],
names: [],
mappings: "AAAA",
}),
),
]);
const { comparison, application } = await compareTrees(previous, current);
const actual = comparison.items.find(
({ source_path }) => source_path === expected,
);
expect(actual?.current_node_ids).toHaveLength(1);
expect(actual?.candidates).toContainEqual(
expect.objectContaining({
signals: expect.arrayContaining([
expect.objectContaining({ kind: "source-map-original-path" }),
]),
}),
);
expect(
comparison.items.find(({ source_path }) => source_path === decoy)
?.current_node_ids,
).toEqual([]);
expect(application.graph.nodes).toContainEqual(
expect.objectContaining({
identity: expect.objectContaining({ original_source: source }),
}),
);
},
);
it.each([
{ source: "", historicalPath: "maps.js" },
{ source: ".", historicalPath: "maps.js" },
{ source: "./", historicalPath: "maps.js" },
{ source: "child/..", historicalPath: "maps.js" },
{ source: "child.js/", historicalPath: "maps.js/child.js" },
{ source: "#original.js", historicalPath: "maps.js" },
{ source: "?version.js", historicalPath: "maps.js" },
{ source: "./?version.js", historicalPath: "maps.js" },
{ source: "%2e", historicalPath: "maps.js" },
{ source: "%2e%2e", historicalPath: "maps.js" },
{ source: "child%2fpart.js", historicalPath: "maps.js/child/part.js" },
{ source: "child%5cpart.js", historicalPath: "maps.js/child/part.js" },
{ source: "bad%zz.js", historicalPath: "maps.js/bad%zz.js" },
{ source: "\\\\bad host\\share\\a.js", historicalPath: "share/a.js" },
{ source: "../../outside/a.js", historicalPath: "outside/a.js" },
])(
"declines source references without a trustworthy file path: $source",
async ({ source, historicalPath }) => {
const root = await createTestTempDirectory("rea-source-map-empty-");
const previous = join(root, "previous");
const current = join(root, "current");
await Promise.all([
mkdir(join(previous, historicalPath, ".."), { recursive: true }),
mkdir(join(current, "maps.js"), { recursive: true }),
]);
await Promise.all([
writeFile(join(previous, historicalPath), "export const value = 1;"),
writeFile(
join(current, "maps.js/main.js.map"),
JSON.stringify({
version: 3,
sources: [source],
sourcesContent: ["export const value = 2;"],
names: [],
mappings: "AAAA",
}),
),
]);
const { comparison } = await compareTrees(previous, current);
expect(comparison.items[0]?.source_path).toBe(historicalPath);
expect(comparison.items[0]?.candidates).toEqual([]);
},
);
it("resolves parent segments against a nested source map's directory", async () => {
const root = await createTestTempDirectory("rea-source-map-nested-");
const previous = join(root, "previous");
const current = join(root, "current");
await Promise.all([
mkdir(join(previous, "web", "src"), { recursive: true }),
mkdir(join(current, "web", "maps"), { recursive: true }),
]);
await Promise.all([
writeFile(
join(previous, "web", "src", "a.js"),
"export const value = 1;\n",
),
writeFile(
join(current, "web", "main.js"),
"var value = 2;\n//# sourceMappingURL=maps/main.js.map\n",
),
writeFile(
join(current, "web", "maps", "main.js.map"),
JSON.stringify({
version: 3,
file: "../main.js",
sources: ["../src/a.js"],
sourcesContent: ["export const value = 2;\n"],
names: [],
mappings: "AAAA",
}),
),
]);
const { comparison } = await compareTrees(previous, current);
expect(comparison.items).toContainEqual(
expect.objectContaining({
source_path: "web/src/a.js",
candidates: expect.arrayContaining([
expect.objectContaining({
current_node_kind: "source-module",
signals: expect.arrayContaining([
expect.objectContaining({
kind: "source-map-original-path",
current_values: ["web/src/a.js"],
}),
]),
}),
]),
}),
);
});
it("rejects static application Evidence whose subject path disagrees with its result", async () => {
const root = await createTestTempDirectory("rea-application-subject-path-");
const analyzed = await analyzeJavaScriptApplication({ input_path: root });
@@ -77,3 +563,24 @@ it("rejects static application Evidence whose subject path disagrees with its re
"JavaScript application Evidence subject does not match its result",
);
});
const compareTrees = async (previous: string, current: string) => {
const reference = await importReferenceSource({
root: previous,
caller: "source-to-bundle-path-test",
policy: { secretPatterns: [] },
});
if (!reference.ok) throw new Error(reference.error.message);
const analyzed = await analyzeJavaScriptApplication({ input_path: current });
if (!analyzed.ok) throw analyzed.error;
const application = parseApplicationGraphEvidence(analyzed.value);
const comparison = compareSourceToBundle({
reference: reference.value,
application: {
evidenceId: application.evidence.evidence_id,
rootArtifactSha256: application.rootArtifactSha256,
graph: application.graph,
},
});
return { reference: reference.value, comparison, application };
};