Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81152 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
* @emails [email protected]
17
*/
18
19
require('mock-modules').autoMockOff();
20
21
describe('jstransform', function() {
22
var transformFn;
23
var Syntax = require('esprima-fb').Syntax;
24
25
beforeEach(function() {
26
require('mock-modules').dumpCache();
27
transformFn = require('../jstransform').transform;
28
});
29
30
function _runVisitor(source, nodeCount, visitor) {
31
var actualVisitationCount = 0;
32
function shimVisitor(traverse, node, path, state) {
33
actualVisitationCount++;
34
return visitor(traverse, node, path, state);
35
}
36
shimVisitor.test = visitor.test;
37
transformFn([shimVisitor], source);
38
expect(actualVisitationCount).toBe(nodeCount);
39
}
40
41
function testScopeBoundary(source, localIdents, nodeCount, visitorTest) {
42
function visitor(traverse, node, path, state) {
43
var actualLocalIdents = Object.keys(state.localScope.identifiers);
44
expect(actualLocalIdents.sort()).toEqual(localIdents.sort());
45
}
46
visitor.test = visitorTest;
47
_runVisitor(source, nodeCount, visitor);
48
}
49
50
function testParentScope(source, parentIdents, nodeCount, visitorTest) {
51
function visitor(traverse, node, path, state) {
52
parentIdents = parentIdents && parentIdents.sort();
53
var parentScope = state.localScope.parentScope;
54
var actualParentIdents =
55
parentScope && Object.keys(parentScope.identifiers).sort();
56
expect(actualParentIdents).toEqual(parentIdents);
57
}
58
visitor.test = visitorTest;
59
_runVisitor(source, nodeCount, visitor);
60
}
61
62
describe('closure scope boundaries', function() {
63
it('creates a scope boundary around Program scope', function() {
64
var source =
65
'var foo;' +
66
'var bar, baz;' +
67
'function blah() {}';
68
var idents = ['foo', 'bar', 'baz', 'blah'];
69
70
testScopeBoundary(source, idents, 3, function(node, path, state) {
71
return path[0] && path[0].type === Syntax.Program;
72
});
73
});
74
75
it('creates a scope boundary around FunctionDeclarations', function() {
76
var source =
77
'var foo;' +
78
'function blah() {' +
79
' var bar;' +
80
' function nested() {' +
81
' var baz;' +
82
' }' +
83
'}';
84
var programIdents = ['foo', 'blah'];
85
var blahIdents = ['arguments', 'bar', 'nested'];
86
var nestedIdents = ['arguments', 'baz'];
87
88
testScopeBoundary(source, programIdents, 2, function(node, path, state) {
89
return path[0] && path[0].type === Syntax.Program;
90
});
91
92
testScopeBoundary(source, blahIdents, 2, function(node, path, state) {
93
// All direct children of blah()
94
return path[0] && path[0].type === Syntax.BlockStatement &&
95
path[1] && path[1].type === Syntax.FunctionDeclaration &&
96
path[1].id.name === 'blah';
97
});
98
99
testScopeBoundary(source, nestedIdents, 1, function(node, path, state) {
100
// All direct children of nested()
101
return path[0] && path[0].type === Syntax.BlockStatement &&
102
path[1] && path[1].type === Syntax.FunctionDeclaration &&
103
path[1].id.name === 'nested';
104
});
105
});
106
107
it('creates a scope boundary around MethodDefinitions', function() {
108
var source =
109
'var foo;' +
110
'class ClassA {' +
111
' blah() {' +
112
' var bar;' +
113
' }' +
114
' another() {' +
115
' var baz;' +
116
' }' +
117
'}';
118
var programIdents = ['foo', 'ClassA'];
119
var blahIdents = ['arguments', 'bar'];
120
var anotherIdents = ['arguments', 'baz'];
121
122
testScopeBoundary(source, programIdents, 2, function(node, path, state) {
123
return path[0] && path[0].type === Syntax.Program;
124
});
125
126
testScopeBoundary(source, blahIdents, 1, function(node, path, state) {
127
// All direct children of blah()
128
return path[0] && path[0].type === Syntax.BlockStatement &&
129
path[1] && path[1].type === Syntax.FunctionExpression &&
130
path[2] && path[2].type === Syntax.MethodDefinition &&
131
path[2].key.name === 'blah';
132
});
133
134
testScopeBoundary(source, anotherIdents, 1, function(node, path, state) {
135
// All direct children of another()
136
return path[0] && path[0].type === Syntax.BlockStatement &&
137
path[1] && path[1].type === Syntax.FunctionExpression &&
138
path[2] && path[2].type === Syntax.MethodDefinition &&
139
path[2].key.name === 'another';
140
});
141
});
142
143
it('uses VariableDeclarations to determine scope boundary', function() {
144
var source =
145
'var foo = 1;' +
146
'function bar() {' +
147
' foo++;' +
148
' function baz() {' +
149
' var foo = 2;' +
150
' }' +
151
'}';
152
var programIdents = ['foo', 'bar'];
153
var barIdents = ['arguments', 'baz'];
154
var bazIdents = ['arguments', 'foo'];
155
156
testScopeBoundary(source, programIdents, 2, function(node, path, state) {
157
return path[0] && path[0].type === Syntax.Program;
158
});
159
160
testScopeBoundary(source, barIdents, 2, function(node, path, state) {
161
// All direct children of blah()
162
return path[0] && path[0].type === Syntax.BlockStatement &&
163
path[1] && path[1].type === Syntax.FunctionDeclaration &&
164
path[1].id.name === 'bar';
165
});
166
167
testScopeBoundary(source, bazIdents, 1, function(node, path, state) {
168
// All direct children of baz()
169
return path[0] && path[0].type === Syntax.BlockStatement &&
170
path[1] && path[1].type === Syntax.FunctionDeclaration &&
171
path[1].id.name === 'baz';
172
});
173
});
174
175
it('includes function args in functions scope boundary', function() {
176
var source =
177
'var foo;' +
178
'function blah(bar) {' +
179
' var baz;' +
180
'}';
181
var programIdents = ['foo', 'blah'];
182
var blahIdents = ['arguments', 'bar', 'baz'];
183
184
testScopeBoundary(source, programIdents, 2, function(node, path, state) {
185
return path[0] && path[0].type === Syntax.Program;
186
});
187
188
testScopeBoundary(source, blahIdents, 1, function(node, path, state) {
189
// All direct children of blah()
190
return path[0] && path[0].type === Syntax.BlockStatement &&
191
path[1] && path[1].type === Syntax.FunctionDeclaration &&
192
path[1].id.name === 'blah';
193
});
194
});
195
196
it('puts FunctionExpression names within function scope', function() {
197
var source =
198
'var foo;' +
199
'var bar = function baz() {' +
200
' var blah;' +
201
'};';
202
var programIdents = ['foo', 'bar'];
203
var bazIdents = ['arguments', 'baz', 'blah'];
204
205
testScopeBoundary(source, programIdents, 2, function(node, path, state) {
206
return path[0] && path[0].type === Syntax.Program;
207
});
208
209
testScopeBoundary(source, bazIdents, 1, function(node, path, state) {
210
// All direct children of baz()
211
return path[0] && path[0].type === Syntax.BlockStatement &&
212
path[1] && path[1].type === Syntax.FunctionExpression &&
213
path[1].id.name === 'baz';
214
});
215
});
216
});
217
218
describe('block scope boundaries', function() {
219
it('creates a scope boundary around CatchClauses with params', function() {
220
var source =
221
'var blah = 0;' +
222
'try {' +
223
'} catch (e) {' +
224
' blah++;' +
225
'}';
226
var programIdents = ['blah'];
227
var catchIdents = ['e'];
228
229
testScopeBoundary(source, programIdents, 2, function(node, path, state) {
230
return path[0] && path[0].type === Syntax.Program;
231
});
232
233
testScopeBoundary(source, catchIdents, 1, function(node, path, state) {
234
// All direct children of catch(e) block
235
return path[0] && path[0].type === Syntax.BlockStatement &&
236
path[1] && path[1].type === Syntax.CatchClause;
237
});
238
});
239
240
it('includes vars defined in CatchClauses in the parent scope', function() {
241
var source =
242
'try {' +
243
'} catch (e) {' +
244
' var blah;' +
245
'}';
246
var programIdents = ['blah'];
247
var catchIdents = ['e'];
248
249
testScopeBoundary(source, programIdents, 1, function(node, path, state) {
250
return path[0] && path[0].type === Syntax.Program;
251
});
252
253
testScopeBoundary(source, catchIdents, 1, function(node, path, state) {
254
// All direct children of catch(e) block
255
return path[0] && path[0].type === Syntax.BlockStatement &&
256
path[1] && path[1].type === Syntax.CatchClause;
257
});
258
});
259
});
260
261
describe('scope chain linking', function() {
262
it('links parent scope boundaries', function() {
263
var source =
264
'var foo;' +
265
'function blah() {' +
266
' var bar;' +
267
' function nested() {' +
268
' var baz;' +
269
' }' +
270
'}';
271
var programIdents = ['foo', 'blah'];
272
var blahIdents = ['arguments', 'bar', 'nested'];
273
274
testParentScope(source, programIdents, 2, function(node, path, state) {
275
// All direct children of blah()
276
return path[0] && path[0].type === Syntax.BlockStatement &&
277
path[1] && path[1].type === Syntax.FunctionDeclaration &&
278
path[1].id.name === 'blah';
279
});
280
281
testParentScope(source, blahIdents, 1, function(node, path, state) {
282
// All direct children of nested()
283
return path[0] && path[0].type === Syntax.BlockStatement &&
284
path[1] && path[1].type === Syntax.FunctionDeclaration &&
285
path[1].id.name === 'nested';
286
});
287
});
288
289
it('nests MethodDefinition boundaries under parent scope', function() {
290
var source =
291
'var foo;' +
292
'class ClassA {' +
293
' blah() {' +
294
' var bar;' +
295
' }' +
296
'}';
297
var programIdents = ['foo', 'ClassA'];
298
299
testParentScope(source, programIdents, 1, function(node, path, state) {
300
// All direct children of blah()
301
return path[0] && path[0].type === Syntax.BlockStatement &&
302
path[1] && path[1].type === Syntax.FunctionExpression &&
303
path[2] && path[2].type === Syntax.MethodDefinition &&
304
path[2].key.name === 'blah';
305
});
306
});
307
});
308
309
describe('"use strict" tracking', function() {
310
function testStrictness(expectedStrict, source) {
311
var visitedNodes = 0;
312
function visitor(traverse, node, path, state) {
313
visitedNodes++;
314
expect(state.scopeIsStrict).toBe(expectedStrict);
315
}
316
visitor.test = function(node, path, state) {
317
return node.type === Syntax.Literal
318
&& node.value === 'testStr';
319
};
320
transformFn([visitor], source);
321
expect(visitedNodes).toBe(1);
322
}
323
324
it('detects program-level strictness', function() {
325
testStrictness(false, '"testStr";');
326
testStrictness(true, '"use strict"; "testStr";');
327
});
328
329
it('detects non-inherited strictness', function() {
330
testStrictness(true, [
331
'function foo() {',
332
' "use strict";',
333
' "testStr";',
334
'}'
335
].join('\n'));
336
});
337
338
it('detects program-inherited strictness', function() {
339
testStrictness(true, [
340
'"use strict";',
341
'function foo() {',
342
' "testStr";',
343
'}'
344
].join('\n'));
345
});
346
347
it('detects function-inherited strictness', function() {
348
testStrictness(true, [
349
'function foo() {',
350
' "use strict";',
351
' function bar() {',
352
' "testStr";',
353
' }',
354
'}'
355
].join('\n'));
356
});
357
358
it('does not detect sibling strictness', function() {
359
testStrictness(false, [
360
'function foo() {',
361
' "use strict";',
362
'}',
363
'function bar() {',
364
' "testStr";',
365
'}'
366
].join('\n'));
367
});
368
});
369
370
describe('visitors', function() {
371
it('should visit nodes in order', function() {
372
var source = [
373
'// Foo comment',
374
'function foo() {}',
375
'',
376
'// Bar comment',
377
'function bar() {}'
378
].join('\n');
379
380
var actualNodes = [];
381
382
function visitFunction(traverse, node, path, state) {
383
actualNodes.push([node.id.name, node.range[0]]);
384
}
385
visitFunction.test = function(node, path, state) {
386
return node.type === Syntax.FunctionDeclaration;
387
};
388
389
function visitComments(traverse, node, path, state) {
390
actualNodes.push([node.value, node.range[0]]);
391
}
392
visitComments.test = function(node, path, state) {
393
return node.type === 'Line';
394
};
395
396
transformFn([visitComments, visitFunction], source);
397
398
expect(actualNodes).toEqual([
399
[' Foo comment', 0],
400
['foo', 15],
401
[' Bar comment', 34],
402
['bar', 49]
403
]);
404
});
405
});
406
});
407
408