fix(sourcemaps)!: preserve nullable source declarations

Accept absent map-level sourcesContent and preserve null source-name slots alongside known originals and aligned content. Represent unnamed local declarations explicitly, retain leaf-local and flattened indices, and preserve declared indexed-section order across consumers.

Account for added indices before browser output allocation. Accept legacy retained rows without indices. Consolidate public CLI/MCP source-map fixtures and update the stale named-source-only assertion.

BREAKING CHANGE: Browser source-map original and mapping rows expose null for unavailable source names instead of inventing an unknown-source string. New index fields are optional when reading older retained evidence.

Refs #2023, #2024
This commit is contained in:
morluto
2026-10-12 11:10:49 +08:00
parent c0904b4c1a
commit ee50ed121e
13 changed files with 361 additions and 70 deletions
@@ -539,6 +539,7 @@ const collectSourceMapOriginals = (
maps: readonly Readonly<Record<string, unknown>>[],
): JavaScriptSourceMapObservation => {
const sources: JavaScriptSourceMapOriginal[] = [];
let declarationIndex = 0;
for (const map of maps) {
const names = map.sources;
if (!Array.isArray(names))
@@ -556,7 +557,7 @@ const collectSourceMapOriginals = (
);
const root = typeof map.sourceRoot === "string" ? map.sourceRoot : null;
for (const [index, raw] of names.entries()) {
if (typeof raw !== "string")
if (raw !== null && typeof raw !== "string")
return invalidSourceMap(
file,
"Source map contains a non-string source name.",
@@ -564,7 +565,12 @@ const collectSourceMapOriginals = (
const rawContent = Array.isArray(contents) ? contents[index] : undefined;
const content = typeof rawContent === "string" ? rawContent : null;
sources.push({
reference: resolveJavaScriptSourceMapReference(raw, root, file.path),
source_index: index,
declaration_index: declarationIndex++,
reference:
typeof raw === "string"
? resolveJavaScriptSourceMapReference(raw, root, file.path)
: null,
content,
content_sha256: content === null ? null : sha256(content),
});
@@ -47,7 +47,11 @@ export interface JavaScriptHtmlScriptObservation {
/** One original source declared by a local source map. */
export interface JavaScriptSourceMapOriginal {
readonly reference: JavaScriptSourceMapReference;
/** Index within the leaf map's sources and sourcesContent arrays. */
readonly source_index: number;
/** Index across the flattened map's leaf declarations. */
readonly declaration_index: number;
readonly reference: JavaScriptSourceMapReference | null;
readonly content: string | null;
readonly content_sha256: string | null;
}
@@ -1,6 +1,7 @@
import {
addArtifactContainsEdge,
addStaticInferenceEdge,
artifactLocalIdentity,
createElectronRoleNode,
linkElectronRoleToAsset,
type JavaScriptArtifactGraphContext,
@@ -118,22 +119,34 @@ export const addJavaScriptSourceMapOriginals = (
if (mapFile === undefined || mapNode === undefined) continue;
const coverage = completeApplicationCoverage();
for (const original of sourceMap.sources) {
const reference = original.reference;
const node = context.accumulator.addNode({
kind: "source-module",
identity: {
strategy: "source-map-original",
stability: "source-map-exact",
source_map_sha256: mapFile.sha256,
original_source: original.reference.source_name,
source_root: original.reference.source_root,
source_sha256: original.content_sha256,
},
kind: reference === null ? "unknown" : "source-module",
identity:
reference === null
? artifactLocalIdentity(
mapFile.sha256,
"source-map-unnamed-declaration",
String(original.declaration_index),
)
: {
strategy: "source-map-original",
stability: "source-map-exact",
source_map_sha256: mapFile.sha256,
original_source: reference.source_name,
source_root: reference.source_root,
source_sha256: original.content_sha256,
},
observations: [
{
label: original.reference.source_name || null,
source_map_reference: original.reference,
label: reference?.source_name || null,
source_map_reference: reference,
properties: {
source_index: original.source_index,
declaration_index: original.declaration_index,
source_name_available: reference !== null,
content_available: original.content !== null,
content_sha256: original.content_sha256,
},
evidence: artifactObservationEvidence({
sha256: mapFile.sha256,
@@ -151,8 +164,10 @@ export const addJavaScriptSourceMapOriginals = (
target_node_id: node.node_id,
relation: "contains",
properties: {
original_source: original.reference.source_name,
source_root: original.reference.source_root,
source_index: original.source_index,
declaration_index: original.declaration_index,
original_source: reference?.source_name ?? null,
source_root: reference?.source_root ?? null,
},
evidence: artifactObservationEvidence({
sha256: mapFile.sha256,