From f4b5d3fc51cfb2b62f182e88b85386b4456c080d Mon Sep 17 00:00:00 2001 From: John Resig Date: Mon, 2 May 2011 16:25:36 -0400 Subject: [PATCH] Update the UglifyJS code. --- build/lib/parse-js.js | 38 ++++----- build/lib/process.js | 96 +++++++++++++++++------ build/uglify.js | 176 +++++++++++++++++++++++++++++++----------- 3 files changed, 221 insertions(+), 89 deletions(-) diff --git a/build/lib/parse-js.js b/build/lib/parse-js.js index 9f90dfee..8edecb73 100644 --- a/build/lib/parse-js.js +++ b/build/lib/parse-js.js @@ -751,14 +751,17 @@ function parse($TEXT, exigent_mode, embed_tokens) { return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end); }; - var statement = embed_tokens ? function() { - var start = S.token; - var ast = $statement.apply(this, arguments); - ast[0] = add_tokens(ast[0], start, prev()); - return ast; - } : $statement; + function maybe_embed_tokens(parser) { + if (embed_tokens) return function() { + var start = S.token; + var ast = parser.apply(this, arguments); + ast[0] = add_tokens(ast[0], start, prev()); + return ast; + }; + else return parser; + }; - function $statement() { + var statement = maybe_embed_tokens(function() { if (is("operator", "/")) { S.peeked = null; S.token = S.input(true); // force regexp @@ -852,7 +855,7 @@ function parse($TEXT, exigent_mode, embed_tokens) { unexpected(); } } - }; + }); function labeled_statement(label) { S.labels.push(label); @@ -910,14 +913,7 @@ function parse($TEXT, exigent_mode, embed_tokens) { return as("for-in", init, lhs, obj, in_loop(statement)); }; - var function_ = embed_tokens ? function() { - var start = prev(); - var ast = $function_.apply(this, arguments); - ast[0] = add_tokens(ast[0], start, prev()); - return ast; - } : $function_; - - function $function_(in_statement) { + var function_ = maybe_embed_tokens(function(in_statement) { var name = is("name") ? prog1(S.token.value, next) : null; if (in_statement && !name) unexpected(); @@ -945,7 +941,7 @@ function parse($TEXT, exigent_mode, embed_tokens) { S.in_loop = loop; return a; })()); - }; + }); function if_() { var cond = parenthesised(), body = statement(), belse; @@ -1053,7 +1049,7 @@ function parse($TEXT, exigent_mode, embed_tokens) { return subscripts(as("new", newexp, args), true); }; - function expr_atom(allow_calls) { + var expr_atom = maybe_embed_tokens(function(allow_calls) { if (is("operator", "new")) { next(); return new_(); @@ -1088,7 +1084,7 @@ function parse($TEXT, exigent_mode, embed_tokens) { return subscripts(prog1(atom, next), allow_calls); } unexpected(); - }; + }); function expr_list(closing, allow_trailing_comma, allow_empty) { var first = true, a = []; @@ -1228,7 +1224,7 @@ function parse($TEXT, exigent_mode, embed_tokens) { return left; }; - function expression(commas, no_in) { + var expression = maybe_embed_tokens(function(commas, no_in) { if (arguments.length == 0) commas = true; var expr = maybe_assign(no_in); @@ -1237,7 +1233,7 @@ function parse($TEXT, exigent_mode, embed_tokens) { return as("seq", expr, expression(true, no_in)); } return expr; - }; + }); function in_loop(cont) { try { diff --git a/build/lib/process.js b/build/lib/process.js index 09cbc2ad..3878c8d6 100644 --- a/build/lib/process.js +++ b/build/lib/process.js @@ -75,6 +75,12 @@ function ast_walker(ast) { return a; }) ]; }; + function _block(statements) { + var out = [ this[0] ]; + if (statements != null) + out.push(MAP(statements, walk)); + return out; + }; var walkers = { "string": function(str) { return [ this[0], str ]; @@ -88,12 +94,8 @@ function ast_walker(ast) { "toplevel": function(statements) { return [ this[0], MAP(statements, walk) ]; }, - "block": function(statements) { - var out = [ this[0] ]; - if (statements != null) - out.push(MAP(statements, walk)); - return out; - }, + "block": _block, + "splice": _block, "var": _vardefs, "const": _vardefs, "try": function(t, c, f) { @@ -377,7 +379,9 @@ function ast_add_scope(ast) { }; function _lambda(name, args, body) { - return [ this[0], this[0] == "defun" ? define(name) : name, args, with_new_scope(function(){ + 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 MAP(body, walk); })]; @@ -463,9 +467,22 @@ function ast_mangle(ast, options) { return scope.get_mangled(name, newMangle); }; + function get_define(name) { + // we always lookup a defined symbol for the current scope FIRST, so declared + // vars trump a DEFINE symbol, but if no such var is found, then match a DEFINE value + if (!scope.has(name)) { + if (HOP(options.defines, name)) { + return options.defines[name]; + } + } + return null; + }; + function _lambda(name, args, body) { - if (name) name = get_mangled(name); + var is_defun = this[0] == "defun"; + if (is_defun && name) name = get_mangled(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); }); @@ -507,7 +524,7 @@ function ast_mangle(ast, options) { "var": _vardefs, "const": _vardefs, "name": function(name) { - return [ this[0], get_mangled(name) ]; + return get_define(name) || [ this[0], get_mangled(name) ]; }, "try": function(t, c, f) { return [ this[0], @@ -583,11 +600,18 @@ function boolean_expr(expr) { }; 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 ]; + return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ]; } else { - return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ]; + 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) { @@ -676,6 +700,18 @@ var when_constant = (function(){ || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) { expr[1] = expr[1].substr(0, 2); } + else if (no && expr[0] == "binary" + && (expr[1] == "||" || expr[1] == "&&")) { + // the whole expression is not constant but the lval may be... + try { + var lval = evaluate(expr[2]); + expr = ((expr[1] == "&&" && (lval ? expr[3] : lval)) || + (expr[1] == "||" && (lval ? lval : expr[3])) || + expr); + } catch(ex2) { + // IGNORE... lval is not constant + } + } return no ? no.call(expr, expr) : null; } else throw ex; @@ -751,9 +787,14 @@ function ast_squeeze(ast, options) { }; function _lambda(name, args, body) { - return [ this[0], name, args, with_scope(body.scope, function(){ - return tighten(MAP(body, walk), "lambda"); - }) ]; + 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)) + name = null; + return ret; + }); + return [ this[0], name, args, body ]; }; // we get here for blocks that have been already transformed. @@ -959,13 +1000,7 @@ function ast_squeeze(ast, options) { return [ branch[0] ? walk(branch[0]) : null, block ]; }) ]; }, - "function": function() { - var ret = _lambda.apply(this, arguments); - if (ret[1] && !HOP(scope.refs, ret[1])) { - ret[1] = null; - } - return ret; - }, + "function": _lambda, "defun": _lambda, "block": function(body) { if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]); @@ -1067,6 +1102,8 @@ function to_ascii(str) { }); }; +var SPLICE_NEEDS_BRACKETS = jsp.array_to_hash([ "if", "while", "do", "for", "for-in", "with" ]); + function gen_code(ast, options) { options = defaults(options, { indent_start : 0, @@ -1197,6 +1234,19 @@ function gen_code(ast, options) { return make_block_statements(statements) .join(newline + newline); }, + "splice": function(statements) { + var parent = $stack[$stack.length - 2][0]; + 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), + function(line, i) { + // the first line is already indented + return i > 0 ? indent(line) : line; + }).join(newline); + } + }, "block": make_block, "var": function(defs) { return "var " + add_commas(MAP(defs, make_1vardef)) + ";"; @@ -1437,7 +1487,7 @@ function gen_code(ast, options) { return add_spaces([ out, make_block(body) ]); }; - function make_block_statements(statements) { + 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); @@ -1455,7 +1505,7 @@ function gen_code(ast, options) { a.push(code); } } - return MAP(a, indent); + return noindent ? a : MAP(a, indent); }; function make_switch_block(body) { diff --git a/build/uglify.js b/build/uglify.js index 943ddd80..aad18e8c 100644 --- a/build/uglify.js +++ b/build/uglify.js @@ -1,14 +1,10 @@ #! /usr/bin/env node -// -*- js2 -*- +// -*- js -*- global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util"); -var fs = require("fs"), - jsp = require("./lib/parse-js"), - pro = require("./lib/process"); - -pro.set_logger(function(msg){ - sys.debug(msg); -}); +var fs = require("fs"); +var jsp = require("./lib/parse-js"), + pro = require("./lib/process"); var options = { ast: false, @@ -17,13 +13,16 @@ var options = { squeeze: true, make_seqs: true, dead_code: true, - beautify: false, verbose: false, show_copyright: true, out_same_file: false, - extra: false, - unsafe: false, // XXX: extra & unsafe? but maybe we don't want both, so.... - beautify_options: { + max_line_length: 32 * 1024, + unsafe: false, + reserved_names: null, + defines: { }, + codegen_options: { + ascii_only: false, + beautify: false, indent_level: 4, indent_start: 0, quote_keys: false, @@ -40,15 +39,15 @@ out: while (args.length > 0) { switch (v) { case "-b": case "--beautify": - options.beautify = true; + options.codegen_options.beautify = true; break; case "-i": case "--indent": - options.beautify_options.indent_level = args.shift(); + options.codegen_options.indent_level = args.shift(); break; case "-q": case "--quote-keys": - options.beautify_options.quote_keys = true; + options.codegen_options.quote_keys = true; break; case "-mt": case "--mangle-toplevel": @@ -86,23 +85,109 @@ out: while (args.length > 0) { case "--ast": options.ast = true; break; - case "--extra": - options.extra = true; - break; case "--unsafe": options.unsafe = true; break; + case "--max-line-len": + options.max_line_length = parseInt(args.shift(), 10); + break; + case "--reserved-names": + options.reserved_names = args.shift().split(","); + break; + case "-d": + case "--define": + var defarg = args.shift(); + try { + var defsym = function(sym) { + // KEYWORDS_ATOM doesn't include NaN or Infinity - should we check + // for them too ?? We don't check reserved words and the like as the + // define values are only substituted AFTER parsing + if (jsp.KEYWORDS_ATOM.hasOwnProperty(sym)) { + throw "Don't define values for inbuilt constant '"+sym+"'"; + } + return sym; + }, + defval = function(v) { + if (v.match(/^"(.*)"$/) || v.match(/^'(.*)'$/)) { + return [ "string", RegExp.$1 ]; + } + else if (!isNaN(parseFloat(v))) { + return [ "num", parseFloat(v) ]; + } + else if (v.match(/^[a-z\$_][a-z\$_0-9]*$/i)) { + return [ "name", v ]; + } + else if (!v.match(/"/)) { + return [ "string", v ]; + } + else if (!v.match(/'/)) { + return [ "string", v ]; + } + throw "Can't understand the specified value: "+v; + }; + if (defarg.match(/^([a-z_\$][a-z_\$0-9]*)(=(.*))?$/i)) { + var sym = defsym(RegExp.$1), + val = RegExp.$2 ? defval(RegExp.$2.substr(1)) : [ 'name', 'true' ]; + options.defines[sym] = val; + } + else { + throw "The --define option expects SYMBOL[=value]"; + } + } catch(ex) { + sys.print("ERROR: In option --define "+defarg+"\n"+ex+"\n"); + process.exit(1); + } + break; + case "--define-from-module": + var defmodarg = args.shift(), + defmodule = require(defmodarg), + sym, + val; + for (sym in defmodule) { + if (defmodule.hasOwnProperty(sym)) { + options.defines[sym] = function(val) { + if (typeof val == "string") + return [ "string", val ]; + if (typeof val == "number") + return [ "num", val ]; + if (val === true) + return [ 'name', 'true' ]; + if (val === false) + return [ 'name', 'false' ]; + if (val === null) + return [ 'name', 'null' ]; + if (val === undefined) + return [ 'name', 'undefined' ]; + sys.print("ERROR: In option --define-from-module "+defmodarg+"\n"); + sys.print("ERROR: Unknown object type for: "+sym+"="+val+"\n"); + process.exit(1); + return null; + }(defmodule[sym]); + } + } + break; + case "--ascii": + options.codegen_options.ascii_only = true; + break; default: filename = v; break out; } } +if (options.verbose) { + pro.set_logger(function(msg){ + sys.debug(msg); + }); +} + +jsp.set_logger(function(msg){ + sys.debug(msg); +}); + if (filename) { fs.readFile(filename, "utf8", function(err, text){ - if (err) { - throw err; - } + if (err) throw err; output(squeeze_it(text)); }); } else { @@ -131,7 +216,9 @@ function output(text) { }); } out.write(text); - out.end(); + if (options.output !== true) { + out.end(); + } }; // --------- main ends here. @@ -152,36 +239,35 @@ function show_copyright(comments) { function squeeze_it(code) { var result = ""; if (options.show_copyright) { - var initial_comments = []; - // keep first comment - var tok = jsp.tokenizer(code, false), c; + var tok = jsp.tokenizer(code), c; c = tok(); - var prev = null; - while (/^comment/.test(c.type) && (!prev || prev == c.type)) { - initial_comments.push(c); - prev = c.type; - c = tok(); - } - result += show_copyright(initial_comments); + result += show_copyright(c.comments_before); } try { var ast = time_it("parse", function(){ return jsp.parse(code); }); - if (options.mangle) - ast = time_it("mangle", function(){ return pro.ast_mangle(ast, options.mangle_toplevel); }); - if (options.squeeze) - ast = time_it("squeeze", function(){ - ast = pro.ast_squeeze(ast, { - make_seqs : options.make_seqs, - dead_code : options.dead_code, - extra : options.extra - }); - if (options.unsafe) - ast = pro.ast_squeeze_more(ast); - return ast; + if (options.mangle) ast = time_it("mangle", function(){ + return pro.ast_mangle(ast, { + toplevel: options.mangle_toplevel, + defines: options.defines, + except: options.reserved_names }); + }); + if (options.squeeze) ast = time_it("squeeze", function(){ + ast = pro.ast_squeeze(ast, { + make_seqs : options.make_seqs, + dead_code : options.dead_code, + keep_comps : !options.unsafe + }); + if (options.unsafe) + ast = pro.ast_squeeze_more(ast); + return ast; + }); if (options.ast) return sys.inspect(ast, null, null); - result += time_it("generate", function(){ return pro.gen_code(ast, options.beautify && options.beautify_options) }); + result += time_it("generate", function(){ return pro.gen_code(ast, options.codegen_options) }); + if (!options.codegen_options.beautify && options.max_line_length) { + result = time_it("split", function(){ return pro.split_lines(result, options.max_line_length) }); + } return result; } catch(ex) { sys.debug(ex.stack);