UNPKG

21.1 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 if (name == "eval" && tw.parent() instanceof AST_Call) {
166 for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
167 s.uses_eval = true;
168 }
169 }
170 var sym = node.scope.find_variable(name);
171 if (!sym) {
172 sym = self.def_global(node);
173 } else if (sym.scope instanceof AST_Lambda && name == "arguments") {
174 sym.scope.uses_arguments = true;
175 }
176 node.thedef = sym;
177 node.reference(options);
178 return true;
179 }
180 // ensure mangling works if catch reuses a scope variable
181 if (node instanceof AST_SymbolCatch) {
182 var def = node.definition().redefined();
183 if (def) for (var s = node.scope; s; s = s.parent_scope) {
184 push_uniq(s.enclosed, def);
185 if (s === def.scope) break;
186 }
187 return true;
188 }
189 });
190 self.walk(tw);
191
192 // pass 3: fix up any scoping issue with IE8
193 if (options.ie8) self.walk(new TreeWalker(function(node) {
194 if (node instanceof AST_SymbolCatch) {
195 var scope = node.thedef.defun;
196 if (scope.name instanceof AST_SymbolLambda && scope.name.name == node.name) {
197 scope = scope.parent_scope.resolve();
198 }
199 redefine(node, scope);
200 return true;
201 }
202 if (node instanceof AST_SymbolLambda) {
203 var def = node.thedef;
204 redefine(node, node.scope.parent_scope.resolve());
205 if (typeof node.thedef.init !== "undefined") {
206 node.thedef.init = false;
207 } else if (def.init) {
208 node.thedef.init = def.init;
209 }
210 return true;
211 }
212 }));
213
214 function redefine(node, scope) {
215 var name = node.name;
216 var old_def = node.thedef;
217 var new_def = scope.find_variable(name);
218 if (new_def) {
219 var redef;
220 while (redef = new_def.redefined()) new_def = redef;
221 } else {
222 new_def = self.globals.get(name) || scope.def_variable(node);
223 }
224 old_def.orig.concat(old_def.references).forEach(function(node) {
225 node.thedef = new_def;
226 node.reference(options);
227 });
228 if (old_def.lambda) new_def.lambda = true;
229 if (new_def.undeclared) self.variables.set(name, new_def);
230 }
231});
232
233AST_Toplevel.DEFMETHOD("def_global", function(node) {
234 var globals = this.globals, name = node.name;
235 if (globals.has(name)) {
236 return globals.get(name);
237 } else {
238 var g = new SymbolDef(this, node);
239 g.undeclared = true;
240 g.global = true;
241 globals.set(name, g);
242 return g;
243 }
244});
245
246AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) {
247 this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
248 this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
249 this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
250 this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
251 this.parent_scope = parent_scope; // the parent scope
252 this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
253 this.cname = -1; // the current index for mangling functions/variables
254});
255
256AST_Lambda.DEFMETHOD("init_scope_vars", function() {
257 AST_Scope.prototype.init_scope_vars.apply(this, arguments);
258 this.uses_arguments = false;
259 this.def_variable(new AST_SymbolFunarg({
260 name: "arguments",
261 start: this.start,
262 end: this.end
263 }));
264});
265
266AST_Symbol.DEFMETHOD("mark_enclosed", function(options) {
267 var def = this.definition();
268 for (var s = this.scope; s; s = s.parent_scope) {
269 push_uniq(s.enclosed, def);
270 if (options.keep_fnames) {
271 s.functions.each(function(d) {
272 push_uniq(def.scope.enclosed, d);
273 });
274 }
275 if (s === def.scope) break;
276 }
277});
278
279AST_Symbol.DEFMETHOD("reference", function(options) {
280 this.definition().references.push(this);
281 this.mark_enclosed(options);
282});
283
284AST_Scope.DEFMETHOD("find_variable", function(name) {
285 if (name instanceof AST_Symbol) name = name.name;
286 return this.variables.get(name)
287 || (this.parent_scope && this.parent_scope.find_variable(name));
288});
289
290AST_Scope.DEFMETHOD("def_function", function(symbol, init) {
291 var def = this.def_variable(symbol, init);
292 if (!def.init || def.init instanceof AST_Defun) def.init = init;
293 this.functions.set(symbol.name, def);
294 return def;
295});
296
297AST_Scope.DEFMETHOD("def_variable", function(symbol, init) {
298 var def = this.variables.get(symbol.name);
299 if (def) {
300 def.orig.push(symbol);
301 if (def.init instanceof AST_Function) def.init = init;
302 } else {
303 def = new SymbolDef(this, symbol, init);
304 this.variables.set(symbol.name, def);
305 def.global = !this.parent_scope;
306 }
307 return symbol.thedef = def;
308});
309
310AST_Lambda.DEFMETHOD("resolve", return_this);
311AST_Scope.DEFMETHOD("resolve", function() {
312 return this.parent_scope.resolve();
313});
314AST_Toplevel.DEFMETHOD("resolve", return_this);
315
316function names_in_use(scope, options) {
317 var names = scope.names_in_use;
318 if (!names) {
319 scope.names_in_use = names = Object.create(scope.mangled_names || null);
320 scope.cname_holes = [];
321 var cache = options.cache && options.cache.props;
322 scope.enclosed.forEach(function(def) {
323 if (def.unmangleable(options)) names[def.name] = true;
324 if (def.global && cache && cache.has(def.name)) {
325 names[cache.get(def.name)] = true;
326 }
327 });
328 }
329 return names;
330}
331
332function next_mangled_name(scope, options, def) {
333 var in_use = names_in_use(scope, options);
334 var holes = scope.cname_holes;
335 var names = Object.create(null);
336 var scopes = [ scope ];
337 def.references.forEach(function(sym) {
338 var scope = sym.scope;
339 do {
340 if (scopes.indexOf(scope) < 0) {
341 for (var name in names_in_use(scope, options)) {
342 names[name] = true;
343 }
344 scopes.push(scope);
345 } else break;
346 } while (scope = scope.parent_scope);
347 });
348 var name;
349 for (var i = 0; i < holes.length; i++) {
350 name = base54(holes[i]);
351 if (names[name]) continue;
352 holes.splice(i, 1);
353 scope.names_in_use[name] = true;
354 return name;
355 }
356 while (true) {
357 name = base54(++scope.cname);
358 if (in_use[name] || RESERVED_WORDS[name] || options.reserved.has[name]) continue;
359 if (!names[name]) break;
360 holes.push(scope.cname);
361 }
362 scope.names_in_use[name] = true;
363 return name;
364}
365
366AST_Symbol.DEFMETHOD("unmangleable", function(options) {
367 var def = this.definition();
368 return !def || def.unmangleable(options);
369});
370
371// labels are always mangleable
372AST_Label.DEFMETHOD("unmangleable", return_false);
373
374AST_Symbol.DEFMETHOD("unreferenced", function() {
375 return !this.definition().references.length && !this.scope.pinned();
376});
377
378AST_Symbol.DEFMETHOD("definition", function() {
379 return this.thedef;
380});
381
382AST_Symbol.DEFMETHOD("global", function() {
383 return this.definition().global;
384});
385
386function _default_mangler_options(options) {
387 options = defaults(options, {
388 eval : false,
389 ie8 : false,
390 keep_fnames : false,
391 reserved : [],
392 toplevel : false,
393 });
394 if (!Array.isArray(options.reserved)) options.reserved = [];
395 // Never mangle arguments
396 push_uniq(options.reserved, "arguments");
397 options.reserved.has = makePredicate(options.reserved);
398 return options;
399}
400
401AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
402 options = _default_mangler_options(options);
403
404 // We only need to mangle declaration nodes. Special logic wired
405 // into the code generator will display the mangled name if it's
406 // present (and for AST_SymbolRef-s it'll use the mangled name of
407 // the AST_SymbolDeclaration that it points to).
408 var lname = -1;
409
410 if (options.cache && options.cache.props) {
411 var mangled_names = this.mangled_names = Object.create(null);
412 options.cache.props.each(function(mangled_name) {
413 mangled_names[mangled_name] = true;
414 });
415 }
416
417 var redefined = [];
418 var tw = new TreeWalker(function(node, descend) {
419 if (node instanceof AST_LabeledStatement) {
420 // lname is incremented when we get to the AST_Label
421 var save_nesting = lname;
422 descend();
423 lname = save_nesting;
424 return true;
425 }
426 if (node instanceof AST_Scope) {
427 descend();
428 if (options.cache && node instanceof AST_Toplevel) {
429 node.globals.each(mangle);
430 }
431 if (node instanceof AST_Defun && tw.has_directive("use asm")) {
432 var sym = new AST_SymbolRef(node.name);
433 sym.scope = node;
434 sym.reference(options);
435 }
436 node.variables.each(function(def) {
437 if (!defer_redef(def)) mangle(def);
438 });
439 return true;
440 }
441 if (node instanceof AST_Label) {
442 var name;
443 do {
444 name = base54(++lname);
445 } while (RESERVED_WORDS[name]);
446 node.mangled_name = name;
447 return true;
448 }
449 if (!options.ie8 && node instanceof AST_Catch) {
450 var def = node.argname.definition();
451 var redef = defer_redef(def, node.argname);
452 descend();
453 if (!redef) mangle(def);
454 return true;
455 }
456 });
457 this.walk(tw);
458 redefined.forEach(mangle);
459
460 function mangle(def) {
461 if (options.reserved.has[def.name]) return;
462 def.mangle(options);
463 }
464
465 function defer_redef(def, node) {
466 var redef = def.redefined();
467 if (!redef) return false;
468 redefined.push(def);
469 def.references.forEach(reference);
470 if (node) reference(node);
471 return true;
472
473 function reference(sym) {
474 sym.thedef = redef;
475 sym.reference(options);
476 sym.thedef = def;
477 }
478 }
479});
480
481AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) {
482 var cache = options.cache && options.cache.props;
483 var avoid = Object.create(null);
484 options.reserved.forEach(to_avoid);
485 this.globals.each(add_def);
486 this.walk(new TreeWalker(function(node) {
487 if (node instanceof AST_Scope) node.variables.each(add_def);
488 if (node instanceof AST_SymbolCatch) add_def(node.definition());
489 }));
490 return avoid;
491
492 function to_avoid(name) {
493 avoid[name] = true;
494 }
495
496 function add_def(def) {
497 var name = def.name;
498 if (def.global && cache && cache.has(name)) name = cache.get(name);
499 else if (!def.unmangleable(options)) return;
500 to_avoid(name);
501 }
502});
503
504AST_Toplevel.DEFMETHOD("expand_names", function(options) {
505 base54.reset();
506 base54.sort();
507 options = _default_mangler_options(options);
508 var avoid = this.find_colliding_names(options);
509 var cname = 0;
510 this.globals.each(rename);
511 this.walk(new TreeWalker(function(node) {
512 if (node instanceof AST_Scope) node.variables.each(rename);
513 if (node instanceof AST_SymbolCatch) rename(node.definition());
514 }));
515
516 function next_name() {
517 var name;
518 do {
519 name = base54(cname++);
520 } while (avoid[name] || RESERVED_WORDS[name]);
521 return name;
522 }
523
524 function rename(def) {
525 if (def.global && options.cache) return;
526 if (def.unmangleable(options)) return;
527 if (options.reserved.has[def.name]) return;
528 var redef = def.redefined();
529 var name = redef ? redef.rename || redef.name : next_name();
530 def.rename = name;
531 def.orig.forEach(function(sym) {
532 sym.name = name;
533 });
534 def.references.forEach(function(sym) {
535 sym.name = name;
536 });
537 }
538});
539
540AST_Node.DEFMETHOD("tail_node", return_this);
541AST_Sequence.DEFMETHOD("tail_node", function() {
542 return this.expressions[this.expressions.length - 1];
543});
544
545AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) {
546 options = _default_mangler_options(options);
547 base54.reset();
548 try {
549 AST_Node.prototype.print = function(stream, force_parens) {
550 this._print(stream, force_parens);
551 if (this instanceof AST_Symbol && !this.unmangleable(options)) {
552 base54.consider(this.name, -1);
553 } else if (options.properties) {
554 if (this instanceof AST_Dot) {
555 base54.consider(this.property, -1);
556 } else if (this instanceof AST_Sub) {
557 skip_string(this.property);
558 }
559 }
560 };
561 base54.consider(this.print_to_string(), 1);
562 } finally {
563 AST_Node.prototype.print = AST_Node.prototype._print;
564 }
565 base54.sort();
566
567 function skip_string(node) {
568 if (node instanceof AST_String) {
569 base54.consider(node.value, -1);
570 } else if (node instanceof AST_Conditional) {
571 skip_string(node.consequent);
572 skip_string(node.alternative);
573 } else if (node instanceof AST_Sequence) {
574 skip_string(node.tail_node());
575 }
576 }
577});
578
579var base54 = (function() {
580 var freq = Object.create(null);
581 function init(chars) {
582 var array = [];
583 for (var i = 0; i < chars.length; i++) {
584 var ch = chars[i];
585 array.push(ch);
586 freq[ch] = -1e-2 * i;
587 }
588 return array;
589 }
590 var digits = init("0123456789");
591 var leading = init("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_");
592 var chars, frequency;
593 function reset() {
594 frequency = Object.create(freq);
595 }
596 base54.consider = function(str, delta) {
597 for (var i = str.length; --i >= 0;) {
598 frequency[str[i]] += delta;
599 }
600 };
601 function compare(a, b) {
602 return frequency[b] - frequency[a];
603 }
604 base54.sort = function() {
605 chars = leading.sort(compare).concat(digits.sort(compare));
606 };
607 base54.reset = reset;
608 reset();
609 function base54(num) {
610 var ret = "", base = 54;
611 num++;
612 do {
613 num--;
614 ret += chars[num % base];
615 num = Math.floor(num / base);
616 base = 64;
617 } while (num > 0);
618 return ret;
619 }
620 return base54;
621})();