From d35881fb27a0bb63133c38c5bdf38955b992c50b Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Sat, 23 Dec 2023 02:52:51 +0000 Subject: [PATCH] Capture `module` objects when function declaration called `module` at top level [fix] Fixes #566. --- lib/init/index.js | 9 +++------ lib/init/module.js | 12 +++++++++++- lib/instrument/modify.js | 3 +-- test/commonjs.test.js | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/init/index.js b/lib/init/index.js index 49e37af8..db21c89d 100644 --- a/lib/init/index.js +++ b/lib/init/index.js @@ -10,8 +10,7 @@ // Imports const createTracker = require('./tracker.js'), getScopeId = require('./getScopeId.js'), - internal = require('../shared/internal.js'), - {COMMON_JS_MODULE} = require('../shared/constants.js'); + internal = require('../shared/internal.js'); // Exports @@ -26,15 +25,13 @@ const {globals, functions: specialFunctions} = internal; * Additional methods which are universal are attached to `livepack_getScopeId`. * * @param {string} filename - File path - * @param {Object} module - `module` object from file * @param {Function} require - `require` function from file * @param {number} nextBlockId - Next block ID * @param {number} prefixNum - Internal vars prefix num * @returns {Array} - Array containing tracker and `getScopeId` functions */ -module.exports = (filename, module, require, nextBlockId, prefixNum) => { - // Record `module` + `require` - globals.set(module, {type: COMMON_JS_MODULE, parent: null, key: 'module'}); +module.exports = (filename, require, nextBlockId, prefixNum) => { + // Record `require` specialFunctions.set(require, {type: 'require', path: filename}); specialFunctions.set(require.resolve, {type: 'require', path: filename}); specialFunctions.set(require.resolve.paths, {type: 'require', path: filename}); diff --git a/lib/init/module.js b/lib/init/module.js index fb485ebb..9f6d68d6 100644 --- a/lib/init/module.js +++ b/lib/init/module.js @@ -11,7 +11,8 @@ const Module = require('module'); // Imports const {catalogBuiltInModule} = require('./globals.js'), {usingInternalModuleCache} = require('../shared/moduleCache.js'), - {globals, functions: specialFunctions} = require('../shared/internal.js'); + {globals, functions: specialFunctions} = require('../shared/internal.js'), + {COMMON_JS_MODULE} = require('../shared/constants.js'); // Exports @@ -36,6 +37,15 @@ module.exports = function patchModule() { return exports; }; Module.prototype.require.main = requireOriginal.main; + + // Patch `Module.prototype.load` to record `module` object + const loadOriginal = Module.prototype.load; + Module.prototype.load = function(filename) { + if (!usingInternalModuleCache()) { + globals.set(this, {type: COMMON_JS_MODULE, parent: null, key: 'module'}); + } + loadOriginal.call(this, filename); + }; }; /** diff --git a/lib/instrument/modify.js b/lib/instrument/modify.js index 1aa81acb..e4adb441 100644 --- a/lib/instrument/modify.js +++ b/lib/instrument/modify.js @@ -233,7 +233,7 @@ function insertImportStatement(programNode, state) { // ``` // const [livepack_tracker, livepack_getScopeId] // = require('/path/to/app/node_modules/livepack/lib/init/index.js') - // ('/path/to/app/file.js', module, require, 100, 0); + // ('/path/to/app/file.js', require, 100, 0); // ``` const statementNode = t.variableDeclaration( 'const', [ @@ -243,7 +243,6 @@ function insertImportStatement(programNode, state) { t.callExpression(t.identifier('require'), [t.stringLiteral(INIT_PATH)]), [ t.stringLiteral(state.filename), - t.identifier('module'), t.identifier('require'), t.numericLiteral(state.nextBlockId), t.numericLiteral(state.internalVarsPrefixNum) diff --git a/test/commonjs.test.js b/test/commonjs.test.js index d404a98a..cd6b97b3 100644 --- a/test/commonjs.test.js +++ b/test/commonjs.test.js @@ -69,6 +69,38 @@ describe('`module`', () => { expect(fn()).toBe(123); } }); + + itSerializes('var overriden by function declaration and module object exported', { + in: ` + 'use strict'; + function module() { return 123; } + // arguments[2] is module object + arguments[2].exports = arguments[2]; + `, + out: '(()=>{const a={};a.exports=a;return a})()', + validate(mod, {isOutput}) { + expect(mod).toBeObject(); + if (isOutput) expect(mod).toHaveOwnPropertyNames(['exports']); + expect(mod.exports).toBe(mod); + } + }); + + itSerializes('var overriden by function declaration and function exported', { + in: ` + 'use strict'; + function module() { return 123; } + exports.x = module; + `, + out: '{x:function module(){return 123}}', + validate(obj) { + expect(obj).toBeObject(); + expect(obj).toHaveOwnPropertyNames(['x']); + const fn = obj.x; + expect(fn).toBeFunction(); + expect(fn.name).toBe('module'); + expect(fn()).toBe(123); + } + }); }); describe('`exports`', () => {