hoist.js
4.22 KB
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* https://raw.github.com/facebook/regenerator/master/LICENSE file. An
* additional grant of patent rights can be found in the PATENTS file in
* the same directory.
*/
var assert = require("assert");
var types = require("recast").types;
var n = types.namedTypes;
var b = types.builders;
var hasOwn = Object.prototype.hasOwnProperty;
// The hoist function takes a FunctionExpression or FunctionDeclaration
// and replaces any Declaration nodes in its body with assignments, then
// returns a VariableDeclaration containing just the names of the removed
// declarations.
exports.hoist = function(funPath) {
assert.ok(funPath instanceof types.NodePath);
n.Function.assert(funPath.value);
var vars = {};
function varDeclToExpr(vdec, includeIdentifiers) {
n.VariableDeclaration.assert(vdec);
var exprs = [];
vdec.declarations.forEach(function(dec) {
vars[dec.id.name] = dec.id;
if (dec.init) {
exprs.push(b.assignmentExpression(
"=", dec.id, dec.init
));
} else if (includeIdentifiers) {
exprs.push(dec.id);
}
});
if (exprs.length === 0)
return null;
if (exprs.length === 1)
return exprs[0];
return b.sequenceExpression(exprs);
}
types.visit(funPath.get("body"), {
visitVariableDeclaration: function(path) {
var expr = varDeclToExpr(path.value, false);
if (expr === null) {
path.replace();
} else {
// We don't need to traverse this expression any further because
// there can't be any new declarations inside an expression.
return b.expressionStatement(expr);
}
// Since the original node has been either removed or replaced,
// avoid traversing it any further.
return false;
},
visitForStatement: function(path) {
var init = path.value.init;
if (n.VariableDeclaration.check(init)) {
path.get("init").replace(varDeclToExpr(init, false));
}
this.traverse(path);
},
visitForInStatement: function(path) {
var left = path.value.left;
if (n.VariableDeclaration.check(left)) {
path.get("left").replace(varDeclToExpr(left, true));
}
this.traverse(path);
},
visitFunctionDeclaration: function(path) {
var node = path.value;
vars[node.id.name] = node.id;
var parentNode = path.parent.node;
var assignment = b.expressionStatement(
b.assignmentExpression(
"=",
node.id,
b.functionExpression(
node.id,
node.params,
node.body,
node.generator,
node.expression
)
)
);
if (n.BlockStatement.check(path.parent.node)) {
// Insert the assignment form before the first statement in the
// enclosing block.
path.parent.get("body").unshift(assignment);
// Remove the function declaration now that we've inserted the
// equivalent assignment form at the beginning of the block.
path.replace();
} else {
// If the parent node is not a block statement, then we can just
// replace the declaration with the equivalent assignment form
// without worrying about hoisting it.
path.replace(assignment);
}
// Don't hoist variables out of inner functions.
return false;
},
visitFunctionExpression: function(path) {
// Don't descend into nested function expressions.
return false;
}
});
var paramNames = {};
funPath.get("params").each(function(paramPath) {
var param = paramPath.value;
if (n.Identifier.check(param)) {
paramNames[param.name] = param;
} else {
// Variables declared by destructuring parameter patterns will be
// harmlessly re-declared.
}
});
var declarations = [];
Object.keys(vars).forEach(function(name) {
if (!hasOwn.call(paramNames, name)) {
declarations.push(b.variableDeclarator(vars[name], null));
}
});
if (declarations.length === 0) {
return null; // Be sure to handle this case!
}
return b.variableDeclaration("var", declarations);
};