fix(javascript): share source-map resolution and preserve path uncertainty

This commit is contained in:
morluto committed 2026-10-13 13:48:24 +08:00
1 parent 73dd8d6ed5
commit 3ae826ceba
16 files changed
+551 -67

No files matched your search

@@ -1,10 +1,11 @@
import { describe, expect, it } from "vitest";
import { TraceMap } from "@jridgewell/trace-mapping";
import { resolveJavaScriptSourceMapReference } from "./javascriptSourceMapPaths.js";
import {
createJavaScriptApplicationGraph,
createJavaScriptApplicationNode,
} from "./javascriptApplicationGraph.js";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import { createHistoricalSourceGraph } from "../referenceSourceGraph.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { compareSourceToBundle } from "./sourceToBundleComparison.js";
@@ -12,7 +13,10 @@ import {
SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS,
sourceToBundleComparisonResultSchema,
} from "./sourceToBundleComparisonSchemas.js";
import { artifactEvidence } from "./javascriptApplicationGraph.fixture.js";
import {
artifactEvidence,
sourceMapReference,
} from "./javascriptApplicationGraph.fixture.js";
const HASH = {
artifact: "1".repeat(64),
@@ -42,9 +46,6 @@ describe("historical source to bundle comparison", () => {
const second = compareSourceToBundle(input);
expect(first).toEqual(second);
expect(() =>
sourceToBundleComparisonResultSchema.parse(first),
).not.toThrow();
expect(first.summary).toEqual({
unchanged: 1,
modified: 0,
@@ -153,7 +154,7 @@ describe("source to bundle digest changes", () => {
observations: [
{
label: "display label",
source_map_reference: resolveJavaScriptSourceMapReference(
source_map_reference: sourceMapReference(
"webpack://fixture/./src/modified.ts",
null,
"dist/main.js.map",
@@ -202,6 +203,90 @@ describe("source to bundle digest changes", () => {
});
});
describe("source to bundle Unicode path collisions", () => {
it("keeps colliding canonical-equivalent current paths ambiguous", () => {
const historicalPath = "src/a\u0301\u0323.ts";
const currentPaths = ["src/a\u0323\u0301.ts", "src/\u1ea1\u0301.ts"];
const nodes = currentPaths.map((path, index) =>
moduleNode(`unicode-${index}`, { path }),
);
const result = comparePathToNodes(historicalPath, nodes);
const mapping = item(result, historicalPath);
expect(mapping).toMatchObject({
status: "unknown",
confidence: "unknown",
current_node_ids: [],
candidates: [
expect.objectContaining({
signals: expect.arrayContaining([
expect.objectContaining({ kind: "unicode-equivalent-path" }),
]),
}),
expect.objectContaining({
signals: expect.arrayContaining([
expect.objectContaining({ kind: "unicode-equivalent-path" }),
]),
}),
],
limitations: [
expect.stringContaining(
"filesystem paths with distinct Unicode spellings",
),
],
});
});
it("prefers a raw source-map path over canonically equivalent alternatives", () => {
const historicalPath = "src/cafe\u0301.ts";
const exact = sourceMapNode(
"exact",
"cafe\u0301.ts",
"../src/",
HASH.modified,
);
const equivalent = sourceMapNode(
"equivalent",
"caf\u00e9.ts",
"../src/",
HASH.removed,
);
const result = comparePathToNodes(historicalPath, [exact, equivalent]);
expect(item(result, historicalPath)).toMatchObject({
status: "modified",
current_node_ids: [exact.node_id],
});
});
it("finds Unicode-equivalent suffixes of absolute source-map URLs", () => {
const historicalPath = "src/caf\u00e9.ts";
const node = sourceMapNode(
"remote",
"cafe\u0301.ts",
"https://cdn.example/src/",
HASH.modified,
);
const result = comparePathToNodes(historicalPath, [node]);
expect(item(result, historicalPath)).toMatchObject({
status: "unknown",
current_node_ids: [],
candidates: [
expect.objectContaining({
current_node_id: node.node_id,
signals: expect.arrayContaining([
expect.objectContaining({
kind: "unicode-equivalent-path",
current_values: ["cdn.example/src/cafe\u0301.ts"],
}),
]),
}),
],
});
});
});
describe("source to bundle path syntax", () => {
it.each([
"src/modified#part.ts",
@@ -282,9 +367,60 @@ describe("source to bundle path syntax", () => {
}),
],
});
expect(
item(result, "src/modified.ts").candidates.flatMap(
({ signals }) => signals,
),
).not.toContainEqual(
expect.objectContaining({ kind: "unicode-equivalent-path" }),
);
});
});
describe("source-map resolver equivalence", () => {
it.each([
{
sourceRoot: "https://cdn.example/src/",
expectedSource: "https://cdn.example/src/a.js",
expectedResolution: { kind: "suffix", path: "cdn.example/src/a.js" },
},
{
sourceRoot: "https://cdn.example/src//",
expectedSource: "https://cdn.example/src/a.js",
expectedResolution: { kind: "suffix", path: "cdn.example/src/a.js" },
},
{
sourceRoot: "webpack://",
expectedSource: "webpack:///a.js",
expectedResolution: { kind: "suffix", path: "a.js" },
},
])(
"matches TraceMap for sourceRoot $sourceRoot",
({ sourceRoot, expectedSource, expectedResolution }) => {
const producerSource = new TraceMap(
{
version: 3,
file: "main.js",
sourceRoot,
sources: ["a.js"],
names: [],
mappings: "",
},
"file:///artifact/dist/main.js.map",
).resolvedSources[0];
const reference = sourceMapReference(
"a.js",
sourceRoot,
"dist/main.js.map",
);
expect(producerSource).toBe(expectedSource);
expect(reference.source_root).toBe(sourceRoot);
expect(reference.resolution).toEqual(expectedResolution);
},
);
});
describe("source to bundle complete metadata", () => {
it("accepts long source metadata and all reported limitations", () => {
const result = compareSourceToBundle({
@@ -328,6 +464,61 @@ describe("source to bundle complete metadata", () => {
});
});
const sourceMapNode = (
key: string,
sourceName: string,
sourceRoot: string,
digest: string,
) =>
createJavaScriptApplicationNode({
kind: "source-module",
identity: {
strategy: "source-map-original",
source_root: sourceRoot,
stability: "source-map-exact",
source_map_sha256: HASH.artifact,
original_source: sourceName,
source_sha256: digest,
},
observations: [
{
label: `${key} source-map spelling`,
source_map_reference: sourceMapReference(
sourceName,
sourceRoot,
`dist/${key}.map`,
),
properties: { content_available: true, content_sha256: digest },
evidence: artifactEvidence(HASH.artifact, `dist/${key}.map`),
},
],
});
const comparePathToNodes = (
sourcePath: string,
nodes: readonly ApplicationNode[],
) =>
compareSourceToBundle({
reference: historicalGraph("complete", [sourcePath]),
application: {
evidenceId: EVIDENCE_ID,
rootArtifactSha256: HASH.artifact,
graph: createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
root_node_ids: nodes.map(({ node_id: nodeId }) => nodeId),
nodes,
edges: [],
coverage: {
status: "complete",
truncated: false,
omitted_count: 0,
limits: [],
},
limitations: [],
}),
},
});
const historicalGraph = (
inventoryState: "complete" | "partial",
paths = [