fix(javascript): match source-map originals that name parent directories (#1215)

* fix(javascript): match source-map originals that name parent directories

* fix(javascript): resolve parent segments against the source map's directory

* fix(javascript): resolve source originals against their map location

* fix(javascript): preserve URI and empty source map path semantics

* fix(javascript): separate source URL paths from opaque URI payloads

* fix(javascript): preserve filesystem names during URI classification

* style(javascript): format source URI presence guard

* fix(javascript): resolve source map references with URL semantics

* fix(javascript): preserve literal UNC source map paths

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
Jay Hemnaniandmorluto authored and GitHub committed 2026-10-09 18:04:25 +08:00
1 parent 24a3aa08d1
commit 889fb374df
4 files changed
+713 -75

No files matched your search

+76 -18
View File
@@ -9,6 +9,7 @@ import {
type SourceToBundleSignal,
} from "./sourceToBundleComparisonSchemas.js";
import { isDigest } from "../digests.js";
import { resolveJavaScriptSourceMapPath } from "./javascriptSourceMapPaths.js";
type SourceFile = Extract<
HistoricalSourceGraph["entries"][number],
@@ -23,6 +24,8 @@ type CurrentPathKind =
interface CurrentPath {
readonly kind: CurrentPathKind;
readonly value: string;
/** Artifact-relative map resolution identifies one path, not its suffixes. */
readonly allowSuffix: boolean;
}
interface CurrentProjection {
@@ -93,7 +96,9 @@ export const buildSourceToBundleCandidateIndex = (
for (const digest of projection.digests)
addIndexValue(byDigest, digest, projection.node.node_id);
for (const path of projection.paths) {
for (const suffix of pathSuffixes(path.value))
for (const suffix of path.allowSuffix
? pathSuffixes(path.value)
: [path.value])
addIndexValue(byPathSuffix, suffix, projection.node.node_id);
addIndexValue(
byBasename,
@@ -169,7 +174,22 @@ const projectCurrentNode = (node: ApplicationNode): CurrentProjection => {
if (node.identity.strategy === "source-map-original") {
if (node.identity.source_sha256 !== null)
digests.add(node.identity.source_sha256);
addPath(paths, "source-map-original", node.identity.original_source);
const mapLocations = node.observations.flatMap(({ evidence }) =>
evidence.extractor.operation === "parse-local-source-map" &&
evidence.location.available &&
evidence.location.value.kind === "artifact-path"
? [evidence.location.value.path]
: [],
);
if (mapLocations.length === 0)
addPath(paths, "source-map-original", node.identity.original_source);
for (const mapPath of mapLocations)
addPath(
paths,
"source-map-original",
node.identity.original_source,
mapPath,
);
}
if (node.identity.strategy === "canonical-path")
addPath(paths, "canonical-path", node.identity.path);
@@ -177,8 +197,18 @@ const projectCurrentNode = (node: ApplicationNode): CurrentProjection => {
const sourceDigest = observation.properties.source_sha256;
if (typeof sourceDigest === "string" && isDigest(sourceDigest))
digests.add(sourceDigest);
for (const key of PATH_PROPERTIES)
addJsonPath(paths, observation.properties[key]);
for (const key of PATH_PROPERTIES) {
const location = observation.evidence.location;
const mapPath =
node.identity.strategy === "source-map-original" &&
observation.evidence.extractor.operation === "parse-local-source-map" &&
(key === "source" || key === "original_source") &&
location.available &&
location.value.kind === "artifact-path"
? location.value.path
: undefined;
addJsonPath(paths, observation.properties[key], mapPath);
}
}
return {
node,
@@ -212,7 +242,10 @@ const candidateSignals = (
signals.push(signal("current-path-exact", source.path, exactPaths));
const suffixPaths = otherPaths
.filter(
({ value }) => value !== source.path && value.endsWith(`/${source.path}`),
({ value, allowSuffix }) =>
allowSuffix &&
value !== source.path &&
value.endsWith(`/${source.path}`),
)
.map(({ value }) => value);
if (suffixPaths.length > 0)
@@ -259,45 +292,70 @@ const matchingPaths = (
): string[] =>
currentPaths
.filter(
({ value }) => value === sourcePath || value.endsWith(`/${sourcePath}`),
({ value, allowSuffix }) =>
value === sourcePath ||
(allowSuffix && value.endsWith(`/${sourcePath}`)),
)
.map(({ value }) => value);
const addJsonPath = (paths: CurrentPath[], value: unknown): void => {
if (typeof value === "string") addPath(paths, "observation-path", value);
const addJsonPath = (
paths: CurrentPath[],
value: unknown,
mapPath?: string,
): void => {
if (typeof value === "string")
addPath(paths, "observation-path", value, mapPath);
};
const addPath = (
paths: CurrentPath[],
kind: CurrentPathKind,
raw: string,
mapPath?: string,
): void => {
if (kind === "source-map-original" || mapPath !== undefined) {
const resolved = resolveJavaScriptSourceMapPath(raw, mapPath);
if (resolved !== null)
paths.push({
kind,
value: resolved.value,
allowSuffix: resolved.scope === "suffix",
});
return;
}
const value = normalizeCurrentPath(raw);
if (value !== null) paths.push({ kind, value });
if (value !== null) paths.push({ kind, value, allowSuffix: true });
};
/** Matches a `scheme://...` URL prefix; bare filesystem paths keep `?`/`#`. */
const SCHEME_URL = /^[a-z][a-z0-9+.-]*:\/\//iu;
/** Matches a `scheme://...` URL prefix with any run of slashes, for stripping. */
const SCHEME_URL_PREFIX = /^[a-z][a-z0-9+.-]*:\/\/+/iu;
const normalizeCurrentPath = (raw: string): string | null => {
const withoutQuery = SCHEME_URL.test(raw)
? (raw.split(/[?#]/u, 1)[0] ?? "")
: raw;
const withoutScheme = withoutQuery.replace(SCHEME_URL_PREFIX, "");
const parts = withoutScheme
.replaceAll("\\", "/")
.split("/")
.filter((part) => part !== "" && part !== ".");
if (parts.length === 0 || parts.includes("..")) return null;
const portable = withoutQuery
.replace(SCHEME_URL_PREFIX, "")
.replaceAll("\\", "/");
const parts: string[] = [];
for (const part of portable.split("/")) {
if (part === "" || part === ".") continue;
if (part === "..") {
if (parts.length === 0) return null;
parts.pop();
} else parts.push(part);
}
if (parts.length === 0) return null;
return parts.join("/");
};
const uniquePaths = (paths: readonly CurrentPath[]): CurrentPath[] =>
[
...new Map(
paths.map((path) => [`${path.kind}\0${path.value}`, path]),
paths.map((path) => [
`${path.kind}\0${path.value}\0${String(path.allowSuffix)}`,
path,
]),
).values(),
].sort((left, right) =>
compareCodePoints(