fix(javascript): analyze source maps with empty source names
A source map whose `sources` contains `""` (a legal relative reference, emitted for virtual or generated modules) made the whole application analysis fail: the empty name became the `source-map-original` identity's `original_source`, which must be nonempty. Display it as `""` at the same accumulator seam; the observation's `properties.source` keeps the exact empty name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
11fc3b9815
commit
58ef8ac3aa
|
|
@ -7,9 +7,14 @@ import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory
|
|||
import { javascriptApplicationAnalysisResultSchema } from "../domain/javascriptApplicationAnalysis.js";
|
||||
import { analyzeJavaScriptApplication } from "./JavaScriptApplicationService.js";
|
||||
|
||||
const analyzeSource = async (source: string) => {
|
||||
const analyzeSource = async (
|
||||
source: string,
|
||||
extraFiles: Readonly<Record<string, string>> = {},
|
||||
) => {
|
||||
const inputPath = await createTestTempDirectory("rea-js-empty-literal-");
|
||||
await writeFile(join(inputPath, "app.js"), `${source}\n`);
|
||||
for (const [path, text] of Object.entries(extraFiles))
|
||||
await writeFile(join(inputPath, path), text);
|
||||
const result = await analyzeJavaScriptApplication({
|
||||
input_path: inputPath,
|
||||
format: "directory",
|
||||
|
|
@ -71,3 +76,30 @@ it("displays empty literals as nonempty text while retaining exact values", asyn
|
|||
})),
|
||||
).toEqual([{ label: '""', eventName: "" }]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ version: 3, sources: [""], names: [], mappings: "AAAA" },
|
||||
{
|
||||
version: 3,
|
||||
sections: [
|
||||
{
|
||||
offset: { line: 0, column: 0 },
|
||||
map: { version: 3, sources: [""], names: [], mappings: "AAAA" },
|
||||
},
|
||||
],
|
||||
},
|
||||
])("projects an empty source-map source name: %j", async (map) => {
|
||||
const output = await analyzeSource("//# sourceMappingURL=app.js.map", {
|
||||
"app.js.map": JSON.stringify(map),
|
||||
});
|
||||
const originals = output.graph.nodes
|
||||
.filter(({ kind }) => kind === "source-module")
|
||||
.map(({ identity, observations }) => ({
|
||||
originalSource:
|
||||
identity.strategy === "source-map-original"
|
||||
? identity.original_source
|
||||
: null,
|
||||
sources: observations.map(({ properties }) => properties.source),
|
||||
}));
|
||||
expect(originals).toEqual([{ originalSource: '""', sources: [""] }]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -68,25 +68,25 @@ const observationInput = (
|
|||
|
||||
/** Node fields whose display form is normalized before validation. */
|
||||
interface DisplayableNodeInput {
|
||||
readonly identity: { readonly strategy: string; readonly key?: string };
|
||||
readonly identity: {
|
||||
readonly strategy: string;
|
||||
readonly key?: string;
|
||||
readonly original_source?: string;
|
||||
};
|
||||
readonly observations: readonly { readonly label: string | null }[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Static findings can carry legal empty strings, such as `fetch("")` or
|
||||
* `require("")`. Labels and artifact-local keys must be nonempty, so display
|
||||
* the empty value as `""`; each observation keeps the exact value in its
|
||||
* properties.
|
||||
* Static findings can carry legal empty strings, such as `fetch("")`,
|
||||
* `require("")`, or a source map's `sources: [""]`. Labels, artifact-local
|
||||
* keys, and original source names must be nonempty, so display the empty value
|
||||
* as `""`; each observation keeps the exact value in its properties.
|
||||
*/
|
||||
const displayableNode = (
|
||||
input: DisplayableNodeInput,
|
||||
): DisplayableNodeInput => ({
|
||||
...input,
|
||||
identity:
|
||||
input.identity.strategy === "artifact-local-key" &&
|
||||
input.identity.key !== undefined
|
||||
? { ...input.identity, key: javascriptDisplayText(input.identity.key) }
|
||||
: input.identity,
|
||||
identity: displayableIdentity(input.identity),
|
||||
observations: input.observations.map((observation) => ({
|
||||
...observation,
|
||||
label:
|
||||
|
|
@ -95,3 +95,19 @@ const displayableNode = (
|
|||
: javascriptDisplayText(observation.label),
|
||||
})),
|
||||
});
|
||||
|
||||
const displayableIdentity = (
|
||||
identity: DisplayableNodeInput["identity"],
|
||||
): DisplayableNodeInput["identity"] => {
|
||||
if (identity.strategy === "artifact-local-key" && identity.key !== undefined)
|
||||
return { ...identity, key: javascriptDisplayText(identity.key) };
|
||||
if (
|
||||
identity.strategy === "source-map-original" &&
|
||||
identity.original_source !== undefined
|
||||
)
|
||||
return {
|
||||
...identity,
|
||||
original_source: javascriptDisplayText(identity.original_source),
|
||||
};
|
||||
return identity;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue