lib/uglify.js in uglifier-1.0.3 vs lib/uglify.js in uglifier-1.0.4

- old
+ new

@@ -237,11 +237,11 @@ "&=", "&&", "||" ]); -var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\u000b\u200b")); +var WHITESPACE_CHARS = array_to_hash(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000")); var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); @@ -305,15 +305,11 @@ function JS_Parse_Error(message, line, col, pos) { this.message = message; this.line = line; this.col = col; this.pos = pos; - try { - ({})(); - } catch(ex) { - this.stack = ex.stack; - }; + this.stack = new Error().stack; }; JS_Parse_Error.prototype.toString = function() { return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack; }; @@ -343,16 +339,16 @@ comments_before : [] }; function peek() { return S.text.charAt(S.pos); }; - function next(signal_eof) { + function next(signal_eof, in_string) { var ch = S.text.charAt(S.pos++); if (signal_eof && !ch) throw EX_EOF; if (ch == "\n") { - S.newline_before = true; + S.newline_before = S.newline_before || !in_string; ++S.line; S.col = 0; } else { ++S.col; } @@ -445,12 +441,12 @@ } else { parse_error("Invalid syntax: " + num); } }; - function read_escaped_char() { - var ch = next(true); + function read_escaped_char(in_string) { + var ch = next(true, in_string); switch (ch) { case "n" : return "\n"; case "r" : return "\r"; case "t" : return "\t"; case "b" : return "\b"; @@ -494,11 +490,11 @@ 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 ch = read_escaped_char(true); } else if (ch == quote) break; ret += ch; } return token("string", ret); @@ -556,13 +552,13 @@ } } return name; }; - function read_regexp() { + function read_regexp(regexp) { return with_eof_error("Unterminated regular expression", function(){ - var prev_backslash = false, regexp = "", ch, in_class = false; + var prev_backslash = false, ch, in_class = false; while ((ch = next(true))) if (prev_backslash) { regexp += "\\" + ch; prev_backslash = false; } else if (ch == "[") { in_class = true; @@ -607,11 +603,11 @@ case "*": S.comments_before.push(read_multiline_comment()); S.regex_allowed = regex_allowed; return next_token(); } - return S.regex_allowed ? read_regexp() : read_operator("/"); + return S.regex_allowed ? read_regexp("") : read_operator("/"); }; function handle_dot() { next(); return is_digit(peek()) @@ -638,12 +634,12 @@ else throw ex; } }; function next_token(force_regexp) { - if (force_regexp) - return read_regexp(); + if (force_regexp != null) + return read_regexp(force_regexp); skip_whitespace(); start_token(); var ch = peek(); if (!ch) return token("eof"); if (is_digit(ch)) return read_num(); @@ -828,13 +824,13 @@ }; else return parser; }; var statement = maybe_embed_tokens(function() { - if (is("operator", "/")) { + if (is("operator", "/") || is("operator", "/=")) { S.peeked = null; - S.token = S.input(true); // force regexp + S.token = S.input(S.token.value.substr(1)); // force regexp } switch (S.token.type) { case "num": case "string": case "regexp": @@ -900,10 +896,12 @@ case "switch": return as("switch", parenthesised(), switch_block_()); case "throw": + if (S.token.nlb) + croak("Illegal newline after 'throw'"); return as("throw", prog1(expression, semicolon)); case "try": return try_(); @@ -1275,11 +1273,11 @@ return expr; }; function is_assignable(expr) { if (!exigent_mode) return true; - switch (expr[0]) { + switch (expr[0]+"") { case "dot": case "sub": case "new": case "call": return true; @@ -1349,11 +1347,11 @@ ret[a[i]] = true; return ret; }; function slice(a, start) { - return Array.prototype.slice.call(a, start == null ? 0 : start); + return Array.prototype.slice.call(a, start || 0); }; function characters(str) { return str.split(""); }; @@ -1455,11 +1453,11 @@ PRECEDENCE = jsp.PRECEDENCE, OPERATORS = jsp.OPERATORS; /* -----[ helper for AST traversal ]----- */ -function ast_walker(ast) { +function ast_walker() { function _vardefs(defs) { return [ this[0], MAP(defs, function(def){ var a = [ def[0] ]; if (def.length > 1) a[1] = walk(def[1]); @@ -1612,10 +1610,21 @@ } finally { stack.pop(); } }; + function dive(ast) { + if (ast == null) + return null; + try { + stack.push(ast); + return walkers[ast[0]].apply(ast, ast.slice(1)); + } finally { + stack.pop(); + } + }; + function with_walkers(walkers, cont){ var save = {}, i; for (i in walkers) if (HOP(walkers, i)) { save[i] = user[i]; user[i] = walkers[i]; @@ -1628,10 +1637,11 @@ return ret; }; return { walk: walk, + dive: dive, with_walkers: with_walkers, parent: function() { return stack[stack.length - 2]; // last one is current node }, stack: function() { @@ -2021,25 +2031,10 @@ (expr[0] == "seq" && boolean_expr(expr[expr.length - 1])) ); }; -function make_conditional(c, t, e) { - var make_real_conditional = function() { - 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 ]; - } - }; - // shortcut the conditional if the expression has a constant value - return when_constant(c, function(ast, val){ - warn_unreachable(val ? e : t); - return (val ? t : e); - }, make_real_conditional); -}; - function empty(b) { return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); }; function is_string(node) { @@ -2063,10 +2058,11 @@ case "name": case "atom": switch (expr[1]) { case "true": return true; case "false": return false; + case "null": return null; } break; case "unary-prefix": switch (expr[1]) { case "!": return !evaluate(expr[2]); @@ -2085,10 +2081,11 @@ case "&" : return evaluate(left) & evaluate(right); case "^" : return evaluate(left) ^ evaluate(right); case "+" : return evaluate(left) + evaluate(right); case "*" : return evaluate(left) * evaluate(right); case "/" : return evaluate(left) / evaluate(right); + case "%" : return evaluate(left) % evaluate(right); case "-" : return evaluate(left) - evaluate(right); case "<<" : return evaluate(left) << evaluate(right); case ">>" : return evaluate(left) >> evaluate(right); case ">>>" : return evaluate(left) >>> evaluate(right); case "==" : return evaluate(left) == evaluate(right); @@ -2363,12 +2360,12 @@ function ast_squeeze(ast, options) { options = defaults(options, { make_seqs : true, dead_code : true, - keep_comps : true, - no_warnings : false + no_warnings : false, + keep_comps : true }); var w = ast_walker(), walk = w.walk, scope; function negate(c) { @@ -2401,10 +2398,28 @@ break; } return not_c; }; + function make_conditional(c, t, e) { + var make_real_conditional = function() { + if (c[0] == "unary-prefix" && c[1] == "!") { + return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ]; + } else { + return e ? best_of( + [ "conditional", c, t, e ], + [ "conditional", negate(c), e, t ] + ) : [ "binary", "&&", c, t ]; + } + }; + // shortcut the conditional if the expression has a constant value + return when_constant(c, function(ast, val){ + warn_unreachable(val ? e : t); + return (val ? t : e); + }, make_real_conditional); + }; + function with_scope(s, cont) { var _scope = scope; scope = s; var ret = cont(); ret.scope = s; @@ -2540,15 +2555,17 @@ }; function make_if(c, t, e) { return when_constant(c, function(ast, val){ if (val) { + t = walk(t); warn_unreachable(e); - return t; + return t || [ "block" ]; } else { + e = walk(e); warn_unreachable(t); - return e; + return e || [ "block" ]; } }, function() { return make_real_if(c, t, e); }); }; @@ -2665,11 +2682,19 @@ }, "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() { - return this; + return function(){ + if(op != "==" && op != "!=") return; + var l = walk(left), r = walk(right); + if(l && l[0] == "unary-prefix" && l[1] == "!" && l[2][0] == "num") + left = ['num', +!l[2][1]]; + else if (r && r[0] == "unary-prefix" && r[1] == "!" && r[2][0] == "num") + right = ['num', +!r[2][1]]; + return ["binary", op, left, right]; + }() || this; }); }, "conditional": function(c, t, e) { return make_conditional(walk(c), walk(t), walk(e)); }, @@ -2694,25 +2719,22 @@ switch (name) { case "true": return [ "unary-prefix", "!", [ "num", 0 ]]; case "false": return [ "unary-prefix", "!", [ "num", 1 ]]; } }, - "new": function(ctor, args) { - if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) { - if (args.length != 1) { - return [ "array", args ]; - } else { - return [ "call", [ "name", "Array" ], args ]; - } + "while": _do_while, + "assign": function(op, lvalue, rvalue) { + lvalue = walk(lvalue); + rvalue = walk(rvalue); + var okOps = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; + if (op === true && lvalue[0] === "name" && rvalue[0] === "binary" && + ~okOps.indexOf(rvalue[1]) && rvalue[2][0] === "name" && + rvalue[2][1] === lvalue[1]) { + return [ this[0], rvalue[1], lvalue, rvalue[3] ] } - }, - "call": function(expr, args) { - if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) { - return [ "array", args ]; - } - }, - "while": _do_while + return [ this[0], op, lvalue, rvalue ]; + } }, function() { for (var i = 0; i < 2; ++i) { ast = prepare_ifs(ast); ast = ast_add_scope(ast); ast = walk(ast); @@ -2729,11 +2751,12 @@ "object", "string", "dot", "sub", "call", - "regexp" + "regexp", + "defun" ]); function make_string(str, ascii_only) { var dq = 0, sq = 0; str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029\0]/g, function(s){ @@ -2879,12 +2902,17 @@ }; function make_num(num) { var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m; if (Math.floor(num) === num) { - a.push("0x" + num.toString(16).toLowerCase(), // probably pointless - "0" + num.toString(8)); // same. + if (num >= 0) { + a.push("0x" + num.toString(16).toLowerCase(), // probably pointless + "0" + num.toString(8)); // same. + } else { + a.push("-0x" + (-num).toString(16).toLowerCase(), // probably pointless + "-0" + (-num).toString(8)); // same. + } if ((m = /^(.*?)(0+)$/.exec(num))) { a.push(m[1] + "e" + m[2].length); } } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) { a.push(m[2] + "e-" + (m[1].length + m[2].length), @@ -2931,11 +2959,13 @@ }, "throw": function(expr) { return add_spaces([ "throw", make(expr) ]) + ";"; }, "new": function(ctor, args) { - args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : ""; + args = args.length > 0 ? "(" + add_commas(MAP(args, function(expr){ + return parenthesize(expr, "seq"); + })) + ")" : ""; return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){ var w = ast_walker(), has_call = {}; try { w.with_walkers({ "call": function() { throw has_call }, @@ -2986,11 +3016,11 @@ out += "." + make_name(arguments[i++]); return out; }, "call": function(func, args) { var f = make(func); - if (needs_parens(func)) + if (f.charAt(0) != "(" && needs_parens(func)) f = "(" + f + ")"; return f + "(" + add_commas(MAP(args, function(expr){ return parenthesize(expr, "seq"); })) + ")"; }, @@ -3034,11 +3064,12 @@ var left = make(lvalue), right = make(rvalue); // XXX: I'm pretty sure other cases will bite here. // we need to be smarter. // adding parens all the time is the safest bet. if (member(lvalue[0], [ "assign", "conditional", "seq" ]) || - lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) { + lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]] || + lvalue[0] == "function" && needs_parens(this)) { left = "(" + left + ")"; } if (member(rvalue[0], [ "assign", "conditional", "seq" ]) || rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] && !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) { @@ -3067,13 +3098,14 @@ if (needs_parens(expr)) hash = "(" + hash + ")"; return hash + "[" + make(subscript) + "]"; }, "object": function(props) { + var obj_needs_parens = needs_parens(this); if (props.length == 0) - return "{}"; - return "{" + newline + with_indent(function(){ + return obj_needs_parens ? "({})" : "{}"; + var out = "{" + newline + with_indent(function(){ return MAP(props, function(p){ 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])); @@ -3090,18 +3122,19 @@ return indent(add_spaces(beautify && options.space_colon ? [ key, ":", val ] : [ key + ":", val ])); }).join("," + newline); }) + newline + indent("}"); + return obj_needs_parens ? "(" + out + ")" : out; }, "regexp": function(rx, mods) { return "/" + rx + "/" + mods; }, "array": function(elements) { if (elements.length == 0) return "[]"; - return add_spaces([ "[", add_commas(MAP(elements, function(el){ - if (!beautify && el[0] == "atom" && el[1] == "undefined") return ""; + return add_spaces([ "[", add_commas(MAP(elements, function(el, i){ + if (!beautify && el[0] == "atom" && el[1] == "undefined") return i === elements.length - 1 ? "," : ""; return parenthesize(el, "seq"); })), "]" ]); }, "stat": function(stmt) { return make(stmt).replace(/;*\s*$/, ";"); @@ -3126,10 +3159,11 @@ // IF having an ELSE clause where the THEN clause ends in an // IF *without* an ELSE block (then the outer ELSE would refer // to the inner IF). This function checks for this case and // adds the block brackets if needed. function make_then(th) { + if (th == null) return ";"; 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 @@ -3155,11 +3189,12 @@ var out = keyword || "function"; if (name) { out += " " + make_name(name); } out += "(" + add_commas(MAP(args, make_name)) + ")"; - return add_spaces([ out, make_block(body) ]); + out = add_spaces([ out, make_block(body) ]); + return needs_parens(this) ? "(" + out + ")" : out; }; function must_has_semicolon(node) { switch (node[0]) { case "with": @@ -3355,23 +3390,52 @@ exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more; }, "squeeze-more": function(exports, require, module) {var jsp = require("./parse-js"), pro = require("./process"), slice = jsp.slice, member = jsp.member, + curry = jsp.curry, + MAP = pro.MAP, PRECEDENCE = jsp.PRECEDENCE, OPERATORS = jsp.OPERATORS; function ast_squeeze_more(ast) { - var w = pro.ast_walker(), walk = w.walk; + var w = pro.ast_walker(), walk = w.walk, scope; + function with_scope(s, cont) { + var save = scope, ret; + scope = s; + ret = cont(); + scope = save; + return ret; + }; + function _lambda(name, args, body) { + return [ this[0], name, args, with_scope(body.scope, curry(MAP, body, walk)) ]; + }; return w.with_walkers({ + "toplevel": function(body) { + return [ this[0], with_scope(this.scope, curry(MAP, body, walk)) ]; + }, + "function": _lambda, + "defun": _lambda, + "new": function(ctor, args) { + if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) { + if (args.length != 1) { + return [ "array", args ]; + } else { + return walk([ "call", [ "name", "Array" ], args ]); + } + } + }, "call": function(expr, args) { if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) { // foo.toString() ==> foo+"" return [ "binary", "+", expr[1], [ "string", "" ]]; } + if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) { + return [ "array", args ]; + } } }, function() { - return walk(ast); + return walk(pro.ast_add_scope(ast)); }); }; exports.ast_squeeze_more = ast_squeeze_more; }});