UNPKG

21.4 kBJavaScriptView Raw
1/***********************************************************************
2
3 A JavaScript tokenizer / parser / beautifier / compressor.
4 https://github.com/mishoo/UglifyJS2
5
6 -------------------------------- (C) ---------------------------------
7
8 Author: Mihai Bazon
9 <mihai.bazon@gmail.com>
10 http://mihai.bazon.net/blog
11
12 Distributed under the BSD license:
13
14 Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
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
46function SymbolDef(scope, orig, init) {
47 this.name = orig.name;
48 this.orig = [ orig ];
49 this.init = init;
50 this.eliminated = 0;
51 this.scope = scope;
52 this.references = [];
53 this.replaced = 0;
54 this.global = false;
55 this.mangled_name = null;
56 this.undeclared = false;
57 this.id = SymbolDef.next_id++;
58 this.lambda = orig instanceof AST_SymbolLambda;
59}
60
61SymbolDef.next_id = 1;
62
63SymbolDef.prototype = {
64 unmangleable: function(options) {
65 return this.global && !options.toplevel
66 || this.undeclared
67 || !options.eval && this.scope.pinned()
68 || options.keep_fnames
69 && (this.orig[0] instanceof AST_SymbolLambda
70 || this.orig[0] instanceof AST_SymbolDefun);
71 },
72 mangle: function(options) {
73 var cache = options.cache && options.cache.props;
74 if (this.global && cache && cache.has(this.name)) {
75 this.mangled_name = cache.get(this.name);
76 } else if (!this.mangled_name && !this.unmangleable(options)) {
77 var def;
78 if (def = this.redefined()) {
79 this.mangled_name = def.mangled_name || def.name;
80 } else {
81 this.mangled_name = next_mangled_name(this.scope, options, this);
82 }
83 if (this.global && cache) {
84 cache.set(this.name, this.mangled_name);
85 }
86 }
87 },
88 redefined: function() {
89 return this.defun && this.defun.variables.get(this.name);
90 }
91};
92
93AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
94 options = defaults(options, {
95 cache: null,
96 ie8: false,
97 });
98
99 // pass 1: setup scope chaining and handle definitions
100 var self = this;
101 var scope = self.parent_scope = null;
102 var defun = null;
103 var tw = new TreeWalker(function(node, descend) {
104 if (node instanceof AST_Catch) {
105 var save_scope = scope;
106 scope = new AST_Scope(node);
107 scope.init_scope_vars(save_scope);
108 descend();
109 scope = save_scope;
110 return true;
111 }
112 if (node instanceof AST_Scope) {
113 node.init_scope_vars(scope);
114 var save_scope = scope;
115 var save_defun = defun;
116 defun = scope = node;
117 descend();
118 scope = save_scope;
119 defun = save_defun;
120 return true;
121 }
122 if (node instanceof AST_With) {
123 for (var s = scope; s; s = s.parent_scope) s.uses_with = true;
124 return;
125 }
126 if (node instanceof AST_Symbol) {
127 node.scope = scope;
128 }
129 if (node instanceof AST_Label) {
130 node.thedef = node;
131 node.references = [];
132 }
133 if (node instanceof AST_SymbolDefun) {
134 // This should be defined in the parent scope, as we encounter the
135 // AST_Defun node before getting to its AST_Symbol.
136 (node.scope = defun.parent_scope.resolve()).def_function(node, defun);
137 } else if (node instanceof AST_SymbolLambda) {
138 var def = defun.def_function(node, node.name == "arguments" ? undefined : defun);
139 if (options.ie8) def.defun = defun.parent_scope.resolve();
140 } else if (node instanceof AST_SymbolVar) {
141 defun.def_variable(node, node.TYPE == "SymbolVar" ? null : undefined);
142 if (defun !== scope) {
143 node.mark_enclosed(options);
144 var def = scope.find_variable(node);
145 if (node.thedef !== def) {
146 node.thedef = def;
147 }
148 node.reference(options);
149 }
150 } else if (node instanceof AST_SymbolCatch) {
151 scope.def_variable(node).defun = defun;
152 }
153 });
154 self.walk(tw);
155
156 // pass 2: find back references and eval
157 self.globals = new Dictionary();
158 var tw = new TreeWalker(function(node) {
159 if (node instanceof AST_LoopControl) {
160 if (node.label) node.label.thedef.references.push(node);
161 return true;
162 }
163 if (node instanceof AST_SymbolRef) {
164 var name = node.name;
165 var sym = node.scope.find_variable(name);
166 if (!sym) {
167 sym = self.def_global(node);
168 } else if (sym.scope instanceof AST_Lambda && name == "arguments") {
169 sym.scope.uses_arguments = true;
170 }
171 if (name == "eval") {
172 var parent = tw.parent();
173 if (parent.TYPE == "Call" && parent.expression === node) {
174 for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
175 s.uses_eval = true;
176 }
177 } else if (sym.undeclared) {
178 self.uses_eval = true;
179 }
180 }
181 node.thedef = sym;
182 node.reference(options);
183 return true;
184 }
185 // ensure mangling works if catch reuses a scope variable
186 if (node instanceof AST_SymbolCatch) {
187 var def = node.definition().redefined();
188 if (def) for (var s = node.scope; s; s = s.parent_scope) {
189 push_uniq(s.enclosed, def);
190 if (s === def.scope) break;
191 }
192 return true;
193 }
194 });
195 self.walk(tw);
196
197 // pass 3: fix up any scoping issue with IE8
198 if (options.ie8) self.walk(new TreeWalker(function(node) {
199 if (node instanceof AST_SymbolCatch) {
200 var scope = node.thedef.defun;
201 if (scope.name instanceof AST_SymbolLambda && scope.name.name == node.name) {
202 scope = scope.parent_scope.resolve();
203 }
204 redefine(node, scope);
205 return true;
206 }
207 if (node instanceof AST_SymbolLambda) {
208 var def = node.thedef;
209 redefine(node, node.scope.parent_scope.resolve());
210 if (typeof node.thedef.init !== "undefined") {
211 node.thedef.init = false;
212 } else if (def.init) {
213 node.thedef.init = def.init;
214 }
215 return true;
216 }
217 }));
218
219 function redefine(node, scope) {
220 var name = node.name;
221 var old_def = node.thedef;
222 var new_def = scope.find_variable(name);
223 if (new_def) {
224 var redef;
225 while (redef = new_def.redefined()) new_def = redef;
226 } else {
227 new_def = self.globals.get(name);
228 }
229 if (new_def) {
230 new_def.orig.push(node);
231 } else {
232 new_def = scope.def_variable(node);
233 }
234 old_def.orig.concat(old_def.references).forEach(function(node) {
235 node.thedef = new_def;
236 node.reference(options);
237 });
238 if (old_def.lambda) new_def.lambda = true;
239 if (new_def.undeclared) self.variables.set(name, new_def);
240 }
241});
242
243AST_Toplevel.DEFMETHOD("def_global", function(node) {
244 var globals = this.globals, name = node.name;
245 if (globals.has(name)) {
246 return globals.get(name);
247 } else {
248 var g = new SymbolDef(this, node);
249 g.undeclared = true;
250 g.global = true;
251 globals.set(name, g);
252 return g;
253 }
254});
255
256AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) {
257 this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
258 this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
259 this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
260 this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
261 this.parent_scope = parent_scope; // the parent scope
262 this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
263 this.cname = -1; // the current index for mangling functions/variables
264});
265
266AST_Lambda.DEFMETHOD("init_scope_vars", function() {
267 AST_Scope.prototype.init_scope_vars.apply(this, arguments);
268 this.uses_arguments = false;
269 this.def_variable(new AST_SymbolFunarg({
270 name: "arguments",
271 start: this.start,
272 end: this.end
273 }));
274});
275
276AST_Symbol.DEFMETHOD("mark_enclosed", function(options) {
277 var def = this.definition();
278 for (var s = this.scope; s; s = s.parent_scope) {
279 push_uniq(s.enclosed, def);
280 if (options.keep_fnames) {
281 s.functions.each(function(d) {
282 push_uniq(def.scope.enclosed, d);
283 });
284 }
285 if (s === def.scope) break;
286 }
287});
288
289AST_Symbol.DEFMETHOD("reference", function(options) {
290 this.definition().references.push(this);
291 this.mark_enclosed(options);
292});
293
294AST_Scope.DEFMETHOD("find_variable", function(name) {
295 if (name instanceof AST_Symbol) name = name.name;
296 return this.variables.get(name)
297 || (this.parent_scope && this.parent_scope.find_variable(name));
298});
299
300AST_Scope.DEFMETHOD("def_function", function(symbol, init) {
301 var def = this.def_variable(symbol, init);
302 if (!def.init || def.init instanceof AST_Defun) def.init = init;
303 this.functions.set(symbol.name, def);
304 return def;
305});
306
307AST_Scope.DEFMETHOD("def_variable", function(symbol, init) {
308 var def = this.variables.get(symbol.name);
309 if (def) {
310 def.orig.push(symbol);
311 if (def.init instanceof AST_Function) def.init = init;
312 } else {
313 def = new SymbolDef(this, symbol, init);
314 this.variables.set(symbol.name, def);
315 def.global = !this.parent_scope;
316 }
317 return symbol.thedef = def;
318});
319
320AST_Lambda.DEFMETHOD("resolve", return_this);
321AST_Scope.DEFMETHOD("resolve", function() {
322 return this.parent_scope.resolve();
323});
324AST_Toplevel.DEFMETHOD("resolve", return_this);
325
326function names_in_use(scope, options) {
327 var names = scope.names_in_use;
328 if (!names) {
329 scope.names_in_use = names = Object.create(scope.mangled_names || null);
330 scope.cname_holes = [];
331 var cache = options.cache && options.cache.props;
332 scope.enclosed.forEach(function(def) {
333 if (def.unmangleable(options)) names[def.name] = true;
334 if (def.global && cache && cache.has(def.name)) {
335 names[cache.get(def.name)] = true;
336 }
337 });
338 }
339 return names;
340}
341
342function next_mangled_name(scope, options, def) {
343 var in_use = names_in_use(scope, options);
344 var holes = scope.cname_holes;
345 var names = Object.create(null);
346 var scopes = [ scope ];
347 def.references.forEach(function(sym) {
348 var scope = sym.scope;
349 do {
350 if (scopes.indexOf(scope) < 0) {
351 for (var name in names_in_use(scope, options)) {
352 names[name] = true;
353 }
354 scopes.push(scope);
355 } else break;
356 } while (scope = scope.parent_scope);
357 });
358 var name;
359 for (var i = 0; i < holes.length; i++) {
360 name = base54(holes[i]);
361 if (names[name]) continue;
362 holes.splice(i, 1);
363 scope.names_in_use[name] = true;
364 return name;
365 }
366 while (true) {
367 name = base54(++scope.cname);
368 if (in_use[name] || RESERVED_WORDS[name] || options.reserved.has[name]) continue;
369 if (!names[name]) break;
370 holes.push(scope.cname);
371 }
372 scope.names_in_use[name] = true;
373 return name;
374}
375
376AST_Symbol.DEFMETHOD("unmangleable", function(options) {
377 var def = this.definition();
378 return !def || def.unmangleable(options);
379});
380
381// labels are always mangleable
382AST_Label.DEFMETHOD("unmangleable", return_false);
383
384AST_Symbol.DEFMETHOD("unreferenced", function() {
385 return !this.definition().references.length && !this.scope.pinned();
386});
387
388AST_Symbol.DEFMETHOD("definition", function() {
389 return this.thedef;
390});
391
392AST_Symbol.DEFMETHOD("global", function() {
393 return this.definition().global;
394});
395
396function _default_mangler_options(options) {
397 options = defaults(options, {
398 eval : false,
399 ie8 : false,
400 keep_fnames : false,
401 reserved : [],
402 toplevel : false,
403 });
404 if (!Array.isArray(options.reserved)) options.reserved = [];
405 // Never mangle arguments
406 push_uniq(options.reserved, "arguments");
407 options.reserved.has = makePredicate(options.reserved);
408 return options;
409}
410
411AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
412 options = _default_mangler_options(options);
413
414 // We only need to mangle declaration nodes. Special logic wired
415 // into the code generator will display the mangled name if it's
416 // present (and for AST_SymbolRef-s it'll use the mangled name of
417 // the AST_SymbolDeclaration that it points to).
418 var lname = -1;
419
420 if (options.cache && options.cache.props) {
421 var mangled_names = this.mangled_names = Object.create(null);
422 options.cache.props.each(function(mangled_name) {
423 mangled_names[mangled_name] = true;
424 });
425 }
426
427 var redefined = [];
428 var tw = new TreeWalker(function(node, descend) {
429 if (node instanceof AST_LabeledStatement) {
430 // lname is incremented when we get to the AST_Label
431 var save_nesting = lname;
432 descend();
433 lname = save_nesting;
434 return true;
435 }
436 if (node instanceof AST_Scope) {
437 descend();
438 if (options.cache && node instanceof AST_Toplevel) {
439 node.globals.each(mangle);
440 }
441 if (node instanceof AST_Defun && tw.has_directive("use asm")) {
442 var sym = new AST_SymbolRef(node.name);
443 sym.scope = node;
444 sym.reference(options);
445 }
446 node.variables.each(function(def) {
447 if (!defer_redef(def)) mangle(def);
448 });
449 return true;
450 }
451 if (node instanceof AST_Label) {
452 var name;
453 do {
454 name = base54(++lname);
455 } while (RESERVED_WORDS[name]);
456 node.mangled_name = name;
457 return true;
458 }
459 if (!options.ie8 && node instanceof AST_Catch) {
460 var def = node.argname.definition();
461 var redef = defer_redef(def, node.argname);
462 descend();
463 if (!redef) mangle(def);
464 return true;
465 }
466 });
467 this.walk(tw);
468 redefined.forEach(mangle);
469
470 function mangle(def) {
471 if (options.reserved.has[def.name]) return;
472 def.mangle(options);
473 }
474
475 function defer_redef(def, node) {
476 var redef = def.redefined();
477 if (!redef) return false;
478 redefined.push(def);
479 def.references.forEach(reference);
480 if (node) reference(node);
481 return true;
482
483 function reference(sym) {
484 sym.thedef = redef;
485 sym.reference(options);
486 sym.thedef = def;
487 }
488 }
489});
490
491AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) {
492 var cache = options.cache && options.cache.props;
493 var avoid = Object.create(null);
494 options.reserved.forEach(to_avoid);
495 this.globals.each(add_def);
496 this.walk(new TreeWalker(function(node) {
497 if (node instanceof AST_Scope) node.variables.each(add_def);
498 if (node instanceof AST_SymbolCatch) add_def(node.definition());
499 }));
500 return avoid;
501
502 function to_avoid(name) {
503 avoid[name] = true;
504 }
505
506 function add_def(def) {
507 var name = def.name;
508 if (def.global && cache && cache.has(name)) name = cache.get(name);
509 else if (!def.unmangleable(options)) return;
510 to_avoid(name);
511 }
512});
513
514AST_Toplevel.DEFMETHOD("expand_names", function(options) {
515 base54.reset();
516 base54.sort();
517 options = _default_mangler_options(options);
518 var avoid = this.find_colliding_names(options);
519 var cname = 0;
520 this.globals.each(rename);
521 this.walk(new TreeWalker(function(node) {
522 if (node instanceof AST_Scope) node.variables.each(rename);
523 if (node instanceof AST_SymbolCatch) rename(node.definition());
524 }));
525
526 function next_name() {
527 var name;
528 do {
529 name = base54(cname++);
530 } while (avoid[name] || RESERVED_WORDS[name]);
531 return name;
532 }
533
534 function rename(def) {
535 if (def.global && options.cache) return;
536 if (def.unmangleable(options)) return;
537 if (options.reserved.has[def.name]) return;
538 var redef = def.redefined();
539 var name = redef ? redef.rename || redef.name : next_name();
540 def.rename = name;
541 def.orig.forEach(function(sym) {
542 sym.name = name;
543 });
544 def.references.forEach(function(sym) {
545 sym.name = name;
546 });
547 }
548});
549
550AST_Node.DEFMETHOD("tail_node", return_this);
551AST_Sequence.DEFMETHOD("tail_node", function() {
552 return this.expressions[this.expressions.length - 1];
553});
554
555AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) {
556 options = _default_mangler_options(options);
557 base54.reset();
558 try {
559 AST_Node.prototype.print = function(stream, force_parens) {
560 this._print(stream, force_parens);
561 if (this instanceof AST_Symbol && !this.unmangleable(options)) {
562 base54.consider(this.name, -1);
563 } else if (options.properties) {
564 if (this instanceof AST_Dot) {
565 base54.consider(this.property, -1);
566 } else if (this instanceof AST_Sub) {
567 skip_string(this.property);
568 }
569 }
570 };
571 base54.consider(this.print_to_string(), 1);
572 } finally {
573 AST_Node.prototype.print = AST_Node.prototype._print;
574 }
575 base54.sort();
576
577 function skip_string(node) {
578 if (node instanceof AST_String) {
579 base54.consider(node.value, -1);
580 } else if (node instanceof AST_Conditional) {
581 skip_string(node.consequent);
582 skip_string(node.alternative);
583 } else if (node instanceof AST_Sequence) {
584 skip_string(node.tail_node());
585 }
586 }
587});
588
589var base54 = (function() {
590 var freq = Object.create(null);
591 function init(chars) {
592 var array = [];
593 for (var i = 0; i < chars.length; i++) {
594 var ch = chars[i];
595 array.push(ch);
596 freq[ch] = -1e-2 * i;
597 }
598 return array;
599 }
600 var digits = init("0123456789");
601 var leading = init("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_");
602 var chars, frequency;
603 function reset() {
604 frequency = Object.create(freq);
605 }
606 base54.consider = function(str, delta) {
607 for (var i = str.length; --i >= 0;) {
608 frequency[str[i]] += delta;
609 }
610 };
611 function compare(a, b) {
612 return frequency[b] - frequency[a];
613 }
614 base54.sort = function() {
615 chars = leading.sort(compare).concat(digits.sort(compare));
616 };
617 base54.reset = reset;
618 reset();
619 function base54(num) {
620 var ret = "", base = 54;
621 num++;
622 do {
623 num--;
624 ret += chars[num % base];
625 num = Math.floor(num / base);
626 base = 64;
627 } while (num > 0);
628 return ret;
629 }
630 return base54;
631})();