rea/src/domain/javascript/javascriptSemanticAnalysis....

155 lines
5.6 KiB
TypeScript

import * as t from "@babel/types";
import { expect, it } from "vitest";
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
import { onlyBinding, origin } from "./javascriptSemanticAnalysis.fixture.js";
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
import { calleeName } from "./javascriptStaticAnalysisHelpers.js";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
const memberChain = ".next".repeat(12_000);
it("retains native addon observations through a deep member initializer", () => {
const analysis = analyzeJavaScriptStaticSource(
`const value = require("./addon.node")${memberChain}.last[""];`,
);
expect(analysis.electron.native_addon_bindings).toEqual([
expect.objectContaining({ specifier: "./addon.node", members: ["last"] }),
]);
expect(analysis.parse_error_count).toBe(0);
});
it("keeps a deep non-native initializer outside native addon observations", () => {
const analysis = analyzeJavaScriptStaticSource(
`const value = root${memberChain}.last;`,
);
expect(analysis.electron.native_addon_bindings).toEqual([]);
expect(analysis.parse_status).toBe("complete");
});
it("retains unknown value diagnostics and references after a deep member initializer", () => {
const ir = analyzeJavaScriptSemantics(
`const value = root${memberChain}.last;`,
);
expect(ir.coverage).toEqual({ status: "complete", omittedCount: 0 });
expect(onlyBinding(ir, "value")).toMatchObject({
value: { status: "unknown", reason: "Cannot project last from unknown." },
provenance: { status: "unknown", reason: "Unbound identifier root." },
});
expect(ir.references.map(({ name, role }) => ({ name, role }))).toEqual([
{ name: "root", role: "read" },
]);
});
it.each([
`const value = require("./dependency.js")${memberChain}[""].last;`,
`const dependency = require("./dependency.js"); const value = dependency${memberChain}[""].last;`,
`const value = require("./dependency.js")?.next${memberChain}[""].last;`,
])("preserves the full literal module path (case %#)", (source) => {
const ir = analyzeJavaScriptSemantics(source);
const path = origin(onlyBinding(ir, "value"));
expect(path.specifier).toBe("./dependency.js");
expect(path.importedPath).toEqual([
...(source.includes("?.next") ? ["next"] : []),
...Array.from({ length: 12_000 }, () => "next"),
"",
"last",
]);
expect(ir.coverage.status).toBe("complete");
});
it("retains a deep CommonJS re-export path without inventing dynamic module links", () => {
const ir = analyzeJavaScriptSemantics(`
module.exports.result = require("./dependency.js")${memberChain}.last;
const dynamic = require("./other.js")${memberChain}[key].last;
`);
expect(ir.moduleLinks).toEqual([
expect.objectContaining({
kind: "commonjs-export",
specifier: "./dependency.js",
importedName: "last",
exportedName: "result",
}),
]);
expect(onlyBinding(ir, "dynamic").provenance).toMatchObject({
status: "unknown",
reason: "Dynamic provenance member.",
});
});
it("invalidates a deep mutation while retaining unrelated literal properties", () => {
const ir = analyzeJavaScriptSemantics(`
const root = { untouched: "retained" };
root${memberChain}.last = 1;
const value = root.untouched;
`);
expect(onlyBinding(ir, "root").value).toEqual({
status: "object",
properties: [
{ name: "untouched", value: { status: "literal", value: "retained" } },
],
unknownProperties: true,
omittedProperties: null,
});
expect(onlyBinding(ir, "value").value).toEqual({
status: "literal",
value: "retained",
});
});
it("retains the complete location and unresolved target of a deep call", () => {
const callee = `root${memberChain}.last`;
const ir = analyzeJavaScriptSemantics(`${callee}();`);
expect(ir.callSites).toEqual([
expect.objectContaining({
kind: "call",
resolution: "unresolved",
calleeCallableIds: [],
calleeLocation: {
start: { line: 1, column: 0 },
end: { line: 1, column: callee.length },
},
}),
]);
});
it("retains deep default values without confusing their reads with parameter bindings", () => {
const ir = analyzeJavaScriptSemantics(
`export function inspect(value = root${memberChain}.last) { return value; }`,
);
expect(ir.references.map(({ name, role }) => ({ name, role }))).toEqual([
{ name: "root", role: "read" },
{ name: "value", role: "read" },
]);
expect(ir.coverage.status).toBe("complete");
});
it("keeps a shadowed require local through a deep member initializer", () => {
const ir = analyzeJavaScriptSemantics(
`function inspect(require) { const value = require("./dependency.js")${memberChain}.last; return value; }`,
);
expect(ir.moduleLinks).toEqual([]);
expect(onlyBinding(ir, "value").provenance.origins).toEqual([]);
});
it.each([
["root.first.last()", "root.first.last"],
['root[""].last()', "root..last"],
['("")[""].last()', "last"],
['("")[""]()', ""],
["root[0].last()", "root.0.last"],
["root[key].last()", "root.[computed@5].last"],
["root?.first.last()", "root.first.last"],
[`root${memberChain}.last()`, `root${memberChain}.last`],
])("preserves exact callee syntax (case %#)", (source, expected) => {
const file = parseJavaScriptSource(source);
const statement = file?.program.body[0];
if (
!t.isExpressionStatement(statement) ||
(!t.isCallExpression(statement.expression) &&
!t.isOptionalCallExpression(statement.expression))
)
throw new Error("Expected parsed call expression");
expect(calleeName(statement.expression.callee)).toBe(expected);
});