add option to mangle names even if eval/with is in use
authorMihai Bazon <mihai@bazon.net>
Tue, 6 Nov 2012 09:39:41 +0000 (11:39 +0200)
committerMihai Bazon <mihai@bazon.net>
Tue, 6 Nov 2012 16:19:51 +0000 (18:19 +0200)
(for more fair comparison to Closure compiler)

bin/uglifyjs2
lib/scope.js
lib/utils.js

index aadda7c..1706629 100755 (executable)
@@ -252,7 +252,7 @@ if (SCOPE_IS_NEEDED) {
     time_it("scope", function(){
         TOPLEVEL.figure_out_scope();
         if (MANGLE) {
-            TOPLEVEL.compute_char_frequency();
+            TOPLEVEL.compute_char_frequency(MANGLE);
         }
     });
 }
index fcd9de1..27cd525 100644 (file)
@@ -43,7 +43,7 @@
 
 "use strict";
 
-function SymbolDef(scope, orig) {
+function SymbolDef(scope, index, orig) {
     this.name = orig.name;
     this.orig = [ orig ];
     this.scope = scope;
@@ -52,15 +52,18 @@ function SymbolDef(scope, orig) {
     this.mangled_name = null;
     this.undeclared = false;
     this.constant = false;
+    this.index = index;
 };
 
 SymbolDef.prototype = {
-    unmangleable: function() {
-        return this.global || this.undeclared || this.scope.uses_eval || this.scope.uses_with;
+    unmangleable: function(options) {
+        return this.global
+            || this.undeclared
+            || (!options.eval && (this.scope.uses_eval || this.scope.uses_with));
     },
-    mangle: function() {
-        if (!this.mangled_name && !this.unmangleable())
-            this.mangled_name = this.scope.next_mangled();
+    mangle: function(options) {
+        if (!this.mangled_name && !this.unmangleable(options))
+            this.mangled_name = this.scope.next_mangled(options);
     }
 };
 
@@ -76,13 +79,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
     var self = this;
     var scope = self.parent_scope = null;
     var labels = new Dictionary();
+    var nesting = 0;
     var tw = new TreeWalker(function(node, descend){
         if (node instanceof AST_Scope) {
-            node.init_scope_vars();
+            node.init_scope_vars(nesting);
             var save_scope = node.parent_scope = scope;
+            ++nesting;
             scope = node;
             descend();
             scope = save_scope;
+            --nesting;
             return true;        // don't descend again in TreeWalker
         }
         if (node instanceof AST_Directive) {
@@ -185,7 +191,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
                 if (globals.has(name)) {
                     g = globals.get(name);
                 } else {
-                    g = new SymbolDef(self, node);
+                    g = new SymbolDef(self, globals.size(), node);
                     g.undeclared = true;
                     globals.set(name, g);
                 }
@@ -207,7 +213,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
     self.walk(tw);
 });
 
-AST_Scope.DEFMETHOD("init_scope_vars", function(){
+AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){
     this.directives = [];     // contains the directives defined in this scope, i.e. "use strict"
     this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
     this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
@@ -216,6 +222,7 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(){
     this.parent_scope = null; // the parent scope
     this.enclosed = [];       // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
     this.cname = -1;          // the current index for mangling functions/variables
+    this.nesting = nesting;   // the nesting level of this scope (0 means toplevel)
 });
 
 AST_Scope.DEFMETHOD("strict", function(){
@@ -223,7 +230,7 @@ AST_Scope.DEFMETHOD("strict", function(){
 });
 
 AST_Lambda.DEFMETHOD("init_scope_vars", function(){
-    AST_Scope.prototype.init_scope_vars.call(this);
+    AST_Scope.prototype.init_scope_vars.apply(this, arguments);
     this.uses_arguments = false;
 });
 
@@ -236,6 +243,7 @@ AST_SymbolRef.DEFMETHOD("reference", function() {
         if (s === def.scope) break;
         s = s.parent_scope;
     }
+    this.frame = this.scope.nesting - def.scope.nesting;
 });
 
 AST_SymbolDeclaration.DEFMETHOD("init_scope_vars", function(){
@@ -268,7 +276,7 @@ AST_Scope.DEFMETHOD("def_function", function(symbol){
 AST_Scope.DEFMETHOD("def_variable", function(symbol){
     var def;
     if (!this.variables.has(symbol.name)) {
-        def = new SymbolDef(this, symbol);
+        def = new SymbolDef(this, this.variables.size(), symbol);
         this.variables.set(symbol.name, def);
         def.global = !this.parent_scope;
     } else {
@@ -278,7 +286,7 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol){
     return symbol.thedef = def;
 });
 
-AST_Scope.DEFMETHOD("next_mangled", function(){
+AST_Scope.DEFMETHOD("next_mangled", function(options){
     var ext = this.enclosed, n = ext.length;
     out: while (true) {
         var m = base54(++this.cname);
@@ -288,7 +296,7 @@ AST_Scope.DEFMETHOD("next_mangled", function(){
         // inner scopes.
         for (var i = n; --i >= 0;) {
             var sym = ext[i];
-            var name = sym.mangled_name || (sym.unmangleable() && sym.name);
+            var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
             if (m == name) continue out;
         }
         return m;
@@ -300,8 +308,8 @@ AST_Scope.DEFMETHOD("references", function(sym){
     return this.enclosed.indexOf(sym) < 0 ? null : sym;
 });
 
-AST_Symbol.DEFMETHOD("unmangleable", function(){
-    return this.definition().unmangleable();
+AST_Symbol.DEFMETHOD("unmangleable", function(options){
+    return this.definition().unmangleable(options);
 });
 
 // labels are always mangleable
@@ -336,7 +344,8 @@ AST_Symbol.DEFMETHOD("global", function(){
 
 AST_Toplevel.DEFMETHOD("mangle_names", function(options){
     options = defaults(options, {
-        except : []
+        except : [],
+        eval   : false,
     });
     // We only need to mangle declaration nodes.  Special logic wired
     // into the code generator will display the mangled name if it's
@@ -375,7 +384,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
     to_mangle.forEach(function(def){ def.mangle(options) });
 });
 
-AST_Toplevel.DEFMETHOD("compute_char_frequency", function(){
+AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
     var tw = new TreeWalker(function(node){
         if (node instanceof AST_Constant)
             base54.consider(node.print_to_string());
@@ -433,7 +442,7 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(){
             base54.consider("catch");
         else if (node instanceof AST_Finally)
             base54.consider("finally");
-        else if (node instanceof AST_Symbol && node.unmangleable())
+        else if (node instanceof AST_Symbol && node.unmangleable(options))
             base54.consider(node.name);
         else if (node instanceof AST_Unary || node instanceof AST_Binary)
             base54.consider(node.operator);
index 27b7975..15eed9b 100644 (file)
@@ -247,16 +247,30 @@ function makePredicate(words) {
 
 function Dictionary() {
     this._values = Object.create(null);
+    this._size = 0;
 };
 Dictionary.prototype = {
-    set: function(key, val) { return this._values["$" + key] = val, this },
+    set: function(key, val) {
+        if (!this.has(key)) ++this._size;
+        this._values["$" + key] = val;
+        return this;
+    },
     get: function(key) { return this._values["$" + key] },
-    del: function(key) { return delete this._values["$" + key], this },
+    del: function(key) {
+        if (this.has(key)) {
+            --this._size;
+            delete this._values["$" + key];
+        }
+        return this;
+    },
     has: function(key) { return ("$" + key) in this._values },
     each: function(f) {
         for (var i in this._values)
             f(this._values[i], i.substr(1));
     },
+    size: function() {
+        return this._size;
+    },
     map: function(f) {
         var ret = [];
         for (var i in this._values)