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
126 lines
3.8 KiB
JavaScript
6 years ago
|
'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'
|
||
|
}
|