From f330ab743aa65234ad8c7d7cea7a6f8cc71fc6a5 Mon Sep 17 00:00:00 2001 From: kzc Date: Fri, 2 Jun 2017 00:07:17 -0400 Subject: [PATCH 01/23] better document behavior of unsafe_Func (#2043) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b6b52a46..95d54464 100644 --- a/README.md +++ b/README.md @@ -572,7 +572,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u comparison are switching. Compression only works if both `comparisons` and `unsafe_comps` are both set to true. -- `unsafe_Func` (default: false) -- compress and mangle `Function(args, code)`. +- `unsafe_Func` (default: false) -- compress and mangle `Function(args, code)` + when both `args` and `code` are string literals. - `unsafe_math` (default: false) -- optimize numerical expressions like `2 * x * 3` into `6 * x`, which may give imprecise floating point results. From 17436218891781a5dc4fad8a9c23080c254aa695 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 3 Jun 2017 14:00:59 +0800 Subject: [PATCH 02/23] clean up `lib/parse.js` (#2047) - remove unused definitions - replace `array_to_hash()` --- lib/parse.js | 16 ++-------------- lib/utils.js | 7 ------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index a02fce8f..553568e1 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -115,8 +115,6 @@ var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,;:")); var PUNC_CHARS = makePredicate(characters("[]{}(),;:")); -var REGEXP_MODIFIERS = makePredicate(characters("gmsiy")); - /* -----[ Tokenizer ]----- */ // regexps adapted from http://xregexp.com/plugins/#unicode @@ -684,9 +682,7 @@ var PRECEDENCE = (function(a, ret){ {} ); -var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); - -var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); +var ATOMIC_START_TOKEN = makePredicate([ "atom", "num", "string", "regexp", "name" ]); /* -----[ Parser ]----- */ @@ -1222,7 +1218,6 @@ function parse($TEXT, options) { var tok = S.token, ret; switch (tok.type) { case "name": - case "keyword": ret = _make_symbol(AST_SymbolRef); break; case "num": @@ -1252,13 +1247,6 @@ function parse($TEXT, options) { break; } break; - case "operator": - if (!is_identifier_string(tok.value)) { - croak("Invalid getter/setter name: " + tok.value, - tok.line, tok.col, tok.pos); - } - ret = _make_symbol(AST_SymbolRef); - break; } next(); return ret; @@ -1292,7 +1280,7 @@ function parse($TEXT, options) { func.end = prev(); return subscripts(func, allow_calls); } - if (ATOMIC_START_TOKEN[S.token.type]) { + if (ATOMIC_START_TOKEN(S.token.type)) { return subscripts(as_atom_node(), allow_calls); } unexpected(); diff --git a/lib/utils.js b/lib/utils.js index e21fc5ec..76306919 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -43,13 +43,6 @@ "use strict"; -function array_to_hash(a) { - var ret = Object.create(null); - for (var i = 0; i < a.length; ++i) - ret[a[i]] = true; - return ret; -}; - function slice(a, start) { return Array.prototype.slice.call(a, start || 0); }; From 84634da4b5e581c36686082b1071fdf646775f49 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 3 Jun 2017 16:08:10 +0800 Subject: [PATCH 03/23] add tests for `AST_SymbolAccessor` (#2049) --- test/compress/properties.js | 87 ++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/test/compress/properties.js b/test/compress/properties.js index d49b9ea4..a83acc10 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -556,7 +556,75 @@ native_prototype: { } } -issue_2040: { +accessor_boolean: { + input: { + var a = 1; + var b = { + get true() { + return a; + }, + set false(c) { + a = c; + } + }; + console.log(b.true, b.false = 2, b.true); + } + expect_exact: 'var a=1;var b={get true(){return a},set false(c){a=c}};console.log(b.true,b.false=2,b.true);' + expect_stdout: "1 2 2" +} + +accessor_get_set: { + input: { + var a = 1; + var b = { + get set() { + return a; + }, + set get(c) { + a = c; + } + }; + console.log(b.set, b.get = 2, b.set); + } + expect_exact: 'var a=1;var b={get set(){return a},set get(c){a=c}};console.log(b.set,b.get=2,b.set);' + expect_stdout: "1 2 2" +} + +accessor_null_undefined: { + input: { + var a = 1; + var b = { + get null() { + return a; + }, + set undefined(c) { + a = c; + } + }; + console.log(b.null, b.undefined = 2, b.null); + } + expect_exact: 'var a=1;var b={get null(){return a},set undefined(c){a=c}};console.log(b.null,b.undefined=2,b.null);' + expect_stdout: "1 2 2" +} + +accessor_number: { + input: { + var a = 1; + var b = { + get 42() { + return a; + }, + set 42(c) { + a = c; + } + }; + console.log(b[42], b[42] = 2, b[42]); + } + expect_exact: 'var a=1;var b={get 42(){return a},set 42(c){a=c}};console.log(b[42],b[42]=2,b[42]);' + expect_stdout: "1 2 2" +} + +accessor_string: { input: { var a = 1; var b = { @@ -572,3 +640,20 @@ issue_2040: { expect_exact: 'var a=1;var b={get"a-b"(){return a},set"a-b"(c){a=c}};console.log(b["a-b"],b["a-b"]=2,b["a-b"]);' expect_stdout: "1 2 2" } + +accessor_this: { + input: { + var a = 1; + var b = { + get this() { + return a; + }, + set this(c) { + a = c; + } + }; + console.log(b.this, b.this = 2, b.this); + } + expect_exact: 'var a=1;var b={get this(){return a},set this(c){a=c}};console.log(b.this,b.this=2,b.this);' + expect_stdout: "1 2 2" +} From 540220b91bb02d50d04422dc782a5fe1905dd27a Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 4 Jun 2017 19:27:43 +0800 Subject: [PATCH 04/23] fix `AST_Function` scope invariance (#2052) improve function name hack in `run_code()` --- lib/compress.js | 2 +- test/compress/drop-unused.js | 12 +++++------ test/compress/reduce_vars.js | 39 +++++++++++++++++++++++++++++++----- test/sandbox.js | 4 +++- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 71cffcee..effa6b11 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2957,7 +2957,7 @@ merge(Compressor.prototype, { && !(def.scope.uses_arguments && def.orig[0] instanceof AST_SymbolFunarg) && !def.scope.uses_eval - && compressor.find_parent(AST_Scope) === def.scope) { + && compressor.find_parent(AST_Scope) === exp.parent_scope) { self.expression = exp; } } diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 2ef6f796..af792bfa 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -751,12 +751,12 @@ issue_1583: { expect: { function m(t) { (function(e) { - t = (function() { - return (function(a) { - return a; - })(function(a) {}); - })(); - })(); + t = e(); + })(function() { + return (function(a) { + return a; + })(function(a) {}); + }); } } } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 2a44492a..078de82d 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1311,19 +1311,48 @@ iife_func_side_effects: { unused: true, } input: { + function x() { + console.log("x"); + } + function y() { + console.log("y"); + } + function z() { + console.log("z"); + } (function(a, b, c) { - return b(); + function y() { + console.log("FAIL"); + } + return y + b(); })(x(), function() { return y(); }, z()); } expect: { + function x() { + console.log("x"); + } + function y() { + console.log("y"); + } + function z() { + console.log("z"); + } (function(a, b, c) { - return function() { - return y(); - }(); - })(x(), 0, z()); + function y() { + console.log("FAIL"); + } + return y + b(); + })(x(), function() { + return y(); + }, z()); } + expect_stdout: [ + "x", + "z", + "y", + ] } issue_1595_1: { diff --git a/test/sandbox.js b/test/sandbox.js index 974f5211..ca1781c6 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -26,16 +26,18 @@ var FUNC_TOSTRING = [ " var i = this.name;", ' if (typeof i != "number") {', " i = ++id;", +].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [ ' Object.defineProperty(this, "name", {', " get: function() {", " return i;", " }", " });", +] : [], [ " }", ' return "[Function: " + i + "]";', " }", "}();", -].join("\n"); +]).join("\n"); exports.run_code = function(code) { var stdout = ""; var original_write = process.stdout.write; From 27c5284d3dc0ab131168a73035be7d87ebda30e9 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 6 Jun 2017 04:06:42 +0800 Subject: [PATCH 05/23] workaround webkit parsing error (#2056) apply `webkit` to jetstream tests --- lib/output.js | 8 ++++++++ test/compress/functions.js | 26 ++++++++++++++++++++++++++ test/jetstream.js | 4 ++-- test/mocha/release.js | 1 + 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/output.js b/lib/output.js index 2fb7441b..38f58531 100644 --- a/lib/output.js +++ b/lib/output.js @@ -70,6 +70,7 @@ function OutputStream(options) { semicolons : true, shebang : true, source_map : null, + webkit : false, width : 80, wrap_iife : false, }, true); @@ -597,6 +598,13 @@ function OutputStream(options) { return true; } + if (output.option('webkit')) { + var p = output.parent(); + if (p instanceof AST_PropAccess && p.expression === this) { + return true; + } + } + if (output.option('wrap_iife')) { var p = output.parent(); return p instanceof AST_Call && p.expression === this; diff --git a/test/compress/functions.js b/test/compress/functions.js index 6a9f2aed..a9ca23f8 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -267,3 +267,29 @@ issue_203: { } expect_stdout: "42" } + +no_webkit: { + beautify = { + webkit: false, + } + input: { + console.log(function() { + 1 + 1; + }.a = 1); + } + expect_exact: "console.log(function(){1+1}.a=1);" + expect_stdout: "1" +} + +webkit: { + beautify = { + webkit: true, + } + input: { + console.log(function() { + 1 + 1; + }.a = 1); + } + expect_exact: "console.log((function(){1+1}).a=1);" + expect_stdout: "1" +} diff --git a/test/jetstream.js b/test/jetstream.js index 4f13a281..cb8f111f 100644 --- a/test/jetstream.js +++ b/test/jetstream.js @@ -12,14 +12,14 @@ if (typeof phantom == "undefined") { }); var args = process.argv.slice(2); if (!args.length) { - args.push("-mc"); + args.push("-mcb", "beautify=false,webkit"); } args.push("--timings"); var child_process = require("child_process"); try { require("phantomjs-prebuilt"); } catch(e) { - child_process.execSync("npm install phantomjs-prebuilt@2.1.14"); + child_process.execSync("npm install phantomjs-prebuilt@2.1.14 --no-save"); } var http = require("http"); var server = http.createServer(function(request, response) { diff --git a/test/mocha/release.js b/test/mocha/release.js index b73a3df7..c96a7926 100644 --- a/test/mocha/release.js +++ b/test/mocha/release.js @@ -48,6 +48,7 @@ describe("test/jetstream.js", function() { it("Should pass with options " + options, function(done) { var args = options.split(/ /); args.unshift("test/jetstream.js"); + args.push("-b", "beautify=false,webkit"); run(process.argv[0], args, done); }); }); From 3493a182b2c94e4011b2b12d61376273b985d29a Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 6 Jun 2017 05:49:53 +0800 Subject: [PATCH 06/23] implement function inlining (#2053) - empty body - single `AST_Return` - single `AST_SimpleStatement` - avoid `/*#__PURE__*/` Miscellaneous - enhance single-use function substitution fixes #281 --- README.md | 2 + lib/compress.js | 88 +++++-- test/compress/collapse_vars.js | 2 +- test/compress/evaluate.js | 2 + test/compress/functions.js | 4 + test/compress/issue-1787.js | 1 + test/compress/issue-281.js | 433 +++++++++++++++++++++++++++++++++ test/compress/negate-iife.js | 6 +- test/compress/reduce_vars.js | 15 +- test/mocha/glob.js | 2 +- test/mocha/minify.js | 2 +- 11 files changed, 528 insertions(+), 29 deletions(-) create mode 100644 test/compress/issue-281.js diff --git a/README.md b/README.md index 95d54464..039bc638 100644 --- a/README.md +++ b/README.md @@ -616,6 +616,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `if_return` -- optimizations for if/return and if/continue +- `inline` -- embed simple functions + - `join_vars` -- join consecutive `var` statements - `cascade` -- small optimization for sequences, transform `x, x` into `x` diff --git a/lib/compress.js b/lib/compress.js index effa6b11..969acd61 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -63,6 +63,7 @@ function Compressor(options, false_by_default) { hoist_vars : false, ie8 : false, if_return : !false_by_default, + inline : !false_by_default, join_vars : !false_by_default, keep_fargs : true, keep_fnames : false, @@ -2943,24 +2944,9 @@ merge(Compressor.prototype, { OPT(AST_Call, function(self, compressor){ var exp = self.expression; - if (compressor.option("reduce_vars") - && exp instanceof AST_SymbolRef) { - var def = exp.definition(); + if (compressor.option("reduce_vars") && exp instanceof AST_SymbolRef) { var fixed = exp.fixed_value(); - if (fixed instanceof AST_Defun) { - def.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true); - } - if (fixed instanceof AST_Function) { - exp = fixed; - if (compressor.option("unused") - && def.references.length == 1 - && !(def.scope.uses_arguments - && def.orig[0] instanceof AST_SymbolFunarg) - && !def.scope.uses_eval - && compressor.find_parent(AST_Scope) === exp.parent_scope) { - self.expression = exp; - } - } + if (fixed instanceof AST_Function) exp = fixed; } if (compressor.option("unused") && exp instanceof AST_Function @@ -3171,13 +3157,61 @@ merge(Compressor.prototype, { } } if (exp instanceof AST_Function) { - if (exp.body[0] instanceof AST_Return) { - var value = exp.body[0].value; + var stat = exp.body[0]; + if (compressor.option("inline") && stat instanceof AST_Return) { + var value = stat && stat.value; if (!value || value.is_constant_expression()) { var args = self.args.concat(value || make_node(AST_Undefined, self)); return make_sequence(self, args).transform(compressor); } } + if (compressor.option("inline") + && !exp.name + && exp.body.length == 1 + && !exp.uses_arguments + && !exp.uses_eval + && !self.has_pure_annotation(compressor)) { + var body; + if (stat instanceof AST_Return) { + body = stat.value.clone(true); + } else if (stat instanceof AST_SimpleStatement) { + body = []; + merge_sequence(body, stat.body.clone(true)); + merge_sequence(body, make_node(AST_Undefined, self)); + body = make_sequence(self, body); + } + if (body) { + var fn = exp.clone(); + fn.argnames = []; + fn.body = [ + make_node(AST_Var, self, { + definitions: exp.argnames.map(function(sym, i) { + return make_node(AST_VarDef, sym, { + name: sym, + value: self.args[i] || make_node(AST_Undefined, self) + }); + }) + }) + ]; + if (self.args.length > exp.argnames.length) { + fn.body.push(make_node(AST_SimpleStatement, self, { + body: make_sequence(self, self.args.slice(exp.argnames.length)) + })); + } + fn.body.push(make_node(AST_Return, self, { + value: body + })); + body = fn.transform(compressor).body; + if (body.length == 0) return make_node(AST_Undefined, self); + if (body.length == 1 && body[0] instanceof AST_Return) { + if (!body[0].value) return make_node(AST_Undefined, self); + body = best_of(compressor, body[0].value, self); + } else { + body = self; + } + if (body !== self) return body; + } + } if (compressor.option("side_effects") && all(exp.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); return make_sequence(self, args).transform(compressor); @@ -3836,12 +3870,22 @@ merge(Compressor.prototype, { return make_node(AST_Infinity, self).optimize(compressor); } } - if (compressor.option("evaluate") - && compressor.option("reduce_vars") + if (compressor.option("reduce_vars") && is_lhs(self, compressor.parent()) !== self) { var d = self.definition(); var fixed = self.fixed_value(); - if (fixed) { + if (fixed instanceof AST_Defun) { + d.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true); + } + if (compressor.option("unused") + && fixed instanceof AST_Function + && d.references.length == 1 + && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) + && !d.scope.uses_eval + && compressor.find_parent(AST_Scope) === fixed.parent_scope) { + return fixed; + } + if (compressor.option("evaluate") && fixed) { if (d.should_replace === undefined) { var init = fixed.evaluate(compressor); if (init !== fixed && (compressor.option("unsafe_regexp") || !(init instanceof RegExp))) { diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 90d7ac93..158d1b4a 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1146,7 +1146,7 @@ collapse_vars_constants: { function f3(x) { var b = x.prop; sideeffect1(); - return b + -9; + return b + (function() { return -9; })(); } } } diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 99245d0d..27d08d47 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -644,6 +644,7 @@ unsafe_prototype_function: { call_args: { options = { evaluate: true, + inline: true, reduce_vars: true, toplevel: true, } @@ -665,6 +666,7 @@ call_args: { call_args_drop_param: { options = { evaluate: true, + inline: true, keep_fargs: false, reduce_vars: true, toplevel: true, diff --git a/test/compress/functions.js b/test/compress/functions.js index a9ca23f8..180bb11a 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -21,6 +21,7 @@ iifes_returning_constants_keep_fargs_true: { join_vars : true, reduce_vars : true, cascade : true, + inline : true, } input: { (function(){ return -1.23; }()); @@ -56,6 +57,7 @@ iifes_returning_constants_keep_fargs_false: { join_vars : true, reduce_vars : true, cascade : true, + inline : true, } input: { (function(){ return -1.23; }()); @@ -82,6 +84,7 @@ issue_485_crashing_1530: { conditionals: true, dead_code: true, evaluate: true, + inline: true, } input: { (function(a) { @@ -154,6 +157,7 @@ function_returning_constant_literal: { evaluate: true, cascade: true, unused: true, + inline: true, } input: { function greeter() { diff --git a/test/compress/issue-1787.js b/test/compress/issue-1787.js index 02fa0f91..2b5372be 100644 --- a/test/compress/issue-1787.js +++ b/test/compress/issue-1787.js @@ -1,6 +1,7 @@ unary_prefix: { options = { evaluate: true, + inline: true, reduce_vars: true, unused: true, } diff --git a/test/compress/issue-281.js b/test/compress/issue-281.js new file mode 100644 index 00000000..6b1b4b40 --- /dev/null +++ b/test/compress/issue-281.js @@ -0,0 +1,433 @@ +collapse_vars_constants: { + options = { + collapse_vars: true, + evaluate: true, + inline: true, + reduce_vars: true, + unused: true, + } + input: { + function f1(x) { + var a = 4, b = x.prop, c = 5, d = sideeffect1(), e = sideeffect2(); + return b + (function() { return d - a * e - c; })(); + } + function f2(x) { + var a = 4, b = x.prop, c = 5, not_used = sideeffect1(), e = sideeffect2(); + return b + (function() { return -a * e - c; })(); + } + } + expect: { + function f1(x) { + var b = x.prop, d = sideeffect1(), e = sideeffect2(); + return b + (d - 4 * e - 5); + } + function f2(x) { + var b = x.prop; + sideeffect1(); + return b + (-4 * sideeffect2() - 5); + } + } +} + +modified: { + options = { + collapse_vars: true, + inline: true, + unused: true, + } + input: { + function f5(b) { + var a = function() { + return b; + }(); + return b++ + a; + } + console.log(f5(1)); + } + expect: { + function f5(b) { + var a = b; + return b++ + a; + } + console.log(f5(1)); + } + expect_stdout: "2" +} + +ref_scope: { + options = { + collapse_vars: true, + inline: true, + unused: true, + } + input: { + console.log(function() { + var a = 1, b = 2, c = 3; + var a = c++, b = b /= a; + return function() { + return a; + }() + b; + }()); + } + expect: { + console.log(function() { + var a = 1, b = 2, c = 3; + b = b /= a = c++; + return a + b; + }()); + } + expect_stdout: true +} + +safe_undefined: { + options = { + conditionals: true, + if_return: true, + inline: true, + unsafe: false, + unused: true, + } + mangle = {} + input: { + var a, c; + console.log(function(undefined) { + return function() { + if (a) + return b; + if (c) + return d; + }; + }(1)()); + } + expect: { + var a, c; + console.log(a ? b : c ? d : void 0); + } + expect_stdout: true +} + +negate_iife_3: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + t ? console.log(true) : console.log(false); + } +} + +negate_iife_3_off: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: false, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + t ? console.log(true) : console.log(false); + } +} + +negate_iife_4: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: true, + sequences: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + (function(){ + console.log("something"); + })(); + } + expect: { + t ? console.log(true) : console.log(false), console.log("something"), void 0; + } +} + +negate_iife_5: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: true, + sequences: true, + } + input: { + if ((function(){ return t })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + t ? foo(true) : bar(false), console.log("something"), void 0; + } +} + +negate_iife_5_off: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: false, + sequences: true, + }; + input: { + if ((function(){ return t })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + t ? foo(true) : bar(false), console.log("something"), void 0; + } +} + +issue_1254_negate_iife_true: { + options = { + expression: true, + inline: true, + negate_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()(); + } + expect_exact: 'console.log("test"),void 0;' + expect_stdout: true +} + +issue_1254_negate_iife_nested: { + options = { + expression: true, + inline: true, + negate_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()()()()(); + } + expect_exact: '(console.log("test"),void 0)()()();' +} + +negate_iife_issue_1073: { + options = { + conditionals: true, + evaluate: true, + inline: true, + negate_iife: true, + reduce_vars: true, + sequences: true, + unused: true, + }; + input: { + new (function(a) { + return function Foo() { + this.x = a; + console.log(this); + }; + }(7))(); + } + expect: { + new function() { + this.x = 7, + console.log(this); + }(); + } + expect_stdout: true +} + +issue_1288_side_effects: { + options = { + conditionals: true, + evaluate: true, + inline: true, + negate_iife: true, + reduce_vars: true, + side_effects: true, + unused: true, + }; + input: { + if (w) ; + else { + (function f() {})(); + } + if (!x) { + (function() { + x = {}; + })(); + } + if (y) + (function() {})(); + else + (function(z) { + return z; + })(0); + } + expect: { + w; + x || (x = {}); + y; + } +} + +inner_var_for_in_1: { + options = { + evaluate: true, + inline: true, + reduce_vars: true, + } + input: { + function f() { + var a = 1, b = 2; + for (b in (function() { + return x(a, b, c); + })()) { + var c = 3, d = 4; + x(a, b, c, d); + } + x(a, b, c, d); + } + } + expect: { + function f() { + var a = 1, b = 2; + for (b in x(1, b, c)) { + var c = 3, d = 4; + x(1, b, c, d); + } + x(1, b, c, d); + } + } +} + +issue_1595_3: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + (function f(a) { + return g(a + 1); + })(2); + } + expect: { + g(3); + } +} + +issue_1758: { + options = { + inline: true, + sequences: true, + side_effects: true, + } + input: { + console.log(function(c) { + var undefined = 42; + return function() { + c--; + c--, c.toString(); + return; + }(); + }()); + } + expect: { + console.log(function(c) { + var undefined = 42; + return c--, c--, void c.toString(); + }()); + } + expect_stdout: "undefined" +} +wrap_iife: { + options = { + inline: true, + negate_iife: false, + } + beautify = { + wrap_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()(); + } + expect_exact: 'console.log("test"),void 0;' +} + +wrap_iife_in_expression: { + options = { + inline: true, + negate_iife: false, + } + beautify = { + wrap_iife: true, + } + input: { + foo = (function () { + return bar(); + })(); + } + expect_exact: 'foo=bar();' +} + +wrap_iife_in_return_call: { + options = { + inline: true, + negate_iife: false, + } + beautify = { + wrap_iife: true, + } + input: { + (function() { + return (function() { + console.log('test') + })(); + })()(); + } + expect_exact: '(console.log("test"),void 0)();' +} + +pure_annotation: { + options = { + inline: true, + side_effects: true, + } + input: { + /*@__PURE__*/(function() { + console.log("hello"); + }()); + } + expect_exact: "" +} diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index 514a15c7..66d24270 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -22,7 +22,8 @@ negate_iife_1_off: { negate_iife_2: { options = { - negate_iife: true + inline: true, + negate_iife: true, }; input: { (function(){ return {} })().x = 10; @@ -32,6 +33,7 @@ negate_iife_2: { negate_iife_2_side_effects: { options = { + inline: true, negate_iife: true, side_effects: true, } @@ -58,6 +60,7 @@ negate_iife_3_evaluate: { options = { conditionals: true, evaluate: true, + inline: true, negate_iife: true, } input: { @@ -100,6 +103,7 @@ negate_iife_3_off_evaluate: { options = { conditionals: true, evaluate: true, + inline: true, negate_iife: false, } input: { diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 078de82d..cef29832 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2,6 +2,7 @@ reduce_vars: { options = { conditionals : true, evaluate : true, + inline : true, global_defs : { C : 0 }, @@ -1032,6 +1033,7 @@ defun_inline_2: { defun_inline_3: { options = { evaluate: true, + inline: true, passes: 2, reduce_vars: true, side_effects: true, @@ -1054,6 +1056,7 @@ defun_inline_3: { defun_call: { options = { + inline: true, reduce_vars: true, unused: true, } @@ -1080,6 +1083,7 @@ defun_call: { defun_redefine: { options = { + inline: true, reduce_vars: true, unused: true, } @@ -1112,6 +1116,7 @@ defun_redefine: { func_inline: { options = { + inline: true, reduce_vars: true, unused: true, } @@ -1138,6 +1143,7 @@ func_inline: { func_modified: { options = { + inline: true, reduce_vars: true, unused: true, } @@ -1340,10 +1346,9 @@ iife_func_side_effects: { console.log("z"); } (function(a, b, c) { - function y() { + return function() { console.log("FAIL"); - } - return y + b(); + } + b(); })(x(), function() { return y(); }, z()); @@ -1716,6 +1721,7 @@ redefine_arguments_1: { redefine_arguments_2: { options = { evaluate: true, + inline: true, keep_fargs: false, reduce_vars: true, side_effects: true, @@ -1752,6 +1758,7 @@ redefine_arguments_2: { redefine_arguments_3: { options = { evaluate: true, + inline: true, keep_fargs: false, passes: 3, reduce_vars: true, @@ -1828,6 +1835,7 @@ redefine_farg_1: { redefine_farg_2: { options = { evaluate: true, + inline: true, keep_fargs: false, reduce_vars: true, side_effects: true, @@ -1864,6 +1872,7 @@ redefine_farg_2: { redefine_farg_3: { options = { evaluate: true, + inline: true, keep_fargs: false, passes: 3, reduce_vars: true, diff --git a/test/mocha/glob.js b/test/mocha/glob.js index 56d3f82a..8567ecb3 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -31,7 +31,7 @@ describe("bin/uglifyjs with input file globs", function() { exec(command, function(err, stdout) { if (err) throw err; - assert.strictEqual(stdout, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){print("Foo:",2*n)}(11);\n'); + assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",2*11);\n'); done(); }); }); diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 77b798ec..519b725c 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -106,7 +106,7 @@ describe("minify", function() { content: "inline" } }); - assert.strictEqual(result.code, "var bar=function(){function foo(bar){return bar}return foo}();"); + assert.strictEqual(result.code, "var bar=function(){return function(bar){return bar}}();"); assert.strictEqual(warnings.length, 1); assert.strictEqual(warnings[0], "inline source map not found"); } finally { From b0eab71470fa887752bcf5442fbb26da3f724ef1 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 6 Jun 2017 19:28:12 +0800 Subject: [PATCH 07/23] implement `test/jetstream.js --debug` (#2058) --- test/jetstream.js | 85 +++++++++++++++++++++++++------------------ test/mocha/release.js | 3 -- 2 files changed, 49 insertions(+), 39 deletions(-) diff --git a/test/jetstream.js b/test/jetstream.js index cb8f111f..8279975c 100644 --- a/test/jetstream.js +++ b/test/jetstream.js @@ -3,7 +3,7 @@ "use strict"; -var site = "http://browserbench.org/JetStream/"; +var site = "http://browserbench.org/JetStream"; if (typeof phantom == "undefined") { // workaround for tty output truncation upon process.exit() [process.stdout, process.stderr].forEach(function(stream){ @@ -11,45 +11,62 @@ if (typeof phantom == "undefined") { stream._handle.setBlocking(true); }); var args = process.argv.slice(2); + var debug = args.indexOf("--debug"); + if (debug >= 0) { + args.splice(debug, 1); + debug = true; + } else { + debug = false; + } if (!args.length) { args.push("-mcb", "beautify=false,webkit"); } args.push("--timings"); var child_process = require("child_process"); - try { - require("phantomjs-prebuilt"); - } catch(e) { - child_process.execSync("npm install phantomjs-prebuilt@2.1.14 --no-save"); - } var http = require("http"); var server = http.createServer(function(request, response) { request.resume(); - var url = decodeURIComponent(request.url.slice(1)); - var stderr = ""; - var uglifyjs = child_process.fork("bin/uglifyjs", args, { - silent: true - }).on("exit", function(code) { - console.log("uglifyjs", url.indexOf(site) == 0 ? url.slice(site.length) : url, args.join(" ")); - console.log(stderr); - if (code) throw new Error("uglifyjs failed with code " + code); - }); - uglifyjs.stderr.on("data", function(data) { - stderr += data; - }).setEncoding("utf8"); - uglifyjs.stdout.pipe(response); + var url = site + request.url; http.get(url, function(res) { - res.pipe(uglifyjs.stdin); - }); - }).listen().on("listening", function() { - var phantomjs = require("phantomjs-prebuilt"); - var program = phantomjs.exec(process.argv[1], server.address().port); - program.stdout.pipe(process.stdout); - program.stderr.pipe(process.stderr); - program.on("exit", function(code) { - server.close(); - if (code) throw new Error("JetStream failed!"); - console.log("JetStream completed successfully."); + response.writeHead(res.statusCode, { + "Content-Type": res.headers["content-type"] + }); + if (/\.js$/.test(url)) { + var stderr = ""; + var uglifyjs = child_process.fork("bin/uglifyjs", args, { + silent: true + }).on("exit", function(code) { + console.log("uglifyjs", url.slice(site.length + 1), args.join(" ")); + console.log(stderr); + if (code) throw new Error("uglifyjs failed with code " + code); + }); + uglifyjs.stderr.on("data", function(data) { + stderr += data; + }).setEncoding("utf8"); + uglifyjs.stdout.pipe(response); + res.pipe(uglifyjs.stdin); + } else { + res.pipe(response); + } }); + }).listen(); + server.on("listening", function() { + var port = server.address().port; + if (debug) { + console.log("http://localhost:" + port + "/"); + } else { + child_process.exec("npm install phantomjs-prebuilt@2.1.14 --no-save", function(error) { + if (error) throw error; + var program = require("phantomjs-prebuilt").exec(process.argv[1], port); + program.stdout.pipe(process.stdout); + program.stderr.pipe(process.stderr); + program.on("exit", function(code) { + server.close(); + if (code) throw new Error("JetStream failed!"); + console.log("JetStream completed successfully."); + }); + }); + } }); server.timeout = 0; } else { @@ -63,10 +80,6 @@ if (typeof phantom == "undefined") { phantom.exit(1); }; var url = "http://localhost:" + require("system").args[1] + "/"; - page.onResourceRequested = function(requestData, networkRequest) { - if (/\.js$/.test(requestData.url)) - networkRequest.changeUrl(url + encodeURIComponent(requestData.url)); - } page.onConsoleMessage = function(msg) { if (/Error:/i.test(msg)) { console.error(msg); @@ -77,8 +90,8 @@ if (typeof phantom == "undefined") { phantom.exit(); } }; - page.open(site, function(status) { - if (status != "success") phantomjs.exit(1); + page.open(url, function(status) { + if (status != "success") phantom.exit(1); page.evaluate(function() { JetStream.switchToQuick(); JetStream.start(); diff --git a/test/mocha/release.js b/test/mocha/release.js index c96a7926..9f894b3d 100644 --- a/test/mocha/release.js +++ b/test/mocha/release.js @@ -38,9 +38,6 @@ describe("test/benchmark.js", function() { 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", "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto", From b9ad53d1ab166115dde78493f937782417e78e00 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 6 Jun 2017 22:55:25 +0800 Subject: [PATCH 08/23] fix `inline` handling of `AST_Call.args` (#2059) --- lib/compress.js | 14 ++++++----- test/compress/issue-281.js | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 969acd61..e3846c31 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3183,19 +3183,21 @@ merge(Compressor.prototype, { if (body) { var fn = exp.clone(); fn.argnames = []; - fn.body = [ - make_node(AST_Var, self, { + fn.body = []; + if (exp.argnames.length > 0) { + fn.body.push(make_node(AST_Var, self, { definitions: exp.argnames.map(function(sym, i) { + var arg = self.args[i]; return make_node(AST_VarDef, sym, { name: sym, - value: self.args[i] || make_node(AST_Undefined, self) + value: arg ? arg.clone(true) : make_node(AST_Undefined, self) }); }) - }) - ]; + })); + } if (self.args.length > exp.argnames.length) { fn.body.push(make_node(AST_SimpleStatement, self, { - body: make_sequence(self, self.args.slice(exp.argnames.length)) + body: make_sequence(self, self.args.slice(exp.argnames.length)).clone(true) })); } fn.body.push(make_node(AST_Return, self, { diff --git a/test/compress/issue-281.js b/test/compress/issue-281.js index 6b1b4b40..7a6c03bc 100644 --- a/test/compress/issue-281.js +++ b/test/compress/issue-281.js @@ -431,3 +431,53 @@ pure_annotation: { } expect_exact: "" } + +drop_fargs: { + options = { + cascade: true, + inline: true, + keep_fargs: false, + side_effects: true, + unused: true, + } + input: { + var a = 1; + !function(a_1) { + a++; + }(a++ + (a && a.var)); + console.log(a); + } + expect: { + var a = 1; + !function() { + a++; + }(++a && a.var); + console.log(a); + } + expect_stdout: "3" +} + +keep_fargs: { + options = { + cascade: true, + inline: true, + keep_fargs: true, + side_effects: true, + unused: true, + } + input: { + var a = 1; + !function(a_1) { + a++; + }(a++ + (a && a.var)); + console.log(a); + } + expect: { + var a = 1; + !function(a_1) { + a++; + }(++a && a.var); + console.log(a); + } + expect_stdout: "3" +} From f2af0934021a83b781f207432d10380cc4c5f396 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 7 Jun 2017 04:25:32 +0800 Subject: [PATCH 09/23] fix CLI output corruption (#2061) Using `console.error()` & `console.log()` result in inconsistent formatting across Node.js versions. Avoid this issue by directly writing to `process.stderr` & `process.stdout` instead. Miscellaneous - prettify invalid option listing --- bin/uglifyjs | 57 ++++++++++++++++++++++++++++++----------------- test/mocha/cli.js | 2 +- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index f2aeb084..52708cc7 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -30,14 +30,7 @@ else if (process.argv.indexOf("options") >= 0) program.helpInformation = functio var options = UglifyJS.default_options(); for (var option in options) { text.push("--" + (option == "output" ? "beautify" : option == "sourceMap" ? "source-map" : option) + " options:"); - var defs = options[option]; - var padding = ""; - Object.keys(defs).map(function(name) { - if (padding.length < name.length) padding = Array(name.length + 1).join(" "); - return [ name, JSON.stringify(defs[name]) ]; - }).forEach(function(tokens) { - text.push(" " + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]); - }); + text.push(format_object(options[option])); text.push(""); } return text.join("\n"); @@ -157,7 +150,7 @@ if (program.verbose) { } if (program.self) { if (program.args.length) { - console.error("WARN: Ignoring input files since --self was passed"); + print_error("WARN: Ignoring input files since --self was passed"); } if (!options.wrap) options.wrap = "UglifyJS"; simple_glob(UglifyJS.FILES).forEach(function(name) { @@ -187,7 +180,7 @@ function convert_ast(fn) { function run() { UglifyJS.AST_Node.warn_function = function(msg) { - console.error("WARN:", msg); + print_error("WARN: " + msg); }; if (program.timings) options.timings = true; try { @@ -216,7 +209,7 @@ function run() { if (result.error) { var ex = result.error; if (ex.name == "SyntaxError") { - console.error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col); + print_error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col); var col = ex.col; var lines = files[ex.filename].split(/\r?\n/); var line = lines[ex.line - 1]; @@ -230,17 +223,17 @@ function run() { line = line.slice(col - limit); col = limit; } - console.error(line.slice(0, 80)); - console.error(line.slice(0, col).replace(/\S/g, " ") + "^"); + print_error(line.slice(0, 80)); + print_error(line.slice(0, col).replace(/\S/g, " ") + "^"); } } if (ex.defs) { - console.error("Supported options:"); - console.error(ex.defs); + print_error("Supported options:"); + print_error(format_object(ex.defs)); } fatal(ex); } else if (program.output == "ast") { - console.log(JSON.stringify(result.ast, function(key, value) { + print(JSON.stringify(result.ast, function(key, value) { if (skip_key(key)) return; if (value instanceof UglifyJS.AST_Token) return; if (value instanceof UglifyJS.Dictionary) return; @@ -256,7 +249,7 @@ function run() { return value; }, 2)); } else if (program.output == "spidermonkey") { - console.log(JSON.stringify(UglifyJS.minify(result.code, { + print(JSON.stringify(UglifyJS.minify(result.code, { compress: false, mangle: false, output: { @@ -270,7 +263,7 @@ function run() { fs.writeFileSync(program.output + ".map", result.map); } } else { - console.log(result.code); + print(result.code); } if (program.nameCache) { fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) { @@ -278,13 +271,13 @@ function run() { })); } if (result.timings) for (var phase in result.timings) { - console.error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s"); + print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s"); } } function fatal(message) { if (message instanceof Error) message = message.stack.replace(/^\S*?Error:/, "ERROR:") - console.error(message); + print_error(message); process.exit(1); } @@ -381,7 +374,7 @@ function parse_source_map() { var hasContent = options && options.sourceMap && "content" in options.sourceMap; var settings = parse(value, options); if (!hasContent && settings.content && settings.content != "inline") { - console.error("INFO: Using input source map:", settings.content); + print_error("INFO: Using input source map: " + settings.content); settings.content = read_file(settings.content, settings.content); } return settings; @@ -403,3 +396,25 @@ function to_cache(key) { function skip_key(key) { return skip_keys.indexOf(key) >= 0; } + +function format_object(obj) { + var lines = []; + var padding = ""; + Object.keys(obj).map(function(name) { + if (padding.length < name.length) padding = Array(name.length + 1).join(" "); + return [ name, JSON.stringify(obj[name]) ]; + }).forEach(function(tokens) { + lines.push(" " + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]); + }); + return lines.join("\n"); +} + +function print_error(msg) { + process.stderr.write(msg); + process.stderr.write("\n"); +} + +function print(txt) { + process.stdout.write(txt); + process.stdout.write("\n"); +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 4234f2fb..0a7f8f2b 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -542,7 +542,7 @@ describe("bin/uglifyjs", function () { exec(command, function (err, stdout, stderr) { assert.ok(err); assert.strictEqual(stdout, ""); - assert.ok(/^Supported options:\n\{[^}]+}\nERROR: `ascii-only` is not a supported option/.test(stderr), stderr); + assert.ok(/^Supported options:\n[\s\S]*?\nERROR: `ascii-only` is not a supported option/.test(stderr), stderr); done(); }); }); From 9db0695b10799349c005fc14ab1268c2478c25fd Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 7 Jun 2017 19:52:01 +0800 Subject: [PATCH 10/23] fix `cascade` on multi-branch evaluations (#2067) Partially reverts #2059 as this has better coverage and performance. fixes #2062 --- lib/compress.js | 8 ++++---- test/compress/sequences.js | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index e3846c31..81b9ccac 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3187,17 +3187,16 @@ merge(Compressor.prototype, { if (exp.argnames.length > 0) { fn.body.push(make_node(AST_Var, self, { definitions: exp.argnames.map(function(sym, i) { - var arg = self.args[i]; return make_node(AST_VarDef, sym, { name: sym, - value: arg ? arg.clone(true) : make_node(AST_Undefined, self) + value: self.args[i] || make_node(AST_Undefined, self) }); }) })); } if (self.args.length > exp.argnames.length) { fn.body.push(make_node(AST_SimpleStatement, self, { - body: make_sequence(self, self.args.slice(exp.argnames.length)).clone(true) + body: make_sequence(self, self.args.slice(exp.argnames.length)) })); } fn.body.push(make_node(AST_Return, self, { @@ -3316,6 +3315,7 @@ merge(Compressor.prototype, { continue; } var parent = null, field; + expressions[j] = cdr = cdr.clone(); while (true) { if (cdr.equivalent_to(left)) { var car = expressions[i]; @@ -3352,7 +3352,7 @@ merge(Compressor.prototype, { break; } parent = cdr; - cdr = cdr[field]; + cdr = cdr[field] = cdr[field].clone(); } } end = i; diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 10492565..f41b603f 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -710,3 +710,23 @@ issue_27: { })(jQuery); } } + +issue_2062: { + options = { + booleans: true, + cascade: true, + conditionals: true, + side_effects: true, + } + input: { + var a = 1; + if ([ a || a++ + a--, a++ + a--, a && a.var ]); + console.log(a); + } + expect: { + var a = 1; + a || (a++, a--), a++, --a && a.var; + console.log(a); + } + expect_stdout: "1" +} From 9c306406f14087ad0a62141bf7bb9e12afcc1a75 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 8 Jun 2017 03:27:03 +0800 Subject: [PATCH 11/23] fix iteration over object with inherited properties (#2068) fixes #2055 --- lib/compress.js | 2 +- lib/minify.js | 4 ++-- test/mocha/minify.js | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 81b9ccac..5f2ac2b0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1419,7 +1419,7 @@ merge(Compressor.prototype, { }); if (value && typeof value == "object") { var props = []; - for (var key in value) { + for (var key in value) if (HOP(value, key)) { props.push(make_node(AST_ObjectKeyVal, orig, { key: key, value: to_node(value[key], orig) diff --git a/lib/minify.js b/lib/minify.js index 16f5b189..cc638be3 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -86,7 +86,7 @@ function minify(files, options) { } options.parse = options.parse || {}; options.parse.toplevel = null; - for (var name in files) { + for (var name in files) if (HOP(files, name)) { options.parse.filename = name; options.parse.toplevel = parse(files[name], options.parse); if (options.sourceMap && options.sourceMap.content == "inline") { @@ -134,7 +134,7 @@ function minify(files, options) { if (options.sourceMap.includeSources) { if (files instanceof AST_Toplevel) { throw new Error("original source content unavailable"); - } else for (var name in files) { + } else for (var name in files) if (HOP(files, name)) { options.output.source_map.get().setSourceContent(name, files[name]); } } diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 519b725c..638e79f7 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -13,6 +13,13 @@ describe("minify", function() { assert.strictEqual(result.code, 'function foo(n){return n?3:7}'); }); + it("Should skip inherited keys from `files`", function() { + var files = Object.create({ skip: this }); + files[0] = "alert(1 + 1)"; + var result = Uglify.minify(files); + assert.strictEqual(result.code, "alert(2);"); + }); + describe("keep_quoted_props", function() { it("Should preserve quotes in object literals", function() { var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; @@ -207,5 +214,17 @@ describe("minify", function() { assert.ok(err instanceof Error); assert.strictEqual(err.stack.split(/\n/)[0], "Error: Can't handle expression: debugger"); }); + it("should skip inherited properties", function() { + var foo = Object.create({ skip: this }); + foo.bar = 42; + var result = Uglify.minify("alert(FOO);", { + compress: { + global_defs: { + FOO: foo + } + } + }); + assert.strictEqual(result.code, "alert({bar:42});"); + }); }); }); From 293c566d6c8c6082c550ce5ca358da4cde124fac Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 9 Jun 2017 04:29:12 +0800 Subject: [PATCH 12/23] marshal `mangle[.properties].reserved` from non-Array values (#2072) --- lib/propmangle.js | 3 ++- lib/scope.js | 4 +++- test/mocha/cli.js | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/propmangle.js b/lib/propmangle.js index efb31cc1..f17909d7 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -78,7 +78,8 @@ function mangle_properties(ast, options) { reserved: null, }); - var reserved = options.reserved || []; + var reserved = options.reserved; + if (!Array.isArray(reserved)) reserved = []; if (!options.builtins) find_builtins(reserved); var cache = options.cache; diff --git a/lib/scope.js b/lib/scope.js index 8bc96072..ea43f752 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -377,13 +377,15 @@ AST_Symbol.DEFMETHOD("global", function(){ }); AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ - return defaults(options, { + options = defaults(options, { eval : false, ie8 : false, keep_fnames : false, reserved : [], toplevel : false, }); + if (!Array.isArray(options.reserved)) options.reserved = []; + return options; }); AST_Toplevel.DEFMETHOD("mangle_names", function(options){ diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 0a7f8f2b..3228e4e3 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -546,4 +546,24 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should work with --mangle reserved=[]", function (done) { + var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=[callback]'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, 'function test(callback){"aaaaaaaaaaaaaaaa";callback(err,data);callback(err,data)}\n'); + done(); + }); + }); + it("Should work with --mangle reserved=false", function (done) { + var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=false'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, 'function test(a){"aaaaaaaaaaaaaaaa";a(err,data);a(err,data)}\n'); + done(); + }); + }); }); From 47c07137474d44b0b3d367c1a6adbcea1020c410 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 9 Jun 2017 15:56:28 +0800 Subject: [PATCH 13/23] report `test/ufuzz.js` failures in `process.stderr` (#2074) --- test/ufuzz.js | 140 +++++++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 65 deletions(-) diff --git a/test/ufuzz.js b/test/ufuzz.js index e3361e38..c98a80d8 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -102,23 +102,23 @@ for (var i = 2; i < process.argv.length; ++i) { case '--help': case '-h': case '-?': - console.log('** UglifyJS fuzzer help **'); - console.log('Valid options (optional):'); - console.log(': generate this many cases (if used must be first arg)'); - console.log('-v: print every generated test case'); - console.log('-V: print every 100th generated test case'); - console.log('-t : generate this many toplevels per run (more take longer)'); - console.log('-r : maximum recursion depth for generator (higher takes longer)'); - console.log('-s1 : force the first level statement to be this one (see list below)'); - console.log('-s2 : force the second level statement to be this one (see list below)'); - console.log('--no-catch-redef: do not redefine catch variables'); - console.log('--no-directive: do not generate directives'); - console.log('--use-strict: generate "use strict"'); - console.log('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise'); - console.log('--only-stmt : a comma delimited white list of statements that may be generated'); - console.log('--without-stmt : a comma delimited black list of statements never to generate'); - console.log('List of accepted statement names: ' + Object.keys(STMT_ARG_TO_ID)); - console.log('** UglifyJS fuzzer exiting **'); + println('** UglifyJS fuzzer help **'); + println('Valid options (optional):'); + println(': generate this many cases (if used must be first arg)'); + println('-v: print every generated test case'); + println('-V: print every 100th generated test case'); + println('-t : generate this many toplevels per run (more take longer)'); + println('-r : maximum recursion depth for generator (higher takes longer)'); + println('-s1 : force the first level statement to be this one (see list below)'); + println('-s2 : force the second level statement to be this one (see list below)'); + println('--no-catch-redef: do not redefine catch variables'); + println('--no-directive: do not generate directives'); + println('--use-strict: generate "use strict"'); + println('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise'); + println('--only-stmt : a comma delimited white list of statements that may be generated'); + println('--without-stmt : a comma delimited black list of statements never to generate'); + println('List of accepted statement names: ' + Object.keys(STMT_ARG_TO_ID)); + println('** UglifyJS fuzzer exiting **'); return 0; default: // first arg may be a number. @@ -941,7 +941,17 @@ if (require.main !== module) { return; } -function try_beautify(code, result) { +function println(msg) { + if (typeof msg != "undefined") process.stdout.write(msg); + process.stdout.write("\n"); +} + +function errorln(msg) { + if (typeof msg != "undefined") process.stderr.write(msg); + process.stderr.write("\n"); +} + +function try_beautify(code, result, printfn) { var beautified = UglifyJS.minify(code, { compress: false, mangle: false, @@ -951,15 +961,15 @@ function try_beautify(code, result) { }, }); if (beautified.error) { - console.log("// !!! beautify failed !!!"); - console.log(beautified.error.stack); + printfn("// !!! beautify failed !!!"); + printfn(beautified.error.stack); } else if (sandbox.same_stdout(sandbox.run_code(beautified.code), result)) { - console.log("// (beautified)"); - console.log(beautified.code); + printfn("// (beautified)"); + printfn(beautified.code); return; } - console.log("//"); - console.log(code); + printfn("//"); + printfn(code); } var default_options = UglifyJS.default_options(); @@ -977,8 +987,8 @@ function log_suspects(minify_options, component) { m[component] = o; var result = UglifyJS.minify(original_code, m); if (result.error) { - console.log("Error testing options." + component + "." + name); - console.log(result.error); + errorln("Error testing options." + component + "." + name); + errorln(result.error); } else { var r = sandbox.run_code(result.code); return sandbox.same_stdout(original_result, r); @@ -986,49 +996,49 @@ function log_suspects(minify_options, component) { } }); if (suspects.length > 0) { - console.log("Suspicious", component, "options:"); + errorln("Suspicious", component, "options:"); suspects.forEach(function(name) { - console.log(" " + name); + errorln(" " + name); }); - console.log(); + errorln(); } } function log(options) { - if (!ok) console.log('\n\n\n\n\n\n!!!!!!!!!!\n\n\n'); - console.log("//============================================================="); - if (!ok) console.log("// !!!!!! Failed... round", round); - console.log("// original code"); - try_beautify(original_code, original_result); - console.log(); - console.log(); - console.log("//-------------------------------------------------------------"); + if (!ok) errorln('\n\n\n\n\n\n!!!!!!!!!!\n\n\n'); + errorln("//============================================================="); + if (!ok) errorln("// !!!!!! Failed... round", round); + errorln("// original code"); + try_beautify(original_code, original_result, errorln); + errorln(); + errorln(); + errorln("//-------------------------------------------------------------"); if (typeof uglify_code == "string") { - console.log("// uglified code"); - try_beautify(uglify_code, uglify_result); - console.log(); - console.log(); - console.log("original result:"); - console.log(original_result); - console.log("uglified result:"); - console.log(uglify_result); + errorln("// uglified code"); + try_beautify(uglify_code, uglify_result, errorln); + errorln(); + errorln(); + errorln("original result:"); + errorln(original_result); + errorln("uglified result:"); + errorln(uglify_result); } else { - console.log("// !!! uglify failed !!!"); - console.log(uglify_code.stack); + errorln("// !!! uglify failed !!!"); + errorln(uglify_code.stack); if (typeof original_result != "string") { - console.log(); - console.log(); - console.log("original stacktrace:"); - console.log(original_result.stack); + errorln(); + errorln(); + errorln("original stacktrace:"); + errorln(original_result.stack); } } - console.log("minify(options):"); + errorln("minify(options):"); options = JSON.parse(options); - console.log(options); - console.log(); + errorln(options); + errorln(); if (!ok && typeof uglify_code == "string") { Object.keys(default_options).forEach(log_suspects.bind(null, options)); - console.log("!!!!!! Failed... round", round); + errorln("!!!!!! Failed... round", round); } } @@ -1058,19 +1068,19 @@ for (var round = 1; round <= num_iterations; round++) { } if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); else if (typeof original_result != "string") { - console.log("//============================================================="); - console.log("// original code"); - try_beautify(original_code, original_result); - console.log(); - console.log(); - console.log("original result:"); - console.log(original_result); - console.log(); + println("//============================================================="); + println("// original code"); + try_beautify(original_code, original_result, println); + println(); + println(); + println("original result:"); + println(original_result); + println(); } if (!ok && isFinite(num_iterations)) { - console.log(); + println(); process.exit(1); } }); } -console.log(); +println(); From 9186859cb7df0b1f7ac328e19979b14967dbadf2 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 10 Jun 2017 00:11:40 +0800 Subject: [PATCH 14/23] fix non-string parameters (#2076) `Stream.write()` is not as versatile as `console.log()` --- test/ufuzz.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/ufuzz.js b/test/ufuzz.js index c98a80d8..e38ffa2f 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -988,7 +988,7 @@ function log_suspects(minify_options, component) { var result = UglifyJS.minify(original_code, m); if (result.error) { errorln("Error testing options." + component + "." + name); - errorln(result.error); + errorln(result.error.stack); } else { var r = sandbox.run_code(result.code); return sandbox.same_stdout(original_result, r); @@ -996,7 +996,7 @@ function log_suspects(minify_options, component) { } }); if (suspects.length > 0) { - errorln("Suspicious", component, "options:"); + errorln("Suspicious " + component + " options:"); suspects.forEach(function(name) { errorln(" " + name); }); @@ -1007,7 +1007,7 @@ function log_suspects(minify_options, component) { function log(options) { if (!ok) errorln('\n\n\n\n\n\n!!!!!!!!!!\n\n\n'); errorln("//============================================================="); - if (!ok) errorln("// !!!!!! Failed... round", round); + if (!ok) errorln("// !!!!!! Failed... round " + round); errorln("// original code"); try_beautify(original_code, original_result, errorln); errorln(); @@ -1019,9 +1019,9 @@ function log(options) { errorln(); errorln(); errorln("original result:"); - errorln(original_result); + errorln(typeof original_result == "string" ? original_result : original_result.stack); errorln("uglified result:"); - errorln(uglify_result); + errorln(typeof uglify_result == "string" ? uglify_result : uglify_result.stack); } else { errorln("// !!! uglify failed !!!"); errorln(uglify_code.stack); @@ -1034,11 +1034,11 @@ function log(options) { } errorln("minify(options):"); options = JSON.parse(options); - errorln(options); + errorln(JSON.stringify(options, null, 2)); errorln(); if (!ok && typeof uglify_code == "string") { Object.keys(default_options).forEach(log_suspects.bind(null, options)); - errorln("!!!!!! Failed... round", round); + errorln("!!!!!! Failed... round " + round); } } @@ -1074,7 +1074,7 @@ for (var round = 1; round <= num_iterations; round++) { println(); println(); println("original result:"); - println(original_result); + println(original_result.stack); println(); } if (!ok && isFinite(num_iterations)) { From 4ad7b1dae46fb5bf98f12859b2906381421563d4 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 10 Jun 2017 01:08:58 +0800 Subject: [PATCH 15/23] fix portability of `sandbox.run_code()` on Node.js 0.1x (#2078) --- test/compress/sandbox.js | 14 ++++++++++++++ test/sandbox.js | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 test/compress/sandbox.js diff --git a/test/compress/sandbox.js b/test/compress/sandbox.js new file mode 100644 index 00000000..6c2be933 --- /dev/null +++ b/test/compress/sandbox.js @@ -0,0 +1,14 @@ +console_log: { + input: { + console.log("%% %s"); + console.log("%% %s", "%s"); + } + expect: { + console.log("%% %s"); + console.log("%% %s", "%s"); + } + expect_stdout: [ + "%% %s", + "% %s", + ] +} diff --git a/test/sandbox.js b/test/sandbox.js index ca1781c6..cb1e18c9 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -52,7 +52,10 @@ exports.run_code = function(code) { "}();", ].join("\n"), { console: { - log: function() { + log: function(msg) { + if (arguments.length == 1 && typeof msg == "string") { + return console.log("%s", msg); + } return console.log.apply(console, [].map.call(arguments, function(arg) { return safe_log(arg, 3); })); From 5ef7cb372abf82f28d7f1b014fd0ddb2ef5bdec8 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 10 Jun 2017 13:55:17 +0800 Subject: [PATCH 16/23] suppress false positives for-in loops (#2080) fixes #2079 --- test/sandbox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox.js b/test/sandbox.js index cb1e18c9..fe2e588e 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -19,7 +19,7 @@ function safe_log(arg, level) { var FUNC_TOSTRING = [ "Function.prototype.toString = Function.prototype.valueOf = function() {", - " var id = 0;", + " var id = 100000;", " return function() {", ' if (this === Array) return "[Function: Array]";', ' if (this === Object) return "[Function: Object]";', From 2bdc8802ddd913a8b3b921426e898fc2f2257265 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 13 Jun 2017 01:40:14 +0800 Subject: [PATCH 17/23] fix variable accounting in `inline` (#2085) fixes #2084 --- lib/compress.js | 34 ++++++++++++++++++++------------- test/compress/functions.js | 39 ++++++++++++++++++++++++++++++++++++++ test/compress/issue-281.js | 14 +++++++------- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 5f2ac2b0..4d768781 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3171,16 +3171,16 @@ merge(Compressor.prototype, { && !exp.uses_arguments && !exp.uses_eval && !self.has_pure_annotation(compressor)) { - var body; + var value; if (stat instanceof AST_Return) { - body = stat.value.clone(true); + value = stat.value.clone(true); } else if (stat instanceof AST_SimpleStatement) { - body = []; - merge_sequence(body, stat.body.clone(true)); - merge_sequence(body, make_node(AST_Undefined, self)); - body = make_sequence(self, body); + value = make_node(AST_UnaryPrefix, stat, { + operator: "void", + expression: stat.body.clone(true) + }); } - if (body) { + if (value) { var fn = exp.clone(); fn.argnames = []; fn.body = []; @@ -3200,17 +3200,25 @@ merge(Compressor.prototype, { })); } fn.body.push(make_node(AST_Return, self, { - value: body + value: value })); - body = fn.transform(compressor).body; + var body = fn.transform(compressor).body; if (body.length == 0) return make_node(AST_Undefined, self); if (body.length == 1 && body[0] instanceof AST_Return) { - if (!body[0].value) return make_node(AST_Undefined, self); - body = best_of(compressor, body[0].value, self); + value = body[0].value; + if (!value) return make_node(AST_Undefined, self); + value.walk(new TreeWalker(function(node) { + if (value === self) return true; + if (node instanceof AST_SymbolRef && exp.variables.has(node.name)) { + value = self; + return true; + } + })); + if (value !== self) value = best_of(compressor, value, self); } else { - body = self; + value = self; } - if (body !== self) return body; + if (value !== self) return value; } } if (compressor.option("side_effects") && all(exp.body, is_empty)) { diff --git a/test/compress/functions.js b/test/compress/functions.js index 180bb11a..1359670e 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -297,3 +297,42 @@ webkit: { expect_exact: "console.log((function(){1+1}).a=1);" expect_stdout: "1" } + +issue_2084: { + options = { + collapse_vars: true, + conditionals: true, + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + var c = 0; + !function() { + !function(c) { + c = 1 + c; + var c = 0; + function f14(a_1) { + if (c = 1 + c, 0 !== 23..toString()) + c = 1 + c, a_1 && (a_1[0] = 0); + } + f14(); + }(-1); + }(); + console.log(c); + } + expect: { + var c = 0; + !function(c) { + c = 1 + c, + c = 1 + (c = 0), + 0 !== 23..toString() && (c = 1 + c); + }(-1), + console.log(c); + } + expect_stdout: "0" +} diff --git a/test/compress/issue-281.js b/test/compress/issue-281.js index 7a6c03bc..9b8c8bfd 100644 --- a/test/compress/issue-281.js +++ b/test/compress/issue-281.js @@ -151,7 +151,7 @@ negate_iife_4: { })(); } expect: { - t ? console.log(true) : console.log(false), console.log("something"), void 0; + t ? console.log(true) : console.log(false), void console.log("something"); } } @@ -174,7 +174,7 @@ negate_iife_5: { })(); } expect: { - t ? foo(true) : bar(false), console.log("something"), void 0; + t ? foo(true) : bar(false), void console.log("something"); } } @@ -197,7 +197,7 @@ negate_iife_5_off: { })(); } expect: { - t ? foo(true) : bar(false), console.log("something"), void 0; + t ? foo(true) : bar(false), void console.log("something"); } } @@ -214,7 +214,7 @@ issue_1254_negate_iife_true: { }; })()(); } - expect_exact: 'console.log("test"),void 0;' + expect_exact: 'void console.log("test");' expect_stdout: true } @@ -231,7 +231,7 @@ issue_1254_negate_iife_nested: { }; })()()()()(); } - expect_exact: '(console.log("test"),void 0)()()();' + expect_exact: '(void console.log("test"))()()();' } negate_iife_issue_1073: { @@ -382,7 +382,7 @@ wrap_iife: { }; })()(); } - expect_exact: 'console.log("test"),void 0;' + expect_exact: 'void console.log("test");' } wrap_iife_in_expression: { @@ -416,7 +416,7 @@ wrap_iife_in_return_call: { })(); })()(); } - expect_exact: '(console.log("test"),void 0)();' + expect_exact: '(void console.log("test"))();' } pure_annotation: { From fed009655623705f44f12f62ef2ecd7d9be86d86 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 13 Jun 2017 04:57:26 +0800 Subject: [PATCH 18/23] allow `expect_stdout` to specify `Error` (#2087) --- test/compress/node_version.js | 28 +++++++++++++++++++++++++++- test/run-tests.js | 20 ++++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/test/compress/node_version.js b/test/compress/node_version.js index ad0bfa15..ef62fd70 100644 --- a/test/compress/node_version.js +++ b/test/compress/node_version.js @@ -1,4 +1,4 @@ -eval_let: { +eval_let_6: { input: { eval("let a;"); console.log(); @@ -10,3 +10,29 @@ eval_let: { expect_stdout: "" node_version: ">=6" } + +eval_let_4: { + input: { + eval("let a;"); + console.log(); + } + expect: { + eval("let a;"); + console.log(); + } + expect_stdout: SyntaxError("Block-scoped declarations (let, const, function, class) not yet supported outside strict mode") + node_version: "4" +} + +eval_let_0: { + input: { + eval("let a;"); + console.log(); + } + expect: { + eval("let a;"); + console.log(); + } + expect_stdout: SyntaxError("Unexpected identifier") + node_version: "<=0.12" +} diff --git a/test/run-tests.js b/test/run-tests.js index 8427d4a8..188532ea 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -294,8 +294,24 @@ function parse_test(file) { if (label.name == "expect_exact" || label.name == "node_version") { test[label.name] = read_string(stat); } else if (label.name == "expect_stdout") { - if (stat.TYPE == "SimpleStatement" && stat.body instanceof U.AST_Boolean) { - test[label.name] = stat.body.value; + if (stat.TYPE == "SimpleStatement") { + var body = stat.body; + if (body instanceof U.AST_Boolean) { + test[label.name] = body.value; + } else if (body instanceof U.AST_Call) { + var ctor = global[body.expression.name]; + assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", { + line: label.start.line, + col: label.start.col + })); + test[label.name] = ctor.apply(null, body.args.map(function(node) { + assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", { + line: label.start.line, + col: label.start.col + })); + return node.value; + })); + } } else { test[label.name] = read_string(stat) + "\n"; } From 3dc9e140e4300f1b3f4f9fa3744bf579163ad204 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 13 Jun 2017 06:21:16 +0800 Subject: [PATCH 19/23] add Node.js 8 to Travis CI (#2086) - explicitly terminate `test/jetstream.js` upon completion - log verbose output from `test/benchmark.js` & `test/jetstream.js` - remove obsolete workaround for Travis CI --- .travis.yml | 1 + test/jetstream.js | 1 + test/mocha/release.js | 6 +----- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index cdee4301..968addfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ node_js: - "0.12" - "4" - "6" + - "8" env: - UGLIFYJS_TEST_ALL=1 matrix: diff --git a/test/jetstream.js b/test/jetstream.js index 8279975c..a1b041a8 100644 --- a/test/jetstream.js +++ b/test/jetstream.js @@ -64,6 +64,7 @@ if (typeof phantom == "undefined") { server.close(); if (code) throw new Error("JetStream failed!"); console.log("JetStream completed successfully."); + process.exit(0); }); }); } diff --git a/test/mocha/release.js b/test/mocha/release.js index 9f894b3d..bd1154ad 100644 --- a/test/mocha/release.js +++ b/test/mocha/release.js @@ -4,13 +4,9 @@ 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" + stdio: [ "ignore", 1, 2 ] }).on("exit", function(code) { - clearInterval(id); assert.strictEqual(code, 0); done(); }); From 82db9188ac2f0a6ffa4c7ab4c7d4c0ade3d93de9 Mon Sep 17 00:00:00 2001 From: Ziad El Khoury Hanna Date: Tue, 13 Jun 2017 10:30:46 +0200 Subject: [PATCH 20/23] fix CLI parsing of `--source-map content` (#2088) fixes #2082 --- bin/uglifyjs | 2 +- test/input/issue-2082/sample.js | 1 + test/input/issue-2082/sample.js.map | 1 + test/mocha/cli.js | 17 +++++++++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 test/input/issue-2082/sample.js create mode 100644 test/input/issue-2082/sample.js.map diff --git a/bin/uglifyjs b/bin/uglifyjs index 52708cc7..f4feb39a 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -371,7 +371,7 @@ function parse_js(flag, constants) { function parse_source_map() { var parse = parse_js("sourceMap", true); return function(value, options) { - var hasContent = options && options.sourceMap && "content" in options.sourceMap; + var hasContent = options && "content" in options; var settings = parse(value, options); if (!hasContent && settings.content && settings.content != "inline") { print_error("INFO: Using input source map: " + settings.content); diff --git a/test/input/issue-2082/sample.js b/test/input/issue-2082/sample.js new file mode 100644 index 00000000..f92e3a10 --- /dev/null +++ b/test/input/issue-2082/sample.js @@ -0,0 +1 @@ +console.log(x); \ No newline at end of file diff --git a/test/input/issue-2082/sample.js.map b/test/input/issue-2082/sample.js.map new file mode 100644 index 00000000..88b42f5a --- /dev/null +++ b/test/input/issue-2082/sample.js.map @@ -0,0 +1 @@ +{"version": 3,"sources": ["index.js"],"mappings": ";"} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 3228e4e3..fa6f1464 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -77,6 +77,23 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("should not consider source map file content as source map file name (issue #2082)", function (done) { + var command = [ + uglifyjscmd, + "test/input/issue-2082/sample.js", + "--source-map", "content=test/input/issue-2082/sample.js.map", + "--source-map", "url=inline", + ].join(" "); + + exec(command, function (err, stdout, stderr) { + if (err) throw err; + + var stderrLines = stderr.split('\n'); + assert.strictEqual(stderrLines[0], 'INFO: Using input source map: test/input/issue-2082/sample.js.map'); + assert.notStrictEqual(stderrLines[1], 'INFO: Using input source map: {"version": 3,"sources": ["index.js"],"mappings": ";"}'); + done(); + }); + }); it("Should work with --keep-fnames (mangle only)", function (done) { var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m'; From 41beae4dd71920d2173bb2bc1efeb1231373c2a8 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 14 Jun 2017 11:53:10 +0800 Subject: [PATCH 21/23] cache web assets between CI runs (#2089) - skip `test/jetstream.js` for `node@0.12` --- .travis.yml | 2 ++ test/benchmark.js | 4 +++- test/fetch.js | 31 +++++++++++++++++++++++++++++++ test/jetstream.js | 12 +++++++++--- test/mocha/release.js | 2 ++ 5 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 test/fetch.js diff --git a/.travis.yml b/.travis.yml index 968addfc..e199df24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,3 +10,5 @@ env: matrix: fast_finish: true sudo: false +cache: + directories: tmp diff --git a/test/benchmark.js b/test/benchmark.js index e27ed2c3..e34f0858 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -4,6 +4,7 @@ "use strict"; var createHash = require("crypto").createHash; +var fetch = require("./fetch"); var fork = require("child_process").fork; var args = process.argv.slice(2); if (!args.length) { @@ -52,7 +53,8 @@ urls.forEach(function(url) { output: 0, log: "" }; - require(url.slice(0, url.indexOf(":"))).get(url, function(res) { + fetch(url, function(err, res) { + if (err) throw err; var uglifyjs = fork("bin/uglifyjs", args, { silent: true }); res.on("data", function(data) { results[url].input += data.length; diff --git a/test/fetch.js b/test/fetch.js new file mode 100644 index 00000000..5ca95bbb --- /dev/null +++ b/test/fetch.js @@ -0,0 +1,31 @@ +var fs = require("fs"); +var path = require("path"); + +try { + fs.mkdirSync("./tmp"); +} catch (e) { + if (e.code != "EEXIST") throw e; +} + +function local(url) { + return path.join("./tmp", encodeURIComponent(url)); +} + +function read(url) { + return fs.createReadStream(local(url)); +} + +module.exports = function(url, callback) { + var result = read(url); + result.on("error", function(e) { + if (e.code != "ENOENT") return callback(e); + require(url.slice(0, url.indexOf(":"))).get(url, function(res) { + if (res.statusCode !== 200) return callback(res); + res.pipe(fs.createWriteStream(local(url)).on("close", function() { + callback(null, read(url)); + })); + }); + }).on("open", function() { + callback(null, result); + }); +}; diff --git a/test/jetstream.js b/test/jetstream.js index a1b041a8..1cc6d4a7 100644 --- a/test/jetstream.js +++ b/test/jetstream.js @@ -23,13 +23,19 @@ if (typeof phantom == "undefined") { } args.push("--timings"); var child_process = require("child_process"); + var fetch = require("./fetch"); var http = require("http"); var server = http.createServer(function(request, response) { request.resume(); var url = site + request.url; - http.get(url, function(res) { - response.writeHead(res.statusCode, { - "Content-Type": res.headers["content-type"] + fetch(url, function(err, res) { + if (err) throw err; + response.writeHead(200, { + "Content-Type": { + css: "text/css", + js: "application/javascript", + png: "image/png" + }[url.slice(url.lastIndexOf(".") + 1)] || "text/html; charset=utf-8" }); if (/\.js$/.test(url)) { var stderr = ""; diff --git a/test/mocha/release.js b/test/mocha/release.js index bd1154ad..1bf6e87b 100644 --- a/test/mocha/release.js +++ b/test/mocha/release.js @@ -1,4 +1,5 @@ var assert = require("assert"); +var semver = require("semver"); var spawn = require("child_process").spawn; if (!process.env.UGLIFYJS_TEST_ALL) return; @@ -32,6 +33,7 @@ describe("test/benchmark.js", function() { }); }); +if (semver.satisfies(process.version, "0.12")) return; describe("test/jetstream.js", function() { this.timeout(20 * 60 * 1000); [ From da2de350c3c59fa90665ed68ae0f41f8d77f50d8 Mon Sep 17 00:00:00 2001 From: kzc Date: Wed, 14 Jun 2017 00:23:03 -0400 Subject: [PATCH 22/23] add comment about quote_style and gzip (#2092) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 039bc638..de2c2c95 100644 --- a/README.md +++ b/README.md @@ -766,7 +766,7 @@ can pass additional arguments that control the code output: - `quote_style` (default `0`) -- preferred quote style for strings (affects quoted property names and directives as well): - `0` -- prefers double quotes, switches to single quotes when there are - more double quotes in the string itself. + more double quotes in the string itself. `0` is best for gzip size. - `1` -- always use single quotes - `2` -- always use double quotes - `3` -- always use the original quotes From 4231f7323e608ae4e4c9461d3d0c1f638ae69546 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 14 Jun 2017 16:45:09 +0800 Subject: [PATCH 23/23] v3.0.16 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1d04fdb..fde98e7a 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": "3.0.15", + "version": "3.0.16", "engines": { "node": ">=0.8.0" },