vendor/uglifyjs/lib/process.js in uglifier-0.1.0 vs vendor/uglifyjs/lib/process.js in uglifier-0.1.1
- old
+ new
@@ -403,13 +403,11 @@
},
"try": function(t, c, f) {
if (c != null) return [
"try",
t.map(walk),
- with_new_scope(function(){
- return [ define(c[0]), c[1].map(walk) ];
- }),
+ [ define(c[0]), c[1].map(walk) ],
f != null ? f.map(walk) : null
];
},
"name": function(name) {
if (name == "eval")
@@ -507,13 +505,11 @@
return [ "name", get_mangled(name) ];
},
"try": function(t, c, f) {
return [ "try",
t.map(walk),
- c ? with_scope(c.scope, function(){
- return [ get_mangled(c[0]), c[1].map(walk) ];
- }) : null,
+ c != null ? [ get_mangled(c[0]), c[1].map(walk) ] : null,
f != null ? f.map(walk) : null ];
},
"toplevel": function(body) {
return with_scope(this.scope, function(){
return [ "toplevel", body.map(walk) ];
@@ -525,40 +521,10 @@
}, function() {
return walk(ast_add_scope(ast));
});
};
-// function ast_has_side_effects(ast) {
-// var w = ast_walker();
-// var FOUND_SIDE_EFFECTS = {};
-// function _found() { throw FOUND_SIDE_EFFECTS };
-// try {
-// w.with_walkers({
-// "new": _found,
-// "call": _found,
-// "assign": _found,
-// "defun": _found,
-// "var": _found,
-// "const": _found,
-// "throw": _found,
-// "return": _found,
-// "break": _found,
-// "continue": _found,
-// "label": _found,
-// "function": function(name) {
-// if (name) _found();
-// }
-// }, function(){
-// w.walk(ast);
-// });
-// } catch(ex) {
-// if (ex === FOUND_SIDE_EFFECTS)
-// return true;
-// throw ex;
-// }
-// };
-
/* -----[
- compress foo["bar"] into foo.bar,
- remove block brackets {} where possible
- join consecutive var declarations
- various optimizations for IFs:
@@ -570,39 +536,176 @@
function warn(msg) {
sys.debug(msg);
};
+function best_of(ast1, ast2) {
+ return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
+};
+
+function last_stat(b) {
+ if (b[0] == "block" && b[1] && b[1].length > 0)
+ return b[1][b[1].length - 1];
+ return b;
+}
+
+function aborts(t) {
+ if (t) {
+ t = last_stat(t);
+ if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
+ return true;
+ }
+};
+
+function negate(c) {
+ var not_c = [ "unary-prefix", "!", c ];
+ switch (c[0]) {
+ case "unary-prefix":
+ return c[1] == "!" ? c[2] : c;
+ case "atom":
+ switch (c[1]) {
+ case "false": return [ "num", 0 ];
+ case "true": return [ "num", 1 ];
+ }
+ break;
+ case "binary":
+ var op = c[1], left = c[2], right = c[3];
+ switch (op) {
+ case "<=": return [ "binary", ">", left, right ];
+ case "<": return [ "binary", ">=", left, right ];
+ case ">=": return [ "binary", "<", left, right ];
+ case ">": return [ "binary", "<=", left, right ];
+ case "==": return [ "binary", "!=", left, right ];
+ case "!=": return [ "binary", "==", left, right ];
+ case "===": return [ "binary", "!==", left, right ];
+ case "!==": return [ "binary", "===", left, right ];
+ case "&&": return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
+ case "||": return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
+ }
+ break;
+ }
+ return not_c;
+};
+
+function make_conditional(c, t, e) {
+ if (c[0] == "unary-prefix" && c[1] == "!") {
+ return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
+ } else {
+ return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ];
+ }
+};
+
+function empty(b) {
+ return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
+};
+
function ast_squeeze(ast, options) {
options = defaults(options, {
- make_seqs: true,
- dead_code: true,
- no_warnings: false
+ make_seqs : true,
+ dead_code : true,
+ no_warnings : false,
+ extra : false
});
var w = ast_walker(), walk = w.walk;
function is_constant(node) {
return node[0] == "string" || node[0] == "num";
};
+ function find_first_execute(node) {
+ if (!node)
+ return false;
+
+ switch (node[0]) {
+ case "num":
+ case "string":
+ case "name":
+ return node;
+ case "call":
+ case "conditional":
+ case "for":
+ case "if":
+ case "new":
+ case "return":
+ case "stat":
+ case "switch":
+ case "throw":
+ return find_first_execute(node[1]);
+ case "binary":
+ return find_first_execute(node[2]);
+ case "assign":
+ if (node[1] === true)
+ return find_first_execute(node[3]);
+ break;
+ case "var":
+ if (node[1][0].length > 1)
+ return find_first_execute(node[1][0][1]);
+ break;
+ }
+ return null;
+ }
+
+ function find_assign_recursive(p, v) {
+ if (p[0] == "assign" && p[1] != true || p[0] == "unary-prefix") {
+ if (p[2][0] == "name" && v[0] == "name" && p[2][1] == v[1])
+ return true;
+ return false;
+ }
+
+ if (p[0] != "assign" || p[1] !== true)
+ return false;
+
+ if ((is_constant(p[3]) && p[3][0] == v[0] && p[3][1] == v[1]) ||
+ (p[3][0] == "name" && v[0] == "name" && p[3][1] == v[1]) ||
+ (p[2][0] == "name" && v[0] == "name" && p[2][1] == v[1]))
+ return true;
+
+ return find_assign_recursive(p[3], v);
+ };
+
function rmblock(block) {
if (block != null && block[0] == "block" && block[1] && block[1].length == 1)
block = block[1][0];
return block;
};
+ function clone(obj) {
+ if (obj && obj.constructor == Array)
+ return obj.map(clone);
+ return obj;
+ };
+
+ function make_seq_to_statements(node) {
+ if (node[0] != "seq") {
+ switch (node[0]) {
+ case "var":
+ case "const":
+ return [ node ];
+ default:
+ return [ [ "stat", node ] ];
+ }
+ }
+
+ var ret = [];
+ for (var i = 1; i < node.length; i++)
+ ret.push.apply(ret, make_seq_to_statements(node[i]));
+
+ return ret;
+ };
+
function _lambda(name, args, body) {
return [ this[0], name, args, tighten(body.map(walk), "lambda") ];
};
// we get here for blocks that have been already transformed.
- // this function does two things:
+ // this function does a few things:
// 1. discard useless blocks
// 2. join consecutive var declarations
// 3. remove obviously dead code
// 4. transform consecutive statements using the comma operator
+ // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
function tighten(statements, block_type) {
statements = statements.reduce(function(a, stat){
if (stat[0] == "block") {
if (stat[1]) {
a.push.apply(a, stat[1]);
@@ -611,10 +714,68 @@
a.push(stat);
}
return a;
}, []);
+ if (options.extra) {
+ // Detightening things. We do this because then we can assume that the
+ // statements are structured in a specific way.
+ statements = (function(a, prev) {
+ statements.forEach(function(cur) {
+ switch (cur[0]) {
+ case "for":
+ if (cur[1] != null) {
+ a.push.apply(a, make_seq_to_statements(cur[1]));
+ cur[1] = null;
+ }
+ a.push(cur);
+ break;
+ case "stat":
+ var stats = make_seq_to_statements(cur[1]);
+ stats.forEach(function(s) {
+ if (s[1][0] == "unary-postfix")
+ s[1][0] = "unary-prefix";
+ });
+ a.push.apply(a, stats);
+ break;
+ default:
+ a.push(cur);
+ }
+ });
+ return a;
+ })([]);
+
+ statements = (function(a, prev) {
+ statements.forEach(function(cur) {
+ if (!(prev && prev[0] == "stat")) {
+ a.push(cur);
+ prev = cur;
+ return;
+ }
+
+ var p = prev[1];
+ var c = find_first_execute(cur);
+ if (c && find_assign_recursive(p, c)) {
+ var old_cur = clone(cur);
+ c.splice(0, c.length);
+ c.push.apply(c, p);
+ var tmp_cur = best_of(cur, [ "toplevel", [ prev, old_cur ] ]);
+ if (tmp_cur == cur) {
+ a[a.length -1] = cur;
+ } else {
+ cur = old_cur;
+ a.push(cur);
+ }
+ } else {
+ a.push(cur);
+ }
+ prev = cur;
+ });
+ return a;
+ })([]);
+ }
+
statements = (function(a, prev){
statements.forEach(function(cur){
if (prev && ((cur[0] == "var" && prev[0] == "var") ||
(cur[0] == "const" && prev[0] == "const"))) {
prev[1] = prev[1].concat(cur[1]);
@@ -644,85 +805,82 @@
return a;
})([]);
if (options.make_seqs) statements = (function(a, prev) {
statements.forEach(function(cur){
- if (!prev) {
- a.push(cur);
- if (cur[0] == "stat") prev = cur;
- } else if (cur[0] == "stat" && prev[0] == "stat") {
+ if (prev && prev[0] == "stat" && cur[0] == "stat") {
prev[1] = [ "seq", prev[1], cur[1] ];
} else {
a.push(cur);
- prev = null;
+ prev = cur;
}
});
return a;
})([]);
+ if (options.extra) {
+ statements = (function(a, prev){
+ statements.forEach(function(cur){
+ var replaced = false;
+ if (prev && cur[0] == "for" && cur[1] == null && (prev[0] == "var" || prev[0] == "const" || prev[0] == "stat")) {
+ cur[1] = prev;
+ a[a.length - 1] = cur;
+ } else {
+ a.push(cur);
+ }
+ prev = cur;
+ });
+ return a;
+ })([]);
+ }
+
if (block_type == "lambda") statements = (function(i, a, stat){
while (i < statements.length) {
stat = statements[i++];
- if (stat[0] == "if" && stat[2][0] == "return" && stat[2][1] == null && !stat[3]) {
- a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
- break;
- } else {
- a.push(stat);
+ if (stat[0] == "if" && !stat[3]) {
+ if (stat[2][0] == "return" && stat[2][1] == null) {
+ a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
+ break;
+ }
+ var last = last_stat(stat[2]);
+ if (last[0] == "return" && last[1] == null) {
+ a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
+ break;
+ }
}
+ a.push(stat);
}
return a;
})(0, []);
return statements;
};
- function best_of(ast1, ast2) {
- return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
- };
-
- function aborts(t) {
- if (t[0] == "block" && t[1] && t[1].length > 0)
- t = t[1][t[1].length - 1]; // interested in last statement
- if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
- return true;
- };
-
- function negate(c) {
- if (c[0] == "unary-prefix" && c[1] == "!") return c[2];
- else return [ "unary-prefix", "!", c ];
- };
-
- function make_conditional(c, t, e) {
- if (c[0] == "unary-prefix" && c[1] == "!") {
- return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
- } else {
- return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ];
- }
- };
-
- function empty(b) {
- return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
- };
-
function make_if(c, t, e) {
c = walk(c);
t = walk(t);
e = walk(e);
- var negated = c[0] == "unary-prefix" && c[1] == "!";
+
if (empty(t)) {
- if (negated) c = c[2];
- else c = [ "unary-prefix", "!", c ];
+ c = negate(c);
t = e;
e = null;
- }
- if (empty(e)) {
+ } else if (empty(e)) {
e = null;
} else {
- if (negated) {
- c = c[2];
- var tmp = t; t = e; e = tmp;
- }
+ // if we have both else and then, maybe it makes sense to switch them?
+ (function(){
+ var a = gen_code(c);
+ var n = negate(c);
+ var b = gen_code(n);
+ if (b.length < a.length) {
+ var tmp = t;
+ t = e;
+ e = tmp;
+ c = n;
+ }
+ })();
}
if (empty(e) && empty(t))
return [ "stat", c ];
var ret = [ "if", c, t, e ];
if (t[0] == "stat") {
@@ -746,10 +904,19 @@
else {
ret.push(e);
}
ret = walk([ "block", ret ]);
}
+ else if (t && aborts(e)) {
+ ret = [ [ "if", negate(c), e ] ];
+ if (t[0] == "block") {
+ if (t[1]) ret = ret.concat(t[1]);
+ } else {
+ ret.push(t);
+ }
+ ret = walk([ "block", ret ]);
+ }
return ret;
};
return w.with_walkers({
"sub": function(expr, subscript) {
@@ -783,24 +950,28 @@
},
"binary": function(op, left, right) {
left = walk(left);
right = walk(right);
var best = [ "binary", op, left, right ];
- if (is_constant(left) && is_constant(right)) {
- var val = null;
- switch (op) {
- case "+": val = left[1] + right[1]; break;
- case "*": val = left[1] * right[1]; break;
- case "/": val = left[1] / right[1]; break;
- case "-": val = left[1] - right[1]; break;
- case "<<": val = left[1] << right[1]; break;
- case ">>": val = left[1] >> right[1]; break;
- case ">>>": val = left[1] >>> right[1]; break;
+ if (is_constant(right)) {
+ if (is_constant(left)) {
+ var val = null;
+ switch (op) {
+ case "+": val = left[1] + right[1]; break;
+ case "*": val = left[1] * right[1]; break;
+ case "/": val = left[1] / right[1]; break;
+ case "-": val = left[1] - right[1]; break;
+ case "<<": val = left[1] << right[1]; break;
+ case ">>": val = left[1] >> right[1]; break;
+ case ">>>": val = left[1] >>> right[1]; break;
+ }
+ if (val != null) {
+ best = best_of(best, [ typeof val == "string" ? "string" : "num", val ]);
+ }
+ } else if (left[0] == "binary" && left[1] == "+" && left[3][0] == "string") {
+ best = best_of(best, [ "binary", "+", left[2], [ "string", left[3][1] + right[1] ] ]);
}
- if (val != null) {
- best = best_of(best, [ typeof val == "string" ? "string" : "num", val ]);
- }
}
return best;
},
"conditional": function(c, t, e) {
return make_conditional(walk(c), walk(t), walk(e));
@@ -810,10 +981,20 @@
"try",
tighten(t.map(walk)),
c != null ? [ c[0], tighten(c[1].map(walk)) ] : null,
f != null ? tighten(f.map(walk)) : null
];
+ },
+ "unary-prefix": function(op, cond) {
+ if (op == "!")
+ return best_of(this, negate(cond));
+ },
+ "call": function(expr, args) {
+ if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
+ // foo.toString() ==> foo+""
+ return [ "binary", "+", expr[1], [ "string", "" ]];
+ }
}
}, function() {
return walk(ast);
});
@@ -1003,11 +1184,11 @@
parenthesize(el, "seq") ]);
},
"assign": function(op, lvalue, rvalue) {
if (op && op !== true) op += "=";
else op = "=";
- return add_spaces([ make(lvalue), op, make(rvalue) ]);
+ return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
},
"dot": function(expr) {
var out = make(expr), i = 1;
if (needs_parens(expr))
out = "(" + out + ")";
@@ -1191,9 +1372,13 @@
// + '"';
return JSON.stringify(str); // STILL cheating.
};
function make_name(name) {
+ switch (name) {
+ case "true": return beautify ? "true" : "!0";
+ case "false": return beautify ? "false" : "!1";
+ }
return name.toString();
};
function make_block_statements(statements) {
for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {