Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80743 views
1
/***********************************************************************
2
3
A JavaScript tokenizer / parser / beautifier / compressor.
4
https://github.com/mishoo/UglifyJS2
5
6
-------------------------------- (C) ---------------------------------
7
8
Author: Mihai Bazon
9
<[email protected]>
10
http://mihai.bazon.net/blog
11
12
Distributed under the BSD license:
13
14
Copyright 2012 (c) Mihai Bazon <[email protected]>
15
16
Redistribution and use in source and binary forms, with or without
17
modification, are permitted provided that the following conditions
18
are met:
19
20
* Redistributions of source code must retain the above
21
copyright notice, this list of conditions and the following
22
disclaimer.
23
24
* Redistributions in binary form must reproduce the above
25
copyright notice, this list of conditions and the following
26
disclaimer in the documentation and/or other materials
27
provided with the distribution.
28
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
30
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
33
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
34
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
35
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
36
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
38
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
39
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40
SUCH DAMAGE.
41
42
***********************************************************************/
43
44
"use strict";
45
46
function SymbolDef(scope, index, orig) {
47
this.name = orig.name;
48
this.orig = [ orig ];
49
this.scope = scope;
50
this.references = [];
51
this.global = false;
52
this.mangled_name = null;
53
this.undeclared = false;
54
this.constant = false;
55
this.index = index;
56
};
57
58
SymbolDef.prototype = {
59
unmangleable: function(options) {
60
if (!options) options = {};
61
62
return (this.global && !options.toplevel)
63
|| this.undeclared
64
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with))
65
|| (options.keep_fnames
66
&& (this.orig[0] instanceof AST_SymbolLambda
67
|| this.orig[0] instanceof AST_SymbolDefun));
68
},
69
mangle: function(options) {
70
var cache = options.cache && options.cache.props;
71
if (this.global && cache && cache.has(this.name)) {
72
this.mangled_name = cache.get(this.name);
73
}
74
else if (!this.mangled_name && !this.unmangleable(options)) {
75
var s = this.scope;
76
if (!options.screw_ie8 && this.orig[0] instanceof AST_SymbolLambda)
77
s = s.parent_scope;
78
this.mangled_name = s.next_mangled(options, this);
79
if (this.global && cache) {
80
cache.set(this.name, this.mangled_name);
81
}
82
}
83
}
84
};
85
86
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
87
options = defaults(options, {
88
screw_ie8: false,
89
cache: null
90
});
91
92
// pass 1: setup scope chaining and handle definitions
93
var self = this;
94
var scope = self.parent_scope = null;
95
var defun = null;
96
var nesting = 0;
97
var tw = new TreeWalker(function(node, descend){
98
if (options.screw_ie8 && node instanceof AST_Catch) {
99
var save_scope = scope;
100
scope = new AST_Scope(node);
101
scope.init_scope_vars(nesting);
102
scope.parent_scope = save_scope;
103
descend();
104
scope = save_scope;
105
return true;
106
}
107
if (node instanceof AST_Scope) {
108
node.init_scope_vars(nesting);
109
var save_scope = node.parent_scope = scope;
110
var save_defun = defun;
111
defun = scope = node;
112
++nesting; descend(); --nesting;
113
scope = save_scope;
114
defun = save_defun;
115
return true; // don't descend again in TreeWalker
116
}
117
if (node instanceof AST_Directive) {
118
node.scope = scope;
119
push_uniq(scope.directives, node.value);
120
return true;
121
}
122
if (node instanceof AST_With) {
123
for (var s = scope; s; s = s.parent_scope)
124
s.uses_with = true;
125
return;
126
}
127
if (node instanceof AST_Symbol) {
128
node.scope = scope;
129
}
130
if (node instanceof AST_SymbolLambda) {
131
defun.def_function(node);
132
}
133
else if (node instanceof AST_SymbolDefun) {
134
// Careful here, the scope where this should be defined is
135
// the parent scope. The reason is that we enter a new
136
// scope when we encounter the AST_Defun node (which is
137
// instanceof AST_Scope) but we get to the symbol a bit
138
// later.
139
(node.scope = defun.parent_scope).def_function(node);
140
}
141
else if (node instanceof AST_SymbolVar
142
|| node instanceof AST_SymbolConst) {
143
var def = defun.def_variable(node);
144
def.constant = node instanceof AST_SymbolConst;
145
def.init = tw.parent().value;
146
}
147
else if (node instanceof AST_SymbolCatch) {
148
(options.screw_ie8 ? scope : defun)
149
.def_variable(node);
150
}
151
});
152
self.walk(tw);
153
154
// pass 2: find back references and eval
155
var func = null;
156
var globals = self.globals = new Dictionary();
157
var tw = new TreeWalker(function(node, descend){
158
if (node instanceof AST_Lambda) {
159
var prev_func = func;
160
func = node;
161
descend();
162
func = prev_func;
163
return true;
164
}
165
if (node instanceof AST_SymbolRef) {
166
var name = node.name;
167
var sym = node.scope.find_variable(name);
168
if (!sym) {
169
var g;
170
if (globals.has(name)) {
171
g = globals.get(name);
172
} else {
173
g = new SymbolDef(self, globals.size(), node);
174
g.undeclared = true;
175
g.global = true;
176
globals.set(name, g);
177
}
178
node.thedef = g;
179
if (name == "eval" && tw.parent() instanceof AST_Call) {
180
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope)
181
s.uses_eval = true;
182
}
183
if (func && name == "arguments") {
184
func.uses_arguments = true;
185
}
186
} else {
187
node.thedef = sym;
188
}
189
node.reference();
190
return true;
191
}
192
});
193
self.walk(tw);
194
195
if (options.cache) {
196
this.cname = options.cache.cname;
197
}
198
});
199
200
AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
201
this.directives = []; // contains the directives defined in this scope, i.e. "use strict"
202
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
203
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
204
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
205
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
206
this.parent_scope = null; // the parent scope
207
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
208
this.cname = -1; // the current index for mangling functions/variables
209
this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
210
});
211
212
AST_Scope.DEFMETHOD("strict", function(){
213
return this.has_directive("use strict");
214
});
215
216
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
217
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
218
this.uses_arguments = false;
219
});
220
221
AST_SymbolRef.DEFMETHOD("reference", function() {
222
var def = this.definition();
223
def.references.push(this);
224
var s = this.scope;
225
while (s) {
226
push_uniq(s.enclosed, def);
227
if (s === def.scope) break;
228
s = s.parent_scope;
229
}
230
this.frame = this.scope.nesting - def.scope.nesting;
231
});
232
233
AST_Scope.DEFMETHOD("find_variable", function(name){
234
if (name instanceof AST_Symbol) name = name.name;
235
return this.variables.get(name)
236
|| (this.parent_scope && this.parent_scope.find_variable(name));
237
});
238
239
AST_Scope.DEFMETHOD("has_directive", function(value){
240
return this.parent_scope && this.parent_scope.has_directive(value)
241
|| (this.directives.indexOf(value) >= 0 ? this : null);
242
});
243
244
AST_Scope.DEFMETHOD("def_function", function(symbol){
245
this.functions.set(symbol.name, this.def_variable(symbol));
246
});
247
248
AST_Scope.DEFMETHOD("def_variable", function(symbol){
249
var def;
250
if (!this.variables.has(symbol.name)) {
251
def = new SymbolDef(this, this.variables.size(), symbol);
252
this.variables.set(symbol.name, def);
253
def.global = !this.parent_scope;
254
} else {
255
def = this.variables.get(symbol.name);
256
def.orig.push(symbol);
257
}
258
return symbol.thedef = def;
259
});
260
261
AST_Scope.DEFMETHOD("next_mangled", function(options){
262
var ext = this.enclosed;
263
out: while (true) {
264
var m = base54(++this.cname);
265
if (!is_identifier(m)) continue; // skip over "do"
266
267
// https://github.com/mishoo/UglifyJS2/issues/242 -- do not
268
// shadow a name excepted from mangling.
269
if (options.except.indexOf(m) >= 0) continue;
270
271
// we must ensure that the mangled name does not shadow a name
272
// from some parent scope that is referenced in this or in
273
// inner scopes.
274
for (var i = ext.length; --i >= 0;) {
275
var sym = ext[i];
276
var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
277
if (m == name) continue out;
278
}
279
return m;
280
}
281
});
282
283
AST_Function.DEFMETHOD("next_mangled", function(options, def){
284
// #179, #326
285
// in Safari strict mode, something like (function x(x){...}) is a syntax error;
286
// a function expression's argument cannot shadow the function expression's name
287
288
var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition();
289
while (true) {
290
var name = AST_Lambda.prototype.next_mangled.call(this, options, def);
291
if (!(tricky_def && tricky_def.mangled_name == name))
292
return name;
293
}
294
});
295
296
AST_Scope.DEFMETHOD("references", function(sym){
297
if (sym instanceof AST_Symbol) sym = sym.definition();
298
return this.enclosed.indexOf(sym) < 0 ? null : sym;
299
});
300
301
AST_Symbol.DEFMETHOD("unmangleable", function(options){
302
return this.definition().unmangleable(options);
303
});
304
305
// property accessors are not mangleable
306
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
307
return true;
308
});
309
310
// labels are always mangleable
311
AST_Label.DEFMETHOD("unmangleable", function(){
312
return false;
313
});
314
315
AST_Symbol.DEFMETHOD("unreferenced", function(){
316
return this.definition().references.length == 0
317
&& !(this.scope.uses_eval || this.scope.uses_with);
318
});
319
320
AST_Symbol.DEFMETHOD("undeclared", function(){
321
return this.definition().undeclared;
322
});
323
324
AST_LabelRef.DEFMETHOD("undeclared", function(){
325
return false;
326
});
327
328
AST_Label.DEFMETHOD("undeclared", function(){
329
return false;
330
});
331
332
AST_Symbol.DEFMETHOD("definition", function(){
333
return this.thedef;
334
});
335
336
AST_Symbol.DEFMETHOD("global", function(){
337
return this.definition().global;
338
});
339
340
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
341
return defaults(options, {
342
except : [],
343
eval : false,
344
sort : false,
345
toplevel : false,
346
screw_ie8 : false,
347
keep_fnames : false
348
});
349
});
350
351
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
352
options = this._default_mangler_options(options);
353
// We only need to mangle declaration nodes. Special logic wired
354
// into the code generator will display the mangled name if it's
355
// present (and for AST_SymbolRef-s it'll use the mangled name of
356
// the AST_SymbolDeclaration that it points to).
357
var lname = -1;
358
var to_mangle = [];
359
360
if (options.cache) {
361
this.globals.each(function(symbol){
362
if (options.except.indexOf(symbol.name) < 0) {
363
to_mangle.push(symbol);
364
}
365
});
366
}
367
368
var tw = new TreeWalker(function(node, descend){
369
if (node instanceof AST_LabeledStatement) {
370
// lname is incremented when we get to the AST_Label
371
var save_nesting = lname;
372
descend();
373
lname = save_nesting;
374
return true; // don't descend again in TreeWalker
375
}
376
if (node instanceof AST_Scope) {
377
var p = tw.parent(), a = [];
378
node.variables.each(function(symbol){
379
if (options.except.indexOf(symbol.name) < 0) {
380
a.push(symbol);
381
}
382
});
383
if (options.sort) a.sort(function(a, b){
384
return b.references.length - a.references.length;
385
});
386
to_mangle.push.apply(to_mangle, a);
387
return;
388
}
389
if (node instanceof AST_Label) {
390
var name;
391
do name = base54(++lname); while (!is_identifier(name));
392
node.mangled_name = name;
393
return true;
394
}
395
if (options.screw_ie8 && node instanceof AST_SymbolCatch) {
396
to_mangle.push(node.definition());
397
return;
398
}
399
});
400
this.walk(tw);
401
to_mangle.forEach(function(def){ def.mangle(options) });
402
403
if (options.cache) {
404
options.cache.cname = this.cname;
405
}
406
});
407
408
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
409
options = this._default_mangler_options(options);
410
var tw = new TreeWalker(function(node){
411
if (node instanceof AST_Constant)
412
base54.consider(node.print_to_string());
413
else if (node instanceof AST_Return)
414
base54.consider("return");
415
else if (node instanceof AST_Throw)
416
base54.consider("throw");
417
else if (node instanceof AST_Continue)
418
base54.consider("continue");
419
else if (node instanceof AST_Break)
420
base54.consider("break");
421
else if (node instanceof AST_Debugger)
422
base54.consider("debugger");
423
else if (node instanceof AST_Directive)
424
base54.consider(node.value);
425
else if (node instanceof AST_While)
426
base54.consider("while");
427
else if (node instanceof AST_Do)
428
base54.consider("do while");
429
else if (node instanceof AST_If) {
430
base54.consider("if");
431
if (node.alternative) base54.consider("else");
432
}
433
else if (node instanceof AST_Var)
434
base54.consider("var");
435
else if (node instanceof AST_Const)
436
base54.consider("const");
437
else if (node instanceof AST_Lambda)
438
base54.consider("function");
439
else if (node instanceof AST_For)
440
base54.consider("for");
441
else if (node instanceof AST_ForIn)
442
base54.consider("for in");
443
else if (node instanceof AST_Switch)
444
base54.consider("switch");
445
else if (node instanceof AST_Case)
446
base54.consider("case");
447
else if (node instanceof AST_Default)
448
base54.consider("default");
449
else if (node instanceof AST_With)
450
base54.consider("with");
451
else if (node instanceof AST_ObjectSetter)
452
base54.consider("set" + node.key);
453
else if (node instanceof AST_ObjectGetter)
454
base54.consider("get" + node.key);
455
else if (node instanceof AST_ObjectKeyVal)
456
base54.consider(node.key);
457
else if (node instanceof AST_New)
458
base54.consider("new");
459
else if (node instanceof AST_This)
460
base54.consider("this");
461
else if (node instanceof AST_Try)
462
base54.consider("try");
463
else if (node instanceof AST_Catch)
464
base54.consider("catch");
465
else if (node instanceof AST_Finally)
466
base54.consider("finally");
467
else if (node instanceof AST_Symbol && node.unmangleable(options))
468
base54.consider(node.name);
469
else if (node instanceof AST_Unary || node instanceof AST_Binary)
470
base54.consider(node.operator);
471
else if (node instanceof AST_Dot)
472
base54.consider(node.property);
473
});
474
this.walk(tw);
475
base54.sort();
476
});
477
478
var base54 = (function() {
479
var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
480
var chars, frequency;
481
function reset() {
482
frequency = Object.create(null);
483
chars = string.split("").map(function(ch){ return ch.charCodeAt(0) });
484
chars.forEach(function(ch){ frequency[ch] = 0 });
485
}
486
base54.consider = function(str){
487
for (var i = str.length; --i >= 0;) {
488
var code = str.charCodeAt(i);
489
if (code in frequency) ++frequency[code];
490
}
491
};
492
base54.sort = function() {
493
chars = mergeSort(chars, function(a, b){
494
if (is_digit(a) && !is_digit(b)) return 1;
495
if (is_digit(b) && !is_digit(a)) return -1;
496
return frequency[b] - frequency[a];
497
});
498
};
499
base54.reset = reset;
500
reset();
501
base54.get = function(){ return chars };
502
base54.freq = function(){ return frequency };
503
function base54(num) {
504
var ret = "", base = 54;
505
num++;
506
do {
507
num--;
508
ret += String.fromCharCode(chars[num % base]);
509
num = Math.floor(num / base);
510
base = 64;
511
} while (num > 0);
512
return ret;
513
};
514
return base54;
515
})();
516
517
AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
518
options = defaults(options, {
519
undeclared : false, // this makes a lot of noise
520
unreferenced : true,
521
assign_to_global : true,
522
func_arguments : true,
523
nested_defuns : true,
524
eval : true
525
});
526
var tw = new TreeWalker(function(node){
527
if (options.undeclared
528
&& node instanceof AST_SymbolRef
529
&& node.undeclared())
530
{
531
// XXX: this also warns about JS standard names,
532
// i.e. Object, Array, parseInt etc. Should add a list of
533
// exceptions.
534
AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", {
535
name: node.name,
536
file: node.start.file,
537
line: node.start.line,
538
col: node.start.col
539
});
540
}
541
if (options.assign_to_global)
542
{
543
var sym = null;
544
if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
545
sym = node.left;
546
else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
547
sym = node.init;
548
if (sym
549
&& (sym.undeclared()
550
|| (sym.global() && sym.scope !== sym.definition().scope))) {
551
AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", {
552
msg: sym.undeclared() ? "Accidental global?" : "Assignment to global",
553
name: sym.name,
554
file: sym.start.file,
555
line: sym.start.line,
556
col: sym.start.col
557
});
558
}
559
}
560
if (options.eval
561
&& node instanceof AST_SymbolRef
562
&& node.undeclared()
563
&& node.name == "eval") {
564
AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start);
565
}
566
if (options.unreferenced
567
&& (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
568
&& !(node instanceof AST_SymbolCatch)
569
&& node.unreferenced()) {
570
AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
571
type: node instanceof AST_Label ? "Label" : "Symbol",
572
name: node.name,
573
file: node.start.file,
574
line: node.start.line,
575
col: node.start.col
576
});
577
}
578
if (options.func_arguments
579
&& node instanceof AST_Lambda
580
&& node.uses_arguments) {
581
AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", {
582
name: node.name ? node.name.name : "anonymous",
583
file: node.start.file,
584
line: node.start.line,
585
col: node.start.col
586
});
587
}
588
if (options.nested_defuns
589
&& node instanceof AST_Defun
590
&& !(tw.parent() instanceof AST_Scope)) {
591
AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", {
592
name: node.name.name,
593
type: tw.parent().TYPE,
594
file: node.start.file,
595
line: node.start.line,
596
col: node.start.col
597
});
598
}
599
});
600
this.walk(tw);
601
});
602
603