From fe647b083e0def2bf6445534c7017baed09541a9 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 17 Oct 2017 01:18:55 +0800 Subject: [PATCH 1/9] account for side-effects from `AST_This` in `collapse_vars` (#2365) --- lib/compress.js | 6 ++-- test/compress/collapse_vars.js | 52 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index c3876f9d..5e391aba 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -950,9 +950,11 @@ merge(Compressor.prototype, { scope = save_scope; return true; } - if (node instanceof AST_SymbolRef || node instanceof AST_PropAccess) { + if (node instanceof AST_PropAccess + || node instanceof AST_SymbolRef + || node instanceof AST_This) { var sym = get_symbol(node); - if (sym instanceof AST_SymbolRef) { + if (sym instanceof AST_SymbolRef || node instanceof AST_This) { lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent()); } } diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 7d66f7c6..ec94e9f0 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2600,3 +2600,55 @@ prop_side_effects_2: { "2", ] } + +issue_2364: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + console.log(function(a) { + var b = a.f; + a.f++; + return b; + }({ f: 1 })); + console.log(function() { + var a = { f: 1 }, b = a.f; + a.f++; + return b; + }()); + console.log({ + f: 1, + g: function() { + var b = this.f; + this.f++; + return b; + } + }.g()); + } + expect: { + console.log(function(a) { + var b = a.f; + a.f++; + return b; + }({ f: 1 })); + console.log(function() { + var a = { f: 1 }, b = a.f; + a.f++; + return b; + }()); + console.log({ + f: 1, + g: function() { + var b = this.f; + this.f++; + return b; + } + }.g()); + } + expect_stdout: [ + "1", + "1", + "1", + ] +} From f2b9c11e2a0ab3597f798cf85770e24733609b1f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 17 Oct 2017 18:33:03 +0800 Subject: [PATCH 2/9] fix `AST_PropAccess` in `collapse_vars` (#2370) fixes #2364 --- lib/compress.js | 4 ++- test/compress/collapse_vars.js | 61 +++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 5e391aba..3909c65c 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -838,7 +838,9 @@ merge(Compressor.prototype, { var sym; if (node instanceof AST_Call || node instanceof AST_Exit - || node instanceof AST_PropAccess && node.has_side_effects(compressor) + || node instanceof AST_PropAccess + && (node.has_side_effects(compressor) + || get_symbol(node).name in lvalues) || node instanceof AST_SymbolRef && (lvalues[node.name] || side_effects && !references_in_scope(node.definition())) diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index ec94e9f0..baa18ea1 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2601,7 +2601,7 @@ prop_side_effects_2: { ] } -issue_2364: { +issue_2365: { options = { collapse_vars: true, pure_getters: true, @@ -2652,3 +2652,62 @@ issue_2364: { "1", ] } + +issue_2364_1: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function inc(obj) { + return obj.count++; + } + function foo() { + var first = arguments[0]; + var result = inc(first); + return foo.amount = first.count, result; + } + var data = { + count: 0, + }; + var answer = foo(data); + console.log(foo.amount, answer); + } + expect: { + function inc(obj) { + return obj.count++; + } + function foo() { + var first = arguments[0]; + var result = inc(first); + return foo.amount = first.count, result; + } + var data = { + count: 0 + }; + var answer = foo(data); + console.log(foo.amount, answer); + } + expect_stdout: "1 0" +} + +issue_2364_2: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function callValidate() { + var validate = compilation.validate; + var result = validate.apply(null, arguments); + return callValidate.errors = validate.errors, result; + } + } + expect: { + function callValidate() { + var validate = compilation.validate; + var result = validate.apply(null, arguments); + return callValidate.errors = validate.errors, result; + } + } +} From 0d2fe8e3efdb096af4044b011dd7e2e463581400 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 17 Oct 2017 22:59:15 +0800 Subject: [PATCH 3/9] fix `AST_PropAccess` in `collapse_vars` (take 2) (#2372) fixes #2364 --- lib/compress.js | 3 +- test/compress/collapse_vars.js | 72 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 3909c65c..488cc6fc 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -839,8 +839,7 @@ merge(Compressor.prototype, { if (node instanceof AST_Call || node instanceof AST_Exit || node instanceof AST_PropAccess - && (node.has_side_effects(compressor) - || get_symbol(node).name in lvalues) + && (side_effects || node.has_side_effects(compressor)) || node instanceof AST_SymbolRef && (lvalues[node.name] || side_effects && !references_in_scope(node.definition())) diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index baa18ea1..c115763c 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2711,3 +2711,75 @@ issue_2364_2: { } } } + +issue_2364_3: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function inc(obj) { + return obj.count++; + } + function foo(bar) { + var result = inc(bar); + return foo.amount = bar.count, result; + } + var data = { + count: 0, + }; + var answer = foo(data); + console.log(foo.amount, answer); + } + expect: { + function inc(obj) { + return obj.count++; + } + function foo(bar) { + var result = inc(bar); + return foo.amount = bar.count, result; + } + var data = { + count: 0, + }; + var answer = foo(data); + console.log(foo.amount, answer); + } + expect_stdout: "1 0" +} + +issue_2364_4: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function inc(obj) { + return obj.count++; + } + function foo(bar, baz) { + var result = inc(bar); + return foo.amount = baz.count, result; + } + var data = { + count: 0, + }; + var answer = foo(data, data); + console.log(foo.amount, answer); + } + expect: { + function inc(obj) { + return obj.count++; + } + function foo(bar, baz) { + var result = inc(bar); + return foo.amount = baz.count, result; + } + var data = { + count: 0, + }; + var answer = foo(data, data); + console.log(foo.amount, answer); + } + expect_stdout: "1 0" +} From c1346e06b70806995f3b75919d35bf5348ce88ed Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 17 Oct 2017 23:25:45 +0800 Subject: [PATCH 4/9] clean up lazy operator detection (#2373) --- lib/compress.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 488cc6fc..3ab54f16 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -404,8 +404,7 @@ merge(Compressor.prototype, { pop(); return true; } - if (node instanceof AST_Binary - && (node.operator == "&&" || node.operator == "||")) { + if (node instanceof AST_Binary && lazy_op(node.operator)) { node.left.walk(tw); push(); node.right.walk(tw); @@ -844,8 +843,7 @@ merge(Compressor.prototype, { && (lvalues[node.name] || side_effects && !references_in_scope(node.definition())) || (sym = lhs_or_def(node)) && get_symbol(sym).name in lvalues - || parent instanceof AST_Binary - && (parent.operator == "&&" || parent.operator == "||") + || parent instanceof AST_Binary && lazy_op(parent.operator) || parent instanceof AST_Case || parent instanceof AST_Conditional || parent instanceof AST_For @@ -1434,9 +1432,10 @@ merge(Compressor.prototype, { return member(this.operator, unary_bool); }); def(AST_Binary, function(){ - return member(this.operator, binary_bool) || - ( (this.operator == "&&" || this.operator == "||") && - this.left.is_boolean() && this.right.is_boolean() ); + return member(this.operator, binary_bool) + || lazy_op(this.operator) + && this.left.is_boolean() + && this.right.is_boolean(); }); def(AST_Conditional, function(){ return this.consequent.is_boolean() && this.alternative.is_boolean(); @@ -1505,6 +1504,7 @@ merge(Compressor.prototype, { node.DEFMETHOD("is_string", func); }); + var lazy_op = makePredicate("&& ||"); var unary_side_effects = makePredicate("delete ++ --"); function is_lhs(node, parent) { @@ -2701,14 +2701,12 @@ merge(Compressor.prototype, { def(AST_Binary, function(compressor, first_in_statement){ var right = this.right.drop_side_effect_free(compressor); if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement); - switch (this.operator) { - case "&&": - case "||": + if (lazy_op(this.operator)) { if (right === this.right) return this; var node = this.clone(); node.right = right; return node; - default: + } else { var left = this.left.drop_side_effect_free(compressor, first_in_statement); if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement); return make_sequence(this, [ left, right ]); @@ -3574,7 +3572,7 @@ merge(Compressor.prototype, { } if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) { if (cdr.left.is_constant()) { - if (cdr.operator == "||" || cdr.operator == "&&") { + if (lazy_op(cdr.operator)) { expressions[++i] = expressions[j]; break; } @@ -4074,8 +4072,7 @@ merge(Compressor.prototype, { // "x" + (y + "z")==> "x" + y + "z" if (self.right instanceof AST_Binary && self.right.operator == self.operator - && (self.operator == "&&" - || self.operator == "||" + && (lazy_op(self.operator) || (self.operator == "+" && (self.right.left.is_string(compressor) || (self.left.is_string(compressor) From 7e5b5cac97a71d0eb0d23f0cba7554e8a9eec179 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 18 Oct 2017 02:54:51 +0800 Subject: [PATCH 5/9] fix `AST_PropAccess` in `collapse_vars` (take 3) (#2375) Suppress scanning beyond assignment to `a.b` --- lib/compress.js | 28 ++---- test/compress/collapse_vars.js | 168 +++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 21 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 3ab54f16..b89e0df9 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -838,11 +838,12 @@ merge(Compressor.prototype, { if (node instanceof AST_Call || node instanceof AST_Exit || node instanceof AST_PropAccess - && (side_effects || node.has_side_effects(compressor)) + && (side_effects || node.expression.may_throw_on_access(compressor)) || node instanceof AST_SymbolRef && (lvalues[node.name] || side_effects && !references_in_scope(node.definition())) - || (sym = lhs_or_def(node)) && get_symbol(sym).name in lvalues + || (sym = lhs_or_def(node)) + && (sym instanceof AST_PropAccess || sym.name in lvalues) || parent instanceof AST_Binary && lazy_op(parent.operator) || parent instanceof AST_Case || parent instanceof AST_Conditional @@ -933,29 +934,14 @@ merge(Compressor.prototype, { } } - function get_symbol(node) { - while (node instanceof AST_PropAccess) node = node.expression; - return node; - } - function get_lvalues(expr) { var lvalues = Object.create(null); if (expr instanceof AST_Unary) return lvalues; - var scope; var tw = new TreeWalker(function(node, descend) { - if (node instanceof AST_Scope) { - var save_scope = scope; - descend(); - scope = save_scope; - return true; - } - if (node instanceof AST_PropAccess - || node instanceof AST_SymbolRef - || node instanceof AST_This) { - var sym = get_symbol(node); - if (sym instanceof AST_SymbolRef || node instanceof AST_This) { - lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent()); - } + var sym = node; + while (sym instanceof AST_PropAccess) sym = sym.expression; + if (sym instanceof AST_SymbolRef || sym instanceof AST_This) { + lvalues[sym.name] = lvalues[sym.name] || is_lhs(node, tw.parent()); } }); expr[expr instanceof AST_Assign ? "right" : "value"].walk(tw); diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index c115763c..13fff97a 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2783,3 +2783,171 @@ issue_2364_4: { } expect_stdout: "1 0" } + +issue_2364_5: { + options = { + collapse_vars: true, + evaluate: true, + pure_getters: true, + properties: true, + reduce_vars: true, + unused: true, + } + input: { + function f0(o, a, h) { + var b = 3 - a; + var obj = o; + var seven = 7; + var prop = 'run'; + var t = obj[prop](b)[seven] = h; + return t; + } + } + expect: { + function f0(o, a, h) { + return o.run(3 - a)[7] = h; + } + } +} + +issue_2364_6: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function f(a, b) { + var c = a.p; + b.p = "FAIL"; + return c; + } + var o = { + p: "PASS" + } + console.log(f(o, o)); + } + expect: { + function f(a, b) { + var c = a.p; + b.p = "FAIL"; + return c; + } + var o = { + p: "PASS" + } + console.log(f(o, o)); + } + expect_stdout: "PASS" +} + +issue_2364_7: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function f(a, b) { + var c = a.p; + b.f(); + return c; + } + var o = { + p: "PASS", + f: function() { + this.p = "FAIL"; + } + } + console.log(f(o, o)); + } + expect: { + function f(a, b) { + var c = a.p; + b.f(); + return c; + } + var o = { + p: "PASS", + f: function() { + this.p = "FAIL"; + } + } + console.log(f(o, o)); + } + expect_stdout: "PASS" +} + +issue_2364_8: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function f(a, b, c) { + var d = a[b.f = function() { + return "PASS"; + }]; + return c.f(d); + } + var o = { + f: function() { + return "FAIL"; + } + }; + console.log(f({}, o, o)); + } + expect: { + function f(a, b, c) { + var d = a[b.f = function() { + return "PASS"; + }]; + return c.f(d); + } + var o = { + f: function() { + return "FAIL"; + } + }; + console.log(f({}, o, o)); + } + expect_stdout: "PASS" +} + +issue_2364_9: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function f(a, b) { + var d = a(); + return b.f(d); + } + var o = { + f: function() { + return "FAIL"; + } + }; + console.log(f(function() { + o.f = function() { + return "PASS"; + }; + }, o)); + } + expect: { + function f(a, b) { + var d = a(); + return b.f(d); + } + var o = { + f: function() { + return "FAIL"; + } + }; + console.log(f(function() { + o.f = function() { + return "PASS"; + }; + }, o)); + } + expect_stdout: "PASS" +} From 0f2ef3367ce980f20cb4f72e8978aec5d1b8a8f2 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 19 Oct 2017 04:52:00 +0800 Subject: [PATCH 6/9] enhance `collapse_vars` around lazy operations (#2369) --- lib/compress.js | 11 +++--- test/compress/collapse_vars.js | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index b89e0df9..374456ac 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -778,6 +778,7 @@ merge(Compressor.prototype, { // Locate symbols which may execute code outside of scanning range var lvalues = get_lvalues(candidate); if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false; + var one_off = lhs instanceof AST_Symbol && lhs.definition().references.length == 1; var side_effects = value_has_side_effects(candidate); var hit = candidate.name instanceof AST_SymbolFunarg; var abort = false, replaced = false; @@ -844,11 +845,11 @@ merge(Compressor.prototype, { || side_effects && !references_in_scope(node.definition())) || (sym = lhs_or_def(node)) && (sym instanceof AST_PropAccess || sym.name in lvalues) - || parent instanceof AST_Binary && lazy_op(parent.operator) - || parent instanceof AST_Case - || parent instanceof AST_Conditional - || parent instanceof AST_For - || parent instanceof AST_If) { + || (side_effects || !one_off) + && (parent instanceof AST_Binary && lazy_op(parent.operator) + || parent instanceof AST_Case + || parent instanceof AST_Conditional + || parent instanceof AST_If)) { if (!(node instanceof AST_Scope)) descend(node, tt); abort = true; return node; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 13fff97a..52b2ddf8 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2951,3 +2951,68 @@ issue_2364_9: { } expect_stdout: "PASS" } + +pure_getters_chain: { + options = { + collapse_vars: true, + pure_getters: true, + } + input: { + function o(t, r) { + var a = t[1], s = t[2], o = t[3], i = t[5]; + return a <= 23 && s <= 59 && o <= 59 && (!r || i); + } + console.log(o([ , 23, 59, 59, , 42], 1)); + } + expect: { + function o(t, r) { + return t[1] <= 23 && t[2] <= 59 && t[3] <= 59 && (!r || t[5]); + } + console.log(o([ , 23, 59, 59, , 42], 1)); + } + expect_stdout: "42" +} + +conditional_1: { + options = { + collapse_vars: true, + } + input: { + function f(a, b) { + var c = ""; + var d = b ? ">" : "<"; + if (a) c += "="; + return c += d; + } + console.log(f(0, 0), f(0, 1), f(1, 0), f(1, 1)); + } + expect: { + function f(a, b) { + var c = ""; + if (a) c += "="; + return c += b ? ">" : "<"; + } + console.log(f(0, 0), f(0, 1), f(1, 0), f(1, 1)); + } + expect_stdout: "< > =< =>" +} + +conditional_2: { + options = { + collapse_vars: true, + } + input: { + function f(a, b) { + var c = a + 1, d = a + 2; + return b ? c : d; + } + console.log(f(3, 0), f(4, 1)); + } + expect: { + function f(a, b) { + return b ? a + 1 : a + 2; + } + console.log(f(3, 0), f(4, 1)); + } + expect_stdout: "5 5" +} From 9f4b98f8e461dabf3a3c97c247d9c72f981aa28d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 19 Oct 2017 23:02:27 +0800 Subject: [PATCH 7/9] backport #2374 (#2376) --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 64bbafc4..f31486e8 100644 --- a/README.md +++ b/README.md @@ -515,7 +515,6 @@ if (result.error) throw result.error; ```javascript { - warnings: false, parse: { // parse options }, @@ -538,6 +537,7 @@ if (result.error) throw result.error; nameCache: null, // or specify a name cache object toplevel: false, ie8: false, + warnings: false, } ``` @@ -645,6 +645,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u compressor from discarding unused function arguments. You need this for code which relies on `Function.length`. +- `keep_fnames` (default: `false`) -- Pass `true` to prevent the + compressor from discarding function names. Useful for code relying on + `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). + - `keep_infinity` (default: `false`) -- default `false`. Pass `true` to prevent `Infinity` from being compressed into `1/0`, which may cause performance issues on Chrome. @@ -709,6 +713,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u `foo === void 0`. Note: recommend to set this value to `false` for IE10 and earlier versions due to known issues. +- `unsafe` (default: `false`) -- apply "unsafe" transformations (discussion below) + - `unsafe_comps` (default: `false`) -- Reverse `<` and `<=` to `>` and `>=` to allow improved compression. This might be unsafe when an at least one of two operands is an object with computed values due the use of methods like `get`, @@ -716,8 +722,6 @@ 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` (default: `false`) -- apply "unsafe" transformations (discussion below) - - `unsafe_Func` (default: `false`) -- compress and mangle `Function(args, code)` when both `args` and `code` are string literals. @@ -738,15 +742,19 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u ## Mangle options +- `eval` (default `false`). Pass `true` to mangle names visible in scopes + where `eval` or `with` are used. + +- `keep_fnames` (default `false`). Pass `true` to not mangle function names. + Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames` + [compress option](#compress-options). + - `reserved` (default `[]`). Pass an array of identifiers that should be excluded from mangling. Example: `["foo", "bar"]`. - `toplevel` (default `false`). Pass `true` to mangle names declared in the top level scope. -- `eval` (default `false`). Pass `true` to mangle names visible in scopes - where `eval` or `with` are used. - Examples: ```javascript From c927cea6322788b654128f9bbbf3a06441b964c2 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 21 Oct 2017 04:08:26 +0800 Subject: [PATCH 8/9] `unsafe` fix-ups for #2351 (#2379) --- lib/compress.js | 55 +++++++++++++++++-------------- test/compress/reduce_vars.js | 64 ++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 374456ac..1f58b390 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -151,7 +151,7 @@ merge(Compressor.prototype, { var last_count = 1 / 0; for (var pass = 0; pass < passes; pass++) { if (pass > 0 || this.option("reduce_vars")) - node.reset_opt_flags(this, true); + node.reset_opt_flags(this); node = node.transform(this); if (passes > 1) { var count = 0; @@ -283,8 +283,9 @@ merge(Compressor.prototype, { self.transform(tt); }); - AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan) { - var reduce_vars = rescan && compressor.option("reduce_vars"); + AST_Node.DEFMETHOD("reset_opt_flags", function(compressor) { + var reduce_vars = compressor.option("reduce_vars"); + var unused = compressor.option("unused"); // Stack of look-up tables to keep track of whether a `SymbolDef` has been // properly assigned before use: // - `push()` & `pop()` when visiting conditional branches @@ -308,16 +309,31 @@ merge(Compressor.prototype, { if (node instanceof AST_SymbolRef) { var d = node.definition(); d.references.push(node); - if (d.fixed === undefined || !safe_to_read(d) - || is_modified(node, 0, is_immutable(node))) { + if (d.fixed === undefined || !safe_to_read(d) || d.single_use == "m") { d.fixed = false; } else { - var parent = tw.parent(); - if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right - || parent instanceof AST_Call && node !== parent.expression - || parent instanceof AST_Return && node === parent.value && node.scope !== d.scope - || parent instanceof AST_VarDef && node === parent.value) { - d.escaped = true; + var value = node.fixed_value(); + if (unused) { + d.single_use = value + && d.references.length == 1 + && loop_ids[d.id] === in_loop + && d.scope === node.scope + && value.is_constant_expression(); + } + if (is_modified(node, 0, is_immutable(value))) { + if (d.single_use) { + d.single_use = "m"; + } else { + d.fixed = false; + } + } else { + var parent = tw.parent(); + if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right + || parent instanceof AST_Call && node !== parent.expression + || parent instanceof AST_Return && node === parent.value && node.scope !== d.scope + || parent instanceof AST_VarDef && node === parent.value) { + d.escaped = true; + } } } } @@ -550,20 +566,8 @@ merge(Compressor.prototype, { def.single_use = undefined; } - function is_immutable(node) { - var value = node.fixed_value(); - if (!value) return false; - if (value.is_constant()) return true; - if (compressor.option("unused")) { - var d = node.definition(); - if (d.single_use === undefined) { - d.single_use = loop_ids[d.id] === in_loop - && d.scope === node.scope - && value.is_constant_expression(); - } - if (d.references.length == 1 && d.single_use) return true; - } - return value instanceof AST_Lambda; + function is_immutable(value) { + return value && (value.is_constant() || value instanceof AST_Lambda); } function is_modified(node, level, immutable) { @@ -4107,6 +4111,7 @@ merge(Compressor.prototype, { d.fixed = fixed = make_node(AST_Function, fixed, fixed); } if (compressor.option("unused") + && fixed && d.references.length == 1 && (d.single_use || fixed instanceof AST_Function && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index d93f0cd1..a03bc1c8 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1190,10 +1190,10 @@ defun_label: { !function() { console.log(function(a) { L: { - if (a) break L; + if (2) break L; return 1; } - }(2)); + }()); }(); } expect_stdout: true @@ -2894,3 +2894,63 @@ array_forin_2: { } expect_stdout: "3" } + +const_expr_1: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2 + }; + o.a++; + console.log(o.a, o.b); + } + expect: { + var o = { + a: 1, + b: 2 + }; + o.a++; + console.log(o.a, o.b); + } + expect_stdout: "2 2" +} + +const_expr_2: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + Object.prototype.c = function() { + this.a++; + }; + var o = { + a: 1, + b: 2 + }; + o.c(); + console.log(o.a, o.b); + } + expect: { + Object.prototype.c = function() { + this.a++; + }; + var o = { + a: 1, + b: 2 + }; + o.c(); + console.log(o.a, o.b); + } + expect_stdout: "2 2" +} From 96439ca24690782dc67c5cc3768226bf96c38885 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 22 Oct 2017 00:27:26 +0800 Subject: [PATCH 9/9] v3.1.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d40030bf..5b93d84b 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.1.4", + "version": "3.1.5", "engines": { "node": ">=0.8.0" },