mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 17:59:08 +00:00
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.
218 lines
7.3 KiB
TypeScript
218 lines
7.3 KiB
TypeScript
import { z } from "zod";
|
|
|
|
/** Artifact source declarations do not require generated-position decoding. */
|
|
type SourceMapRecord = Readonly<Record<string, unknown>>;
|
|
|
|
export interface ExternalSourceMapSection {
|
|
readonly section_path: readonly number[];
|
|
readonly url: string;
|
|
}
|
|
|
|
export interface InvalidSourceMapSection {
|
|
readonly section_path: readonly number[];
|
|
readonly reason: string;
|
|
}
|
|
|
|
export interface SourceMapLeaf {
|
|
readonly section_path: readonly number[];
|
|
readonly map: Readonly<Record<string, unknown>>;
|
|
}
|
|
|
|
export type SourceMapSection =
|
|
| { readonly kind: "embedded"; readonly map: SourceMapRecord }
|
|
| { readonly kind: "external"; readonly url: string }
|
|
| { readonly kind: "invalid"; readonly reason: string };
|
|
|
|
export type SourceMapSectionList =
|
|
| { readonly kind: "regular" }
|
|
| { readonly kind: "indexed"; readonly sections: readonly unknown[] }
|
|
| { readonly kind: "invalid"; readonly reason: string };
|
|
|
|
export type SourceMapEnvelope =
|
|
| { readonly status: "invalid"; readonly reason: string }
|
|
| {
|
|
readonly status: "included" | "unsupported" | "malformed";
|
|
readonly leaves: readonly SourceMapLeaf[];
|
|
readonly external_sections: readonly ExternalSourceMapSection[];
|
|
readonly invalid_sections: readonly InvalidSourceMapSection[];
|
|
};
|
|
|
|
/** ECMA-426 requires a string mapping payload on every regular map leaf. */
|
|
export const sourceMapMappingsSchema = z.string();
|
|
|
|
/**
|
|
* Return the JSON parsing view of retained source-map text. ECMA-426 permits
|
|
* HTTP responses to start with `)]}'`; the first line is transport framing.
|
|
* Keep the original text for artifact identity and Evidence provenance.
|
|
* https://tc39.es/ecma426/#sec-fetchsourcemap
|
|
*/
|
|
export const sourceMapJsonText = (text: string): string => {
|
|
const withoutBom = text.replace(/^\uFEFF/u, "");
|
|
if (!withoutBom.startsWith(")]}'")) return withoutBom;
|
|
const newline = withoutBom.search(/[\r\n]/u);
|
|
return newline === -1 ? "" : withoutBom.slice(newline);
|
|
};
|
|
|
|
const isRecord = (value: unknown): value is SourceMapRecord =>
|
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
|
|
/** Classify section ownership once; an external URL takes precedence over map. */
|
|
export const classifySourceMapSection = (
|
|
section: unknown,
|
|
): SourceMapSection => {
|
|
if (!isRecord(section))
|
|
return {
|
|
kind: "invalid",
|
|
reason: "Indexed source-map section must be an object.",
|
|
};
|
|
if (Object.prototype.hasOwnProperty.call(section, "url"))
|
|
return typeof section.url === "string"
|
|
? { kind: "external", url: section.url }
|
|
: {
|
|
kind: "invalid",
|
|
reason: "External source-map section URL must be a string.",
|
|
};
|
|
if (!isRecord(section.map))
|
|
return {
|
|
kind: "invalid",
|
|
reason:
|
|
"Indexed source-map section requires an embedded map or external URL.",
|
|
};
|
|
return { kind: "embedded", map: section.map };
|
|
};
|
|
|
|
/** A present `sections` member must be an array in every source-map consumer. */
|
|
export const classifySourceMapSections = (
|
|
map: unknown,
|
|
): SourceMapSectionList => {
|
|
if (!isRecord(map))
|
|
return { kind: "invalid", reason: "Source map must be an object." };
|
|
if (!Object.prototype.hasOwnProperty.call(map, "sections"))
|
|
return { kind: "regular" };
|
|
return Array.isArray(map.sections)
|
|
? { kind: "indexed", sections: map.sections }
|
|
: {
|
|
kind: "invalid",
|
|
reason: "Indexed source-map sections must be an array.",
|
|
};
|
|
};
|
|
|
|
/** Whether a parsed value has the version-3 map shape. */
|
|
const isVersion3Map = (value: unknown): value is SourceMapRecord =>
|
|
isRecord(value) && value.version === 3;
|
|
|
|
/**
|
|
* Collect valid embedded leaves and retain external or malformed section
|
|
* facts. A map with neither `mappings` nor `sections` is a leaf; callers
|
|
* decide whether that leaf has valid declarations and mappings.
|
|
*/
|
|
interface SectionPath {
|
|
readonly parent: SectionPath | null;
|
|
readonly index: number;
|
|
}
|
|
|
|
const materializeSectionPath = (path: SectionPath | null): number[] => {
|
|
const result: number[] = [];
|
|
for (let current = path; current !== null; current = current.parent)
|
|
result.push(current.index);
|
|
return result.reverse();
|
|
};
|
|
|
|
const compareSectionPaths = (
|
|
left: readonly number[],
|
|
right: readonly number[],
|
|
): number => {
|
|
for (let index = 0; index < Math.min(left.length, right.length); index += 1) {
|
|
const leftIndex = left[index] ?? 0;
|
|
const rightIndex = right[index] ?? 0;
|
|
if (leftIndex !== rightIndex) return leftIndex - rightIndex;
|
|
}
|
|
return left.length - right.length;
|
|
};
|
|
|
|
/** Walk embedded maps while retaining unsupported and malformed section facts. */
|
|
export const inspectSourceMapEnvelope = (root: unknown): SourceMapEnvelope => {
|
|
if (!isVersion3Map(root))
|
|
return { status: "invalid", reason: "Source map is not a version 3 map." };
|
|
const leaves: SourceMapLeaf[] = [];
|
|
const externalSections: {
|
|
readonly path: SectionPath;
|
|
readonly url: string;
|
|
}[] = [];
|
|
const invalidSections: {
|
|
readonly path: SectionPath | null;
|
|
readonly reason: string;
|
|
}[] = [];
|
|
const pending: {
|
|
readonly map: SourceMapRecord;
|
|
readonly path: SectionPath | null;
|
|
}[] = [{ map: root, path: null }];
|
|
while (pending.length > 0) {
|
|
const current = pending.pop();
|
|
if (current === undefined) break;
|
|
const map = current.map;
|
|
const sectionList = classifySourceMapSections(map);
|
|
if (sectionList.kind === "invalid") {
|
|
invalidSections.push({ path: current.path, reason: sectionList.reason });
|
|
continue;
|
|
}
|
|
if (sectionList.kind === "indexed") {
|
|
const children: {
|
|
readonly map: SourceMapRecord;
|
|
readonly path: SectionPath;
|
|
}[] = [];
|
|
for (let index = 0; index < sectionList.sections.length; index += 1) {
|
|
const section = sectionList.sections[index];
|
|
const path = { parent: current.path, index };
|
|
const classification = classifySourceMapSection(section);
|
|
if (classification.kind === "external")
|
|
externalSections.push({ path, url: classification.url });
|
|
else if (classification.kind === "invalid")
|
|
invalidSections.push({ path, reason: classification.reason });
|
|
else if (!isVersion3Map(classification.map))
|
|
invalidSections.push({
|
|
path,
|
|
reason: "Indexed source-map section map is not a version 3 map.",
|
|
});
|
|
else children.push({ map: classification.map, path });
|
|
}
|
|
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
const child = children[index];
|
|
if (child !== undefined) pending.push(child);
|
|
}
|
|
} else
|
|
leaves.push({
|
|
section_path: materializeSectionPath(current.path),
|
|
map,
|
|
});
|
|
}
|
|
const external = externalSections
|
|
.map(({ path, url }) => ({
|
|
section_path: materializeSectionPath(path),
|
|
url,
|
|
}))
|
|
.sort((left, right) =>
|
|
compareSectionPaths(left.section_path, right.section_path),
|
|
);
|
|
const invalid = invalidSections
|
|
.map(({ path, reason }) => ({
|
|
section_path: materializeSectionPath(path),
|
|
reason,
|
|
}))
|
|
.sort((left, right) =>
|
|
compareSectionPaths(left.section_path, right.section_path),
|
|
);
|
|
const status =
|
|
invalid.length > 0
|
|
? "malformed"
|
|
: external.length > 0
|
|
? "unsupported"
|
|
: "included";
|
|
return {
|
|
status,
|
|
leaves,
|
|
external_sections: external,
|
|
invalid_sections: invalid,
|
|
};
|
|
};
|