1 /***********************************************************************
3 A JavaScript tokenizer / parser / beautifier / compressor.
4 https://github.com/mishoo/UglifyJS2
6 -------------------------------- (C) ---------------------------------
9 <mihai.bazon@gmail.com>
10 http://mihai.bazon.net/blog
12 Distributed under the BSD license:
14 Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
16 Redistribution and use in source and binary forms, with or without
17 modification, are permitted provided that the following conditions
20 * Redistributions of source code must retain the above
21 copyright notice, this list of conditions and the following
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.
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
42 ***********************************************************************/
46 function SymbolDef(scope, index, orig) {
47 this.name = orig.name;
52 this.mangled_name = null;
53 this.undeclared = false;
55 this.id = SymbolDef.next_id++;
58 SymbolDef.next_id = 1;
60 SymbolDef.prototype = {
61 unmangleable: function(options) {
62 if (!options) options = {};
64 return (this.global && !options.toplevel)
66 || (!options.eval && (this.scope.uses_eval || this.scope.uses_with))
67 || (options.keep_fnames
68 && (this.orig[0] instanceof AST_SymbolLambda
69 || this.orig[0] instanceof AST_SymbolDefun));
71 mangle: function(options) {
72 var cache = options.cache && options.cache.props;
73 if (this.global && cache && cache.has(this.name)) {
74 this.mangled_name = cache.get(this.name);
76 else if (!this.mangled_name && !this.unmangleable(options)) {
78 if (!options.screw_ie8 && this.orig[0] instanceof AST_SymbolLambda)
80 this.mangled_name = s.next_mangled(options, this);
81 if (this.global && cache) {
82 cache.set(this.name, this.mangled_name);
88 AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
89 options = defaults(options, {
94 // pass 1: setup scope chaining and handle definitions
96 var scope = self.parent_scope = null;
97 var labels = new Dictionary();
99 var tw = new TreeWalker(function(node, descend){
100 if (node instanceof AST_Catch) {
101 var save_scope = scope;
102 scope = new AST_Scope(node);
103 scope.init_scope_vars();
104 scope.parent_scope = save_scope;
109 if (node instanceof AST_Scope) {
110 node.init_scope_vars();
111 var save_scope = node.parent_scope = scope;
112 var save_defun = defun;
113 var save_labels = labels;
114 defun = scope = node;
115 labels = new Dictionary();
119 labels = save_labels;
120 return true; // don't descend again in TreeWalker
122 if (node instanceof AST_LabeledStatement) {
124 if (labels.has(l.name)) {
125 throw new Error(string_template("Label {name} defined twice", l));
127 labels.set(l.name, l);
130 return true; // no descend again
132 if (node instanceof AST_With) {
133 for (var s = scope; s; s = s.parent_scope)
137 if (node instanceof AST_Symbol) {
140 if (node instanceof AST_Label) {
142 node.references = [];
144 if (node instanceof AST_SymbolLambda) {
145 defun.def_function(node);
147 else if (node instanceof AST_SymbolDefun) {
148 // Careful here, the scope where this should be defined is
149 // the parent scope. The reason is that we enter a new
150 // scope when we encounter the AST_Defun node (which is
151 // instanceof AST_Scope) but we get to the symbol a bit
153 (node.scope = defun.parent_scope).def_function(node);
155 else if (node instanceof AST_SymbolVar
156 || node instanceof AST_SymbolConst) {
157 defun.def_variable(node);
159 else if (node instanceof AST_SymbolCatch) {
160 scope.def_variable(node);
162 else if (node instanceof AST_LabelRef) {
163 var sym = labels.get(node.name);
164 if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", {
166 line: node.start.line,
174 // pass 2: find back references and eval
176 var globals = self.globals = new Dictionary();
177 var tw = new TreeWalker(function(node, descend){
178 if (node instanceof AST_Lambda) {
179 var prev_func = func;
185 if (node instanceof AST_LoopControl && node.label) {
186 node.label.thedef.references.push(node);
189 if (node instanceof AST_SymbolRef) {
190 var name = node.name;
191 if (name == "eval" && tw.parent() instanceof AST_Call) {
192 for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
196 var sym = node.scope.find_variable(name);
197 if (node.scope instanceof AST_Lambda && name == "arguments") {
198 node.scope.uses_arguments = true;
201 sym = self.def_global(node);
204 node.reference(options);
210 // pass 3: fix up any scoping issue with IE8
211 if (!options.screw_ie8) {
212 self.walk(new TreeWalker(function(node, descend) {
213 if (node instanceof AST_SymbolCatch) {
214 var name = node.name;
215 var scope = node.thedef.scope.parent_scope;
216 var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node);
217 node.thedef.references.forEach(function(ref) {
219 ref.reference(options);
228 this.cname = options.cache.cname;
232 AST_Toplevel.DEFMETHOD("def_global", function(node){
233 var globals = this.globals, name = node.name;
234 if (globals.has(name)) {
235 return globals.get(name);
237 var g = new SymbolDef(this, globals.size(), node);
240 globals.set(name, g);
245 AST_Scope.DEFMETHOD("init_scope_vars", function(){
246 this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
247 this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
248 this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
249 this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
250 this.parent_scope = null; // the parent scope
251 this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
252 this.cname = -1; // the current index for mangling functions/variables
255 AST_Lambda.DEFMETHOD("init_scope_vars", function(){
256 AST_Scope.prototype.init_scope_vars.apply(this, arguments);
257 this.uses_arguments = false;
259 var symbol = new AST_VarDef({ name: "arguments", start: this.start, end: this.end });
260 var def = new SymbolDef(this, this.variables.size(), symbol);
261 this.variables.set(symbol.name, def);
264 AST_SymbolRef.DEFMETHOD("reference", function(options) {
265 var def = this.definition();
266 def.references.push(this);
269 push_uniq(s.enclosed, def);
270 if (options.keep_fnames) {
271 s.functions.each(function(d) {
272 push_uniq(def.scope.enclosed, d);
275 if (s === def.scope) break;
280 AST_Scope.DEFMETHOD("find_variable", function(name){
281 if (name instanceof AST_Symbol) name = name.name;
282 return this.variables.get(name)
283 || (this.parent_scope && this.parent_scope.find_variable(name));
286 AST_Scope.DEFMETHOD("def_function", function(symbol){
287 this.functions.set(symbol.name, this.def_variable(symbol));
290 AST_Scope.DEFMETHOD("def_variable", function(symbol){
292 if (!this.variables.has(symbol.name)) {
293 def = new SymbolDef(this, this.variables.size(), symbol);
294 this.variables.set(symbol.name, def);
295 def.global = !this.parent_scope;
297 def = this.variables.get(symbol.name);
298 def.orig.push(symbol);
300 return symbol.thedef = def;
303 AST_Scope.DEFMETHOD("next_mangled", function(options){
304 var ext = this.enclosed;
306 var m = base54(++this.cname);
307 if (!is_identifier(m)) continue; // skip over "do"
309 // https://github.com/mishoo/UglifyJS2/issues/242 -- do not
310 // shadow a name excepted from mangling.
311 if (options.except.indexOf(m) >= 0) continue;
313 // we must ensure that the mangled name does not shadow a name
314 // from some parent scope that is referenced in this or in
316 for (var i = ext.length; --i >= 0;) {
318 var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
319 if (m == name) continue out;
325 AST_Function.DEFMETHOD("next_mangled", function(options, def){
327 // in Safari strict mode, something like (function x(x){...}) is a syntax error;
328 // a function expression's argument cannot shadow the function expression's name
330 var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition();
332 // the function's mangled_name is null when keep_fnames is true
333 var tricky_name = tricky_def ? tricky_def.mangled_name || tricky_def.name : null;
336 var name = AST_Lambda.prototype.next_mangled.call(this, options, def);
337 if (!tricky_name || tricky_name != name)
342 AST_Symbol.DEFMETHOD("unmangleable", function(options){
343 return this.definition().unmangleable(options);
346 // property accessors are not mangleable
347 AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
351 // labels are always mangleable
352 AST_Label.DEFMETHOD("unmangleable", function(){
356 AST_Symbol.DEFMETHOD("unreferenced", function(){
357 return this.definition().references.length == 0
358 && !(this.scope.uses_eval || this.scope.uses_with);
361 AST_Symbol.DEFMETHOD("undeclared", function(){
362 return this.definition().undeclared;
365 AST_LabelRef.DEFMETHOD("undeclared", function(){
369 AST_Label.DEFMETHOD("undeclared", function(){
373 AST_Symbol.DEFMETHOD("definition", function(){
377 AST_Symbol.DEFMETHOD("global", function(){
378 return this.definition().global;
381 AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
382 return defaults(options, {
385 sort : false, // Ignored. Flag retained for backwards compatibility.
392 AST_Toplevel.DEFMETHOD("mangle_names", function(options){
393 options = this._default_mangler_options(options);
395 // Never mangle arguments
396 options.except.push('arguments');
398 // We only need to mangle declaration nodes. Special logic wired
399 // into the code generator will display the mangled name if it's
400 // present (and for AST_SymbolRef-s it'll use the mangled name of
401 // the AST_SymbolDeclaration that it points to).
406 this.globals.each(function(symbol){
407 if (options.except.indexOf(symbol.name) < 0) {
408 to_mangle.push(symbol);
413 var tw = new TreeWalker(function(node, descend){
414 if (node instanceof AST_LabeledStatement) {
415 // lname is incremented when we get to the AST_Label
416 var save_nesting = lname;
418 lname = save_nesting;
419 return true; // don't descend again in TreeWalker
421 if (node instanceof AST_Scope) {
422 var p = tw.parent(), a = [];
423 node.variables.each(function(symbol){
424 if (options.except.indexOf(symbol.name) < 0) {
428 to_mangle.push.apply(to_mangle, a);
431 if (node instanceof AST_Label) {
433 do name = base54(++lname); while (!is_identifier(name));
434 node.mangled_name = name;
437 if (options.screw_ie8 && node instanceof AST_SymbolCatch) {
438 to_mangle.push(node.definition());
443 to_mangle.forEach(function(def){ def.mangle(options) });
446 options.cache.cname = this.cname;
450 AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
451 options = this._default_mangler_options(options);
452 var tw = new TreeWalker(function(node){
453 if (node instanceof AST_Constant)
454 base54.consider(node.print_to_string());
455 else if (node instanceof AST_Return)
456 base54.consider("return");
457 else if (node instanceof AST_Throw)
458 base54.consider("throw");
459 else if (node instanceof AST_Continue)
460 base54.consider("continue");
461 else if (node instanceof AST_Break)
462 base54.consider("break");
463 else if (node instanceof AST_Debugger)
464 base54.consider("debugger");
465 else if (node instanceof AST_Directive)
466 base54.consider(node.value);
467 else if (node instanceof AST_While)
468 base54.consider("while");
469 else if (node instanceof AST_Do)
470 base54.consider("do while");
471 else if (node instanceof AST_If) {
472 base54.consider("if");
473 if (node.alternative) base54.consider("else");
475 else if (node instanceof AST_Var)
476 base54.consider("var");
477 else if (node instanceof AST_Const)
478 base54.consider("const");
479 else if (node instanceof AST_Lambda)
480 base54.consider("function");
481 else if (node instanceof AST_For)
482 base54.consider("for");
483 else if (node instanceof AST_ForIn)
484 base54.consider("for in");
485 else if (node instanceof AST_Switch)
486 base54.consider("switch");
487 else if (node instanceof AST_Case)
488 base54.consider("case");
489 else if (node instanceof AST_Default)
490 base54.consider("default");
491 else if (node instanceof AST_With)
492 base54.consider("with");
493 else if (node instanceof AST_ObjectSetter)
494 base54.consider("set" + node.key);
495 else if (node instanceof AST_ObjectGetter)
496 base54.consider("get" + node.key);
497 else if (node instanceof AST_ObjectKeyVal)
498 base54.consider(node.key);
499 else if (node instanceof AST_New)
500 base54.consider("new");
501 else if (node instanceof AST_This)
502 base54.consider("this");
503 else if (node instanceof AST_Try)
504 base54.consider("try");
505 else if (node instanceof AST_Catch)
506 base54.consider("catch");
507 else if (node instanceof AST_Finally)
508 base54.consider("finally");
509 else if (node instanceof AST_Symbol && node.unmangleable(options))
510 base54.consider(node.name);
511 else if (node instanceof AST_Unary || node instanceof AST_Binary)
512 base54.consider(node.operator);
513 else if (node instanceof AST_Dot)
514 base54.consider(node.property);
520 var base54 = (function() {
521 var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
522 var chars, frequency;
524 frequency = Object.create(null);
525 chars = string.split("").map(function(ch){ return ch.charCodeAt(0) });
526 chars.forEach(function(ch){ frequency[ch] = 0 });
528 base54.consider = function(str){
529 for (var i = str.length; --i >= 0;) {
530 var code = str.charCodeAt(i);
531 if (code in frequency) ++frequency[code];
534 base54.sort = function() {
535 chars = mergeSort(chars, function(a, b){
536 if (is_digit(a) && !is_digit(b)) return 1;
537 if (is_digit(b) && !is_digit(a)) return -1;
538 return frequency[b] - frequency[a];
541 base54.reset = reset;
543 base54.get = function(){ return chars };
544 base54.freq = function(){ return frequency };
545 function base54(num) {
546 var ret = "", base = 54;
550 ret += String.fromCharCode(chars[num % base]);
551 num = Math.floor(num / base);
559 AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
560 options = defaults(options, {
561 undeclared : false, // this makes a lot of noise
563 assign_to_global : true,
564 func_arguments : true,
565 nested_defuns : true,
568 var tw = new TreeWalker(function(node){
569 if (options.undeclared
570 && node instanceof AST_SymbolRef
571 && node.undeclared())
573 // XXX: this also warns about JS standard names,
574 // i.e. Object, Array, parseInt etc. Should add a list of
576 AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", {
578 file: node.start.file,
579 line: node.start.line,
583 if (options.assign_to_global)
586 if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
588 else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
592 || (sym.global() && sym.scope !== sym.definition().scope))) {
593 AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", {
594 msg: sym.undeclared() ? "Accidental global?" : "Assignment to global",
596 file: sym.start.file,
597 line: sym.start.line,
603 && node instanceof AST_SymbolRef
605 && node.name == "eval") {
606 AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start);
608 if (options.unreferenced
609 && (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
610 && !(node instanceof AST_SymbolCatch)
611 && node.unreferenced()) {
612 AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
613 type: node instanceof AST_Label ? "Label" : "Symbol",
615 file: node.start.file,
616 line: node.start.line,
620 if (options.func_arguments
621 && node instanceof AST_Lambda
622 && node.uses_arguments) {
623 AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", {
624 name: node.name ? node.name.name : "anonymous",
625 file: node.start.file,
626 line: node.start.line,
630 if (options.nested_defuns
631 && node instanceof AST_Defun
632 && !(tw.parent() instanceof AST_Scope)) {
633 AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", {
634 name: node.name.name,
635 type: tw.parent().TYPE,
636 file: node.start.file,
637 line: node.start.line,