mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
fix(javascript): track public CommonJS export receiver lifetimes (#3567)
* fix(javascript): track public CommonJS export receiver lifetimes * fix(javascript): retain exports across uncertain identity effects
This commit is contained in:
@@ -393,7 +393,105 @@ describe("JavaScript semantic analysis: structure 2", () => {
|
||||
]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("CommonJS receiver lifetimes", () => {
|
||||
it.each([
|
||||
'Object.defineProperty(module.exports, "other", { value: exports });',
|
||||
"Object.assign(module.exports, { other: exports });",
|
||||
])(
|
||||
"keeps the receiver identity intact when mutating its contents: %s",
|
||||
(mutation) => {
|
||||
const ir = analyzeJavaScriptSemantics(
|
||||
`exports.parse = () => 1; ${mutation}`,
|
||||
);
|
||||
const exported = ir.moduleLinks.find(
|
||||
({ exportedName }) => exportedName === "parse",
|
||||
);
|
||||
expect(exported?.callableId).toEqual(expect.any(String));
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
"module.exports = {}; exports.parse = () => 1;",
|
||||
"exports = {}; exports.parse = () => 1;",
|
||||
"exports.parse = () => 1; module.exports = {};",
|
||||
"module.exports.parse = () => 1; module.exports = [];",
|
||||
'module.exports = {}; exports.status = "enabled";',
|
||||
"module.exports = exports = {}; module.exports = {}; exports.parse = () => 1;",
|
||||
'module.exports = {}; Reflect.set(module, "other", exports); exports.parse = () => 1;',
|
||||
'module.exports = {}; Object.defineProperty(globalThis.module, "exports", { value: exports }); exports.parse = () => 1;',
|
||||
"module.exports = {}; let wrapper = module; wrapper = {}; Object.assign(wrapper, { exports }); exports.parse = () => 1;",
|
||||
])(
|
||||
"excludes a detached CommonJS receiver across statements: %s",
|
||||
(source) => {
|
||||
const ir = analyzeJavaScriptSemantics(source);
|
||||
expect(
|
||||
ir.moduleLinks.filter(
|
||||
({ exportedName }) =>
|
||||
exportedName === "parse" || exportedName === "status",
|
||||
),
|
||||
).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
"exports.parse = () => 1;",
|
||||
"module.exports = {}; module.exports.parse = () => 1;",
|
||||
"module.exports = {}; exports = module.exports; exports.parse = () => 1;",
|
||||
"module.exports = exports = {}; exports.parse = () => 1;",
|
||||
"exports = module.exports = {}; exports.parse = () => 1;",
|
||||
"module.exports = module.exports; exports.parse = () => 1;",
|
||||
"module.exports = external; exports = module.exports; exports.parse = () => 1;",
|
||||
"module.exports = external; module.exports.parse = () => 1;",
|
||||
"exports.parse = (exports = {} , () => 1);",
|
||||
"exports.parse = (module.exports = {}); module.exports = exports;",
|
||||
"(module.exports = {}, exports = module.exports, exports.parse = () => 1);",
|
||||
])("retains a connected CommonJS receiver: %s", (source) => {
|
||||
expect(analyzeJavaScriptSemantics(source).moduleLinks).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "commonjs-export",
|
||||
exportedName: "parse",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"module.exports = external; exports.parse = () => 1;",
|
||||
"if (flag) module.exports = {}; exports.parse = () => 1;",
|
||||
"flag && (module.exports = {}); exports.parse = () => 1;",
|
||||
"holder ||= (module.exports = {}); exports.parse = () => 1;",
|
||||
"invoke?.(module.exports = {}); exports.parse = () => 1;",
|
||||
"const { value = (module.exports = {}) } = source; exports.parse = () => 1;",
|
||||
"module[key] = {}; exports.parse = () => 1;",
|
||||
"exports.parse = () => 1; if (flag) module.exports = {};",
|
||||
"function replace() { module.exports = {}; } exports.parse = () => 1; replace();",
|
||||
"function replace() { exports = {}; } replace(); exports.parse = () => 1;",
|
||||
"module.exports = {}; function install() { exports.parse = () => 1; } install();",
|
||||
"for (exports of values) {} exports.parse = () => 1;",
|
||||
"({ exports } = external); exports.parse = () => 1;",
|
||||
"module = {}; module.exports = {}; module.exports.parse = () => 1;",
|
||||
'const holder = {}; ({ [(module.exports = exports, "x")]: holder.x } = (module.exports = {})); exports.parse = () => 1;',
|
||||
'const { [(module.exports = exports, "x")]: value } = (module.exports = {}); exports.parse = () => 1;',
|
||||
"const [value = (module.exports = exports)] = (module.exports = []); exports.parse = () => 1;",
|
||||
"module.exports = {}; Object.assign(module, { exports }); exports.parse = () => 1;",
|
||||
'module.exports = {}; Object.defineProperty(module, "exports", { value: exports }); exports.parse = () => 1;',
|
||||
'module.exports = {}; Reflect.set(module, "exports", exports); exports.parse = () => 1;',
|
||||
"module.exports = {}; const assign = Object.assign; assign.call(null, module, { exports }); exports.parse = () => 1;",
|
||||
"module.exports = {}; const wrapper = module; Object.assign(wrapper, { exports }); exports.parse = () => 1;",
|
||||
])("keeps uncertain CommonJS receiver identity unresolved: %s", (source) => {
|
||||
expect(analyzeJavaScriptSemantics(source).moduleLinks).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "commonjs-export",
|
||||
exportedName: "parse",
|
||||
callableId: null,
|
||||
localName: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("JavaScript semantic analysis: write references", () => {
|
||||
it("classifies destructuring and loop targets as writes, not reads", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
let a, b, c;
|
||||
|
||||
Reference in New Issue
Block a user