mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 16:26:36 +00:00
fix(javascript): share source-map resolution and preserve path uncertainty
This commit is contained in:
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 = [
|
||||
|
||||
Reference in new issue
Block a user