lib/uglify.js in uglifier-1.0.0 vs lib/uglify.js in uglifier-1.0.1
- old
+ new
@@ -237,11 +237,11 @@
"&=",
"&&",
"||"
]);
-var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\v\u200b"));
+var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\u000b\u200b"));
var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
var PUNC_CHARS = array_to_hash(characters("[]{}(),;:"));
@@ -452,15 +452,16 @@
switch (ch) {
case "n" : return "\n";
case "r" : return "\r";
case "t" : return "\t";
case "b" : return "\b";
- case "v" : return "\v";
+ case "v" : return "\u000b";
case "f" : return "\f";
case "0" : return "\0";
case "x" : return String.fromCharCode(hex_bytes(2));
case "u" : return String.fromCharCode(hex_bytes(4));
+ case "\n": return "";
default : return ch;
}
};
function hex_bytes(n) {
@@ -477,11 +478,28 @@
function read_string() {
return with_eof_error("Unterminated string constant", function(){
var quote = next(), ret = "";
for (;;) {
var ch = next(true);
- if (ch == "\\") ch = read_escaped_char();
+ if (ch == "\\") {
+ // read OctalEscapeSequence (XXX: deprecated if "strict mode")
+ // https://github.com/mishoo/UglifyJS/issues/178
+ var octal_len = 0, first = null;
+ ch = read_while(function(ch){
+ if (ch >= "0" && ch <= "7") {
+ if (!first) {
+ first = ch;
+ return ++octal_len;
+ }
+ else if (first <= "3" && octal_len <= 2) return ++octal_len;
+ else if (first >= "4" && octal_len <= 1) return ++octal_len;
+ }
+ return false;
+ });
+ if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8));
+ else ch = read_escaped_char();
+ }
else if (ch == quote) break;
ret += ch;
}
return token("string", ret);
});
@@ -1105,15 +1123,10 @@
var expr_atom = maybe_embed_tokens(function(allow_calls) {
if (is("operator", "new")) {
next();
return new_();
}
- if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
- return make_unary("unary-prefix",
- prog1(S.token.value, next),
- expr_atom(allow_calls));
- }
if (is("punc")) {
switch (S.token.value) {
case "(":
next();
return subscripts(prog1(expression, curry(expect, ")")), allow_calls);
@@ -1210,17 +1223,27 @@
}
if (allow_calls && is("punc", "(")) {
next();
return subscripts(as("call", expr, expr_list(")")), true);
}
- if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) {
- return prog1(curry(make_unary, "unary-postfix", S.token.value, expr),
- next);
- }
return expr;
};
+ function maybe_unary(allow_calls) {
+ if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
+ return make_unary("unary-prefix",
+ prog1(S.token.value, next),
+ maybe_unary(allow_calls));
+ }
+ var val = expr_atom(allow_calls);
+ while (is("operator") && HOP(UNARY_POSTFIX, S.token.value) && !S.token.nlb) {
+ val = make_unary("unary-postfix", S.token.value, val);
+ next();
+ }
+ return val;
+ };
+
function make_unary(tag, op, expr) {
if ((op == "++" || op == "--") && !is_assignable(expr))
croak("Invalid use of " + op + " operator");
return as(tag, op, expr);
};
@@ -1229,18 +1252,18 @@
var op = is("operator") ? S.token.value : null;
if (op && op == "in" && no_in) op = null;
var prec = op != null ? PRECEDENCE[op] : null;
if (prec != null && prec > min_prec) {
next();
- var right = expr_op(expr_atom(true), prec, no_in);
+ var right = expr_op(maybe_unary(true), prec, no_in);
return expr_op(as("binary", op, left, right), min_prec, no_in);
}
return left;
};
function expr_ops(no_in) {
- return expr_op(expr_atom(true), 0, no_in);
+ return expr_op(maybe_unary(true), 0, no_in);
};
function maybe_conditional(no_in) {
var expr = expr_ops(no_in);
if (is("operator", "?")) {
@@ -1705,24 +1728,31 @@
continue;
return m;
}
},
+ set_mangle: function(name, m) {
+ this.rev_mangled[m] = name;
+ return this.mangled[name] = m;
+ },
get_mangled: function(name, newMangle) {
if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
var s = this.has(name);
if (!s) return name; // not in visible scope, no mangle
if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
if (!newMangle) return name; // not found and no mangling requested
-
- var m = s.next_mangled();
- s.rev_mangled[m] = name;
- return s.mangled[name] = m;
+ return s.set_mangle(name, s.next_mangled());
},
- define: function(name) {
- if (name != null)
- return this.names[name] = name;
+ references: function(name) {
+ return name && !this.parent || this.uses_with || this.uses_eval || this.refs[name];
+ },
+ define: function(name, type) {
+ if (name != null) {
+ if (type == "var" || !HOP(this.names, name))
+ this.names[name] = type || "var";
+ return name;
+ }
}
};
function ast_add_scope(ast) {
@@ -1736,47 +1766,55 @@
ret.scope = current_scope;
current_scope = current_scope.parent;
return ret;
};
- function define(name) {
- return current_scope.define(name);
+ function define(name, type) {
+ return current_scope.define(name, type);
};
function reference(name) {
current_scope.refs[name] = true;
};
function _lambda(name, args, body) {
var is_defun = this[0] == "defun";
- return [ this[0], is_defun ? define(name) : name, args, with_new_scope(function(){
- if (!is_defun) define(name);
- MAP(args, define);
+ return [ this[0], is_defun ? define(name, "defun") : name, args, with_new_scope(function(){
+ if (!is_defun) define(name, "lambda");
+ MAP(args, function(name){ define(name, "arg") });
return MAP(body, walk);
})];
};
+ function _vardefs(type) {
+ return function(defs) {
+ MAP(defs, function(d){
+ define(d[0], type);
+ if (d[1]) reference(d[0]);
+ });
+ };
+ };
+
return with_new_scope(function(){
// process AST
var ret = w.with_walkers({
"function": _lambda,
"defun": _lambda,
+ "label": function(name, stat) { define(name, "label") },
+ "break": function(label) { if (label) reference(label) },
+ "continue": function(label) { if (label) reference(label) },
"with": function(expr, block) {
for (var s = current_scope; s; s = s.parent)
s.uses_with = true;
},
- "var": function(defs) {
- MAP(defs, function(d){ define(d[0]) });
- },
- "const": function(defs) {
- MAP(defs, function(d){ define(d[0]) });
- },
+ "var": _vardefs("var"),
+ "const": _vardefs("const"),
"try": function(t, c, f) {
if (c != null) return [
this[0],
MAP(t, walk),
- [ define(c[0]), MAP(c[1], walk) ],
+ [ define(c[0], "catch"), MAP(c[1], walk) ],
f != null ? MAP(f, walk) : null
];
},
"name": function(name) {
if (name == "eval")
@@ -1847,23 +1885,34 @@
return null;
}
};
function _lambda(name, args, body) {
- var is_defun = this[0] == "defun";
- if (is_defun && name) name = get_mangled(name);
+ var is_defun = this[0] == "defun", extra;
+ if (name) {
+ if (is_defun) name = get_mangled(name);
+ else {
+ extra = {};
+ if (!(scope.uses_eval || scope.uses_with))
+ name = extra[name] = scope.next_mangled();
+ else
+ extra[name] = name;
+ }
+ }
body = with_scope(body.scope, function(){
- if (!is_defun && name) name = get_mangled(name);
args = MAP(args, function(name){ return get_mangled(name) });
return MAP(body, walk);
- });
+ }, extra);
return [ this[0], name, args, body ];
};
- function with_scope(s, cont) {
+ function with_scope(s, cont, extra) {
var _scope = scope;
scope = s;
+ if (extra) for (var i in extra) if (HOP(extra, i)) {
+ s.set_mangle(i, extra[i]);
+ }
for (var i in s.names) if (HOP(s.names, i)) {
get_mangled(i, true);
}
var ret = cont();
ret.scope = s;
@@ -1889,10 +1938,13 @@
case "defun":
return MAP.at_top(ast);
}
return ast;
},
+ "label": function(label, stat) { return [ this[0], get_mangled(label), walk(stat) ] },
+ "break": function(label) { if (label) return [ this[0], get_mangled(label) ] },
+ "continue": function(label) { if (label) return [ this[0], get_mangled(label) ] },
"var": _vardefs,
"const": _vardefs,
"name": function(name) {
return get_define(name) || [ this[0], get_mangled(name) ];
},
@@ -1935,14 +1987,16 @@
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;
+ if (t) switch (last_stat(t)[0]) {
+ case "return":
+ case "break":
+ case "continue":
+ case "throw":
+ return true;
}
};
function boolean_expr(expr) {
return ( (expr[0] == "unary-prefix"
@@ -2093,10 +2147,222 @@
function warn_unreachable(ast) {
if (!empty(ast))
warn("Dropping unreachable code: " + gen_code(ast, true));
};
+function prepare_ifs(ast) {
+ var w = ast_walker(), walk = w.walk;
+ // In this first pass, we rewrite ifs which abort with no else with an
+ // if-else. For example:
+ //
+ // if (x) {
+ // blah();
+ // return y;
+ // }
+ // foobar();
+ //
+ // is rewritten into:
+ //
+ // if (x) {
+ // blah();
+ // return y;
+ // } else {
+ // foobar();
+ // }
+ function redo_if(statements) {
+ statements = MAP(statements, walk);
+
+ for (var i = 0; i < statements.length; ++i) {
+ var fi = statements[i];
+ if (fi[0] != "if") continue;
+
+ if (fi[3] && walk(fi[3])) continue;
+
+ var t = walk(fi[2]);
+ if (!aborts(t)) continue;
+
+ var conditional = walk(fi[1]);
+
+ var e_body = statements.slice(i + 1);
+ var e = e_body.length == 1 ? e_body[0] : [ "block", e_body ];
+
+ var ret = statements.slice(0, i).concat([ [
+ fi[0], // "if"
+ conditional, // conditional
+ t, // then
+ e // else
+ ] ]);
+
+ return redo_if(ret);
+ }
+
+ return statements;
+ };
+
+ function redo_if_lambda(name, args, body) {
+ body = redo_if(body);
+ return [ this[0], name, args, body ];
+ };
+
+ function redo_if_block(statements) {
+ return [ this[0], statements != null ? redo_if(statements) : null ];
+ };
+
+ return w.with_walkers({
+ "defun": redo_if_lambda,
+ "function": redo_if_lambda,
+ "block": redo_if_block,
+ "splice": redo_if_block,
+ "toplevel": function(statements) {
+ return [ this[0], redo_if(statements) ];
+ },
+ "try": function(t, c, f) {
+ return [
+ this[0],
+ redo_if(t),
+ c != null ? [ c[0], redo_if(c[1]) ] : null,
+ f != null ? redo_if(f) : null
+ ];
+ }
+ }, function() {
+ return walk(ast);
+ });
+};
+
+function for_side_effects(ast, handler) {
+ var w = ast_walker(), walk = w.walk;
+ var $stop = {}, $restart = {};
+ function stop() { throw $stop };
+ function restart() { throw $restart };
+ function found(){ return handler.call(this, this, w, stop, restart) };
+ function unary(op) {
+ if (op == "++" || op == "--")
+ return found.apply(this, arguments);
+ };
+ return w.with_walkers({
+ "try": found,
+ "throw": found,
+ "return": found,
+ "new": found,
+ "switch": found,
+ "break": found,
+ "continue": found,
+ "assign": found,
+ "call": found,
+ "if": found,
+ "for": found,
+ "for-in": found,
+ "while": found,
+ "do": found,
+ "return": found,
+ "unary-prefix": unary,
+ "unary-postfix": unary,
+ "defun": found
+ }, function(){
+ while (true) try {
+ walk(ast);
+ break;
+ } catch(ex) {
+ if (ex === $stop) break;
+ if (ex === $restart) continue;
+ throw ex;
+ }
+ });
+};
+
+function ast_lift_variables(ast) {
+ var w = ast_walker(), walk = w.walk, scope;
+ function do_body(body, env) {
+ var _scope = scope;
+ scope = env;
+ body = MAP(body, walk);
+ var hash = {}, names = MAP(env.names, function(type, name){
+ if (type != "var") return MAP.skip;
+ if (!env.references(name)) return MAP.skip;
+ hash[name] = true;
+ return [ name ];
+ });
+ if (names.length > 0) {
+ // looking for assignments to any of these variables.
+ // we can save considerable space by moving the definitions
+ // in the var declaration.
+ for_side_effects([ "block", body ], function(ast, walker, stop, restart) {
+ if (ast[0] == "assign"
+ && ast[1] === true
+ && ast[2][0] == "name"
+ && HOP(hash, ast[2][1])) {
+ // insert the definition into the var declaration
+ for (var i = names.length; --i >= 0;) {
+ if (names[i][0] == ast[2][1]) {
+ if (names[i][1]) // this name already defined, we must stop
+ stop();
+ names[i][1] = ast[3]; // definition
+ names.push(names.splice(i, 1)[0]);
+ break;
+ }
+ }
+ // remove this assignment from the AST.
+ var p = walker.parent();
+ if (p[0] == "seq") {
+ var a = p[2];
+ a.unshift(0, p.length);
+ p.splice.apply(p, a);
+ }
+ else if (p[0] == "stat") {
+ p.splice(0, p.length, "block"); // empty statement
+ }
+ else {
+ stop();
+ }
+ restart();
+ }
+ stop();
+ });
+ body.unshift([ "var", names ]);
+ }
+ scope = _scope;
+ return body;
+ };
+ function _vardefs(defs) {
+ var ret = null;
+ for (var i = defs.length; --i >= 0;) {
+ var d = defs[i];
+ if (!d[1]) continue;
+ d = [ "assign", true, [ "name", d[0] ], d[1] ];
+ if (ret == null) ret = d;
+ else ret = [ "seq", d, ret ];
+ }
+ if (ret == null) {
+ if (w.parent()[0] == "for-in")
+ return [ "name", defs[0][0] ];
+ return MAP.skip;
+ }
+ return [ "stat", ret ];
+ };
+ function _toplevel(body) {
+ return [ this[0], do_body(body, this.scope) ];
+ };
+ return w.with_walkers({
+ "function": function(name, args, body){
+ for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
+ args.pop();
+ if (!body.scope.references(name)) name = null;
+ return [ this[0], name, args, do_body(body, body.scope) ];
+ },
+ "defun": function(name, args, body){
+ if (!scope.references(name)) return MAP.skip;
+ for (var i = args.length; --i >= 0 && !body.scope.references(args[i]);)
+ args.pop();
+ return [ this[0], name, args, do_body(body, body.scope) ];
+ },
+ "var": _vardefs,
+ "toplevel": _toplevel
+ }, function(){
+ return walk(ast_add_scope(ast));
+ });
+};
+
function ast_squeeze(ast, options) {
options = defaults(options, {
make_seqs : true,
dead_code : true,
keep_comps : true,
@@ -2157,26 +2423,27 @@
};
function _lambda(name, args, body) {
var is_defun = this[0] == "defun";
body = with_scope(body.scope, function(){
- var ret = tighten(MAP(body, walk), "lambda");
- if (!is_defun && name && !HOP(scope.refs, name))
+ var ret = tighten(body, "lambda");
+ if (!is_defun && name && !scope.references(name))
name = null;
return ret;
});
return [ this[0], name, args, body ];
};
- // we get here for blocks that have been already transformed.
// 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 = MAP(statements, walk);
+
statements = statements.reduce(function(a, stat){
if (stat[0] == "block") {
if (stat[1]) {
a.push.apply(a, stat[1]);
}
@@ -2200,13 +2467,23 @@
})([]);
if (options.dead_code) statements = (function(a, has_quit){
statements.forEach(function(st){
if (has_quit) {
- if (member(st[0], [ "function", "defun" , "var", "const" ])) {
+ if (st[0] == "function" || st[0] == "defun") {
a.push(st);
}
+ else if (st[0] == "var" || st[0] == "const") {
+ if (!options.no_warnings)
+ warn("Variables declared in unreachable code");
+ st[1] = MAP(st[1], function(def){
+ if (def[1] && !options.no_warnings)
+ warn_unreachable([ "assign", true, [ "name", def[0] ], def[1] ]);
+ return [ def[0] ];
+ });
+ a.push(st);
+ }
else if (!options.no_warnings)
warn_unreachable(st);
}
else {
a.push(st);
@@ -2224,31 +2501,42 @@
} else {
a.push(cur);
prev = cur;
}
});
+ if (a.length >= 2
+ && a[a.length-2][0] == "stat"
+ && (a[a.length-1][0] == "return" || a[a.length-1][0] == "throw")
+ && a[a.length-1][1])
+ {
+ a.splice(a.length - 2, 2,
+ [ a[a.length-1][0],
+ [ "seq", a[a.length-2][1], a[a.length-1][1] ]]);
+ }
return a;
})([]);
- if (block_type == "lambda") statements = (function(i, a, stat){
- while (i < statements.length) {
- stat = statements[i++];
- 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, []);
+ // this increases jQuery by 1K. Probably not such a good idea after all..
+ // part of this is done in prepare_ifs anyway.
+ // if (block_type == "lambda") statements = (function(i, a, stat){
+ // while (i < statements.length) {
+ // stat = statements[i++];
+ // 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 make_if(c, t, e) {
@@ -2353,17 +2641,17 @@
}
},
"if": make_if,
"toplevel": function(body) {
return [ "toplevel", with_scope(this.scope, function(){
- return tighten(MAP(body, walk));
+ return tighten(body);
}) ];
},
"switch": function(expr, body) {
var last = body.length - 1;
return [ "switch", walk(expr), MAP(body, function(branch, i){
- var block = tighten(MAP(branch[1], walk));
+ var block = tighten(branch[1]);
if (i == last && block.length > 0) {
var node = block[block.length - 1];
if (node[0] == "break" && !node[1])
block.pop();
}
@@ -2371,11 +2659,11 @@
}) ];
},
"function": _lambda,
"defun": _lambda,
"block": function(body) {
- if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]);
+ if (body) return rmblock([ "block", tighten(body) ]);
},
"binary": function(op, left, right) {
return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
return best_of(walk(c), this);
}, function no() {
@@ -2386,13 +2674,13 @@
return make_conditional(walk(c), walk(t), walk(e));
},
"try": function(t, c, f) {
return [
"try",
- tighten(MAP(t, walk)),
- c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null,
- f != null ? tighten(MAP(f, walk)) : null
+ tighten(t),
+ c != null ? [ c[0], tighten(c[1]) ] : null,
+ f != null ? tighten(f) : null
];
},
"unary-prefix": function(op, expr) {
expr = walk(expr);
var ret = [ "unary-prefix", op, expr ];
@@ -2422,11 +2710,16 @@
return [ "array", args ];
}
},
"while": _do_while
}, function() {
- return walk(ast_add_scope(ast));
+ for (var i = 0; i < 2; ++i) {
+ ast = prepare_ifs(ast);
+ ast = ast_add_scope(ast);
+ ast = walk(ast);
+ }
+ return ast;
});
};
/* -----[ re-generate code from the AST ]----- */
@@ -2479,19 +2772,23 @@
indent_start : 0,
indent_level : 4,
quote_keys : false,
space_colon : false,
beautify : false,
- ascii_only : false
+ ascii_only : false,
+ inline_script: false
});
var beautify = !!options.beautify;
var indentation = 0,
newline = beautify ? "\n" : "",
space = beautify ? " " : "";
function encode_string(str) {
- return make_string(str, options.ascii_only);
+ var ret = make_string(str, options.ascii_only);
+ if (options.inline_script)
+ ret = ret.replace(/<\x2fscript([>/\t\n\f\r ])/gi, "<\\/script$1");
+ return ret;
};
function make_name(name) {
name = name.toString();
if (options.ascii_only)
@@ -2564,11 +2861,11 @@
// statement. This means that the parent is
// "stat", but it could also be a "seq" and
// we're the first in this "seq" and the
// parent is "stat", and so on. Messy stuff,
// but it worths the trouble.
- var a = slice($stack), self = a.pop(), p = a.pop();
+ var a = slice(w.stack()), self = a.pop(), p = a.pop();
while (p) {
if (p[0] == "stat") return true;
if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) {
self = p;
@@ -2594,20 +2891,22 @@
str.substr(str.indexOf(".")));
}
return best_of(a);
};
- var generators = {
+ var w = ast_walker();
+ var make = w.walk;
+ return w.with_walkers({
"string": encode_string,
"num": make_num,
"name": make_name,
"toplevel": function(statements) {
return make_block_statements(statements)
.join(newline + newline);
},
"splice": function(statements) {
- var parent = $stack[$stack.length - 2][0];
+ var parent = w.parent();
if (HOP(SPLICE_NEEDS_BRACKETS, parent)) {
// we need block brackets in this case
return make_block.apply(this, arguments);
} else {
return MAP(make_block_statements(statements, true),
@@ -2743,10 +3042,14 @@
if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
!(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
right = "(" + right + ")";
}
+ else if (!beautify && options.inline_script && (operator == "<" || operator == "<<")
+ && rvalue[0] == "regexp" && /^script/i.test(rvalue[1])) {
+ right = " " + right;
+ }
return add_spaces([ left, operator, right ]);
},
"unary-prefix": function(operator, expr) {
var val = make(expr);
if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
@@ -2773,11 +3076,11 @@
if (p.length == 3) {
// getter/setter. The name is in p[0], the arg.list in p[1][2], the
// body in p[1][3] and type ("get" / "set") in p[2].
return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
}
- var key = p[0], val = make(p[1]);
+ var key = p[0], val = parenthesize(p[1], "seq");
if (options.quote_keys) {
key = encode_string(key);
} else if ((typeof key == "number" || !beautify && +key + "" == key)
&& parseFloat(key) >= 0) {
key = make_num(+key);
@@ -2813,11 +3116,11 @@
return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
},
"atom": function(name) {
return make_name(name);
}
- };
+ }, function(){ return make(ast) });
// The squeezer replaces "block"-s that contain only a single
// statement with the statement itself; technically, the AST
// is correct, but this can create problems when we output an
// IF having an ELSE clause where the THEN clause ends in an
@@ -2828,11 +3131,11 @@
if (th[0] == "do") {
// https://github.com/mishoo/UglifyJS/issues/#issue/57
// IE croaks with "syntax error" on code like this:
// if (foo) do ... while(cond); else ...
// we need block brackets around do/while
- return make([ "block", [ th ]]);
+ return make_block([ th ]);
}
var b = th;
while (true) {
var type = b[0];
if (type == "if") {
@@ -2855,24 +3158,35 @@
}
out += "(" + add_commas(MAP(args, make_name)) + ")";
return add_spaces([ out, make_block(body) ]);
};
+ function must_has_semicolon(node) {
+ switch (node[0]) {
+ case "with":
+ case "while":
+ return empty(node[2]); // `with' or `while' with empty body?
+ case "for":
+ case "for-in":
+ return empty(node[4]); // `for' with empty body?
+ case "if":
+ if (empty(node[2]) && !node[3]) return true; // `if' with empty `then' and no `else'
+ if (node[3]) {
+ if (empty(node[3])) return true; // `else' present but empty
+ return must_has_semicolon(node[3]); // dive into the `else' branch
+ }
+ return must_has_semicolon(node[2]); // dive into the `then' branch
+ }
+ };
+
function make_block_statements(statements, noindent) {
for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
var stat = statements[i];
var code = make(stat);
if (code != ";") {
- if (!beautify && i == last) {
- if ((stat[0] == "while" && empty(stat[2])) ||
- (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) ||
- (stat[0] == "if" && empty(stat[2]) && !stat[3]) ||
- (stat[0] == "if" && stat[3] && empty(stat[3]))) {
- code = code.replace(/;*\s*$/, ";");
- } else {
- code = code.replace(/;+\s*$/, "");
- }
+ if (!beautify && i == last && !must_has_semicolon(stat)) {
+ code = code.replace(/;+\s*$/, "");
}
a.push(code);
}
}
return noindent ? a : MAP(a, indent);
@@ -2908,24 +3222,10 @@
if (val != null)
name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
return name;
};
- var $stack = [];
-
- function make(node) {
- var type = node[0];
- var gen = generators[type];
- if (!gen)
- throw new Error("Can't find generator for \"" + type + "\"");
- $stack.push(node);
- var ret = gen.apply(type, node.slice(1));
- $stack.pop();
- return ret;
- };
-
- return make(ast);
};
function split_lines(code, max_line_length) {
var splits = [ 0 ];
jsp.parse(function(){
@@ -3006,26 +3306,45 @@
var MAP;
(function(){
MAP = function(a, f, o) {
- var ret = [];
- for (var i = 0; i < a.length; ++i) {
+ var ret = [], top = [], i;
+ function doit() {
var val = f.call(o, a[i], i);
- if (val instanceof AtTop) ret.unshift(val.v);
- else ret.push(val);
- }
- return ret;
+ if (val instanceof AtTop) {
+ val = val.v;
+ if (val instanceof Splice) {
+ top.push.apply(top, val.v);
+ } else {
+ top.push(val);
+ }
+ }
+ else if (val != skip) {
+ if (val instanceof Splice) {
+ ret.push.apply(ret, val.v);
+ } else {
+ ret.push(val);
+ }
+ }
+ };
+ if (a instanceof Array) for (i = 0; i < a.length; ++i) doit();
+ else for (i in a) if (HOP(a, i)) doit();
+ return top.concat(ret);
};
MAP.at_top = function(val) { return new AtTop(val) };
+ MAP.splice = function(val) { return new Splice(val) };
+ var skip = MAP.skip = {};
function AtTop(val) { this.v = val };
+ function Splice(val) { this.v = val };
})();
/* -----[ Exports ]----- */
exports.ast_walker = ast_walker;
exports.ast_mangle = ast_mangle;
exports.ast_squeeze = ast_squeeze;
+exports.ast_lift_variables = ast_lift_variables;
exports.gen_code = gen_code;
exports.ast_add_scope = ast_add_scope;
exports.set_logger = function(logger) { warn = logger };
exports.make_string = make_string;
exports.split_lines = split_lines;