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
/*jslint node:true*/
18
19
/**
20
* @typechecks
21
*/
22
'use strict';
23
24
var base62 = require('base62');
25
var Syntax = require('esprima-fb').Syntax;
26
var utils = require('../src/utils');
27
var reservedWordsHelper = require('./reserved-words-helper');
28
29
var declareIdentInLocalScope = utils.declareIdentInLocalScope;
30
var initScopeMetadata = utils.initScopeMetadata;
31
32
var SUPER_PROTO_IDENT_PREFIX = '____SuperProtoOf';
33
34
var _anonClassUUIDCounter = 0;
35
var _mungedSymbolMaps = {};
36
37
function resetSymbols() {
38
_anonClassUUIDCounter = 0;
39
_mungedSymbolMaps = {};
40
}
41
42
/**
43
* Used to generate a unique class for use with code-gens for anonymous class
44
* expressions.
45
*
46
* @param {object} state
47
* @return {string}
48
*/
49
function _generateAnonymousClassName(state) {
50
var mungeNamespace = state.mungeNamespace || '';
51
return '____Class' + mungeNamespace + base62.encode(_anonClassUUIDCounter++);
52
}
53
54
/**
55
* Given an identifier name, munge it using the current state's mungeNamespace.
56
*
57
* @param {string} identName
58
* @param {object} state
59
* @return {string}
60
*/
61
function _getMungedName(identName, state) {
62
var mungeNamespace = state.mungeNamespace;
63
var shouldMinify = state.g.opts.minify;
64
65
if (shouldMinify) {
66
if (!_mungedSymbolMaps[mungeNamespace]) {
67
_mungedSymbolMaps[mungeNamespace] = {
68
symbolMap: {},
69
identUUIDCounter: 0
70
};
71
}
72
73
var symbolMap = _mungedSymbolMaps[mungeNamespace].symbolMap;
74
if (!symbolMap[identName]) {
75
symbolMap[identName] =
76
base62.encode(_mungedSymbolMaps[mungeNamespace].identUUIDCounter++);
77
}
78
identName = symbolMap[identName];
79
}
80
return '$' + mungeNamespace + identName;
81
}
82
83
/**
84
* Extracts super class information from a class node.
85
*
86
* Information includes name of the super class and/or the expression string
87
* (if extending from an expression)
88
*
89
* @param {object} node
90
* @param {object} state
91
* @return {object}
92
*/
93
function _getSuperClassInfo(node, state) {
94
var ret = {
95
name: null,
96
expression: null
97
};
98
if (node.superClass) {
99
if (node.superClass.type === Syntax.Identifier) {
100
ret.name = node.superClass.name;
101
} else {
102
// Extension from an expression
103
ret.name = _generateAnonymousClassName(state);
104
ret.expression = state.g.source.substring(
105
node.superClass.range[0],
106
node.superClass.range[1]
107
);
108
}
109
}
110
return ret;
111
}
112
113
/**
114
* Used with .filter() to find the constructor method in a list of
115
* MethodDefinition nodes.
116
*
117
* @param {object} classElement
118
* @return {boolean}
119
*/
120
function _isConstructorMethod(classElement) {
121
return classElement.type === Syntax.MethodDefinition &&
122
classElement.key.type === Syntax.Identifier &&
123
classElement.key.name === 'constructor';
124
}
125
126
/**
127
* @param {object} node
128
* @param {object} state
129
* @return {boolean}
130
*/
131
function _shouldMungeIdentifier(node, state) {
132
return (
133
!!state.methodFuncNode &&
134
!utils.getDocblock(state).hasOwnProperty('preventMunge') &&
135
/^_(?!_)/.test(node.name)
136
);
137
}
138
139
/**
140
* @param {function} traverse
141
* @param {object} node
142
* @param {array} path
143
* @param {object} state
144
*/
145
function visitClassMethod(traverse, node, path, state) {
146
if (!state.g.opts.es5 && (node.kind === 'get' || node.kind === 'set')) {
147
throw new Error(
148
'This transform does not support ' + node.kind + 'ter methods for ES6 ' +
149
'classes. (line: ' + node.loc.start.line + ', col: ' +
150
node.loc.start.column + ')'
151
);
152
}
153
state = utils.updateState(state, {
154
methodNode: node
155
});
156
utils.catchup(node.range[0], state);
157
path.unshift(node);
158
traverse(node.value, path, state);
159
path.shift();
160
return false;
161
}
162
visitClassMethod.test = function(node, path, state) {
163
return node.type === Syntax.MethodDefinition;
164
};
165
166
/**
167
* @param {function} traverse
168
* @param {object} node
169
* @param {array} path
170
* @param {object} state
171
*/
172
function visitClassFunctionExpression(traverse, node, path, state) {
173
var methodNode = path[0];
174
var isGetter = methodNode.kind === 'get';
175
var isSetter = methodNode.kind === 'set';
176
177
state = utils.updateState(state, {
178
methodFuncNode: node
179
});
180
181
if (methodNode.key.name === 'constructor') {
182
utils.append('function ' + state.className, state);
183
} else {
184
var methodAccessorComputed = false;
185
var methodAccessor;
186
var prototypeOrStatic = methodNode.static ? '' : '.prototype';
187
var objectAccessor = state.className + prototypeOrStatic;
188
189
if (methodNode.key.type === Syntax.Identifier) {
190
// foo() {}
191
methodAccessor = methodNode.key.name;
192
if (_shouldMungeIdentifier(methodNode.key, state)) {
193
methodAccessor = _getMungedName(methodAccessor, state);
194
}
195
if (isGetter || isSetter) {
196
methodAccessor = JSON.stringify(methodAccessor);
197
} else if (reservedWordsHelper.isReservedWord(methodAccessor)) {
198
methodAccessorComputed = true;
199
methodAccessor = JSON.stringify(methodAccessor);
200
}
201
} else if (methodNode.key.type === Syntax.Literal) {
202
// 'foo bar'() {} | get 'foo bar'() {} | set 'foo bar'() {}
203
methodAccessor = JSON.stringify(methodNode.key.value);
204
methodAccessorComputed = true;
205
}
206
207
if (isSetter || isGetter) {
208
utils.append(
209
'Object.defineProperty(' +
210
objectAccessor + ',' +
211
methodAccessor + ',' +
212
'{configurable:true,' +
213
methodNode.kind + ':function',
214
state
215
);
216
} else {
217
if (state.g.opts.es3) {
218
if (methodAccessorComputed) {
219
methodAccessor = '[' + methodAccessor + ']';
220
} else {
221
methodAccessor = '.' + methodAccessor;
222
}
223
utils.append(
224
objectAccessor +
225
methodAccessor + '=function' + (node.generator ? '*' : ''),
226
state
227
);
228
} else {
229
if (!methodAccessorComputed) {
230
methodAccessor = JSON.stringify(methodAccessor);
231
}
232
utils.append(
233
'Object.defineProperty(' +
234
objectAccessor + ',' +
235
methodAccessor + ',' +
236
'{writable:true,configurable:true,' +
237
'value:function' + (node.generator ? '*' : ''),
238
state
239
);
240
}
241
}
242
}
243
utils.move(methodNode.key.range[1], state);
244
utils.append('(', state);
245
246
var params = node.params;
247
if (params.length > 0) {
248
utils.catchupNewlines(params[0].range[0], state);
249
for (var i = 0; i < params.length; i++) {
250
utils.catchup(node.params[i].range[0], state);
251
path.unshift(node);
252
traverse(params[i], path, state);
253
path.shift();
254
}
255
}
256
257
var closingParenPosition = utils.getNextSyntacticCharOffset(')', state);
258
utils.catchupWhiteSpace(closingParenPosition, state);
259
260
var openingBracketPosition = utils.getNextSyntacticCharOffset('{', state);
261
utils.catchup(openingBracketPosition + 1, state);
262
263
if (!state.scopeIsStrict) {
264
utils.append('"use strict";', state);
265
state = utils.updateState(state, {
266
scopeIsStrict: true
267
});
268
}
269
utils.move(node.body.range[0] + '{'.length, state);
270
271
path.unshift(node);
272
traverse(node.body, path, state);
273
path.shift();
274
utils.catchup(node.body.range[1], state);
275
276
if (methodNode.key.name !== 'constructor') {
277
if (isGetter || isSetter || !state.g.opts.es3) {
278
utils.append('})', state);
279
}
280
utils.append(';', state);
281
}
282
return false;
283
}
284
visitClassFunctionExpression.test = function(node, path, state) {
285
return node.type === Syntax.FunctionExpression
286
&& path[0].type === Syntax.MethodDefinition;
287
};
288
289
function visitClassMethodParam(traverse, node, path, state) {
290
var paramName = node.name;
291
if (_shouldMungeIdentifier(node, state)) {
292
paramName = _getMungedName(node.name, state);
293
}
294
utils.append(paramName, state);
295
utils.move(node.range[1], state);
296
}
297
visitClassMethodParam.test = function(node, path, state) {
298
if (!path[0] || !path[1]) {
299
return;
300
}
301
302
var parentFuncExpr = path[0];
303
var parentClassMethod = path[1];
304
305
return parentFuncExpr.type === Syntax.FunctionExpression
306
&& parentClassMethod.type === Syntax.MethodDefinition
307
&& node.type === Syntax.Identifier;
308
};
309
310
/**
311
* @param {function} traverse
312
* @param {object} node
313
* @param {array} path
314
* @param {object} state
315
*/
316
function _renderClassBody(traverse, node, path, state) {
317
var className = state.className;
318
var superClass = state.superClass;
319
320
// Set up prototype of constructor on same line as `extends` for line-number
321
// preservation. This relies on function-hoisting if a constructor function is
322
// defined in the class body.
323
if (superClass.name) {
324
// If the super class is an expression, we need to memoize the output of the
325
// expression into the generated class name variable and use that to refer
326
// to the super class going forward. Example:
327
//
328
// class Foo extends mixin(Bar, Baz) {}
329
// --transforms to--
330
// function Foo() {} var ____Class0Blah = mixin(Bar, Baz);
331
if (superClass.expression !== null) {
332
utils.append(
333
'var ' + superClass.name + '=' + superClass.expression + ';',
334
state
335
);
336
}
337
338
var keyName = superClass.name + '____Key';
339
var keyNameDeclarator = '';
340
if (!utils.identWithinLexicalScope(keyName, state)) {
341
keyNameDeclarator = 'var ';
342
declareIdentInLocalScope(keyName, initScopeMetadata(node), state);
343
}
344
utils.append(
345
'for(' + keyNameDeclarator + keyName + ' in ' + superClass.name + '){' +
346
'if(' + superClass.name + '.hasOwnProperty(' + keyName + ')){' +
347
className + '[' + keyName + ']=' +
348
superClass.name + '[' + keyName + '];' +
349
'}' +
350
'}',
351
state
352
);
353
354
var superProtoIdentStr = SUPER_PROTO_IDENT_PREFIX + superClass.name;
355
if (!utils.identWithinLexicalScope(superProtoIdentStr, state)) {
356
utils.append(
357
'var ' + superProtoIdentStr + '=' + superClass.name + '===null?' +
358
'null:' + superClass.name + '.prototype;',
359
state
360
);
361
declareIdentInLocalScope(superProtoIdentStr, initScopeMetadata(node), state);
362
}
363
364
utils.append(
365
className + '.prototype=Object.create(' + superProtoIdentStr + ');',
366
state
367
);
368
utils.append(
369
className + '.prototype.constructor=' + className + ';',
370
state
371
);
372
utils.append(
373
className + '.__superConstructor__=' + superClass.name + ';',
374
state
375
);
376
}
377
378
// If there's no constructor method specified in the class body, create an
379
// empty constructor function at the top (same line as the class keyword)
380
if (!node.body.body.filter(_isConstructorMethod).pop()) {
381
utils.append('function ' + className + '(){', state);
382
if (!state.scopeIsStrict) {
383
utils.append('"use strict";', state);
384
}
385
if (superClass.name) {
386
utils.append(
387
'if(' + superClass.name + '!==null){' +
388
superClass.name + '.apply(this,arguments);}',
389
state
390
);
391
}
392
utils.append('}', state);
393
}
394
395
utils.move(node.body.range[0] + '{'.length, state);
396
traverse(node.body, path, state);
397
utils.catchupWhiteSpace(node.range[1], state);
398
}
399
400
/**
401
* @param {function} traverse
402
* @param {object} node
403
* @param {array} path
404
* @param {object} state
405
*/
406
function visitClassDeclaration(traverse, node, path, state) {
407
var className = node.id.name;
408
var superClass = _getSuperClassInfo(node, state);
409
410
state = utils.updateState(state, {
411
mungeNamespace: className,
412
className: className,
413
superClass: superClass
414
});
415
416
_renderClassBody(traverse, node, path, state);
417
418
return false;
419
}
420
visitClassDeclaration.test = function(node, path, state) {
421
return node.type === Syntax.ClassDeclaration;
422
};
423
424
/**
425
* @param {function} traverse
426
* @param {object} node
427
* @param {array} path
428
* @param {object} state
429
*/
430
function visitClassExpression(traverse, node, path, state) {
431
var className = node.id && node.id.name || _generateAnonymousClassName(state);
432
var superClass = _getSuperClassInfo(node, state);
433
434
utils.append('(function(){', state);
435
436
state = utils.updateState(state, {
437
mungeNamespace: className,
438
className: className,
439
superClass: superClass
440
});
441
442
_renderClassBody(traverse, node, path, state);
443
444
utils.append('return ' + className + ';})()', state);
445
return false;
446
}
447
visitClassExpression.test = function(node, path, state) {
448
return node.type === Syntax.ClassExpression;
449
};
450
451
/**
452
* @param {function} traverse
453
* @param {object} node
454
* @param {array} path
455
* @param {object} state
456
*/
457
function visitPrivateIdentifier(traverse, node, path, state) {
458
utils.append(_getMungedName(node.name, state), state);
459
utils.move(node.range[1], state);
460
}
461
visitPrivateIdentifier.test = function(node, path, state) {
462
if (node.type === Syntax.Identifier && _shouldMungeIdentifier(node, state)) {
463
// Always munge non-computed properties of MemberExpressions
464
// (a la preventing access of properties of unowned objects)
465
if (path[0].type === Syntax.MemberExpression && path[0].object !== node
466
&& path[0].computed === false) {
467
return true;
468
}
469
470
// Always munge identifiers that were declared within the method function
471
// scope
472
if (utils.identWithinLexicalScope(node.name, state, state.methodFuncNode)) {
473
return true;
474
}
475
476
// Always munge private keys on object literals defined within a method's
477
// scope.
478
if (path[0].type === Syntax.Property
479
&& path[1].type === Syntax.ObjectExpression) {
480
return true;
481
}
482
483
// Always munge function parameters
484
if (path[0].type === Syntax.FunctionExpression
485
|| path[0].type === Syntax.FunctionDeclaration
486
|| path[0].type === Syntax.ArrowFunctionExpression) {
487
for (var i = 0; i < path[0].params.length; i++) {
488
if (path[0].params[i] === node) {
489
return true;
490
}
491
}
492
}
493
}
494
return false;
495
};
496
497
/**
498
* @param {function} traverse
499
* @param {object} node
500
* @param {array} path
501
* @param {object} state
502
*/
503
function visitSuperCallExpression(traverse, node, path, state) {
504
var superClassName = state.superClass.name;
505
506
if (node.callee.type === Syntax.Identifier) {
507
if (_isConstructorMethod(state.methodNode)) {
508
utils.append(superClassName + '.call(', state);
509
} else {
510
var protoProp = SUPER_PROTO_IDENT_PREFIX + superClassName;
511
if (state.methodNode.key.type === Syntax.Identifier) {
512
protoProp += '.' + state.methodNode.key.name;
513
} else if (state.methodNode.key.type === Syntax.Literal) {
514
protoProp += '[' + JSON.stringify(state.methodNode.key.value) + ']';
515
}
516
utils.append(protoProp + ".call(", state);
517
}
518
utils.move(node.callee.range[1], state);
519
} else if (node.callee.type === Syntax.MemberExpression) {
520
utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);
521
utils.move(node.callee.object.range[1], state);
522
523
if (node.callee.computed) {
524
// ["a" + "b"]
525
utils.catchup(node.callee.property.range[1] + ']'.length, state);
526
} else {
527
// .ab
528
utils.append('.' + node.callee.property.name, state);
529
}
530
531
utils.append('.call(', state);
532
utils.move(node.callee.range[1], state);
533
}
534
535
utils.append('this', state);
536
if (node.arguments.length > 0) {
537
utils.append(',', state);
538
utils.catchupWhiteSpace(node.arguments[0].range[0], state);
539
traverse(node.arguments, path, state);
540
}
541
542
utils.catchupWhiteSpace(node.range[1], state);
543
utils.append(')', state);
544
return false;
545
}
546
visitSuperCallExpression.test = function(node, path, state) {
547
if (state.superClass && node.type === Syntax.CallExpression) {
548
var callee = node.callee;
549
if (callee.type === Syntax.Identifier && callee.name === 'super'
550
|| callee.type == Syntax.MemberExpression
551
&& callee.object.name === 'super') {
552
return true;
553
}
554
}
555
return false;
556
};
557
558
/**
559
* @param {function} traverse
560
* @param {object} node
561
* @param {array} path
562
* @param {object} state
563
*/
564
function visitSuperMemberExpression(traverse, node, path, state) {
565
var superClassName = state.superClass.name;
566
567
utils.append(SUPER_PROTO_IDENT_PREFIX + superClassName, state);
568
utils.move(node.object.range[1], state);
569
}
570
visitSuperMemberExpression.test = function(node, path, state) {
571
return state.superClass
572
&& node.type === Syntax.MemberExpression
573
&& node.object.type === Syntax.Identifier
574
&& node.object.name === 'super';
575
};
576
577
exports.resetSymbols = resetSymbols;
578
579
exports.visitorList = [
580
visitClassDeclaration,
581
visitClassExpression,
582
visitClassFunctionExpression,
583
visitClassMethod,
584
visitClassMethodParam,
585
visitPrivateIdentifier,
586
visitSuperCallExpression,
587
visitSuperMemberExpression
588
];
589
590