Pass typed map objects into the codec and reuse validated TraceMap instances for regular and indexed lookups. Remove whole-map serialization, duplicate leaf decoding, an array-copy wrapper, and the unused offset-validation mode in artifact declaration collection. Validation: source-map and browser consumer suites (126 tests), declaration tests, and artifact reconstruction boundaries pass. Section overlap checks and raw source declarations remain intact.
223 lines
7.3 KiB
TypeScript
223 lines
7.3 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { createRequire } from "node:module";
|
|
import { getHeapStatistics } from "node:v8";
|
|
import {
|
|
AnyMap,
|
|
TraceMap,
|
|
decodedMappings,
|
|
type SourceMapSegment,
|
|
} from "@jridgewell/trace-mapping";
|
|
import { z } from "zod";
|
|
import { javaScriptRuntime } from "../../domain/runtimeVersion.js";
|
|
import { javaScriptWorkerMemoryLimitBytes } from "../../process/JavaScriptWorkerMemoryLimit.js";
|
|
import { webSourceOffset } from "../../domain/webSourcePosition.js";
|
|
import {
|
|
webSourceMapReportSchema,
|
|
type WebSourceMapReport,
|
|
WEB_SOURCE_MAP_LIMITS,
|
|
} from "../../domain/webSourceLocation.js";
|
|
import type { WebSourcePosition } from "../../domain/webSourcePosition.js";
|
|
import {
|
|
inspectSourceMap,
|
|
SourceMapFormatFailure,
|
|
type SourceMapSourceDeclaration,
|
|
} from "./SourceMapFormat.js";
|
|
import {
|
|
decodeValidatedSourceMapLeaves,
|
|
isBeforeSourceMapLeafStop,
|
|
} from "./DecodedSourceMap.js";
|
|
|
|
/** Decode with the pinned upstream codec; preserve every equal-position candidate. */
|
|
export const traceSourceMap = (
|
|
text: string,
|
|
url: string,
|
|
position: WebSourcePosition,
|
|
): WebSourceMapReport => {
|
|
const inspected = inspectSourceMap(text);
|
|
const decodedLeaves = decodeValidatedSourceMapLeaves(inspected.leaves, url);
|
|
for (const { leaf, rows } of decodedLeaves)
|
|
for (const [line, row] of rows.entries())
|
|
for (const segment of row)
|
|
if (!isBeforeSourceMapLeafStop(leaf, line, segment[0]))
|
|
throw new SourceMapFormatFailure(
|
|
"format",
|
|
"Embedded source-map mappings overlap the following section boundary.",
|
|
);
|
|
let map: TraceMap;
|
|
if (inspected.format === "regular") {
|
|
const decoded = decodedLeaves[0];
|
|
if (decoded === undefined)
|
|
throw new SourceMapFormatFailure(
|
|
"format",
|
|
"Regular source map has no validated leaf.",
|
|
);
|
|
map = decoded.map;
|
|
} else {
|
|
map = new AnyMap(
|
|
{
|
|
version: 3,
|
|
sections: decodedLeaves.map(({ leaf, map: sectionMap }) => ({
|
|
offset: leaf.offset,
|
|
map: sectionMap,
|
|
})),
|
|
},
|
|
url,
|
|
);
|
|
}
|
|
const rows =
|
|
inspected.format === "regular"
|
|
? (decodedLeaves[0]?.rows ?? [])
|
|
: decodedMappings(map);
|
|
validateFlattenedRows(inspected, rows, map);
|
|
const matched = greatestPosition(rows, position);
|
|
const segments =
|
|
matched === null
|
|
? []
|
|
: (rows[matched.line - 1] ?? []).filter(
|
|
(segment) => segment[0] === matched.column,
|
|
);
|
|
let bytes = 0;
|
|
const matches: WebSourceMapReport["matches"] = [];
|
|
for (const segment of segments) {
|
|
const match = originalMatch(segment, map, inspected.declarations);
|
|
bytes += Buffer.byteLength(JSON.stringify(match)) + 1;
|
|
if (bytes > WEB_SOURCE_MAP_LIMITS.outputBytes)
|
|
throw new SourceMapFormatFailure(
|
|
"limit",
|
|
"Complete source-map point evidence exceeds the 32 MiB output budget; no partial evidence was returned.",
|
|
);
|
|
matches.push(match);
|
|
}
|
|
const metadata: unknown = createRequire(import.meta.url)(
|
|
"@jridgewell/trace-mapping/package.json",
|
|
);
|
|
const version = z
|
|
.object({ version: z.string().min(1) })
|
|
.parse(metadata).version;
|
|
const runtime = javaScriptRuntime(process.versions);
|
|
return webSourceMapReportSchema.parse({
|
|
source_map_sha256: createHash("sha256").update(text).digest("hex"),
|
|
url_context: url,
|
|
engine: { id: "jridgewell-trace-mapping", version },
|
|
// Bun reports V8-shaped heap statistics but ignores the codec's heap flags.
|
|
// Its launcher applies the same runtime policy, and the parent verifies it.
|
|
runtime:
|
|
runtime.id === "node"
|
|
? {
|
|
id: "node-v8",
|
|
version: process.version,
|
|
v8_heap_limit_bytes: getHeapStatistics().heap_size_limit,
|
|
}
|
|
: {
|
|
id: "bun",
|
|
version: runtime.version,
|
|
node_compatibility_version: runtime.nodeCompatibilityVersion,
|
|
v8_heap_limit_bytes: null,
|
|
process_memory_limit_bytes: javaScriptWorkerMemoryLimitBytes(
|
|
WEB_SOURCE_MAP_LIMITS.codecHeapMiB,
|
|
),
|
|
},
|
|
format: inspected.format,
|
|
reported_file: inspected.reportedFile,
|
|
lookup: {
|
|
requested: position,
|
|
matched_generated_position: matched,
|
|
semantics:
|
|
"greatest generated position <= requested, across lines; all equal-position segments",
|
|
location_units: "one-based lines; zero-based UTF-16 columns",
|
|
},
|
|
matches,
|
|
diagnostics: [],
|
|
});
|
|
};
|
|
|
|
const validateFlattenedRows = (
|
|
inspected: ReturnType<typeof inspectSourceMap>,
|
|
rows: ReturnType<typeof decodedMappings>,
|
|
map: ReturnType<typeof AnyMap>,
|
|
): void => {
|
|
for (const row of rows)
|
|
for (const segment of row) {
|
|
if (
|
|
(segment.length !== 1 && segment[1] >= inspected.declarations.length) ||
|
|
(segment.length === 5 && segment[4] >= map.names.length)
|
|
)
|
|
throw new SourceMapFormatFailure(
|
|
"format",
|
|
"Decoded mapping has invalid component count, position, source index or name index.",
|
|
);
|
|
}
|
|
};
|
|
|
|
const greatestPosition = (
|
|
rows: ReturnType<typeof decodedMappings>,
|
|
position: WebSourcePosition,
|
|
): WebSourcePosition | null => {
|
|
for (
|
|
let line = Math.min(position.line - 1, rows.length - 1);
|
|
line >= 0;
|
|
line -= 1
|
|
) {
|
|
let column: number | undefined;
|
|
for (const segment of rows[line] ?? [])
|
|
if (line < position.line - 1 || segment[0] <= position.column)
|
|
column = segment[0];
|
|
if (column !== undefined) return { line: line + 1, column };
|
|
}
|
|
return null;
|
|
};
|
|
const originalMatch = (
|
|
segment: Readonly<SourceMapSegment>,
|
|
map: TraceMap,
|
|
declarations: readonly SourceMapSourceDeclaration[],
|
|
): WebSourceMapReport["matches"][number] => {
|
|
if (segment.length === 1) return { state: "generated-only" };
|
|
const declaration = declarations[segment[1]];
|
|
if (declaration === undefined)
|
|
throw new SourceMapFormatFailure(
|
|
"format",
|
|
"Mapping references an absent source declaration.",
|
|
);
|
|
const original = { line: segment[2] + 1, column: segment[3] };
|
|
const offset =
|
|
declaration.content === null
|
|
? undefined
|
|
: webSourceOffset(declaration.content, original);
|
|
return {
|
|
state: "mapped",
|
|
flattened_source_index: segment[1],
|
|
section_path: [...declaration.sectionPath],
|
|
source_index: declaration.sourceIndex,
|
|
reported_source: declaration.source,
|
|
reported_source_root: declaration.root,
|
|
resolved_url:
|
|
declaration.source === null
|
|
? null
|
|
: (map.resolvedSources[segment[1]] ?? null),
|
|
ignored: declaration.ignored,
|
|
content:
|
|
declaration.content === null
|
|
? { state: "unavailable", reason: "sourcesContent-not-retained" }
|
|
: {
|
|
state: "embedded",
|
|
text: declaration.content,
|
|
utf8_sha256: createHash("sha256")
|
|
.update(declaration.content)
|
|
.digest("hex"),
|
|
utf8_bytes: Buffer.byteLength(declaration.content),
|
|
representation: "UTF-8 encoding of decoded sourcesContent text",
|
|
},
|
|
original_position: {
|
|
...original,
|
|
offset: offset ?? null,
|
|
content_position:
|
|
declaration.content === null
|
|
? "content-unavailable"
|
|
: offset === undefined
|
|
? "outside-embedded-text"
|
|
: "verified-in-embedded-text",
|
|
},
|
|
name: segment.length === 5 ? (map.names[segment[4]] ?? null) : null,
|
|
};
|
|
};
|