diff --git a/lib/init/getScopeId.js b/lib/init/getScopeId.js index 82c92b01..4c0202a8 100644 --- a/lib/init/getScopeId.js +++ b/lib/init/getScopeId.js @@ -5,7 +5,7 @@ 'use strict'; -const {defineProperties, getOwnPropertyDescriptors} = Object; +const {defineProperty, defineProperties, getOwnPropertyDescriptors} = Object; // Exports @@ -23,6 +23,7 @@ let nextScopeId = 1; // Add additional static methods getScopeId.toRest = toRest; +getScopeId.renameRequireAlias = renameRequireAlias; /** * Convert object to array. @@ -33,3 +34,12 @@ getScopeId.toRest = toRest; function toRest(obj) { return defineProperties([], getOwnPropertyDescriptors(obj)); } + +/** + * Set name property of a function to 'require'. + * @param {Function} req - Function + * @returns {Function} - Input function + */ +function renameRequireAlias(req) { + return defineProperty(req, 'name', {value: 'require'}); +} diff --git a/lib/instrument/internalVars.js b/lib/instrument/internalVars.js index 03ae6b8a..645174b9 100644 --- a/lib/instrument/internalVars.js +++ b/lib/instrument/internalVars.js @@ -31,7 +31,8 @@ module.exports = { createTempVarNode, createFnInfoVarNode, checkInternalVarNameClash, - renameInternalVars + renameInternalVars, + addToInternalVars }; /* diff --git a/lib/instrument/modify.js b/lib/instrument/modify.js index e4adb441..24c4ee76 100644 --- a/lib/instrument/modify.js +++ b/lib/instrument/modify.js @@ -87,6 +87,7 @@ function modifyAst(ast, filename, isCommonJs, isStrict, sources, evalState) { trackerVarNode: undefined, getScopeIdVarNode: undefined, getSourcesNode: undefined, + requireAliasTempVarNode: undefined, functions: [], fileContainsFunctionsOrEval: false, secondPass: (fn, ...params) => secondPassQueue.push({fn, params}) @@ -198,6 +199,7 @@ function modifySecondPass(ast, secondPassQueue, isEvalCode, sources, state) { processQueue(secondPassQueue, state); insertBlockVarsIntoBlockStatement(state.programBlock, programNode, state); insertFunctionInfoFunctions(programNode, isEvalCode, sources, state); + insertRequireAlias(programNode, state); } if (!isEvalCode) insertImportStatement(programNode, state); @@ -295,6 +297,32 @@ function insertFunctionInfoFunctions(programNode, isEvalCode, sources, state) { } } +/** + * Insert alias reassignment for `require`. + * If CommonJS code containing top-level function declaration(s) called 'require', + * they've been renamed to a temporary name earlier in instrumentation. + * Insert `var` statement at top of file to restore the binding. + * @param {Object} programNode - Program AST node + * @param {Object} state - State object + * @returns {undefined} + */ +function insertRequireAlias(programNode, state) { + if (!state.requireAliasTempVarNode) return; + + // `var require = livepack_getScopeId.renameRequireAlias(livepack_temp_3);` + programNode.body.unshift( + t.variableDeclaration('var', [ + t.variableDeclarator( + t.identifier('require'), + t.callExpression( + t.memberExpression(state.getScopeIdVarNode, t.identifier('renameRequireAlias')), + [state.requireAliasTempVarNode] + ) + ) + ]) + ); +} + /** * Get current AST node, to get location where instrumentation failed and threw error. * Uses current function node and trail to get node. If no node at that trail, get parent node. diff --git a/lib/instrument/visitors/function.js b/lib/instrument/visitors/function.js index 8541e773..ab3e33a6 100644 --- a/lib/instrument/visitors/function.js +++ b/lib/instrument/visitors/function.js @@ -38,7 +38,7 @@ const Statement = require('./statement.js'), createNewTargetBinding, getOrCreateExternalVar } = require('../blocks.js'), {insertTrackerCodeIntoFunction} = require('../tracking.js'), - {createFnInfoVarNode} = require('../internalVars.js'), + {createFnInfoVarNode, createTempVarNode, addToInternalVars} = require('../internalVars.js'), {insertComment, hasUseStrictDirective, stringLiteralWithSingleQuotes} = require('../utils.js'), {combineArraysWithDedup} = require('../../shared/functions.js'), { @@ -109,6 +109,12 @@ function FunctionDeclaration(node, state, parent, key) { // Insert tracker comment insertFunctionDeclarationOrExpressionTrackerComment(fn, node, state); + + // If top-level function declaration called 'require' in CommonJS, rename it in 2nd pass + if ( + fnName === 'require' && block === state.programBlock + && state.programBlock.parent.bindings.has('require') + ) state.secondPass(renameRequireFunctionDeclaration, node, state); } /** @@ -691,6 +697,26 @@ function insertArrowFunctionTrackerComment(fn, node, state) { ); } +/** + * Rename top-level function declaration in CommonJS file called `require` to a temp name. + * Otherwise, this prevents loading Livepack's `init` code. + * At end of instrumentation, a `var require =` statement will be added at top of file + * to restore the binding. + * @param {Object} fnNode - Function declaration AST node + * @param {Object} state - State object + * @returns {undefined} + */ +function renameRequireFunctionDeclaration(fnNode, state) { + // Use same temp var name for all `require` functions in this file + const tempVarNode = state.requireAliasTempVarNode + || (state.requireAliasTempVarNode = createTempVarNode(state)); + + // Set `name` of existing identifier, rather than replacing with `tempVarNode` + // to preserve any attached comments (including tracker comment) + fnNode.id.name = tempVarNode.name; + addToInternalVars(fnNode.id, state); +} + /** * Insert tracker comment. * @param {number} fnId - Function ID