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