mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 09:21:16 +00:00
fix(javascript): retain unbound CommonJS semantic imports
Collect unbound literal CommonJS dependencies at the semantic module-link owner, retain null binding names and source provenance, and avoid duplicate links for calls already represented by bindings or re-exports. Preserve distinct nested calls, shadowing exclusions, dynamic binding uncertainty, and existing static asset references. Fixes #1793.
This commit is contained in:
1 parent
40d9c7b836
commit
3a17882cc4
10 files changed
+388
-33
No files matched your search
@@ -0,0 +1,154 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
||||
import {
|
||||
origin,
|
||||
topLevelBinding,
|
||||
} from "./javascriptSemanticAnalysis.fixture.js";
|
||||
|
||||
describe("CommonJS dependency links", () => {
|
||||
it.each([
|
||||
'require("dependency");',
|
||||
'void require("dependency");',
|
||||
'consume(require("dependency"));',
|
||||
'require("dependency").member;',
|
||||
'require("dependency")[key];',
|
||||
'const {} = require("dependency");',
|
||||
'const [] = require("dependency");',
|
||||
'let assigned; assigned = require("dependency");',
|
||||
'function run(value = require("dependency")) {}',
|
||||
'(require("dependency") as unknown);',
|
||||
])("retains an unbound dependency in %s", (source) => {
|
||||
const ir = analyzeJavaScriptSemantics(source);
|
||||
expect(ir.moduleLinks).toEqual([
|
||||
expect.objectContaining({
|
||||
kind: "require",
|
||||
specifier: "dependency",
|
||||
importedName: null,
|
||||
localName: null,
|
||||
exportedName: null,
|
||||
callableId: null,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'function run(require) { require("dependency"); }',
|
||||
'require("dependency"); const require = loader;',
|
||||
'function run() { require("dependency"); var require; }',
|
||||
'with (environment) { require("dependency"); }',
|
||||
"require(dynamicSpecifier);",
|
||||
])("does not invent a static global dependency in %s", (source) => {
|
||||
expect(analyzeJavaScriptSemantics(source).moduleLinks).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps bindings and re-exports without extra dependency links", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const bound = (require("bound") as unknown);
|
||||
const { left, right } = require("destructured");
|
||||
module.exports = require("forwarded");
|
||||
exports.member = require("member").value;
|
||||
exports.first = exports.second = require("chained");
|
||||
`);
|
||||
expect(
|
||||
ir.moduleLinks.map(({ kind, specifier, localName, exportedName }) => ({
|
||||
kind,
|
||||
specifier,
|
||||
localName,
|
||||
exportedName,
|
||||
})),
|
||||
).toEqual([
|
||||
{
|
||||
kind: "require",
|
||||
specifier: "bound",
|
||||
localName: "bound",
|
||||
exportedName: null,
|
||||
},
|
||||
{
|
||||
kind: "require",
|
||||
specifier: "destructured",
|
||||
localName: "left",
|
||||
exportedName: null,
|
||||
},
|
||||
{
|
||||
kind: "require",
|
||||
specifier: "destructured",
|
||||
localName: "right",
|
||||
exportedName: null,
|
||||
},
|
||||
{
|
||||
kind: "commonjs-export",
|
||||
specifier: "forwarded",
|
||||
localName: null,
|
||||
exportedName: "default",
|
||||
},
|
||||
{
|
||||
kind: "commonjs-export",
|
||||
specifier: "member",
|
||||
localName: null,
|
||||
exportedName: "member",
|
||||
},
|
||||
{
|
||||
kind: "commonjs-export",
|
||||
specifier: "chained",
|
||||
localName: null,
|
||||
exportedName: "first",
|
||||
},
|
||||
{
|
||||
kind: "commonjs-export",
|
||||
specifier: "chained",
|
||||
localName: null,
|
||||
exportedName: "second",
|
||||
},
|
||||
]);
|
||||
expect(
|
||||
ir.bindings.find(({ name }) => name === "bound")?.provenance.origins,
|
||||
).toEqual([{ specifier: "bound", importedPath: [] }]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'require("outer", require("inner"));',
|
||||
'const bound = require("outer", require("inner"));',
|
||||
'module.exports = require("outer", require("inner"));',
|
||||
])("retains a distinct nested call in %s", (source) => {
|
||||
const links = analyzeJavaScriptSemantics(source).moduleLinks;
|
||||
expect(links.map(({ specifier }) => specifier)).toEqual(["outer", "inner"]);
|
||||
expect(links[1]).toMatchObject({
|
||||
kind: "require",
|
||||
importedName: null,
|
||||
localName: null,
|
||||
exportedName: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves unshadowed require origins through aliases and assignments", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
const { ipcMain: bus } = require("electron");
|
||||
const forwarded = bus;
|
||||
let assigned;
|
||||
assigned = require("electron").ipcMain;
|
||||
{ const require = localLoader; require("./local.js"); }
|
||||
`);
|
||||
for (const name of ["bus", "forwarded"])
|
||||
expect(origin(topLevelBinding(ir, name))).toEqual({
|
||||
specifier: "electron",
|
||||
importedPath: ["ipcMain"],
|
||||
});
|
||||
expect(topLevelBinding(ir, "assigned").provenance).toMatchObject({
|
||||
status: "ambiguous",
|
||||
origins: [{ specifier: "electron", importedPath: ["ipcMain"] }],
|
||||
});
|
||||
expect(ir.moduleLinks.filter(({ kind }) => kind === "require")).toEqual([
|
||||
expect.objectContaining({
|
||||
specifier: "electron",
|
||||
importedName: "ipcMain",
|
||||
localName: "bus",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
specifier: "electron",
|
||||
importedName: null,
|
||||
localName: null,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
Reference in new issue
Block a user