From a9fc9ddc3371a2ba051bca849db3c4b73983f24d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 6 Mar 2017 17:31:35 +0800 Subject: [PATCH 1/7] suppress semicolons after do/while (#1556) - unless both `beautify` & `screw-ie8` are enabled - deprecate workaround for if-do-while-else fixes #186 --- lib/output.js | 13 +--- test/compress/loops.js | 156 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 10 deletions(-) diff --git a/lib/output.js b/lib/output.js index 10fed135..7e16644d 100644 --- a/lib/output.js +++ b/lib/output.js @@ -784,7 +784,9 @@ function OutputStream(options) { output.with_parens(function(){ self.condition.print(output); }); - output.semicolon(); + if (output.option("beautify") && output.option("screw_ie8")) { + output.semicolon(); + } }); DEFPRINT(AST_While, function(self, output){ output.print("while"); @@ -917,15 +919,6 @@ function OutputStream(options) { // adds the block brackets if needed. if (!self.body) return output.force_semicolon(); - if (self.body instanceof AST_Do) { - // Unconditionally use the if/do-while workaround for all browsers. - // https://github.com/mishoo/UglifyJS/issues/#issue/57 IE - // croaks with "syntax error" on code like this: if (foo) - // do ... while(cond); else ... we need block brackets - // around do/while - make_block(self.body, output); - return; - } var b = self.body; while (true) { if (b instanceof AST_If) { diff --git a/test/compress/loops.js b/test/compress/loops.js index e26dc79f..2d04e235 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -240,3 +240,159 @@ issue_1532: { } } } + +issue_186: { + beautify = { + beautify: false, + screw_ie8: true, + } + input: { + 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: { + beautify = { + beautify: false, + screw_ie8: false, + } + input: { + 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: { + beautify = { + beautify: true, + screw_ie8: true, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: 'var x = 3;\n\nif (foo()) do do alert(x); while (--x); while (x); else bar();' +} + +issue_186_beautify_ie8: { + beautify = { + beautify: true, + screw_ie8: false, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: 'var x = 3;\n\nif (foo()) do do alert(x); while (--x) while (x) else bar();' +} + +issue_186_bracketize: { + beautify = { + beautify: false, + bracketize: true, + screw_ie8: true, + } + input: { + 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_bracketize_ie8: { + beautify = { + beautify: false, + bracketize: true, + screw_ie8: false, + } + input: { + 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_bracketize: { + beautify = { + beautify: true, + bracketize: true, + screw_ie8: true, + } + input: { + var x = 3; + if (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 {\n bar();\n}' +} + +issue_186_beautify_bracketize_ie8: { + beautify = { + beautify: true, + bracketize: true, + screw_ie8: false, + } + input: { + var x = 3; + if (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 {\n bar();\n}' +} From 3ac24219322384539caae803482ea257e7971cf2 Mon Sep 17 00:00:00 2001 From: kzc Date: Mon, 6 Mar 2017 12:42:33 -0500 Subject: [PATCH 2/7] collapse_vars: do not replace a constant in loop condition or init (#1562) --- lib/compress.js | 9 ++++-- test/compress/collapse_vars.js | 50 ++++++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index d9a67c16..8c3fb155 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -535,10 +535,13 @@ merge(Compressor.prototype, { // Constant single use vars can be replaced in any scope. if (var_decl.value.is_constant()) { var ctt = new TreeTransformer(function(node) { - if (node === ref - && !ctt.find_parent(AST_ForIn)) { - return replace_var(node, ctt.parent(), true); + var parent = ctt.parent(); + if (parent instanceof AST_IterationStatement + && (parent.condition === node || parent.init === node)) { + return node; } + if (node === ref) + return replace_var(node, parent, true); }); stat.transform(ctt); continue; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 82d943ff..6d7e2d9f 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -344,9 +344,9 @@ collapse_vars_do_while: { } input: { function f1(y) { - // The constant do-while condition `c` will be replaced. + // The constant do-while condition `c` will not be replaced. var c = 9; - do { } while (c === 77); + do {} while (c === 77); } function f2(y) { // The non-constant do-while condition `c` will not be replaced. @@ -381,7 +381,8 @@ collapse_vars_do_while: { } expect: { function f1(y) { - do ; while (false); + var c = 9; + do ; while (77 === c); } function f2(y) { var c = 5 - y; @@ -418,9 +419,9 @@ collapse_vars_do_while_drop_assign: { } input: { function f1(y) { - // The constant do-while condition `c` will be replaced. + // The constant do-while condition `c` will be not replaced. var c = 9; - do { } while (c === 77); + do {} while (c === 77); } function f2(y) { // The non-constant do-while condition `c` will not be replaced. @@ -455,7 +456,8 @@ collapse_vars_do_while_drop_assign: { } expect: { function f1(y) { - do ; while (false); + var c = 9; + do ; while (77 === c); } function f2(y) { var c = 5 - y; @@ -1309,8 +1311,8 @@ collapse_vars_regexp: { }; } (function(){ - var result, rx = /ab*/g; - while (result = rx.exec('acdabcdeabbb')) + var result, s = "acdabcdeabbb", rx = /ab*/g; + while (result = rx.exec(s)) console.log(result[0]); })(); } @@ -1329,3 +1331,35 @@ issue_1537: { for (k in {prop: 'val'}); } } + +issue_1562: { + options = { + collapse_vars: true, + } + input: { + var v = 1, B = 2; + for (v in objs) f(B); + + var x = 3, C = 10; + while(x + 2) bar(C); + + var y = 4, D = 20; + do bar(D); while(y + 2); + + var z = 5, E = 30; + for (; f(z + 2) ;) bar(E); + } + expect: { + var v = 1; + for (v in objs) f(2); + + var x = 3; + while(x + 2) bar(10); + + var y = 4; + do bar(20); while(y + 2); + + var z = 5; + for (; f(z + 2) ;) bar(30); + } +} From d787d70127af1b8df57eee2274c720d34092aef2 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 7 Mar 2017 03:11:03 +0800 Subject: [PATCH 3/7] avoid substitution of global variables (#1557) - unless `toplevel` is enabled - global `const` works as before --- lib/compress.js | 7 +- test/compress/dead-code.js | 2 + test/compress/reduce_vars.js | 390 ++++++++++++++++++++++++++--------- 3 files changed, 306 insertions(+), 93 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 8c3fb155..696e2056 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -223,6 +223,7 @@ merge(Compressor.prototype, { AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){ var reduce_vars = rescan && compressor.option("reduce_vars"); + var toplevel = compressor.option("toplevel"); var ie8 = !compressor.option("screw_ie8"); var safe_ids = []; push(); @@ -334,7 +335,11 @@ merge(Compressor.prototype, { } function reset_def(def) { - def.fixed = undefined; + if (toplevel || !def.global || def.orig[0] instanceof AST_SymbolConst) { + def.fixed = undefined; + } else { + def.fixed = false; + } def.references = []; def.should_replace = undefined; } diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index cd96d02d..45b7af6e 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -123,6 +123,7 @@ dead_code_const_annotation: { conditionals : true, evaluate : true, reduce_vars : true, + toplevel : true, }; input: { var unused; @@ -172,6 +173,7 @@ dead_code_const_annotation_complex_scope: { conditionals : true, evaluate : true, reduce_vars : true, + toplevel : true, }; input: { var unused_var; diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 70e915d3..27f77fb5 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -6,6 +6,7 @@ reduce_vars: { C : 0 }, reduce_vars : true, + toplevel : true, unused : true } input: { @@ -452,22 +453,26 @@ multi_def_2: { reduce_vars: true, } input: { - if (code == 16) - var bitsLength = 2, bitsOffset = 3, what = len; - else if (code == 17) - var bitsLength = 3, bitsOffset = 3, what = (len = 0); - else if (code == 18) - var bitsLength = 7, bitsOffset = 11, what = (len = 0); - var repeatLength = this.getBits(bitsLength) + bitsOffset; + function f(){ + if (code == 16) + var bitsLength = 2, bitsOffset = 3, what = len; + else if (code == 17) + var bitsLength = 3, bitsOffset = 3, what = (len = 0); + else if (code == 18) + var bitsLength = 7, bitsOffset = 11, what = (len = 0); + var repeatLength = this.getBits(bitsLength) + bitsOffset; + } } expect: { - if (16 == code) - var bitsLength = 2, bitsOffset = 3, what = len; - else if (17 == code) - var bitsLength = 3, bitsOffset = 3, what = (len = 0); - else if (18 == code) - var bitsLength = 7, bitsOffset = 11, what = (len = 0); - var repeatLength = this.getBits(bitsLength) + bitsOffset; + function f(){ + if (16 == code) + var bitsLength = 2, bitsOffset = 3, what = len; + else if (17 == code) + var bitsLength = 3, bitsOffset = 3, what = (len = 0); + else if (18 == code) + var bitsLength = 7, bitsOffset = 11, what = (len = 0); + var repeatLength = this.getBits(bitsLength) + bitsOffset; + } } } @@ -477,12 +482,16 @@ use_before_var: { reduce_vars: true, } input: { - console.log(t); - var t = 1; + function f(){ + console.log(t); + var t = 1; + } } expect: { - console.log(t); - var t = 1; + function f(){ + console.log(t); + var t = 1; + } } } @@ -492,22 +501,20 @@ inner_var_if: { reduce_vars: true, } input: { - function f(){ - return 0; + function f(a){ + if (a) + var t = 1; + if (!t) + console.log(t); } - if (f()) - var t = 1; - if (!t) - console.log(t); } expect: { - function f(){ - return 0; + function f(a){ + if (a) + var t = 1; + if (!t) + console.log(t); } - if (f()) - var t = 1; - if (!t) - console.log(t); } } @@ -517,24 +524,22 @@ inner_var_label: { reduce_vars: true, } input: { - function f(){ - return 1; + function f(a){ + l: { + if (a) break l; + var t = 1; + } + console.log(t); } - l: { - if (f()) break l; - var t = 1; - } - console.log(t); } expect: { - function f(){ - return 1; + function f(a){ + l: { + if (a) break l; + var t = 1; + } + console.log(t); } - l: { - if (f()) break l; - var t = 1; - } - console.log(t); } } @@ -544,22 +549,26 @@ inner_var_for: { reduce_vars: true, } input: { - var a = 1; - x(a, b, d); - for (var b = 2, c = 3; x(a, b, c, d); x(a, b, c, d)) { - var d = 4, e = 5; + function f() { + var a = 1; + x(a, b, d); + for (var b = 2, c = 3; x(a, b, c, d); x(a, b, c, d)) { + var d = 4, e = 5; + x(a, b, c, d, e); + } x(a, b, c, d, e); } - x(a, b, c, d, e) } expect: { - var a = 1; - x(1, b, d); - for (var b = 2, c = 3; x(1, b, 3, d); x(1, b, 3, d)) { - var d = 4, e = 5; + function f() { + var a = 1; + x(1, b, d); + for (var b = 2, c = 3; x(1, b, 3, d); x(1, b, 3, d)) { + var d = 4, e = 5; + x(1, b, 3, d, e); + } x(1, b, 3, d, e); } - x(1, b, 3, d, e); } } @@ -569,24 +578,28 @@ inner_var_for_in_1: { reduce_vars: true, } input: { - var a = 1, b = 2; - for (b in (function() { - return x(a, b, c); - })()) { - var c = 3, d = 4; + 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); } - x(a, b, c, d); } expect: { - var a = 1, b = 2; - for (b in (function() { - return x(1, b, c); - })()) { - var c = 3, d = 4; + function f() { + var a = 1, b = 2; + for (b in (function() { + return x(1, b, c); + })()) { + var c = 3, d = 4; + x(1, b, c, d); + } x(1, b, c, d); } - x(1, b, c, d); } } @@ -596,12 +609,16 @@ inner_var_for_in_2: { reduce_vars: true, } input: { - for (var long_name in {}) - console.log(long_name); + function f() { + for (var long_name in {}) + console.log(long_name); + } } expect: { - for (var long_name in {}) - console.log(long_name); + function f() { + for (var long_name in {}) + console.log(long_name); + } } } @@ -611,20 +628,24 @@ inner_var_catch: { reduce_vars: true, } input: { - try { - a(); - } catch (e) { - var b = 1; + function f() { + try { + a(); + } catch (e) { + var b = 1; + } + console.log(b); } - console.log(b); } expect: { - try { - a(); - } catch (e) { - var b = 1; + function f() { + try { + a(); + } catch (e) { + var b = 1; + } + console.log(b); } - console.log(b); } } @@ -634,14 +655,18 @@ issue_1533_1: { reduce_vars: true, } input: { - var id = ""; - for (id in {break: "me"}) - console.log(id); + function f() { + var id = ""; + for (id in {break: "me"}) + console.log(id); + } } expect: { - var id = ""; - for (id in {break: "me"}) - console.log(id); + function f() { + var id = ""; + for (id in {break: "me"}) + console.log(id); + } } } @@ -651,15 +676,196 @@ issue_1533_2: { reduce_vars: true, } input: { - var id = ""; - for (var id in {break: "me"}) + function f() { + var id = ""; + for (var id in {break: "me"}) + console.log(id); console.log(id); - console.log(id); + } } expect: { - var id = ""; - for (var id in {break: "me"}) + function f() { + var id = ""; + for (var id in {break: "me"}) + console.log(id); console.log(id); - console.log(id); + } + } +} + +toplevel_on: { + options = { + evaluate: true, + reduce_vars: true, + toplevel:true, + unused: true, + } + input: { + var x = 3; + console.log(x); + } + expect: { + console.log(3); + } +} + +toplevel_off: { + options = { + evaluate: true, + reduce_vars: true, + toplevel:false, + unused: true, + } + input: { + var x = 3; + console.log(x); + } + expect: { + var x = 3; + console.log(x); + } +} + +toplevel_on_loops_1: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:true, + unused: true, + } + input: { + function bar() { + console.log("bar:", --x); + } + var x = 3; + do + bar(); + while (x); + } + expect: { + function bar() { + console.log("bar:", --x); + } + var x = 3; + do + bar(); + while (x); + } +} + +toplevel_off_loops_1: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:false, + unused: true, + } + input: { + function bar() { + console.log("bar:", --x); + } + var x = 3; + do + bar(); + while (x); + } + expect: { + function bar() { + console.log("bar:", --x); + } + var x = 3; + do + bar(); + while (x); + } +} + +toplevel_on_loops_2: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:true, + unused: true, + } + input: { + function bar() { + console.log("bar:"); + } + var x = 3; + do + bar(); + while (x); + } + expect: { + function bar() { + console.log("bar:"); + } + for (;;) bar(); + } +} + +toplevel_off_loops_2: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:false, + unused: true, + } + input: { + function bar() { + console.log("bar:"); + } + var x = 3; + do + bar(); + while (x); + } + expect: { + function bar() { + console.log("bar:"); + } + var x = 3; + do + bar(); + while (x); + } +} + +toplevel_on_loops_3: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:true, + unused: true, + } + input: { + var x = 3; + while (x) bar(); + } + expect: { + for (;;) bar(); + } +} + +toplevel_off_loops_3: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:false, + unused: true, + } + input: { + var x = 3; + while (x) bar(); + } + expect: { + var x = 3; + for (;x;) bar(); } } From 8153b7bd8a70ad94666904bd41f12ebd6be684c8 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 7 Mar 2017 15:37:52 +0800 Subject: [PATCH 4/7] transform function calls to IIFEs (#1560) - expose function body to call sites for potential optimisations - suppress substitution of variable used within `AST_Defun` --- lib/ast.js | 10 +- lib/compress.js | 51 ++++++-- test/compress/reduce_vars.js | 238 +++++++++++++++++++++++++++++++++-- test/mocha/cli.js | 2 +- test/mocha/glob.js | 12 +- 5 files changed, 284 insertions(+), 29 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 1f163304..a2125e70 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -91,7 +91,15 @@ var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos }, null); var AST_Node = DEFNODE("Node", "start end", { - clone: function() { + clone: function(deep) { + if (deep) { + var self = this.clone(); + return self.transform(new TreeTransformer(function(node) { + if (node !== self) { + return node.clone(true); + } + })); + } return new this.CTOR(this); }, $documentation: "Base class of all AST nodes", diff --git a/lib/compress.js b/lib/compress.js index 696e2056..8bbbc3f6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -245,7 +245,8 @@ merge(Compressor.prototype, { if (node instanceof AST_SymbolRef) { var d = node.definition(); d.references.push(node); - if (!d.fixed || isModified(node, 0) || !is_safe(d)) { + if (!d.fixed || !is_safe(d) + || is_modified(node, 0, d.fixed instanceof AST_Lambda)) { d.fixed = false; } } @@ -261,6 +262,21 @@ merge(Compressor.prototype, { d.fixed = false; } } + if (node instanceof AST_Defun) { + var d = node.name.definition(); + if (!toplevel && d.global || is_safe(d)) { + d.fixed = false; + } else { + d.fixed = node; + mark_as_safe(d); + } + var save_ids = safe_ids; + safe_ids = []; + push(); + descend(); + safe_ids = save_ids; + return true; + } var iife; if (node instanceof AST_Function && (iife = tw.parent()) instanceof AST_Call @@ -344,13 +360,13 @@ merge(Compressor.prototype, { def.should_replace = undefined; } - function isModified(node, level) { + function is_modified(node, level, func) { var parent = tw.parent(level); if (isLHS(node, parent) - || parent instanceof AST_Call && parent.expression === node) { + || !func && parent instanceof AST_Call && parent.expression === node) { return true; } else if (parent instanceof AST_PropAccess && parent.expression === node) { - return isModified(parent, level + 1); + return !func && is_modified(parent, level + 1); } } }); @@ -1307,14 +1323,7 @@ merge(Compressor.prototype, { def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); - // XXX: AST_Accessor and AST_Function both inherit from AST_Scope, - // which itself inherits from AST_Statement; however, they aren't - // really statements. This could bite in other places too. :-( - // Wish JS had multiple inheritance. - def(AST_Accessor, function(){ - throw def; - }); - def(AST_Function, function(){ + def(AST_Lambda, function(){ throw def; }); function ev(node, compressor) { @@ -2590,6 +2599,24 @@ 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 (def.fixed instanceof AST_Defun) { + def.fixed = make_node(AST_Function, def.fixed, def.fixed).clone(true); + } + if (def.fixed instanceof AST_Function) { + exp = def.fixed; + 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; + } + } + } if (compressor.option("unused") && exp instanceof AST_Function && !exp.uses_arguments diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 27f77fb5..a373de29 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -744,12 +744,11 @@ toplevel_on_loops_1: { while (x); } expect: { - function bar() { - console.log("bar:", --x); - } var x = 3; do - bar(); + (function() { + console.log("bar:", --x); + })(); while (x); } } @@ -800,10 +799,9 @@ toplevel_on_loops_2: { while (x); } expect: { - function bar() { + for (;;) (function() { console.log("bar:"); - } - for (;;) bar(); + })(); } } @@ -869,3 +867,229 @@ toplevel_off_loops_3: { for (;x;) bar(); } } + +defun_reference: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f() { + function g() { + x(); + return a; + } + var a = h(); + var b = 2; + return a + b; + function h() { + y(); + return b; + } + } + } + expect: { + function f() { + function g() { + x(); + return a; + } + var a = h(); + var b = 2; + return a + b; + function h() { + y(); + return b; + } + } + } +} + +defun_inline_1: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + return g(2) + h(); + function g(b) { + return b; + } + function h() { + return h(); + } + } + } + expect: { + function f() { + return function(b) { + return b; + }(2) + h(); + function h() { + return h(); + } + } + } +} + +defun_inline_2: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g(b) { + return b; + } + function h() { + return h(); + } + return g(2) + h(); + } + } + expect: { + function f() { + function h() { + return h(); + } + return function(b) { + return b; + }(2) + h(); + } + } +} + +defun_inline_3: { + options = { + evaluate: true, + passes: 2, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + function f() { + return g(2); + function g(b) { + return b; + } + } + } + expect: { + function f() { + return 2; + } + } +} + +defun_call: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + return g() + h(1) - h(g(), 2, 3); + function g() { + return 4; + } + function h(a) { + return a; + } + } + } + expect: { + function f() { + return 4 + h(1) - h(4); + function h(a) { + return a; + } + } + } +} + +defun_redefine: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g() { + return 1; + } + function h() { + return 2; + } + g = function() { + return 3; + }; + return g() + h(); + } + } + expect: { + function f() { + function g() { + return 1; + } + g = function() { + return 3; + }; + return g() + 2; + } + } +} + +func_inline: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + var g = function() { + return 1; + }; + console.log(g() + h()); + var h = function() { + return 2; + }; + } + } + expect: { + function f() { + console.log(1 + h()); + var h = function() { + return 2; + }; + } + } +} + +func_modified: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f(a) { + function a() { return 1; } + function b() { return 2; } + function c() { return 3; } + b.inject = []; + c = function() { return 4; }; + return a() + b() + c(); + } + } + expect: { + function f(a) { + function b() { return 2; } + function c() { return 3; } + b.inject = []; + c = function() { return 4; }; + return 1 + 2 + c(); + } + } +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index c07eeee7..e8e07cb5 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -82,7 +82,7 @@ describe("bin/uglifyjs", function () { }); }); it("Should work with --keep-fnames (mangle & compress)", function (done) { - var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c'; + var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c unused=false'; exec(command, function (err, stdout) { if (err) throw err; diff --git a/test/mocha/glob.js b/test/mocha/glob.js index c2fc9464..30313656 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -3,17 +3,13 @@ var assert = require("assert"); describe("minify() with input file globs", function() { it("minify() with one input file glob string.", function() { - var result = Uglify.minify("test/input/issue-1242/foo.*", { - compress: { collapse_vars: true } - }); + var result = Uglify.minify("test/input/issue-1242/foo.*"); assert.strictEqual(result.code, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);'); }); it("minify() with an array of one input file glob.", function() { var result = Uglify.minify([ "test/input/issue-1242/b*.es5", - ], { - compress: { collapse_vars: true } - }); + ]); assert.strictEqual(result.code, 'function bar(n){return 3*n}function baz(n){return n/2}'); }); it("minify() with an array of multiple input file globs.", function() { @@ -21,8 +17,8 @@ describe("minify() with input file globs", function() { "test/input/issue-1242/???.es5", "test/input/issue-1242/*.js", ], { - compress: { collapse_vars: true } + compress: { toplevel: true } }); - assert.strictEqual(result.code, 'function bar(n){return 3*n}function baz(n){return n/2}function foo(n){print("Foo:",2*n)}var print=console.log.bind(console);print("qux",bar(3),baz(12)),foo(11);'); + assert.strictEqual(result.code, 'var print=console.log.bind(console);print("qux",function(n){return 3*n}(3),function(n){return n/2}(12)),function(n){print("Foo:",2*n)}(11);'); }); }); From 8a8a94a596f57981e2cc65ac37921b154a9992b7 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 7 Mar 2017 18:38:27 +0800 Subject: [PATCH 5/7] fix deep cloning of labels (#1565) `AST_Label.references` get `.initialize()` to `[]` every time after `.clone()` So walk down the tree to pick up the cloned `AST_LoopControl` pieces and put it back together. --- lib/ast.js | 19 ++++++++++++++++++- test/compress/reduce_vars.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/ast.js b/lib/ast.js index a2125e70..f7ab52e2 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -91,7 +91,7 @@ var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos }, null); var AST_Node = DEFNODE("Node", "start end", { - clone: function(deep) { + _clone: function(deep) { if (deep) { var self = this.clone(); return self.transform(new TreeTransformer(function(node) { @@ -102,6 +102,9 @@ var AST_Node = DEFNODE("Node", "start end", { } return new this.CTOR(this); }, + clone: function(deep) { + return this._clone(deep); + }, $documentation: "Base class of all AST nodes", $propdoc: { start: "[AST_Token] The first token of this node", @@ -207,6 +210,20 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { this.label._walk(visitor); this.body._walk(visitor); }); + }, + clone: function(deep) { + var node = this._clone(deep); + if (deep) { + var refs = node.label.references; + var label = this.label; + node.walk(new TreeWalker(function(node) { + if (node instanceof AST_LoopControl + && node.label && node.label.thedef === label) { + refs.push(node); + } + })); + } + return node; } }, AST_StatementWithBody); diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index a373de29..53e28152 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1093,3 +1093,32 @@ func_modified: { } } } + +defun_label: { + options = { + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + !function() { + function f(a) { + L: { + if (a) break L; + return 1; + } + } + console.log(f(2)); + }(); + } + expect: { + !function() { + console.log(function(a) { + L: { + if (a) break L; + return 1; + } + }(2)); + }(); + } +} From 65c848cc6f13cb5ae3bc1c0fb5fbb320ed2d0200 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 7 Mar 2017 19:25:12 +0800 Subject: [PATCH 6/7] include benchmark.js in test suite (#1564) - report file sizes and overall run time - exit with non-zero code upon error --- test/benchmark.js | 45 ++++++++++++++++++++++++++++++++++------- test/mocha/benchmark.js | 24 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 test/mocha/benchmark.js diff --git a/test/benchmark.js b/test/benchmark.js index dc176a88..c150e5cf 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -24,26 +24,57 @@ var results = {}; var remaining = 2 * urls.length; function done() { if (!--remaining) { + var failures = []; urls.forEach(function(url) { + var info = results[url]; console.log(); console.log(url); - console.log(results[url].time); - console.log("SHA1:", results[url].sha1); + console.log(info.log); + var elapsed = 0; + info.log.replace(/: ([0-9]+\.[0-9]{3})s/g, function(match, time) { + elapsed += parseFloat(time); + }); + console.log("Run-time:", elapsed.toFixed(3), "s"); + console.log("Original:", info.input, "bytes"); + console.log("Uglified:", info.output, "bytes"); + console.log("SHA1 sum:", info.sha1); + if (info.code) { + failures.push(url); + } }); + if (failures.length) { + console.error("Benchmark failed:"); + failures.forEach(function(url) { + console.error(url); + }); + process.exit(1); + } } } urls.forEach(function(url) { - results[url] = { time: "" }; + results[url] = { + input: 0, + output: 0, + log: "" + }; require(url.slice(0, url.indexOf(":"))).get(url, function(res) { var uglifyjs = fork("bin/uglifyjs", args, { silent: true }); - res.pipe(uglifyjs.stdin); - uglifyjs.stdout.pipe(createHash("sha1")).on("data", function(data) { + res.on("data", function(data) { + results[url].input += data.length; + }).pipe(uglifyjs.stdin); + uglifyjs.stdout.on("data", function(data) { + results[url].output += data.length; + }).pipe(createHash("sha1")).on("data", function(data) { results[url].sha1 = data.toString("hex"); done(); }); uglifyjs.stderr.setEncoding("utf8"); uglifyjs.stderr.on("data", function(data) { - results[url].time += data; - }).on("end", done) + results[url].log += data; + }); + uglifyjs.on("exit", function(code) { + results[url].code = code; + done(); + }); }); }); diff --git a/test/mocha/benchmark.js b/test/mocha/benchmark.js new file mode 100644 index 00000000..c2c7c02c --- /dev/null +++ b/test/mocha/benchmark.js @@ -0,0 +1,24 @@ +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(); + }); + }); + }); +}); From 144052ca491144c65adc9d081b17ce100e3de59d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 7 Mar 2017 19:58:41 +0800 Subject: [PATCH 7/7] v2.8.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 31aaf951..086b6910 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.7", + "version": "2.8.8", "engines": { "node": ">=0.8.0" },