UNPKG

4.15 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2014, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * https://raw.github.com/facebook/regenerator/master/LICENSE file. An
7 * additional grant of patent rights can be found in the PATENTS file in
8 * the same directory.
9 */
10
11import * as t from "babel-types";
12let hasOwn = Object.prototype.hasOwnProperty;
13
14// The hoist function takes a FunctionExpression or FunctionDeclaration
15// and replaces any Declaration nodes in its body with assignments, then
16// returns a VariableDeclaration containing just the names of the removed
17// declarations.
18exports.hoist = function(funPath) {
19 t.assertFunction(funPath.node);
20
21 let vars = {};
22
23 function varDeclToExpr(vdec, includeIdentifiers) {
24 t.assertVariableDeclaration(vdec);
25 // TODO assert.equal(vdec.kind, "var");
26 let exprs = [];
27
28 vdec.declarations.forEach(function(dec) {
29 vars[dec.id.name] = dec.id;
30
31 if (dec.init) {
32 exprs.push(t.assignmentExpression(
33 "=", dec.id, dec.init
34 ));
35 } else if (includeIdentifiers) {
36 exprs.push(dec.id);
37 }
38 });
39
40 if (exprs.length === 0)
41 return null;
42
43 if (exprs.length === 1)
44 return exprs[0];
45
46 return t.sequenceExpression(exprs);
47 }
48
49 funPath.get("body").traverse({
50 VariableDeclaration: {
51 exit: function(path) {
52 let expr = varDeclToExpr(path.node, false);
53 if (expr === null) {
54 path.remove();
55 } else {
56 // We don't need to traverse this expression any further because
57 // there can't be any new declarations inside an expression.
58 path.replaceWith(t.expressionStatement(expr));
59 }
60
61 // Since the original node has been either removed or replaced,
62 // avoid traversing it any further.
63 path.skip();
64 }
65 },
66
67 ForStatement: function(path) {
68 let init = path.node.init;
69 if (t.isVariableDeclaration(init)) {
70 path.get("init").replaceWith(varDeclToExpr(init, false));
71 }
72 },
73
74 ForXStatement: function(path) {
75 let left = path.get("left");
76 if (left.isVariableDeclaration()) {
77 left.replaceWith(varDeclToExpr(left.node, true));
78 }
79 },
80
81 FunctionDeclaration: function(path) {
82 let node = path.node;
83 vars[node.id.name] = node.id;
84
85 let assignment = t.expressionStatement(
86 t.assignmentExpression(
87 "=",
88 node.id,
89 t.functionExpression(
90 node.id,
91 node.params,
92 node.body,
93 node.generator,
94 node.expression
95 )
96 )
97 );
98
99 if (path.parentPath.isBlockStatement()) {
100 // Insert the assignment form before the first statement in the
101 // enclosing block.
102 path.parentPath.unshiftContainer("body", assignment);
103
104 // Remove the function declaration now that we've inserted the
105 // equivalent assignment form at the beginning of the block.
106 path.remove();
107 } else {
108 // If the parent node is not a block statement, then we can just
109 // replace the declaration with the equivalent assignment form
110 // without worrying about hoisting it.
111 path.replaceWith(assignment);
112 }
113
114 // Don't hoist variables out of inner functions.
115 path.skip();
116 },
117
118 FunctionExpression: function(path) {
119 // Don't descend into nested function expressions.
120 path.skip();
121 }
122 });
123
124 let paramNames = {};
125 funPath.get("params").forEach(function(paramPath) {
126 let param = paramPath.node;
127 if (t.isIdentifier(param)) {
128 paramNames[param.name] = param;
129 } else {
130 // Variables declared by destructuring parameter patterns will be
131 // harmlessly re-declared.
132 }
133 });
134
135 let declarations = [];
136
137 Object.keys(vars).forEach(function(name) {
138 if (!hasOwn.call(paramNames, name)) {
139 declarations.push(t.variableDeclarator(vars[name], null));
140 }
141 });
142
143 if (declarations.length === 0) {
144 return null; // Be sure to handle this case!
145 }
146
147 return t.variableDeclaration("var", declarations);
148};