Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80551 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) {
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) {
89
return path[0] && path[0].type === Syntax.Program;
90
});
91
92
testScopeBoundary(source, blahIdents, 2, function(node, path) {
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) {
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) {
123
return path[0] && path[0].type === Syntax.Program;
124
});
125
126
testScopeBoundary(source, blahIdents, 1, function(node, path) {
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) {
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('creates a scope boundary around concise ArrowFunc exprs', function() {
144
var source =
145
'var foo;' +
146
'var bar = baz => baz;';
147
148
var programIdents = ['foo', 'bar'];
149
var barIdents = ['arguments', 'baz'];
150
151
testScopeBoundary(source, programIdents, 2, function(node, path) {
152
return path[0] && path[0].type === Syntax.Program;
153
});
154
155
testScopeBoundary(source, barIdents, 1, function(node, path) {
156
return path[0] && path[0].type === Syntax.ArrowFunctionExpression
157
&& path[0].body === node;
158
});
159
});
160
161
it('uses VariableDeclarations to determine scope boundary', function() {
162
var source =
163
'var foo = 1;' +
164
'function bar() {' +
165
' foo++;' +
166
' function baz() {' +
167
' var foo = 2;' +
168
' }' +
169
'}';
170
var programIdents = ['foo', 'bar'];
171
var barIdents = ['arguments', 'baz'];
172
var bazIdents = ['arguments', 'foo'];
173
174
testScopeBoundary(source, programIdents, 2, function(node, path) {
175
return path[0] && path[0].type === Syntax.Program;
176
});
177
178
testScopeBoundary(source, barIdents, 2, function(node, path) {
179
// All direct children of blah()
180
return path[0] && path[0].type === Syntax.BlockStatement &&
181
path[1] && path[1].type === Syntax.FunctionDeclaration &&
182
path[1].id.name === 'bar';
183
});
184
185
testScopeBoundary(source, bazIdents, 1, function(node, path) {
186
// All direct children of baz()
187
return path[0] && path[0].type === Syntax.BlockStatement &&
188
path[1] && path[1].type === Syntax.FunctionDeclaration &&
189
path[1].id.name === 'baz';
190
});
191
});
192
193
it('includes function args in functions scope boundary', function() {
194
var source =
195
'var foo;' +
196
'function blah(bar) {' +
197
' var baz;' +
198
'}' +
199
'var blah2 = bar2 => {var baz;};' +
200
'var blah3 = bar3 => bar3;';
201
var programIdents = ['foo', 'blah', 'blah2', 'blah3'];
202
var blahIdents = ['arguments', 'bar', 'baz'];
203
var blah2Idents = ['arguments', 'bar2', 'baz'];
204
var blah3Idents = ['arguments', 'bar3'];
205
206
testScopeBoundary(source, programIdents, 4, function(node, path) {
207
return path[0] && path[0].type === Syntax.Program;
208
});
209
210
testScopeBoundary(source, blahIdents, 1, function(node, path) {
211
// All direct children of blah()
212
return path[0] && path[0].type === Syntax.BlockStatement &&
213
path[1] && path[1].type === Syntax.FunctionDeclaration &&
214
path[1].id.name === 'blah';
215
});
216
217
testScopeBoundary(source, blah2Idents, 1, function(node, path) {
218
// All direct children of blah2()
219
return path[0] && path[0].type === Syntax.BlockStatement &&
220
path[1] && path[1].type === Syntax.ArrowFunctionExpression &&
221
path[2].id.name === 'blah2';
222
});
223
224
testScopeBoundary(source, blah3Idents, 1, function(node, path) {
225
// All direct children of blah3()
226
return path[0] && path[0].type === Syntax.ArrowFunctionExpression &&
227
path[0].body === node &&
228
path[1].id.name === 'blah3';
229
});
230
});
231
232
it('includes rest param args in function scope boundaries', function() {
233
var source =
234
'var foo;' +
235
'function blah(...bar) {' +
236
' var baz;' +
237
'}' +
238
'var blah2 = (...bar2) => {var baz;};' +
239
'var blah3 = (...bar3) => bar3;';
240
var programIdents = ['foo', 'blah', 'blah2', 'blah3'];
241
var blahIdents = ['arguments', 'bar', 'baz'];
242
var blah2Idents = ['arguments', 'bar2', 'baz'];
243
var blah3Idents = ['arguments', 'bar3'];
244
245
testScopeBoundary(source, programIdents, 4, function(node, path) {
246
return path[0] && path[0].type === Syntax.Program;
247
});
248
249
testScopeBoundary(source, blahIdents, 1, function(node, path) {
250
// All direct children of blah()
251
return path[0] && path[0].type === Syntax.BlockStatement &&
252
path[1] && path[1].type === Syntax.FunctionDeclaration &&
253
path[1].id.name === 'blah';
254
});
255
256
testScopeBoundary(source, blah2Idents, 1, function(node, path) {
257
// All direct children of blah2()
258
return path[0] && path[0].type === Syntax.BlockStatement &&
259
path[1] && path[1].type === Syntax.ArrowFunctionExpression &&
260
path[2].id.name === 'blah2';
261
});
262
263
testScopeBoundary(source, blah3Idents, 1, function(node, path) {
264
// All direct children of blah3()
265
return path[0] && path[0].type === Syntax.ArrowFunctionExpression &&
266
path[0].body === node &&
267
path[1].id.name === 'blah3';
268
});
269
});
270
271
it('puts FunctionExpression names within function scope', function() {
272
var source =
273
'var foo;' +
274
'var bar = function baz() {' +
275
' var blah;' +
276
'};';
277
var programIdents = ['foo', 'bar'];
278
var bazIdents = ['arguments', 'baz', 'blah'];
279
280
testScopeBoundary(source, programIdents, 2, function(node, path) {
281
return path[0] && path[0].type === Syntax.Program;
282
});
283
284
testScopeBoundary(source, bazIdents, 1, function(node, path) {
285
// All direct children of baz()
286
return path[0] && path[0].type === Syntax.BlockStatement &&
287
path[1] && path[1].type === Syntax.FunctionExpression &&
288
path[1].id.name === 'baz';
289
});
290
});
291
});
292
293
describe('block scope boundaries', function() {
294
it('creates a scope boundary around CatchClauses with params', function() {
295
var source =
296
'var blah = 0;' +
297
'try {' +
298
'} catch (e) {' +
299
' blah++;' +
300
'}';
301
var programIdents = ['blah'];
302
var catchIdents = ['e'];
303
304
testScopeBoundary(source, programIdents, 2, function(node, path) {
305
return path[0] && path[0].type === Syntax.Program;
306
});
307
308
testScopeBoundary(source, catchIdents, 1, function(node, path) {
309
// All direct children of catch(e) block
310
return path[0] && path[0].type === Syntax.BlockStatement &&
311
path[1] && path[1].type === Syntax.CatchClause;
312
});
313
});
314
315
it('includes vars defined in CatchClauses in the parent scope', function() {
316
var source =
317
'try {' +
318
'} catch (e) {' +
319
' var blah;' +
320
'}';
321
var programIdents = ['blah'];
322
var catchIdents = ['e'];
323
324
testScopeBoundary(source, programIdents, 1, function(node, path) {
325
return path[0] && path[0].type === Syntax.Program;
326
});
327
328
testScopeBoundary(source, catchIdents, 1, function(node, path) {
329
// All direct children of catch(e) block
330
return path[0] && path[0].type === Syntax.BlockStatement &&
331
path[1] && path[1].type === Syntax.CatchClause;
332
});
333
});
334
});
335
336
describe('scope chain linking', function() {
337
it('links parent scope boundaries', function() {
338
var source =
339
'var foo;' +
340
'function blah() {' +
341
' var bar;' +
342
' function nested() {' +
343
' var baz;' +
344
' }' +
345
'}';
346
var programIdents = ['foo', 'blah'];
347
var blahIdents = ['arguments', 'bar', 'nested'];
348
349
testParentScope(source, programIdents, 2, function(node, path) {
350
// All direct children of blah()
351
return path[0] && path[0].type === Syntax.BlockStatement &&
352
path[1] && path[1].type === Syntax.FunctionDeclaration &&
353
path[1].id.name === 'blah';
354
});
355
356
testParentScope(source, blahIdents, 1, function(node, path) {
357
// All direct children of nested()
358
return path[0] && path[0].type === Syntax.BlockStatement &&
359
path[1] && path[1].type === Syntax.FunctionDeclaration &&
360
path[1].id.name === 'nested';
361
});
362
});
363
364
it('nests MethodDefinition boundaries under parent scope', function() {
365
var source =
366
'var foo;' +
367
'class ClassA {' +
368
' blah() {' +
369
' var bar;' +
370
' }' +
371
'}';
372
var programIdents = ['foo', 'ClassA'];
373
374
testParentScope(source, programIdents, 1, function(node, path) {
375
// All direct children of blah()
376
return path[0] && path[0].type === Syntax.BlockStatement &&
377
path[1] && path[1].type === Syntax.FunctionExpression &&
378
path[2] && path[2].type === Syntax.MethodDefinition &&
379
path[2].key.name === 'blah';
380
});
381
});
382
});
383
384
describe('"use strict" tracking', function() {
385
function testStrictness(expectedStrict, source) {
386
var visitedNodes = 0;
387
function visitor(traverse, node, path, state) {
388
visitedNodes++;
389
expect(state.scopeIsStrict).toBe(expectedStrict);
390
}
391
visitor.test = function(node, path, state) {
392
return node.type === Syntax.Literal
393
&& node.value === 'testStr';
394
};
395
transformFn([visitor], source);
396
expect(visitedNodes).toBe(1);
397
}
398
399
it('detects program-level strictness', function() {
400
testStrictness(false, '"testStr";');
401
testStrictness(true, '"use strict"; "testStr";');
402
});
403
404
it('detects non-inherited strictness', function() {
405
testStrictness(true, [
406
'function foo() {',
407
' "use strict";',
408
' "testStr";',
409
'}'
410
].join('\n'));
411
});
412
413
it('detects program-inherited strictness', function() {
414
testStrictness(true, [
415
'"use strict";',
416
'function foo() {',
417
' "testStr";',
418
'}'
419
].join('\n'));
420
});
421
422
it('detects function-inherited strictness', function() {
423
testStrictness(true, [
424
'function foo() {',
425
' "use strict";',
426
' function bar() {',
427
' "testStr";',
428
' }',
429
'}'
430
].join('\n'));
431
});
432
433
it('does not detect sibling strictness', function() {
434
testStrictness(false, [
435
'function foo() {',
436
' "use strict";',
437
'}',
438
'function bar() {',
439
' "testStr";',
440
'}'
441
].join('\n'));
442
});
443
});
444
445
describe('visitors', function() {
446
it('should visit nodes in order', function() {
447
var source = [
448
'// Foo comment',
449
'function foo() {}',
450
'',
451
'// Bar comment',
452
'function bar() {}'
453
].join('\n');
454
455
var actualNodes = [];
456
457
function visitFunction(traverse, node, path, state) {
458
actualNodes.push([node.id.name, node.range[0]]);
459
}
460
visitFunction.test = function(node, path, state) {
461
return node.type === Syntax.FunctionDeclaration;
462
};
463
464
function visitComments(traverse, node, path, state) {
465
actualNodes.push([node.value, node.range[0]]);
466
}
467
visitComments.test = function(node, path, state) {
468
return node.type === 'Line';
469
};
470
471
transformFn([visitComments, visitFunction], source);
472
473
expect(actualNodes).toEqual([
474
[' Foo comment', 0],
475
['foo', 15],
476
[' Bar comment', 34],
477
['bar', 49]
478
]);
479
});
480
});
481
});
482
483