You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
111 lines
3.4 KiB
111 lines
3.4 KiB
'use strict'; |
|
|
|
var detect = require('acorn-globals'); |
|
var acorn = require('acorn'); |
|
var walk = require('acorn/dist/walk'); |
|
|
|
// hacky fix for https://github.com/marijnh/acorn/issues/227 |
|
function reallyParse(source) { |
|
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 |
|
&& v !== 'undefined' |
|
&& v !== 'this' |
|
}) |
|
|
|
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: (' + (node.argument ? source(node.argument) : 'undefined') + ')};'); |
|
} |
|
}); |
|
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' |
|
}
|
|
|