mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
fix(javascript): retain partial indexed source map facts
Share section classification across declaration analysis and mapping parsing. Preserve external URLs, malformed section reasons, section paths and safe sibling declarations without fetching nested maps. Refs #5466. All 50 focused tests passed, including real CLI and stdio MCP regressions; typecheck and lint passed.
This commit is contained in:
@@ -11,10 +11,11 @@ import { projectJavaScriptModuleSemantics } from "../../domain/javascript/javasc
|
||||
import { parseJavaScriptSource } from "../../domain/javascript/javascriptSourceParser.js";
|
||||
import { hasValidSourceMapContents } from "../../domain/sourceMapContents.js";
|
||||
import {
|
||||
flattenSourceMapLeaves,
|
||||
inspectSourceMapEnvelope,
|
||||
sourceMapJsonText,
|
||||
sourceMapMappingsSchema,
|
||||
} from "../../domain/sourceMapEnvelope.js";
|
||||
import type { SourceMapLeaf } from "../../domain/sourceMapEnvelope.js";
|
||||
import type { JavaScriptStaticAnalysis } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
import { failedJavaScriptStaticAnalysis } from "../../domain/javascript/javascriptStaticAnalysisHelpers.js";
|
||||
import type {
|
||||
@@ -593,64 +594,109 @@ const parseSourceMap = (
|
||||
void cause;
|
||||
return invalidSourceMap(file, "Source map is not valid JSON.");
|
||||
}
|
||||
const maps = flattenSourceMapLeaves(value);
|
||||
if (maps === undefined)
|
||||
return invalidSourceMap(file, "Source map is not a version 3 map.");
|
||||
return collectSourceMapOriginals(file, maps);
|
||||
const envelope = inspectSourceMapEnvelope(value);
|
||||
if (envelope.status === "invalid")
|
||||
return invalidSourceMap(file, envelope.reason);
|
||||
const observation = collectSourceMapOriginals(file, envelope.leaves);
|
||||
if (envelope.status === "included") return observation;
|
||||
const envelopeLimitations = [
|
||||
...envelope.invalid_sections.map(
|
||||
({ section_path, reason }) =>
|
||||
`Indexed source-map section at [${section_path.join(",")}]: ${reason}`,
|
||||
),
|
||||
...envelope.external_sections.map(
|
||||
({ url }) => `External source-map section URL is not fetched: ${url}`,
|
||||
),
|
||||
];
|
||||
const limitation = [
|
||||
...(observation.status === "invalid" ? [observation.limitation] : []),
|
||||
...envelopeLimitations,
|
||||
].join("; ");
|
||||
return {
|
||||
...observation,
|
||||
status: "invalid",
|
||||
limitation,
|
||||
...(envelope.external_sections.length === 0
|
||||
? {}
|
||||
: { external_sections: envelope.external_sections }),
|
||||
...(envelope.invalid_sections.length === 0
|
||||
? {}
|
||||
: { invalid_sections: envelope.invalid_sections }),
|
||||
};
|
||||
};
|
||||
|
||||
const collectSourceMapOriginals = (
|
||||
file: JavaScriptArtifactFile,
|
||||
maps: readonly Readonly<Record<string, unknown>>[],
|
||||
leaves: readonly SourceMapLeaf[],
|
||||
): JavaScriptSourceMapObservation => {
|
||||
const sources: JavaScriptSourceMapOriginal[] = [];
|
||||
let limitation: string | null = null;
|
||||
const limitations: string[] = [];
|
||||
let declarationIndex = 0;
|
||||
for (const map of maps) {
|
||||
for (const { map, section_path } of leaves) {
|
||||
const location =
|
||||
section_path.length === 0
|
||||
? ""
|
||||
: ` in section [${section_path.join(",")}]`;
|
||||
const names = map.sources;
|
||||
if (!Array.isArray(names))
|
||||
return invalidSourceMap(file, "Source map has no sources array.");
|
||||
if (!Array.isArray(names)) {
|
||||
limitations.push(`Source map has no sources array${location}.`);
|
||||
continue;
|
||||
}
|
||||
const contents = map.sourcesContent;
|
||||
if (!hasValidSourceMapContents(contents))
|
||||
return invalidSourceMap(
|
||||
file,
|
||||
"Source map sourcesContent must be an array of strings or null entries.",
|
||||
limitations.push(
|
||||
`Source map sourcesContent must be an array of strings or null entries${location}.`,
|
||||
);
|
||||
if (map.sourceRoot != null && typeof map.sourceRoot !== "string")
|
||||
return invalidSourceMap(
|
||||
file,
|
||||
"Source map sourceRoot must be a string or null.",
|
||||
if (map.sourceRoot != null && typeof map.sourceRoot !== "string") {
|
||||
limitations.push(
|
||||
`Source map sourceRoot must be a string or null${location}.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const root = typeof map.sourceRoot === "string" ? map.sourceRoot : null;
|
||||
const sourceNames: (string | null)[] = [];
|
||||
for (const raw of names) {
|
||||
if (raw !== null && typeof raw !== "string")
|
||||
return invalidSourceMap(
|
||||
file,
|
||||
"Source map contains a non-string source name.",
|
||||
const sourceDeclarations: {
|
||||
readonly index: number;
|
||||
readonly name: string | null;
|
||||
}[] = [];
|
||||
for (const [index, raw] of names.entries()) {
|
||||
if (raw !== null && typeof raw !== "string") {
|
||||
limitations.push(
|
||||
`Source map contains a non-string source name at index ${String(index)}${location}.`,
|
||||
);
|
||||
sourceNames.push(raw);
|
||||
continue;
|
||||
}
|
||||
sourceDeclarations.push({ index, name: raw });
|
||||
}
|
||||
const references = resolveJavaScriptSourceMapReferences(
|
||||
sourceNames,
|
||||
sourceDeclarations.map(({ name }) => name),
|
||||
root,
|
||||
file.path,
|
||||
);
|
||||
for (const [index, reference] of references.entries()) {
|
||||
const rawContent = Array.isArray(contents) ? contents[index] : undefined;
|
||||
const declaration = sourceDeclarations[index];
|
||||
if (declaration === undefined) continue;
|
||||
const rawContent = Array.isArray(contents)
|
||||
? contents[declaration.index]
|
||||
: undefined;
|
||||
const content = typeof rawContent === "string" ? rawContent : null;
|
||||
sources.push({
|
||||
source_index: index,
|
||||
source_index: declaration.index,
|
||||
declaration_index: declarationIndex++,
|
||||
reference,
|
||||
content,
|
||||
content_sha256: content === null ? null : sha256(content),
|
||||
section_path,
|
||||
});
|
||||
}
|
||||
if (!sourceMapMappingsSchema.safeParse(map.mappings).success)
|
||||
limitation ??= "Source map has no mappings string.";
|
||||
limitations.push(`Source map has no mappings string${location}.`);
|
||||
}
|
||||
if (limitation !== null) return invalidSourceMap(file, limitation, sources);
|
||||
if (limitations.length > 0)
|
||||
return invalidSourceMap(
|
||||
file,
|
||||
[...new Set(limitations)].join("; "),
|
||||
sources,
|
||||
);
|
||||
return {
|
||||
path: file.path,
|
||||
sha256: file.sha256,
|
||||
|
||||
@@ -5,6 +5,10 @@ import type {
|
||||
} from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
import type { JavaScriptSemanticIr } from "../../domain/javascript/javascriptSemanticIr.js";
|
||||
import type { JavaScriptModuleSemanticIr } from "../../domain/javascript/javascriptModuleSemanticIr.js";
|
||||
import type {
|
||||
ExternalSourceMapSection,
|
||||
InvalidSourceMapSection,
|
||||
} from "../../domain/sourceMapEnvelope.js";
|
||||
import type {
|
||||
JavaScriptArtifactFile,
|
||||
JavaScriptArtifactFileKind,
|
||||
@@ -65,6 +69,8 @@ export interface JavaScriptHtmlInlineScriptsObservation {
|
||||
|
||||
/** One original source declared by a local source map. */
|
||||
export interface JavaScriptSourceMapOriginal {
|
||||
/** Indexed-map section containing this declaration; empty for regular maps. */
|
||||
readonly section_path: readonly number[];
|
||||
/** Index within the leaf map's sources and sourcesContent arrays. */
|
||||
readonly source_index: number;
|
||||
/** Index across the flattened map's leaf declarations. */
|
||||
@@ -86,6 +92,8 @@ export type JavaScriptSourceMapObservation = JavaScriptStructuredObservation &
|
||||
readonly status: "invalid";
|
||||
readonly sources: readonly JavaScriptSourceMapOriginal[];
|
||||
readonly limitation: string;
|
||||
readonly external_sections?: readonly ExternalSourceMapSection[];
|
||||
readonly invalid_sections?: readonly InvalidSourceMapSection[];
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -293,6 +293,33 @@ export const addJavaScriptArtifactFiles = (
|
||||
asset: target,
|
||||
...javascriptArtifactAnalysisScopes["source-map"],
|
||||
limitation: sourceMap.limitation,
|
||||
...(sourceMap.external_sections === undefined &&
|
||||
sourceMap.invalid_sections === undefined
|
||||
? {}
|
||||
: {
|
||||
details: {
|
||||
...(sourceMap.external_sections === undefined
|
||||
? {}
|
||||
: {
|
||||
external_sections: sourceMap.external_sections.map(
|
||||
({ section_path, url }) => ({
|
||||
section_path: [...section_path],
|
||||
url,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
...(sourceMap.invalid_sections === undefined
|
||||
? {}
|
||||
: {
|
||||
invalid_sections: sourceMap.invalid_sections.map(
|
||||
({ section_path, reason }) => ({
|
||||
section_path: [...section_path],
|
||||
reason,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const inlineScripts = inlineScriptsByPath.get(file.path);
|
||||
if (inlineScripts !== undefined)
|
||||
|
||||
Reference in New Issue
Block a user