You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
3.8 KiB
JavaScript

'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.<String>} 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'
}