'use strict'; var detect = require('acorn-globals'); var acorn = require('acorn'); var walk = require('acorn/dist/walk'); // polyfill for https://github.com/marijnh/acorn/pull/231 walk.base.ExportNamedDeclaration = walk.base.ExportDefaultDeclaration = function (node, st, c) { return c(node.declaration, st); }; walk.base.ImportDefaultSpecifier = walk.base.ImportNamespaceSpecifier = function () {}; // hacky fix for https://github.com/marijnh/acorn/issues/227 function reallyParse(source) { try { return acorn.parse(source, { ecmaVersion: 5, allowReturnOutsideFunction: true }); } catch (ex) { if (ex.name !== 'SyntaxError') { throw ex; } return acorn.parse(source, { ecmaVersion: 6, allowReturnOutsideFunction: true }); } } module.exports = addWith /** * Mimic `with` as far as possible but at compile time * * @param {String} obj The object part of a with expression * @param {String} src The body of the with expression * @param {Array.} exclude A list of variable names to explicitly exclude */ function addWith(obj, src, exclude) { obj = obj + '' src = src + '' exclude = exclude || [] exclude = exclude.concat(detect(obj).map(function (global) { return global.name; })) var vars = detect(src).map(function (global) { return global.name; }) .filter(function (v) { return exclude.indexOf(v) === -1 }) if (vars.length === 0) return src var declareLocal = '' var local = 'locals_for_with' var result = 'result_of_with' if (/^[a-zA-Z0-9$_]+$/.test(obj)) { local = obj } else { while (vars.indexOf(local) != -1 || exclude.indexOf(local) != -1) { local += '_' } declareLocal = 'var ' + local + ' = (' + obj + ')' } while (vars.indexOf(result) != -1 || exclude.indexOf(result) != -1) { result += '_' } var inputVars = vars.map(function (v) { return JSON.stringify(v) + ' in ' + local + '?' + local + '.' + v + ':' + 'typeof ' + v + '!=="undefined"?' + v + ':undefined' }) src = '(function (' + vars.join(', ') + ') {' + src + '}.call(this' + inputVars.map(function (v) { return ',' + v; }).join('') + '))' return ';' + declareLocal + ';' + unwrapReturns(src, result) + ';' } /** * Take a self calling function, and unwrap it such that return inside the function * results in return outside the function * * @param {String} src Some JavaScript code representing a self-calling function * @param {String} result A temporary variable to store the result in */ function unwrapReturns(src, result) { var originalSource = src var hasReturn = false var ast = reallyParse(src) var ref src = src.split('') // get a reference to the function that was inserted to add an inner context if ((ref = ast.body).length !== 1 || (ref = ref[0]).type !== 'ExpressionStatement' || (ref = ref.expression).type !== 'CallExpression' || (ref = ref.callee).type !== 'MemberExpression' || ref.computed !== false || ref.property.name !== 'call' || (ref = ref.object).type !== 'FunctionExpression') throw new Error('AST does not seem to represent a self-calling function') var fn = ref walk.recursive(ast, null, { Function: function (node, st, c) { if (node === fn) { c(node.body, st, "ScopeBody"); } }, ReturnStatement: function (node) { hasReturn = true replace(node, 'return {value: ' + source(node.argument) + '};'); } }); function source(node) { return src.slice(node.start, node.end).join('') } function replace(node, str) { for (var i = node.start; i < node.end; i++) { src[i] = '' } src[node.start] = str } if (!hasReturn) return originalSource else return 'var ' + result + '=' + src.join('') + ';if (' + result + ') return ' + result + '.value' }