Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81146 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
return node.type === Syntax.BlockStatement && parentIsFunction;
45
}
46
47
function _nodeIsBlockScopeBoundary(node, parentNode) {
48
if (node.type === Syntax.Program) {
49
return false;
50
}
51
52
return node.type === Syntax.BlockStatement
53
&& parentNode.type === Syntax.CatchClause;
54
}
55
56
/**
57
* @param {object} node
58
* @param {array} path
59
* @param {object} state
60
*/
61
function traverse(node, path, state) {
62
// Create a scope stack entry if this is the first node we've encountered in
63
// its local scope
64
var parentNode = path[0];
65
if (!Array.isArray(node) && state.localScope.parentNode !== parentNode) {
66
if (_nodeIsClosureScopeBoundary(node, parentNode)) {
67
var scopeIsStrict =
68
state.scopeIsStrict
69
|| node.body.length > 0
70
&& node.body[0].type === Syntax.ExpressionStatement
71
&& node.body[0].expression.type === Syntax.Literal
72
&& node.body[0].expression.value === 'use strict';
73
74
if (node.type === Syntax.Program) {
75
state = utils.updateState(state, {
76
scopeIsStrict: scopeIsStrict
77
});
78
} else {
79
state = utils.updateState(state, {
80
localScope: {
81
parentNode: parentNode,
82
parentScope: state.localScope,
83
identifiers: {},
84
tempVarIndex: 0
85
},
86
scopeIsStrict: scopeIsStrict
87
});
88
89
// All functions have an implicit 'arguments' object in scope
90
declareIdentInScope('arguments', initScopeMetadata(node), state);
91
92
// Include function arg identifiers in the scope boundaries of the
93
// function
94
if (parentNode.params.length > 0) {
95
var param;
96
for (var i = 0; i < parentNode.params.length; i++) {
97
param = parentNode.params[i];
98
if (param.type === Syntax.Identifier) {
99
declareIdentInScope(
100
param.name, initScopeMetadata(parentNode), state
101
);
102
}
103
}
104
}
105
106
// Named FunctionExpressions scope their name within the body block of
107
// themselves only
108
if (parentNode.type === Syntax.FunctionExpression && parentNode.id) {
109
var metaData =
110
initScopeMetadata(parentNode, path.parentNodeslice, parentNode);
111
declareIdentInScope(parentNode.id.name, metaData, state);
112
}
113
}
114
115
// Traverse and find all local identifiers in this closure first to
116
// account for function/variable declaration hoisting
117
collectClosureIdentsAndTraverse(node, path, state);
118
}
119
120
if (_nodeIsBlockScopeBoundary(node, parentNode)) {
121
state = utils.updateState(state, {
122
localScope: {
123
parentNode: parentNode,
124
parentScope: state.localScope,
125
identifiers: {}
126
}
127
});
128
129
if (parentNode.type === Syntax.CatchClause) {
130
declareIdentInScope(
131
parentNode.param.name, initScopeMetadata(parentNode), state
132
);
133
}
134
collectBlockIdentsAndTraverse(node, path, state);
135
}
136
}
137
138
// Only catchup() before and after traversing a child node
139
function traverser(node, path, state) {
140
node.range && utils.catchup(node.range[0], state);
141
traverse(node, path, state);
142
node.range && utils.catchup(node.range[1], state);
143
}
144
145
utils.analyzeAndTraverse(walker, traverser, node, path, state);
146
}
147
148
function collectClosureIdentsAndTraverse(node, path, state) {
149
utils.analyzeAndTraverse(
150
visitLocalClosureIdentifiers,
151
collectClosureIdentsAndTraverse,
152
node,
153
path,
154
state
155
);
156
}
157
158
function collectBlockIdentsAndTraverse(node, path, state) {
159
utils.analyzeAndTraverse(
160
visitLocalBlockIdentifiers,
161
collectBlockIdentsAndTraverse,
162
node,
163
path,
164
state
165
);
166
}
167
168
function visitLocalClosureIdentifiers(node, path, state) {
169
var metaData;
170
switch (node.type) {
171
case Syntax.FunctionExpression:
172
// Function expressions don't get their names (if there is one) added to
173
// the closure scope they're defined in
174
return false;
175
case Syntax.ClassDeclaration:
176
case Syntax.ClassExpression:
177
case Syntax.FunctionDeclaration:
178
if (node.id) {
179
metaData = initScopeMetadata(getBoundaryNode(path), path.slice(), node);
180
declareIdentInScope(node.id.name, metaData, state);
181
}
182
return false;
183
case Syntax.VariableDeclarator:
184
// Variables have function-local scope
185
if (path[0].kind === 'var') {
186
metaData = initScopeMetadata(getBoundaryNode(path), path.slice(), node);
187
declareIdentInScope(node.id.name, metaData, state);
188
}
189
break;
190
}
191
}
192
193
function visitLocalBlockIdentifiers(node, path, state) {
194
// TODO: Support 'let' here...maybe...one day...or something...
195
if (node.type === Syntax.CatchClause) {
196
return false;
197
}
198
}
199
200
function walker(node, path, state) {
201
var visitors = state.g.visitors;
202
for (var i = 0; i < visitors.length; i++) {
203
if (visitors[i].test(node, path, state)) {
204
return visitors[i](traverse, node, path, state);
205
}
206
}
207
}
208
209
var _astCache = {};
210
211
/**
212
* Applies all available transformations to the source
213
* @param {array} visitors
214
* @param {string} source
215
* @param {?object} options
216
* @return {object}
217
*/
218
function transform(visitors, source, options) {
219
options = options || {};
220
var ast;
221
try {
222
var cachedAst = _astCache[source];
223
ast = cachedAst ||
224
(_astCache[source] = esprima.parse(source, {
225
comment: true,
226
loc: true,
227
range: true
228
}));
229
} catch (e) {
230
e.message = 'Parse Error: ' + e.message;
231
throw e;
232
}
233
var state = utils.createState(source, ast, options);
234
state.g.visitors = visitors;
235
236
if (options.sourceMap) {
237
var SourceMapGenerator = require('source-map').SourceMapGenerator;
238
state.g.sourceMap = new SourceMapGenerator({file: options.filename || 'transformed.js'});
239
}
240
241
traverse(ast, [], state);
242
utils.catchup(source.length, state);
243
244
var ret = {code: state.g.buffer, extra: state.g.extra};
245
if (options.sourceMap) {
246
ret.sourceMap = state.g.sourceMap;
247
ret.sourceMapFilename = options.filename || 'source.js';
248
}
249
return ret;
250
}
251
252
exports.transform = transform;
253
exports.Syntax = Syntax;
254
255