diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..31ba69d322d --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +- Bug report or feature request? +- `uglify-js` version (`uglifyjs -V`) +- JavaScript input - ideally as small as possible. +- The `uglifyjs` CLI command executed or `minify()` options used. +- An example of JavaScript output produced and/or the error or warning. + +Note: the release version of `uglify-js` only supports ES5. Those wishing to minify ES6 should use the experimental [`harmony`](https://github.com/mishoo/UglifyJS2#harmony) branch. diff --git a/.travis.yml b/.travis.yml index b91a80e9b84..b2aef3dc15b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,13 @@ language: node_js before_install: "npm install -g npm" node_js: - - "0.12" - "0.10" + - "0.12" - "4" - "6" + - "7" +env: + - UGLIFYJS_TEST_ALL=1 matrix: fast_finish: true sudo: false diff --git a/README.md b/README.md index 96014f4c17f..e94e0c7b3bf 100644 --- a/README.md +++ b/README.md @@ -391,11 +391,11 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `cascade` -- small optimization for sequences, transform `x, x` into `x` and `x = something(), x` into `x = something()` -- `collapse_vars` -- default `false`. Collapse single-use `var` and `const` - definitions when possible. +- `collapse_vars` -- Collapse single-use `var` and `const` definitions + when possible. -- `reduce_vars` -- default `false`. Improve optimization on variables assigned - with and used as constant values. +- `reduce_vars` -- Improve optimization on variables assigned with and + used as constant values. - `warnings` -- display warnings when dropping unreachable code or unused declarations etc. diff --git a/lib/compress.js b/lib/compress.js index 1d1e902041e..ec4ea67af16 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1943,6 +1943,7 @@ merge(Compressor.prototype, { } if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { var def = node.definitions.filter(function(def){ + if (def.value) def.value = def.value.transform(tt); if (def.is_destructuring()) return true; if (def.name.definition().id in in_use_ids) return true; if (!drop_vars && def.name.definition().global) return true; @@ -2006,18 +2007,16 @@ merge(Compressor.prototype, { } return node; } - if (assign_as_unused) { - var n = node; - while (n instanceof AST_Assign - && n.operator == "=" - && n.left instanceof AST_SymbolRef) { - var def = n.left.definition(); - if (def.id in in_use_ids - || !drop_vars && def.global - || self.variables.get(def.name) !== def) break; - n = n.right; + if (assign_as_unused + && node instanceof AST_Assign + && node.operator == "=" + && node.left instanceof AST_SymbolRef) { + var def = node.left.definition(); + if (!(def.id in in_use_ids) + && (drop_vars || !def.global) + && self.variables.get(def.name) === def) { + return maintain_this_binding(tt.parent(), node, node.right.transform(tt)); } - if (n !== node) return n; } if (node instanceof AST_For) { descend(node, this); @@ -2218,7 +2217,8 @@ merge(Compressor.prototype, { def(AST_This, return_null); def(AST_Call, function(compressor, first_in_statement){ if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) { - if (this.expression instanceof AST_Function) { + if (this.expression instanceof AST_Function + && (!this.expression.name || !this.expression.name.definition().references.length)) { var node = this.clone(); node.expression = node.expression.process_expression(false); return node; @@ -2742,9 +2742,6 @@ merge(Compressor.prototype, { if (compressor.option("unused") && def.references.length == 1 && compressor.find_parent(AST_Scope) === def.scope) { - if (!compressor.option("keep_fnames")) { - exp.name = null; - } self.expression = exp; } } diff --git a/lib/output.js b/lib/output.js index 7c4cc7abd6e..7e846f7d83b 100644 --- a/lib/output.js +++ b/lib/output.js @@ -882,16 +882,14 @@ function OutputStream(options) { DEFPRINT(AST_Do, function(self, output){ output.print("do"); output.space(); - self._do_print_body(output); + make_block(self.body, output); output.space(); output.print("while"); output.space(); output.with_parens(function(){ self.condition.print(output); }); - if (output.option("beautify") && output.option("screw_ie8")) { - output.semicolon(); - } + output.semicolon(); }); DEFPRINT(AST_While, function(self, output){ output.print("while"); @@ -1085,10 +1083,10 @@ function OutputStream(options) { /* -----[ if ]----- */ function make_then(self, output) { - if (output.option("bracketize")) { - make_block(self.body, output); - return; - } + var b = self.body; + if (output.option("bracketize") + || !output.option("screw_ie8") && b instanceof AST_Do) + return make_block(b, output); // 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 @@ -1096,9 +1094,7 @@ function OutputStream(options) { // 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. - if (!self.body) - return output.force_semicolon(); - var b = self.body; + if (!b) return output.force_semicolon(); while (true) { if (b instanceof AST_If) { if (!b.alternative) { @@ -1668,15 +1664,7 @@ function OutputStream(options) { function force_statement(stat, output) { if (output.option("bracketize")) { - if (!stat || stat instanceof AST_EmptyStatement) - output.print("{}"); - else if (stat instanceof AST_BlockStatement) - stat.print(output); - else output.with_block(function(){ - output.indent(); - stat.print(output); - output.newline(); - }); + make_block(stat, output); } else { if (!stat || stat instanceof AST_EmptyStatement) output.force_semicolon(); @@ -1725,11 +1713,11 @@ function OutputStream(options) { }; function make_block(stmt, output) { - if (stmt instanceof AST_BlockStatement) { + if (!stmt || stmt instanceof AST_EmptyStatement) + output.print("{}"); + else if (stmt instanceof AST_BlockStatement) stmt.print(output); - return; - } - output.with_block(function(){ + else output.with_block(function(){ output.indent(); stmt.print(output); output.newline(); diff --git a/package.json b/package.json index 086b6910d78..c56d8f86f2b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "2.8.8", + "version": "2.8.10", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index fd1c2fb3368..e690bb3942d 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -781,3 +781,64 @@ issue_1539: { } } } + +vardef_value: { + options = { + keep_fnames: false, + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g(){ + return x(); + } + var a = g(); + return a(42); + } + } + expect: { + function f() { + var a = function(){ + return x(); + }(); + return a(42); + } + } +} + +assign_binding: { + options = { + cascade: true, + side_effects: true, + unused: true, + } + input: { + function f() { + var a; + a = f.g, a(); + } + } + expect: { + function f() { + (0, f.g)(); + } + } +} + +assign_chain: { + options = { + unused: true, + } + input: { + function f() { + var a, b; + x = a = y = b = 42; + } + } + expect: { + function f() { + x = y = 42; + } + } +} diff --git a/test/compress/issue-1569.js b/test/compress/issue-1569.js new file mode 100644 index 00000000000..5f0bca34b88 --- /dev/null +++ b/test/compress/issue-1569.js @@ -0,0 +1,19 @@ +inner_reference: { + options = { + side_effects: true, + } + input: { + !function f(a) { + return a && f(a - 1) + a; + }(42); + !function g(a) { + return a; + }(42); + } + expect: { + !function f(a) { + return a && f(a - 1) + a; + }(42); + !void 0; + } +} diff --git a/test/compress/loops.js b/test/compress/loops.js index 2d04e235b5d..df3011cda22 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -257,7 +257,7 @@ issue_186: { else bar(); } - expect_exact: 'var x=3;if(foo())do do alert(x);while(--x)while(x)else bar();' + expect_exact: 'var x=3;if(foo())do{do{alert(x)}while(--x)}while(x);else bar();' } issue_186_ie8: { @@ -276,7 +276,7 @@ issue_186_ie8: { else bar(); } - expect_exact: 'var x=3;if(foo())do do alert(x);while(--x)while(x)else bar();' + expect_exact: 'var x=3;if(foo()){do{do{alert(x)}while(--x)}while(x)}else bar();' } issue_186_beautify: { @@ -295,7 +295,7 @@ issue_186_beautify: { else bar(); } - expect_exact: 'var x = 3;\n\nif (foo()) do do alert(x); while (--x); while (x); else bar();' + expect_exact: 'var x = 3;\n\nif (foo()) do {\n do {\n alert(x);\n } while (--x);\n} while (x); else bar();' } issue_186_beautify_ie8: { @@ -314,7 +314,7 @@ issue_186_beautify_ie8: { else bar(); } - expect_exact: 'var x = 3;\n\nif (foo()) do do alert(x); while (--x) while (x) else bar();' + expect_exact: 'var x = 3;\n\nif (foo()) {\n do {\n do {\n alert(x);\n } while (--x);\n } while (x);\n} else bar();' } issue_186_bracketize: { @@ -394,5 +394,5 @@ issue_186_beautify_bracketize_ie8: { else bar(); } - expect_exact: 'var x = 3;\n\nif (foo()) {\n do {\n do {\n alert(x);\n } while (--x)\n } while (x)\n} else {\n bar();\n}' + expect_exact: 'var x = 3;\n\nif (foo()) {\n do {\n do {\n alert(x);\n } while (--x);\n } while (x);\n} else {\n bar();\n}' } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 53e281522e3..10dc9d98cc8 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1122,3 +1122,25 @@ defun_label: { }(); } } + +double_reference: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + var g = function g() { + g(); + }; + g(); + } + } + expect: { + function f() { + (function g() { + g(); + })(); + } + } +} diff --git a/test/mocha/benchmark.js b/test/mocha/benchmark.js deleted file mode 100644 index c2c7c02cc25..00000000000 --- a/test/mocha/benchmark.js +++ /dev/null @@ -1,24 +0,0 @@ -var assert = require("assert"); -var exec = require("child_process").exec; - -describe("test/benchmark.js", function() { - this.timeout(120000); - var command = '"' + process.argv[0] + '" test/benchmark.js '; - [ - "-b", - "-b bracketize", - "-m", - "-mc passes=3", - "-mc passes=3,toplevel", - "-mc passes=3,unsafe", - "-mc keep_fargs=false,passes=3", - "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto", - ].forEach(function(args) { - it("Should pass with options " + args, function(done) { - exec(command + args, function(err) { - if (err) throw err; - done(); - }); - }); - }); -}); diff --git a/test/mocha/release.js b/test/mocha/release.js new file mode 100644 index 00000000000..3b2d9a72f7c --- /dev/null +++ b/test/mocha/release.js @@ -0,0 +1,54 @@ +var assert = require("assert"); +var spawn = require("child_process").spawn; + +if (!process.env.UGLIFYJS_TEST_ALL) return; + +function run(command, args, done) { + var id = setInterval(function() { + process.stdout.write("\0"); + }, 5 * 60 * 1000); + spawn(command, args, { + stdio: "ignore" + }).on("exit", function(code) { + clearInterval(id); + assert.strictEqual(code, 0); + done(); + }); +} + +describe("test/benchmark.js", function() { + this.timeout(5 * 60 * 1000); + [ + "-b", + "-b bracketize", + "-m", + "-mc passes=3", + "-mc passes=3,toplevel", + "-mc passes=3,unsafe", + "-mc keep_fargs=false,passes=3", + "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto", + ].forEach(function(options) { + it("Should pass with options " + options, function(done) { + var args = options.split(/ /); + args.unshift("test/benchmark.js"); + run(process.argv[0], args, done); + }); + }); +}); + +describe("test/jetstream.js", function() { + this.timeout(20 * 60 * 1000); + it("Should install phantomjs-prebuilt", function(done) { + run("npm", ["install", "phantomjs-prebuilt@2.1.14"], done); + }); + [ + "-mc warnings=false", + "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto,warnings=false", + ].forEach(function(options) { + it("Should pass with options " + options, function(done) { + var args = options.split(/ /); + args.unshift("test/jetstream.js"); + run(process.argv[0], args, done); + }); + }); +});