Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80540 views
1
/**
2
* Copyright 2013 Facebook, Inc.
3
*
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
7
*
8
* http://www.apache.org/licenses/LICENSE-2.0
9
*
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
15
*/
16
17
18
/*jslint node: true*/
19
"use strict";
20
21
var esprima = require('esprima-fb');
22
var utils = require('./utils');
23
24
var getBoundaryNode = utils.getBoundaryNode;
25
var declareIdentInScope = utils.declareIdentInLocalScope;
26
var initScopeMetadata = utils.initScopeMetadata;
27
var Syntax = esprima.Syntax;
28
29
/**
30
* @param {object} node
31
* @param {object} parentNode
32
* @return {boolean}
33
*/
34
function _nodeIsClosureScopeBoundary(node, parentNode) {
35
if (node.type === Syntax.Program) {
36
return true;
37
}
38
39
var parentIsFunction =
40
parentNode.type === Syntax.FunctionDeclaration
41
|| parentNode.type === Syntax.FunctionExpression
42
|| parentNode.type === Syntax.ArrowFunctionExpression;
43
44
var parentIsCurlylessArrowFunc =
45
parentNode.type === Syntax.ArrowFunctionExpression
46
&& node === parentNode.body;
47
48
return parentIsFunction
49
&& (node.type === Syntax.BlockStatement || parentIsCurlylessArrowFunc);
50
}
51
52
function _nodeIsBlockScopeBoundary(node, parentNode) {
53
if (node.type === Syntax.Program) {
54
return false;
55
}
56
57
return node.type === Syntax.BlockStatement
58
&& parentNode.type === Syntax.CatchClause;
59
}
60
61
/**
62
* @param {object} node
63
* @param {array} path
64
* @param {object} state
65
*/
66
function traverse(node, path, state) {
67
/*jshint -W004*/
68
// Create a scope stack entry if this is the first node we've encountered in
69
// its local scope
70
var startIndex = null;
71
var parentNode = path[0];
72
if (!Array.isArray(node) && state.localScope.parentNode !== parentNode) {
73
if (_nodeIsClosureScopeBoundary(node, parentNode)) {
74
var scopeIsStrict = state.scopeIsStrict;
75
if (!scopeIsStrict
76
&& (node.type === Syntax.BlockStatement
77
|| node.type === Syntax.Program)) {
78
scopeIsStrict =
79
node.body.length > 0
80
&& node.body[0].type === Syntax.ExpressionStatement
81
&& node.body[0].expression.type === Syntax.Literal
82
&& node.body[0].expression.value === 'use strict';
83
}
84
85
if (node.type === Syntax.Program) {
86
startIndex = state.g.buffer.length;
87
state = utils.updateState(state, {
88
scopeIsStrict: scopeIsStrict
89
});
90
} else {
91
startIndex = state.g.buffer.length + 1;
92
state = utils.updateState(state, {
93
localScope: {
94
parentNode: parentNode,
95
parentScope: state.localScope,
96
identifiers: {},
97
tempVarIndex: 0,
98
tempVars: []
99
},
100
scopeIsStrict: scopeIsStrict
101
});
102
103
// All functions have an implicit 'arguments' object in scope
104
declareIdentInScope('arguments', initScopeMetadata(node), state);
105
106
// Include function arg identifiers in the scope boundaries of the
107
// function
108
if (parentNode.params.length > 0) {
109
var param;
110
var metadata = initScopeMetadata(parentNode, path.slice(1), path[0]);
111
for (var i = 0; i < parentNode.params.length; i++) {
112
param = parentNode.params[i];
113
if (param.type === Syntax.Identifier) {
114
declareIdentInScope(param.name, metadata, state);
115
}
116
}
117
}
118
119
// Include rest arg identifiers in the scope boundaries of their
120
// functions
121
if (parentNode.rest) {
122
var metadata = initScopeMetadata(
123
parentNode,
124
path.slice(1),
125
path[0]
126
);
127
declareIdentInScope(parentNode.rest.name, metadata, state);
128
}
129
130
// Named FunctionExpressions scope their name within the body block of
131
// themselves only
132
if (parentNode.type === Syntax.FunctionExpression && parentNode.id) {
133
var metaData =
134
initScopeMetadata(parentNode, path.parentNodeslice, parentNode);
135
declareIdentInScope(parentNode.id.name, metaData, state);
136
}
137
}
138
139
// Traverse and find all local identifiers in this closure first to
140
// account for function/variable declaration hoisting
141
collectClosureIdentsAndTraverse(node, path, state);
142
}
143
144
if (_nodeIsBlockScopeBoundary(node, parentNode)) {
145
startIndex = state.g.buffer.length;
146
state = utils.updateState(state, {
147
localScope: {
148
parentNode: parentNode,
149
parentScope: state.localScope,
150
identifiers: {},
151
tempVarIndex: 0,
152
tempVars: []
153
}
154
});
155
156
if (parentNode.type === Syntax.CatchClause) {
157
var metadata = initScopeMetadata(
158
parentNode,
159
path.slice(1),
160
parentNode
161
);
162
declareIdentInScope(parentNode.param.name, metadata, state);
163
}
164
collectBlockIdentsAndTraverse(node, path, state);
165
}
166
}
167
168
// Only catchup() before and after traversing a child node
169
function traverser(node, path, state) {
170
node.range && utils.catchup(node.range[0], state);
171
traverse(node, path, state);
172
node.range && utils.catchup(node.range[1], state);
173
}
174
175
utils.analyzeAndTraverse(walker, traverser, node, path, state);
176
177
// Inject temp variables into the scope.
178
if (startIndex !== null) {
179
utils.injectTempVarDeclarations(state, startIndex);
180
}
181
}
182
183
function collectClosureIdentsAndTraverse(node, path, state) {
184
utils.analyzeAndTraverse(
185
visitLocalClosureIdentifiers,
186
collectClosureIdentsAndTraverse,
187
node,
188
path,
189
state
190
);
191
}
192
193
function collectBlockIdentsAndTraverse(node, path, state) {
194
utils.analyzeAndTraverse(
195
visitLocalBlockIdentifiers,
196
collectBlockIdentsAndTraverse,
197
node,
198
path,
199
state
200
);
201
}
202
203
function visitLocalClosureIdentifiers(node, path, state) {
204
var metaData;
205
switch (node.type) {
206
case Syntax.ArrowFunctionExpression:
207
case Syntax.FunctionExpression:
208
// Function expressions don't get their names (if there is one) added to
209
// the closure scope they're defined in
210
return false;
211
case Syntax.ClassDeclaration:
212
case Syntax.ClassExpression:
213
case Syntax.FunctionDeclaration:
214
if (node.id) {
215
metaData = initScopeMetadata(getBoundaryNode(path), path.slice(), node);
216
declareIdentInScope(node.id.name, metaData, state);
217
}
218
return false;
219
case Syntax.VariableDeclarator:
220
// Variables have function-local scope
221
if (path[0].kind === 'var') {
222
metaData = initScopeMetadata(getBoundaryNode(path), path.slice(), node);
223
declareIdentInScope(node.id.name, metaData, state);
224
}
225
break;
226
}
227
}
228
229
function visitLocalBlockIdentifiers(node, path, state) {
230
// TODO: Support 'let' here...maybe...one day...or something...
231
if (node.type === Syntax.CatchClause) {
232
return false;
233
}
234
}
235
236
function walker(node, path, state) {
237
var visitors = state.g.visitors;
238
for (var i = 0; i < visitors.length; i++) {
239
if (visitors[i].test(node, path, state)) {
240
return visitors[i](traverse, node, path, state);
241
}
242
}
243
}
244
245
var _astCache = {};
246
247
function getAstForSource(source, options) {
248
if (_astCache[source] && !options.disableAstCache) {
249
return _astCache[source];
250
}
251
var ast = esprima.parse(source, {
252
comment: true,
253
loc: true,
254
range: true,
255
sourceType: options.sourceType
256
});
257
if (!options.disableAstCache) {
258
_astCache[source] = ast;
259
}
260
return ast;
261
}
262
263
/**
264
* Applies all available transformations to the source
265
* @param {array} visitors
266
* @param {string} source
267
* @param {?object} options
268
* @return {object}
269
*/
270
function transform(visitors, source, options) {
271
options = options || {};
272
var ast;
273
try {
274
ast = getAstForSource(source, options);
275
} catch (e) {
276
e.message = 'Parse Error: ' + e.message;
277
throw e;
278
}
279
var state = utils.createState(source, ast, options);
280
state.g.visitors = visitors;
281
282
if (options.sourceMap) {
283
var SourceMapGenerator = require('source-map').SourceMapGenerator;
284
state.g.sourceMap = new SourceMapGenerator({file: options.filename || 'transformed.js'});
285
}
286
287
traverse(ast, [], state);
288
utils.catchup(source.length, state);
289
290
var ret = {code: state.g.buffer, extra: state.g.extra};
291
if (options.sourceMap) {
292
ret.sourceMap = state.g.sourceMap;
293
ret.sourceMapFilename = options.filename || 'source.js';
294
}
295
return ret;
296
}
297
298
exports.transform = transform;
299
exports.Syntax = Syntax;
300
301