Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80760 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
return this.global
61
|| this.undeclared
62
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with));
63
},
64
mangle: function(options) {
65
if (!this.mangled_name && !this.unmangleable(options))
66
this.mangled_name = this.scope.next_mangled(options);
67
}
68
};
69
70
AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
71
// This does what ast_add_scope did in UglifyJS v1.
72
//
73
// Part of it could be done at parse time, but it would complicate
74
// the parser (and it's already kinda complex). It's also worth
75
// having it separated because we might need to call it multiple
76
// times on the same tree.
77
78
// pass 1: setup scope chaining and handle definitions
79
var self = this;
80
var scope = self.parent_scope = null;
81
var labels = new Dictionary();
82
var nesting = 0;
83
var tw = new TreeWalker(function(node, descend){
84
if (node instanceof AST_Scope) {
85
node.init_scope_vars(nesting);
86
var save_scope = node.parent_scope = scope;
87
var save_labels = labels;
88
++nesting;
89
scope = node;
90
labels = new Dictionary();
91
descend();
92
labels = save_labels;
93
scope = save_scope;
94
--nesting;
95
return true; // don't descend again in TreeWalker
96
}
97
if (node instanceof AST_Directive) {
98
node.scope = scope;
99
push_uniq(scope.directives, node.value);
100
return true;
101
}
102
if (node instanceof AST_With) {
103
for (var s = scope; s; s = s.parent_scope)
104
s.uses_with = true;
105
return;
106
}
107
if (node instanceof AST_LabeledStatement) {
108
var l = node.label;
109
if (labels.has(l.name))
110
throw new Error(string_template("Label {name} defined twice", l));
111
labels.set(l.name, l);
112
descend();
113
labels.del(l.name);
114
return true; // no descend again
115
}
116
if (node instanceof AST_Symbol) {
117
node.scope = scope;
118
}
119
if (node instanceof AST_Label) {
120
node.thedef = node;
121
node.init_scope_vars();
122
}
123
if (node instanceof AST_SymbolLambda) {
124
//scope.def_function(node);
125
//
126
// https://github.com/mishoo/UglifyJS2/issues/24 — MSIE
127
// leaks function expression names into the containing
128
// scope. Don't like this fix but seems we can't do any
129
// better. IE: please die. Please!
130
(node.scope = scope.parent_scope).def_function(node);
131
}
132
else if (node instanceof AST_SymbolDefun) {
133
// Careful here, the scope where this should be defined is
134
// the parent scope. The reason is that we enter a new
135
// scope when we encounter the AST_Defun node (which is
136
// instanceof AST_Scope) but we get to the symbol a bit
137
// later.
138
(node.scope = scope.parent_scope).def_function(node);
139
}
140
else if (node instanceof AST_SymbolVar
141
|| node instanceof AST_SymbolConst) {
142
var def = scope.def_variable(node);
143
def.constant = node instanceof AST_SymbolConst;
144
def.init = tw.parent().value;
145
}
146
else if (node instanceof AST_SymbolCatch) {
147
// XXX: this is wrong according to ECMA-262 (12.4). the
148
// `catch` argument name should be visible only inside the
149
// catch block. For a quick fix AST_Catch should inherit
150
// from AST_Scope. Keeping it this way because of IE,
151
// which doesn't obey the standard. (it introduces the
152
// identifier in the enclosing scope)
153
scope.def_variable(node);
154
}
155
if (node instanceof AST_LabelRef) {
156
var sym = labels.get(node.name);
157
if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", {
158
name: node.name,
159
line: node.start.line,
160
col: node.start.col
161
}));
162
node.thedef = sym;
163
}
164
});
165
self.walk(tw);
166
167
// pass 2: find back references and eval
168
var func = null;
169
var globals = self.globals = new Dictionary();
170
var tw = new TreeWalker(function(node, descend){
171
if (node instanceof AST_Lambda) {
172
var prev_func = func;
173
func = node;
174
descend();
175
func = prev_func;
176
return true;
177
}
178
if (node instanceof AST_LabelRef) {
179
node.reference();
180
return true;
181
}
182
if (node instanceof AST_SymbolRef) {
183
var name = node.name;
184
var sym = node.scope.find_variable(name);
185
if (!sym) {
186
var g;
187
if (globals.has(name)) {
188
g = globals.get(name);
189
} else {
190
g = new SymbolDef(self, globals.size(), node);
191
g.undeclared = true;
192
globals.set(name, g);
193
}
194
node.thedef = g;
195
if (name == "eval" && tw.parent() instanceof AST_Call) {
196
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope)
197
s.uses_eval = true;
198
}
199
if (name == "arguments") {
200
func.uses_arguments = true;
201
}
202
} else {
203
node.thedef = sym;
204
}
205
node.reference();
206
return true;
207
}
208
});
209
self.walk(tw);
210
});
211
212
AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
213
this.directives = []; // contains the directives defined in this scope, i.e. "use strict"
214
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
215
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
216
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
217
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
218
this.parent_scope = null; // the parent scope
219
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
220
this.cname = -1; // the current index for mangling functions/variables
221
this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
222
});
223
224
AST_Scope.DEFMETHOD("strict", function(){
225
return this.has_directive("use strict");
226
});
227
228
AST_Lambda.DEFMETHOD("init_scope_vars", function(){
229
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
230
this.uses_arguments = false;
231
});
232
233
AST_SymbolRef.DEFMETHOD("reference", function() {
234
var def = this.definition();
235
def.references.push(this);
236
var s = this.scope;
237
while (s) {
238
push_uniq(s.enclosed, def);
239
if (s === def.scope) break;
240
s = s.parent_scope;
241
}
242
this.frame = this.scope.nesting - def.scope.nesting;
243
});
244
245
AST_Label.DEFMETHOD("init_scope_vars", function(){
246
this.references = [];
247
});
248
249
AST_LabelRef.DEFMETHOD("reference", function(){
250
this.thedef.references.push(this);
251
});
252
253
AST_Scope.DEFMETHOD("find_variable", function(name){
254
if (name instanceof AST_Symbol) name = name.name;
255
return this.variables.get(name)
256
|| (this.parent_scope && this.parent_scope.find_variable(name));
257
});
258
259
AST_Scope.DEFMETHOD("has_directive", function(value){
260
return this.parent_scope && this.parent_scope.has_directive(value)
261
|| (this.directives.indexOf(value) >= 0 ? this : null);
262
});
263
264
AST_Scope.DEFMETHOD("def_function", function(symbol){
265
this.functions.set(symbol.name, this.def_variable(symbol));
266
});
267
268
AST_Scope.DEFMETHOD("def_variable", function(symbol){
269
var def;
270
if (!this.variables.has(symbol.name)) {
271
def = new SymbolDef(this, this.variables.size(), symbol);
272
this.variables.set(symbol.name, def);
273
def.global = !this.parent_scope;
274
} else {
275
def = this.variables.get(symbol.name);
276
def.orig.push(symbol);
277
}
278
return symbol.thedef = def;
279
});
280
281
AST_Scope.DEFMETHOD("next_mangled", function(options){
282
var ext = this.enclosed, n = ext.length;
283
out: while (true) {
284
var m = base54(++this.cname);
285
if (!is_identifier(m)) continue; // skip over "do"
286
// we must ensure that the mangled name does not shadow a name
287
// from some parent scope that is referenced in this or in
288
// inner scopes.
289
for (var i = n; --i >= 0;) {
290
var sym = ext[i];
291
var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
292
if (m == name) continue out;
293
}
294
return m;
295
}
296
});
297
298
AST_Scope.DEFMETHOD("references", function(sym){
299
if (sym instanceof AST_Symbol) sym = sym.definition();
300
return this.enclosed.indexOf(sym) < 0 ? null : sym;
301
});
302
303
AST_Symbol.DEFMETHOD("unmangleable", function(options){
304
return this.definition().unmangleable(options);
305
});
306
307
// property accessors are not mangleable
308
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
309
return true;
310
});
311
312
// labels are always mangleable
313
AST_Label.DEFMETHOD("unmangleable", function(){
314
return false;
315
});
316
317
AST_Symbol.DEFMETHOD("unreferenced", function(){
318
return this.definition().references.length == 0
319
&& !(this.scope.uses_eval || this.scope.uses_with);
320
});
321
322
AST_Symbol.DEFMETHOD("undeclared", function(){
323
return this.definition().undeclared;
324
});
325
326
AST_LabelRef.DEFMETHOD("undeclared", function(){
327
return false;
328
});
329
330
AST_Label.DEFMETHOD("undeclared", function(){
331
return false;
332
});
333
334
AST_Symbol.DEFMETHOD("definition", function(){
335
return this.thedef;
336
});
337
338
AST_Symbol.DEFMETHOD("global", function(){
339
return this.definition().global;
340
});
341
342
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
343
return defaults(options, {
344
except : [],
345
eval : false,
346
sort : false
347
});
348
});
349
350
AST_Toplevel.DEFMETHOD("mangle_names", function(options){
351
options = this._default_mangler_options(options);
352
// We only need to mangle declaration nodes. Special logic wired
353
// into the code generator will display the mangled name if it's
354
// present (and for AST_SymbolRef-s it'll use the mangled name of
355
// the AST_SymbolDeclaration that it points to).
356
var lname = -1;
357
var to_mangle = [];
358
var tw = new TreeWalker(function(node, descend){
359
if (node instanceof AST_LabeledStatement) {
360
// lname is incremented when we get to the AST_Label
361
var save_nesting = lname;
362
descend();
363
lname = save_nesting;
364
return true; // don't descend again in TreeWalker
365
}
366
if (node instanceof AST_Scope) {
367
var p = tw.parent(), a = [];
368
node.variables.each(function(symbol){
369
if (options.except.indexOf(symbol.name) < 0) {
370
a.push(symbol);
371
}
372
});
373
if (options.sort) a.sort(function(a, b){
374
return b.references.length - a.references.length;
375
});
376
to_mangle.push.apply(to_mangle, a);
377
return;
378
}
379
if (node instanceof AST_Label) {
380
var name;
381
do name = base54(++lname); while (!is_identifier(name));
382
node.mangled_name = name;
383
return true;
384
}
385
});
386
this.walk(tw);
387
to_mangle.forEach(function(def){ def.mangle(options) });
388
});
389
390
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
391
options = this._default_mangler_options(options);
392
var tw = new TreeWalker(function(node){
393
if (node instanceof AST_Constant)
394
base54.consider(node.print_to_string());
395
else if (node instanceof AST_Return)
396
base54.consider("return");
397
else if (node instanceof AST_Throw)
398
base54.consider("throw");
399
else if (node instanceof AST_Continue)
400
base54.consider("continue");
401
else if (node instanceof AST_Break)
402
base54.consider("break");
403
else if (node instanceof AST_Debugger)
404
base54.consider("debugger");
405
else if (node instanceof AST_Directive)
406
base54.consider(node.value);
407
else if (node instanceof AST_While)
408
base54.consider("while");
409
else if (node instanceof AST_Do)
410
base54.consider("do while");
411
else if (node instanceof AST_If) {
412
base54.consider("if");
413
if (node.alternative) base54.consider("else");
414
}
415
else if (node instanceof AST_Var)
416
base54.consider("var");
417
else if (node instanceof AST_Const)
418
base54.consider("const");
419
else if (node instanceof AST_Lambda)
420
base54.consider("function");
421
else if (node instanceof AST_For)
422
base54.consider("for");
423
else if (node instanceof AST_ForIn)
424
base54.consider("for in");
425
else if (node instanceof AST_Switch)
426
base54.consider("switch");
427
else if (node instanceof AST_Case)
428
base54.consider("case");
429
else if (node instanceof AST_Default)
430
base54.consider("default");
431
else if (node instanceof AST_With)
432
base54.consider("with");
433
else if (node instanceof AST_ObjectSetter)
434
base54.consider("set" + node.key);
435
else if (node instanceof AST_ObjectGetter)
436
base54.consider("get" + node.key);
437
else if (node instanceof AST_ObjectKeyVal)
438
base54.consider(node.key);
439
else if (node instanceof AST_New)
440
base54.consider("new");
441
else if (node instanceof AST_This)
442
base54.consider("this");
443
else if (node instanceof AST_Try)
444
base54.consider("try");
445
else if (node instanceof AST_Catch)
446
base54.consider("catch");
447
else if (node instanceof AST_Finally)
448
base54.consider("finally");
449
else if (node instanceof AST_Symbol && node.unmangleable(options))
450
base54.consider(node.name);
451
else if (node instanceof AST_Unary || node instanceof AST_Binary)
452
base54.consider(node.operator);
453
else if (node instanceof AST_Dot)
454
base54.consider(node.property);
455
});
456
this.walk(tw);
457
base54.sort();
458
});
459
460
var base54 = (function() {
461
var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
462
var chars, frequency;
463
function reset() {
464
frequency = Object.create(null);
465
chars = string.split("").map(function(ch){ return ch.charCodeAt(0) });
466
chars.forEach(function(ch){ frequency[ch] = 0 });
467
}
468
base54.consider = function(str){
469
for (var i = str.length; --i >= 0;) {
470
var code = str.charCodeAt(i);
471
if (code in frequency) ++frequency[code];
472
}
473
};
474
base54.sort = function() {
475
chars = mergeSort(chars, function(a, b){
476
if (is_digit(a) && !is_digit(b)) return 1;
477
if (is_digit(b) && !is_digit(a)) return -1;
478
return frequency[b] - frequency[a];
479
});
480
};
481
base54.reset = reset;
482
reset();
483
base54.get = function(){ return chars };
484
base54.freq = function(){ return frequency };
485
function base54(num) {
486
var ret = "", base = 54;
487
do {
488
ret += String.fromCharCode(chars[num % base]);
489
num = Math.floor(num / base);
490
base = 64;
491
} while (num > 0);
492
return ret;
493
};
494
return base54;
495
})();
496
497
AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
498
options = defaults(options, {
499
undeclared : false, // this makes a lot of noise
500
unreferenced : true,
501
assign_to_global : true,
502
func_arguments : true,
503
nested_defuns : true,
504
eval : true
505
});
506
var tw = new TreeWalker(function(node){
507
if (options.undeclared
508
&& node instanceof AST_SymbolRef
509
&& node.undeclared())
510
{
511
// XXX: this also warns about JS standard names,
512
// i.e. Object, Array, parseInt etc. Should add a list of
513
// exceptions.
514
AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", {
515
name: node.name,
516
file: node.start.file,
517
line: node.start.line,
518
col: node.start.col
519
});
520
}
521
if (options.assign_to_global)
522
{
523
var sym = null;
524
if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
525
sym = node.left;
526
else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
527
sym = node.init;
528
if (sym
529
&& (sym.undeclared()
530
|| (sym.global() && sym.scope !== sym.definition().scope))) {
531
AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", {
532
msg: sym.undeclared() ? "Accidental global?" : "Assignment to global",
533
name: sym.name,
534
file: sym.start.file,
535
line: sym.start.line,
536
col: sym.start.col
537
});
538
}
539
}
540
if (options.eval
541
&& node instanceof AST_SymbolRef
542
&& node.undeclared()
543
&& node.name == "eval") {
544
AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start);
545
}
546
if (options.unreferenced
547
&& (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
548
&& node.unreferenced()) {
549
AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
550
type: node instanceof AST_Label ? "Label" : "Symbol",
551
name: node.name,
552
file: node.start.file,
553
line: node.start.line,
554
col: node.start.col
555
});
556
}
557
if (options.func_arguments
558
&& node instanceof AST_Lambda
559
&& node.uses_arguments) {
560
AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", {
561
name: node.name ? node.name.name : "anonymous",
562
file: node.start.file,
563
line: node.start.line,
564
col: node.start.col
565
});
566
}
567
if (options.nested_defuns
568
&& node instanceof AST_Defun
569
&& !(tw.parent() instanceof AST_Scope)) {
570
AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", {
571
name: node.name.name,
572
type: tw.parent().TYPE,
573
file: node.start.file,
574
line: node.start.line,
575
col: node.start.col
576
});
577
}
578
});
579
this.walk(tw);
580
});
581
582