diff --git a/.gitignore b/.gitignore index 94fceeb2..6000c6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +/node_modules/ +/npm-debug.log tmp/ -node_modules/ diff --git a/.npmignore b/.npmignore deleted file mode 100644 index efab07fb..00000000 --- a/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -test -.travis.yml diff --git a/.travis.yml b/.travis.yml index 82460de5..4e046b8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ language: node_js before_install: "npm install -g npm" node_js: - - "0.10" + - "iojs" + - "0.12" - "0.11" + - "0.10" +matrix: + fast_finish: true +sudo: false diff --git a/bin/uglifyjs b/bin/uglifyjs index df7b7832..8851660f 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -132,24 +132,24 @@ normalize(ARGS); if (ARGS.noerr) { UglifyJS.DefaultsError.croak = function(msg, defs) { - sys.error("WARN: " + msg); + print_error("WARN: " + msg); }; } if (ARGS.version || ARGS.V) { var json = require("../package.json"); - sys.puts(json.name + ' ' + json.version); + print(json.name + ' ' + json.version); process.exit(0); } if (ARGS.ast_help) { var desc = UglifyJS.describe_ast(); - sys.puts(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2)); + print(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2)); process.exit(0); } if (ARGS.h || ARGS.help) { - sys.puts(yargs.help()); + print(yargs.help()); process.exit(0); } @@ -221,7 +221,7 @@ if (ARGS.comments != null) { try { OUTPUT_OPTIONS.comments = new RegExp(ARGS.comments.substr(1, regex_pos - 1), ARGS.comments.substr(regex_pos + 1)); } catch (e) { - sys.error("ERROR: Invalid --comments: " + e.message); + print_error("ERROR: Invalid --comments: " + e.message); } } else if (ARGS.comments == "all") { OUTPUT_OPTIONS.comments = true; @@ -241,7 +241,7 @@ var files = ARGS._.slice(); if (ARGS.self) { if (files.length > 0) { - sys.error("WARN: Ignoring input files since --self was passed"); + print_error("WARN: Ignoring input files since --self was passed"); } files = UglifyJS.FILES; if (!ARGS.wrap) ARGS.wrap = "UglifyJS"; @@ -253,7 +253,7 @@ var ORIG_MAP = ARGS.in_source_map; if (ORIG_MAP) { ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP)); if (files.length == 0) { - sys.error("INFO: Using file from the input source map: " + ORIG_MAP.file); + print_error("INFO: Using file from the input source map: " + ORIG_MAP.file); files = [ ORIG_MAP.file ]; } if (ARGS.source_map_root == null) { @@ -266,12 +266,12 @@ if (files.length == 0) { } if (files.indexOf("-") >= 0 && ARGS.source_map) { - sys.error("ERROR: Source map doesn't work with input from STDIN"); + print_error("ERROR: Source map doesn't work with input from STDIN"); process.exit(1); } if (files.filter(function(el){ return el == "-" }).length > 1) { - sys.error("ERROR: Can read a single file from STDIN (two or more dashes specified)"); + print_error("ERROR: Can read a single file from STDIN (two or more dashes specified)"); process.exit(1); } @@ -294,9 +294,9 @@ try { var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS); } catch(ex) { if (ex instanceof UglifyJS.DefaultsError) { - sys.error(ex.msg); - sys.error("Supported options:"); - sys.error(sys.inspect(ex.defs)); + print_error(ex.msg); + print_error("Supported options:"); + print_error(sys.inspect(ex.defs)); process.exit(1); } } @@ -304,7 +304,7 @@ try { async.eachLimit(files, 1, function (file, cb) { read_whole_file(file, function (err, code) { if (err) { - sys.error("ERROR: can't read file: " + file); + print_error("ERROR: can't read file: " + file); process.exit(1); } if (ARGS.p != null) { @@ -341,9 +341,9 @@ async.eachLimit(files, 1, function (file, cb) { }); } catch(ex) { if (ex instanceof UglifyJS.JS_Parse_Error) { - sys.error("Parse error at " + file + ":" + ex.line + "," + ex.col); - sys.error(ex.message); - sys.error(ex.stack); + print_error("Parse error at " + file + ":" + ex.line + "," + ex.col); + print_error(ex.message); + print_error(ex.stack); process.exit(1); } throw ex; @@ -372,12 +372,13 @@ async.eachLimit(files, 1, function (file, cb) { TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list); } - if (ARGS.mangle_props) (function(){ + if (ARGS.mangle_props || ARGS.name_cache) (function(){ var reserved = RESERVED ? RESERVED.props : null; var cache = readNameCache("props"); TOPLEVEL = UglifyJS.mangle_properties(TOPLEVEL, { - reserved: reserved, - cache: cache + reserved : reserved, + cache : cache, + only_cache : !ARGS.mangle_props }); writeNameCache("props", cache); })(); @@ -443,15 +444,15 @@ async.eachLimit(files, 1, function (file, cb) { if (OUTPUT_FILE) { fs.writeFileSync(OUTPUT_FILE, output, "utf8"); } else { - sys.print(output); + print(output); } if (ARGS.stats) { - sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", { + print_error(UglifyJS.string_template("Timing information (compressed {count} files):", { count: files.length })); for (var i in STATS) if (STATS.hasOwnProperty(i)) { - sys.error(UglifyJS.string_template("- {name}: {time}s", { + print_error(UglifyJS.string_template("- {name}: {time}s", { name: i, time: (STATS[i] / 1000).toFixed(3) })); @@ -478,7 +479,7 @@ function getOptions(x, constants) { ast = UglifyJS.parse(x, { expression: true }); } catch(ex) { if (ex instanceof UglifyJS.JS_Parse_Error) { - sys.error("Error parsing arguments in: " + x); + print_error("Error parsing arguments in: " + x); process.exit(1); } } @@ -497,8 +498,8 @@ function getOptions(x, constants) { ret[name] = true; return true; // no descend } - sys.error(node.TYPE) - sys.error("Error parsing arguments in: " + x); + print_error(node.TYPE) + print_error("Error parsing arguments in: " + x); process.exit(1); })); } @@ -530,3 +531,11 @@ function time_it(name, cont) { } return ret; } + +function print_error(msg) { + console.error("%s", msg); +} + +function print(txt) { + console.log("%s", txt); +} diff --git a/lib/compress.js b/lib/compress.js index fa89c322..530e7c2f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1168,12 +1168,12 @@ merge(Compressor.prototype, { return make_node(AST_EmptyStatement, node); } if (def.length == 0) { - return side_effects; + return in_list ? MAP.splice(side_effects.body) : side_effects; } node.definitions = def; if (side_effects) { side_effects.body.unshift(node); - node = side_effects; + return in_list ? MAP.splice(side_effects.body) : side_effects; } return node; } @@ -2272,14 +2272,6 @@ merge(Compressor.prototype, { }); }); - OPT(AST_NaN, function (self, compressor) { - return make_node(AST_Binary, self, { - operator : '/', - left : make_node(AST_Number, self, {value: 0}), - right : make_node(AST_Number, self, {value: 0}) - }); - }); - OPT(AST_Undefined, function(self, compressor){ if (compressor.option("unsafe")) { var scope = compressor.find_parent(AST_Scope); @@ -2364,6 +2356,7 @@ merge(Compressor.prototype, { if (consequent instanceof AST_Call && alternative.TYPE === consequent.TYPE && consequent.args.length == alternative.args.length + && !consequent.expression.has_side_effects(compressor) && consequent.expression.equivalent_to(alternative.expression)) { if (consequent.args.length == 0) { return make_node(AST_Seq, self, { diff --git a/lib/output.js b/lib/output.js index 8271a6f7..cb743bd6 100644 --- a/lib/output.js +++ b/lib/output.js @@ -176,7 +176,6 @@ function OutputStream(options) { might_need_space = false; } might_need_semicolon = false; - maybe_newline(); } if (!options.beautify && options.preserve_line && stack[stack.length - 1]) { diff --git a/lib/parse.js b/lib/parse.js index 0323d2cc..f1874b30 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -110,7 +110,7 @@ var OPERATORS = makePredicate([ "||" ]); -var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000")); +var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\uFEFF")); var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,.;:")); @@ -215,7 +215,7 @@ var EX_EOF = {}; function tokenizer($TEXT, filename, html5_comments) { var S = { - text : $TEXT.replace(/\uFEFF/g, ''), + text : $TEXT, filename : filename, pos : 0, tokpos : 0, @@ -356,8 +356,13 @@ function tokenizer($TEXT, filename, html5_comments) { case 120 : return String.fromCharCode(hex_bytes(2)); // \x case 117 : return String.fromCharCode(hex_bytes(4)); // \u case 10 : return ""; // newline - default : return ch; + case 13 : // \r + if (peek() == "\n") { // DOS newline + next(true, in_string); + return ""; + } } + return ch; }; function hex_bytes(n) { @@ -374,7 +379,7 @@ function tokenizer($TEXT, filename, html5_comments) { var read_string = with_eof_error("Unterminated string constant", function(quote_char){ var quote = next(), ret = ""; for (;;) { - var ch = next(true); + var ch = next(true, true); if (ch == "\\") { // read OctalEscapeSequence (XXX: deprecated if "strict mode") // https://github.com/mishoo/UglifyJS/issues/178 diff --git a/lib/propmangle.js b/lib/propmangle.js index 46fb7521..0890eaa7 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -63,7 +63,8 @@ function find_builtins() { function mangle_properties(ast, options) { options = defaults(options, { reserved : null, - cache : null + cache : null, + only_cache : false }); var reserved = options.reserved; @@ -140,11 +141,15 @@ function mangle_properties(ast, options) { function can_mangle(name) { if (reserved.indexOf(name) >= 0) return false; + if (options.only_cache) { + return cache.props.has(name); + } if (/^[0-9.]+$/.test(name)) return false; return true; } function should_mangle(name) { + if (reserved.indexOf(name) >= 0) return false; return cache.props.has(name) || names_to_mangle.indexOf(name) >= 0; } diff --git a/package.json b/package.json index aaa4359d..27f8c857 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,51 @@ { "name": "uglify-js", "description": "JavaScript parser, mangler/compressor and beautifier toolkit", - "homepage": "http://lisperator.net/uglifyjs", - "main": "tools/node.js", - "version": "2.4.20", - "engines": { "node" : ">=0.4.0" }, - "maintainers": [{ - "name": "Mihai Bazon", - "email": "mihai.bazon@gmail.com", - "web": "http://lisperator.net/" - }], - "repository": { - "type": "git", - "url": "https://github.com/mishoo/UglifyJS2.git" - }, - "dependencies": { - "async" : "~0.2.6", - "source-map" : "0.1.34", - "yargs": "~3.5.4", - "uglify-to-browserify": "~1.0.0" - }, - "devDependencies": { - "acorn": "~0.6.0", - "escodegen": "~1.3.3", - "esfuzz": "~0.3.1", - "estraverse": "~1.5.1" - }, - "browserify": { - "transform": [ "uglify-to-browserify" ] - }, - "bin": { - "uglifyjs" : "bin/uglifyjs" - }, - "license": "BSD", - "scripts": {"test": "node test/run-tests.js"} + "homepage": "http://lisperator.net/uglifyjs", + "author": "Mihai Bazon (http://lisperator.net/)", + "license": "BSD", + "version": "2.4.23", + "engines": { + "node": ">=0.4.0" + }, + "maintainers": [ + "Mihai Bazon (http://lisperator.net/)" + ], + "repository": { + "type": "git", + "url": "https://github.com/mishoo/UglifyJS2.git" + }, + "bugs": { + "url": "https://github.com/mishoo/UglifyJS2/issues" + }, + "main": "tools/node.js", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "files": [ + "bin", + "lib", + "tools", + "LICENSE" + ], + "dependencies": { + "async": "~0.2.6", + "source-map": "0.1.34", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.5.4" + }, + "devDependencies": { + "acorn": "~0.6.0", + "escodegen": "~1.3.3", + "esfuzz": "~0.3.1", + "estraverse": "~1.5.1" + }, + "browserify": { + "transform": [ + "uglify-to-browserify" + ] + }, + "scripts": { + "test": "node test/run-tests.js" + } } diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 299097be..1b961812 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -153,6 +153,7 @@ cond_1: { conditionals: true }; input: { + var do_something; // if undeclared it's assumed to have side-effects if (some_condition()) { do_something(x); } else { @@ -160,6 +161,7 @@ cond_1: { } } expect: { + var do_something; do_something(some_condition() ? x : y); } } @@ -169,7 +171,7 @@ cond_2: { conditionals: true }; input: { - var x; + var x, FooBar; if (some_condition()) { x = new FooBar(1); } else { @@ -177,7 +179,7 @@ cond_2: { } } expect: { - var x; + var x, FooBar; x = new FooBar(some_condition() ? 1 : 2); } } @@ -187,6 +189,7 @@ cond_3: { conditionals: true }; input: { + var FooBar; if (some_condition()) { new FooBar(1); } else { @@ -194,6 +197,7 @@ cond_3: { } } expect: { + var FooBar; some_condition() ? new FooBar(1) : FooBar(2); } } @@ -203,6 +207,7 @@ cond_4: { conditionals: true }; input: { + var do_something; if (some_condition()) { do_something(); } else { @@ -210,6 +215,7 @@ cond_4: { } } expect: { + var do_something; some_condition(), do_something(); } } diff --git a/test/compress/issue-597.js b/test/compress/issue-597.js index 3c651a4c..f243223a 100644 --- a/test/compress/issue-597.js +++ b/test/compress/issue-597.js @@ -6,7 +6,7 @@ NaN_and_Infinity_must_have_parens: { } expect: { (1/0).toString(); - (0/0).toString(); + NaN.toString(); // transformation to 0/0 dropped } } diff --git a/test/run-tests.js b/test/run-tests.js index 94bf6ad9..215f6af8 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -84,7 +84,12 @@ function run_compress_tests() { warnings: false }); var cmp = new U.Compressor(options, true); - var expect = make_code(as_toplevel(test.expect), false); + var expect; + if (test.expect) { + expect = make_code(as_toplevel(test.expect), false); + } else { + expect = test.expect_exact; + } var input = as_toplevel(test.input); var input_code = make_code(test.input); var output = input.transform(cmp); @@ -150,7 +155,7 @@ function parse_test(file) { } if (node instanceof U.AST_LabeledStatement) { assert.ok( - node.label.name == "input" || node.label.name == "expect", + node.label.name == "input" || node.label.name == "expect" || node.label.name == "expect_exact", tmpl("Unsupported label {name} [{line},{col}]", { name: node.label.name, line: node.label.start.line, @@ -162,7 +167,16 @@ function parse_test(file) { if (stat.body.length == 1) stat = stat.body[0]; else if (stat.body.length == 0) stat = new U.AST_EmptyStatement(); } - test[node.label.name] = stat; + if (node.label.name === "expect_exact") { + if (!(stat.TYPE === "SimpleStatement" && stat.body.TYPE === "String")) { + throw new Error( + "The value of the expect_exact clause should be a string, " + + "like `expect_exact: \"some.exact.javascript;\"`"); + } + test[node.label.name] = stat.body.start.value + } else { + test[node.label.name] = stat; + } return true; } }); diff --git a/tools/node.js b/tools/node.js index 054a05c0..cbe49e39 100644 --- a/tools/node.js +++ b/tools/node.js @@ -1,10 +1,8 @@ var path = require("path"); var fs = require("fs"); var vm = require("vm"); -var sys = require("util"); var UglifyJS = vm.createContext({ - sys : sys, console : console, process : process, Buffer : Buffer, @@ -19,7 +17,7 @@ function load_global(file) { } catch(ex) { // XXX: in case of a syntax error, the message is kinda // useless. (no location information). - sys.debug("ERROR in file: " + file + " / " + ex); + console.log("ERROR in file: " + file + " / " + ex); process.exit(1); } }; @@ -42,7 +40,7 @@ var FILES = exports.FILES = [ FILES.forEach(load_global); UglifyJS.AST_Node.warn_function = function(txt) { - sys.error("WARN: " + txt); + console.error("WARN: %s", txt); }; // XXX: perhaps we shouldn't export everything but heck, I'm lazy.