-
Notifications
You must be signed in to change notification settings - Fork 7
/
index.js
116 lines (104 loc) · 3.4 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
'use strict';
var uglify = require('uglify-js')
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))
var vars = detect(src)
.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) + ';'
}
/**
* Detect, and return a list of, any global variables in a function
*
* @param {String} src Some JavaScript code
*/
function detect(src) {
var ast = uglify.parse('(function () {' + src + '}())') // allow return keyword
ast.figure_out_scope()
var globals = ast.globals
.map(function (node, name) {
return name
})
return globals
}
/**
* 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 = uglify.parse(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 !== 'SimpleStatement'
|| (ref = ref.body).TYPE !== 'Call'
|| (ref = ref.expression).TYPE !== 'Dot' || ref.property !== 'call'
|| (ref = ref.expression).TYPE !== 'Function')
throw new Error('AST does not seem to represent a self-calling function')
var fn = ref
var walker = new uglify.TreeWalker(visitor)
function visitor(node, descend) {
if (node !== fn && (node.TYPE === 'Defun' || node.TYPE === 'Function')) {
return true //don't descend into functions
}
if (node.TYPE === 'Return') {
descend()
hasReturn = true
replace(node, 'return {value: ' + source(node.value) + '};')
return true //don't descend again
}
}
function source(node) {
return src.slice(node.start.pos, node.end.endpos).join('')
}
function replace(node, str) {
for (var i = node.start.pos; i < node.end.endpos; i++) {
src[i] = ''
}
src[node.start.pos] = str
}
ast.walk(walker)
if (!hasReturn) return originalSource
else return 'var ' + result + '=' + src.join('') + ';if (' + result + ') return ' + result + '.value'
}