From 0913db8c84c670a2d34fd312295e54c679807558 Mon Sep 17 00:00:00 2001 From: Wiktor Kwapisiewicz Date: Wed, 30 Nov 2016 14:54:15 +0100 Subject: [PATCH 1/8] Add note about name mangling when using --mangle-props=unquoted (#1314) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index eb3305b6..1be63009 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,17 @@ of mangled property names. Using the name cache is not necessary if you compress all your files in a single call to UglifyJS. +#### Mangling unquoted names (`--mangle-props=unquoted` or `--mangle-props=2`) + +Using quoted property name (`o["foo"]`) reserves the property name (`foo`) +so that it is not mangled throughout the entire script even when used in an +unquoted style (`o.foo`). Example: + +``` +$ echo 'var o={"foo":1, bar:3}; o.foo += o.bar; console.log(o.foo);' | uglifyjs --mangle-props=2 -mc +var o={"foo":1,a:3};o.foo+=o.a,console.log(o.foo); +``` + #### Debugging property name mangling You can also pass `--mangle-props-debug` in order to mangle property names From da17766ddda3b89f94706ad7e329faa66a3e3a3e Mon Sep 17 00:00:00 2001 From: Anthony Van de Gejuchte Date: Wed, 30 Nov 2016 21:54:23 +0100 Subject: [PATCH 2/8] Add preventive test involving non-ascii function identifiers --- test/compress/functions.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/compress/functions.js diff --git a/test/compress/functions.js b/test/compress/functions.js new file mode 100644 index 00000000..3a8701b7 --- /dev/null +++ b/test/compress/functions.js @@ -0,0 +1,8 @@ +non_ascii_function_identifier_name: { + input: { + function fooλ(δλ) {} + function λ(δλ) {} + (function λ(δλ) {})() + } + expect_exact: "function fooλ(δλ){}function λ(δλ){}(function λ(δλ){})();" +} From ec2e5fa3a2e5cf421aebd94b93c668b18e540c69 Mon Sep 17 00:00:00 2001 From: kzc Date: Wed, 21 Dec 2016 10:52:30 -0500 Subject: [PATCH 3/8] Have minify() and tests use figure_out_scope() as uglifyjs CLI does Clarify docs, help and tests for --support-ie8 and screw_ie8=false --- README.md | 9 ++++++--- bin/uglifyjs | 4 ++-- test/compress/screw-ie8.js | 8 ++++++-- test/run-tests.js | 8 ++++---- tools/node.js | 2 +- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1be63009..a8b55843 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,13 @@ The available options are: JS that was generated from some other original code. --screw-ie8 Use this flag if you don't wish to support - Internet Explorer 6-8 quirks. + Internet Explorer 6/7/8. By default UglifyJS will not try to be IE-proof. - --support-ie8 Use this flag to support Internet Explorer 6-8 quirks. - Note: may break standards compliant `catch` identifiers. + --support-ie8 Use this flag to support Internet Explorer 6/7/8. + Equivalent to setting `screw_ie8: false` in `minify()` + for `compress`, `mangle` and `output` options. + Note: `--support-ie8` may generate incorrect code + for `try`/`catch` in ES5 compliant browsers. --expr Parse a single expression, rather than a program (for parsing JSON) -p, --prefix Skip prefix for original filenames that appear diff --git a/bin/uglifyjs b/bin/uglifyjs index d5025827..747fb151 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -26,8 +26,8 @@ mangling you need to use `-c` and `-m`.\ .describe("source-map-inline", "Write base64-encoded source map to the end of js output. Disabled by default") .describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.") .describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.") - .describe("screw-ie8", "Do not support Internet Explorer 6-8 quirks. This flag is enabled by default.") - .describe("support-ie8", "Support non-standard Internet Explorer 6-8 javascript. Note: may break standards compliant `catch` identifiers.") + .describe("screw-ie8", "Do not support Internet Explorer 6/7/8. This flag is enabled by default.") + .describe("support-ie8", "Support non-standard Internet Explorer 6/7/8 javascript. Note: may generate incorrect code for try/catch in ES5 compliant browsers.") .describe("expr", "Parse a single expression, rather than a program (for parsing JSON)") .describe("p", "Skip prefix for original filenames that appear in source maps. \ For example -p 3 will drop 3 directories from file names and ensure they are relative paths. \ diff --git a/test/compress/screw-ie8.js b/test/compress/screw-ie8.js index 0a4e2323..31c448fd 100644 --- a/test/compress/screw-ie8.js +++ b/test/compress/screw-ie8.js @@ -46,6 +46,8 @@ do_screw_try_catch: { } dont_screw_try_catch: { + // This test is known to generate incorrect code for screw_ie8=false. + // Update expected result in the event this bug is ever fixed. options = { screw_ie8: false }; mangle = { screw_ie8: false }; beautify = { screw_ie8: false }; @@ -102,6 +104,8 @@ do_screw_try_catch_undefined: { } dont_screw_try_catch_undefined: { + // This test is known to generate incorrect code for screw_ie8=false. + // Update expected result in the event this bug is ever fixed. options = { screw_ie8: false }; mangle = { screw_ie8: false }; beautify = { screw_ie8: false }; @@ -123,8 +127,8 @@ dont_screw_try_catch_undefined: { } catch (n) { console.log("caught: "+n) } - console.log("undefined is " + void 0); - return void 0===o + console.log("undefined is " + n); + return o === n } } } diff --git a/test/run-tests.js b/test/run-tests.js index 8fb93c83..a4721399 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -70,12 +70,12 @@ function test_directory(dir) { return path.resolve(tests_dir, dir); } -function as_toplevel(input) { +function as_toplevel(input, mangle_options) { if (input instanceof U.AST_BlockStatement) input = input.body; else if (input instanceof U.AST_Statement) input = [ input ]; else throw new Error("Unsupported input syntax"); var toplevel = new U.AST_Toplevel({ body: input }); - toplevel.figure_out_scope(); + toplevel.figure_out_scope(mangle_options); return toplevel; } @@ -103,11 +103,11 @@ function run_compress_tests() { var output_options = test.beautify || {}; var expect; if (test.expect) { - expect = make_code(as_toplevel(test.expect), output_options); + expect = make_code(as_toplevel(test.expect, test.mangle), output_options); } else { expect = test.expect_exact; } - var input = as_toplevel(test.input); + var input = as_toplevel(test.input, test.mangle); var input_code = make_code(test.input, { beautify: true, quote_style: 3, diff --git a/tools/node.js b/tools/node.js index 0b2d5197..c68faaa5 100644 --- a/tools/node.js +++ b/tools/node.js @@ -94,7 +94,7 @@ exports.minify = function(files, options) { if (options.compress) { var compress = { warnings: options.warnings }; UglifyJS.merge(compress, options.compress); - toplevel.figure_out_scope(); + toplevel.figure_out_scope(options.mangle); var sq = UglifyJS.Compressor(compress); toplevel = sq.compress(toplevel); } From 48284844a461e6113bb9911cdcdad7ab8a3d85de Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Tue, 17 Jan 2017 17:33:40 +0800 Subject: [PATCH 4/8] add missing LHS cases which global_defs should avoid --- lib/compress.js | 13 +++++-------- lib/scope.js | 2 +- test/compress/issue-208.js | 30 ++++++++++++++++++++++++------ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 5879b93b..bbd3659d 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -970,6 +970,11 @@ merge(Compressor.prototype, { node.DEFMETHOD("is_string", func); }); + function isLHS(node, parent) { + return parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") + || parent instanceof AST_Assign && parent.left === node; + } + function best_of(ast1, ast2) { return ast1.print_to_string().length > ast2.print_to_string().length @@ -2608,14 +2613,6 @@ merge(Compressor.prototype, { }); OPT(AST_SymbolRef, function(self, compressor){ - function isLHS(symbol, parent) { - return ( - parent instanceof AST_Binary && - parent.operator === '=' && - parent.left === symbol - ); - } - if (self.undeclared() && !isLHS(self, compressor.parent())) { var defines = compressor.option("global_defs"); if (defines && HOP(defines, self.name)) { diff --git a/lib/scope.js b/lib/scope.js index 4943b568..bc5db90d 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -220,7 +220,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ sym = g; } node.thedef = sym; - if (parent instanceof AST_Unary && (parent.operator === '++' || parent.operator === '--') + if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") || parent instanceof AST_Assign && parent.left === node) { sym.modified = true; } diff --git a/test/compress/issue-208.js b/test/compress/issue-208.js index 14752b8a..2f103786 100644 --- a/test/compress/issue-208.js +++ b/test/compress/issue-208.js @@ -1,11 +1,29 @@ do_not_update_lhs: { - options = { global_defs: { DEBUG: false } }; - input: { DEBUG = false; } - expect: { DEBUG = false; } + options = { + global_defs: { DEBUG: 0 } + } + input: { + DEBUG++; + DEBUG += 1; + DEBUG = 1; + } + expect: { + DEBUG++; + DEBUG += 1; + DEBUG = 1; + } } do_update_rhs: { - options = { global_defs: { DEBUG: false } }; - input: { MY_DEBUG = DEBUG; } - expect: { MY_DEBUG = false; } + options = { + global_defs: { DEBUG: 0 } + } + input: { + MY_DEBUG = DEBUG; + MY_DEBUG += DEBUG; + } + expect: { + MY_DEBUG = 0; + MY_DEBUG += 0; + } } From 0d7d4918eb6fb73c3cf9863479b3e66d38fad6df Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 26 Jan 2017 19:14:18 +0800 Subject: [PATCH 5/8] augment evaluate to extract within objects (#1425) - gated by `unsafe` - replaces previous optimisation specific to String.length - "123"[0] => 1 - [1, 2, 3][0] => 1 - [1, 2, 3].length => 3 - does not apply to objects with overridden prototype functions --- lib/compress.js | 66 +++++- lib/scope.js | 17 +- test/compress/arrays.js | 49 +++++ test/compress/evaluate.js | 377 +++++++++++++++++++++++++++++++++++ test/compress/properties.js | 51 ++++- test/compress/reduce_vars.js | 183 ++++++++++++++++- 6 files changed, 729 insertions(+), 14 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index bbd3659d..5c019623 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -152,6 +152,12 @@ merge(Compressor.prototype, { AST_Node.DEFMETHOD("clear_opt_flags", function(){ this.walk(new TreeWalker(function(node){ + if (node instanceof AST_SymbolRef) { + var d = node.definition(); + if (d && d.init) { + delete d.init._evaluated; + } + } if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { node._squeezed = false; node._optimized = false; @@ -992,13 +998,20 @@ merge(Compressor.prototype, { // constant; otherwise it's the original or a replacement node. AST_Node.DEFMETHOD("evaluate", function(compressor){ if (!compressor.option("evaluate")) return [ this ]; + var val; try { - var val = this._eval(compressor); - return [ best_of(make_node_from_constant(compressor, val, this), this), val ]; + val = this._eval(compressor); } catch(ex) { if (ex !== def) throw ex; return [ this ]; } + var node; + try { + node = make_node_from_constant(compressor, val, this); + } catch(ex) { + return [ this ]; + } + return [ best_of(node, this), val ]; }); AST_Node.DEFMETHOD("is_constant", function(compressor){ // Accomodate when compress option evaluate=false @@ -1047,6 +1060,32 @@ merge(Compressor.prototype, { def(AST_Constant, function(){ return this.getValue(); }); + def(AST_Array, function(compressor){ + if (compressor.option("unsafe")) { + return this.elements.map(function(element) { + return ev(element, compressor); + }); + } + throw def; + }); + def(AST_Object, function(compressor){ + if (compressor.option("unsafe")) { + var val = {}; + for (var i = 0, len = this.properties.length; i < len; i++) { + var prop = this.properties[i]; + var key = prop.key; + if (key instanceof AST_Node) { + key = ev(key, compressor); + } + if (typeof Object.prototype[key] === 'function') { + throw def; + } + val[key] = ev(prop.value, compressor); + } + return val; + } + throw def; + }); def(AST_UnaryPrefix, function(compressor){ var e = this.expression; switch (this.operator) { @@ -1114,6 +1153,12 @@ merge(Compressor.prototype, { try { var d = this.definition(); if (d && (d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) { + if (compressor.option("unsafe")) { + if (!HOP(d.init, '_evaluated')) { + d.init._evaluated = ev(d.init, compressor); + } + return d.init._evaluated; + } return ev(d.init, compressor); } } finally { @@ -1121,11 +1166,16 @@ merge(Compressor.prototype, { } throw def; }); - def(AST_Dot, function(compressor){ - if (compressor.option("unsafe") && this.property == "length") { - var str = ev(this.expression, compressor); - if (typeof str == "string") - return str.length; + def(AST_PropAccess, function(compressor){ + if (compressor.option("unsafe")) { + var key = this.property; + if (key instanceof AST_Node) { + key = ev(key, compressor); + } + var val = ev(this.expression, compressor); + if (val && HOP(val, key)) { + return val[key]; + } } throw def; }); @@ -2890,7 +2940,7 @@ merge(Compressor.prototype, { }); } } - return self; + return self.evaluate(compressor)[0]; }); OPT(AST_Dot, function(self, compressor){ diff --git a/lib/scope.js b/lib/scope.js index bc5db90d..ae792a0a 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -184,6 +184,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var func = null; var globals = self.globals = new Dictionary(); var tw = new TreeWalker(function(node, descend){ + function isModified(node, level) { + var parent = tw.parent(level); + if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") + || parent instanceof AST_Assign && parent.left === node + || parent instanceof AST_Call && parent.expression === node) { + return true; + } else if (parent instanceof AST_PropAccess && parent.expression === node) { + return isModified(parent, level + 1); + } + } + if (node instanceof AST_Lambda) { var prev_func = func; func = node; @@ -197,8 +208,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ } if (node instanceof AST_SymbolRef) { var name = node.name; - var parent = tw.parent(); - if (name == "eval" && parent instanceof AST_Call) { + if (name == "eval" && tw.parent() instanceof AST_Call) { for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) { s.uses_eval = true; } @@ -220,8 +230,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ sym = g; } node.thedef = sym; - if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") - || parent instanceof AST_Assign && parent.left === node) { + if (isModified(node, 0)) { sym.modified = true; } node.reference(); diff --git a/test/compress/arrays.js b/test/compress/arrays.js index e636347f..807ba206 100644 --- a/test/compress/arrays.js +++ b/test/compress/arrays.js @@ -72,3 +72,52 @@ constant_join_2: { var f = "strstr" + variable + "foobarmoo" + foo; } } + +for_loop: { + options = { + unsafe : true, + evaluate : true, + reduce_vars : true + }; + input: { + function f0() { + var a = [1, 2, 3]; + for (var i = 0; i < a.length; i++) { + console.log(a[i]); + } + } + + function f1() { + var a = [1, 2, 3]; + for (var i = 0, len = a.length; i < len; i++) { + console.log(a[i]); + } + } + + function f2() { + var a = [1, 2, 3]; + for (var i = 0; i < a.length; i++) { + a[i]++; + } + } + } + expect: { + function f0() { + var a = [1, 2, 3]; + for (var i = 0; i < 3; i++) + console.log(a[i]); + } + + function f1() { + var a = [1, 2, 3]; + for (var i = 0, len = 3; i < len; i++) + console.log(a[i]); + } + + function f2() { + var a = [1, 2, 3]; + for (var i = 0; i < a.length; i++) + a[i]++; + } + } +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index d27582f3..c74c7b24 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -37,3 +37,380 @@ positive_zero: { ); } } + +unsafe_constant: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + true.a, + false.a, + null.a, + undefined.a + ); + } + expect: { + console.log( + true.a, + false.a, + null.a, + (void 0).a + ); + } +} + +unsafe_object: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({a:1}) + 1, + ({a:1}).a + 1, + ({a:1}).b + 1, + ({a:1}).a.b + 1 + ); + } + expect: { + console.log( + ({a:1}) + 1, + 2, + ({a:1}).b + 1, + 1..b + 1 + ); + } +} + +unsafe_object_nested: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({a:{b:1}}) + 1, + ({a:{b:1}}).a + 1, + ({a:{b:1}}).b + 1, + ({a:{b:1}}).a.b + 1 + ); + } + expect: { + console.log( + ({a:{b:1}}) + 1, + ({a:{b:1}}).a + 1, + ({a:{b:1}}).b + 1, + 2 + ); + } +} + +unsafe_object_complex: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({a:{b:1},b:1}) + 1, + ({a:{b:1},b:1}).a + 1, + ({a:{b:1},b:1}).b + 1, + ({a:{b:1},b:1}).a.b + 1 + ); + } + expect: { + console.log( + ({a:{b:1},b:1}) + 1, + ({a:{b:1},b:1}).a + 1, + 2, + 2 + ); + } +} + +unsafe_object_repeated: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({a:{b:1},a:1}) + 1, + ({a:{b:1},a:1}).a + 1, + ({a:{b:1},a:1}).b + 1, + ({a:{b:1},a:1}).a.b + 1 + ); + } + expect: { + console.log( + ({a:{b:1},a:1}) + 1, + 2, + ({a:{b:1},a:1}).b + 1, + 1..b + 1 + ); + } +} + +unsafe_function: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({a:{b:1},b:function(){}}) + 1, + ({a:{b:1},b:function(){}}).a + 1, + ({a:{b:1},b:function(){}}).b + 1, + ({a:{b:1},b:function(){}}).a.b + 1 + ); + } + expect: { + console.log( + ({a:{b:1},b:function(){}}) + 1, + ({a:{b:1},b:function(){}}).a + 1, + ({a:{b:1},b:function(){}}).b + 1, + ({a:{b:1},b:function(){}}).a.b + 1 + ); + } +} + +unsafe_integer_key: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({0:1}) + 1, + ({0:1})[0] + 1, + ({0:1})["0"] + 1, + ({0:1})[1] + 1, + ({0:1})[0][1] + 1, + ({0:1})[0]["1"] + 1 + ); + } + expect: { + console.log( + ({0:1}) + 1, + 2, + 2, + ({0:1})[1] + 1, + 1[1] + 1, + 1["1"] + 1 + ); + } +} + +unsafe_integer_key_complex: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({0:{1:1},1:1}) + 1, + ({0:{1:1},1:1})[0] + 1, + ({0:{1:1},1:1})["0"] + 1, + ({0:{1:1},1:1})[1] + 1, + ({0:{1:1},1:1})[0][1] + 1, + ({0:{1:1},1:1})[0]["1"] + 1 + ); + } + expect: { + console.log( + ({0:{1:1},1:1}) + 1, + "[object Object]1", + "[object Object]1", + 2, + 2, + 2 + ); + } +} + +unsafe_float_key: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({2.72:1}) + 1, + ({2.72:1})[2.72] + 1, + ({2.72:1})["2.72"] + 1, + ({2.72:1})[3.14] + 1, + ({2.72:1})[2.72][3.14] + 1, + ({2.72:1})[2.72]["3.14"] + 1 + ); + } + expect: { + console.log( + ({2.72:1}) + 1, + 2, + 2, + ({2.72:1})[3.14] + 1, + 1[3.14] + 1, + 1["3.14"] + 1 + ); + } +} + +unsafe_float_key_complex: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + ({2.72:{3.14:1},3.14:1}) + 1, + ({2.72:{3.14:1},3.14:1})[2.72] + 1, + ({2.72:{3.14:1},3.14:1})["2.72"] + 1, + ({2.72:{3.14:1},3.14:1})[3.14] + 1, + ({2.72:{3.14:1},3.14:1})[2.72][3.14] + 1, + ({2.72:{3.14:1},3.14:1})[2.72]["3.14"] + 1 + ); + } + expect: { + console.log( + "[object Object]1", + "[object Object]1", + "[object Object]1", + 2, + 2, + 2 + ); + } +} + +unsafe_array: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + [1, , 3][1], + [1, 2, 3, a] + 1, + [1, 2, 3, 4] + 1, + [1, 2, 3, a][0] + 1, + [1, 2, 3, 4][0] + 1, + [1, 2, 3, 4][6 - 5] + 1, + [1, , 3, 4][6 - 5] + 1, + [[1, 2], [3, 4]][0] + 1, + [[1, 2], [3, 4]][6 - 5][1] + 1, + [[1, 2], , [3, 4]][6 - 5][1] + 1 + ); + } + expect: { + console.log( + void 0, + [1, 2, 3, a] + 1, + "1,2,3,41", + [1, 2, 3, a][0] + 1, + 2, + 3, + NaN, + "1,21", + 5, + (void 0)[1] + 1 + ); + } +} + +unsafe_string: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + "1234" + 1, + "1234"[0] + 1, + "1234"[6 - 5] + 1, + ("12" + "34")[0] + 1, + ("12" + "34")[6 - 5] + 1, + [1, 2, 3, 4].join("")[0] + 1 + ); + } + expect: { + console.log( + "12341", + "11", + "21", + "11", + "21", + "11" + ); + } +} + +unsafe_array_bad_index: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + [1, 2, 3, 4].a + 1, + [1, 2, 3, 4]["a"] + 1, + [1, 2, 3, 4][3.14] + 1 + ); + } + expect: { + console.log( + [1, 2, 3, 4].a + 1, + [1, 2, 3, 4]["a"] + 1, + [1, 2, 3, 4][3.14] + 1 + ); + } +} + +unsafe_string_bad_index: { + options = { + evaluate : true, + unsafe : true + } + input: { + console.log( + "1234".a + 1, + "1234"["a"] + 1, + "1234"[3.14] + 1 + ); + } + expect: { + console.log( + "1234".a + 1, + "1234"["a"] + 1, + "1234"[3.14] + 1 + ); + } +} + +unsafe_prototype_function: { + options = { + evaluate : true, + unsafe : true + } + input: { + var a = ({valueOf: 0}) < 1; + var b = ({toString: 0}) < 1; + var c = ({valueOf: 0}) + ""; + var d = ({toString: 0}) + ""; + var e = (({valueOf: 0}) + "")[2]; + var f = (({toString: 0}) + "")[2]; + var g = ({valueOf: 0}).valueOf(); + var h = ({toString: 0}).toString(); + } + expect: { + var a = ({valueOf: 0}) < 1; + var b = ({toString: 0}) < 1; + var c = ({valueOf: 0}) + ""; + var d = ({toString: 0}) + ""; + var e = (({valueOf: 0}) + "")[2]; + var f = (({toString: 0}) + "")[2]; + var g = ({valueOf: 0}).valueOf(); + var h = "" + ({toString: 0}); + } +} diff --git a/test/compress/properties.js b/test/compress/properties.js index 22f2c339..7ad54ebe 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -54,7 +54,56 @@ dot_properties_es5: { } } -evaluate_length: { +sub_properties: { + options = { + evaluate: true, + properties: true + }; + input: { + a[0] = 0; + a["0"] = 1; + a[3.14] = 2; + a["3" + ".14"] = 3; + a["i" + "f"] = 4; + a["foo" + " bar"] = 5; + a[0 / 0] = 6; + a[null] = 7; + a[undefined] = 8; + } + expect: { + a[0] = 0; + a[0] = 1; + a[3.14] = 2; + a[3.14] = 3; + a.if = 4; + a["foo bar"] = 5; + a[NaN] = 6; + a[null] = 7; + a[void 0] = 8; + } +} + +evaluate_array_length: { + options = { + properties: true, + unsafe: true, + evaluate: true + }; + input: { + a = [1, 2, 3].length; + a = [1, 2, 3].join()["len" + "gth"]; + a = [1, 2, b].length; + a = [1, 2, 3].join(b).length; + } + expect: { + a = 3; + a = 5; + a = [1, 2, b].length; + a = [1, 2, 3].join(b).length; + } +} + +evaluate_string_length: { options = { properties: true, unsafe: true, diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index a1d05012..c401ac66 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -168,4 +168,185 @@ modified: { console.log(B ? 'yes' : 'no'); } } -} \ No newline at end of file +} + +unsafe_evaluate: { + options = { + evaluate : true, + reduce_vars : true, + unsafe : true, + unused : true + } + input: { + function f0(){ + var a = { + b:1 + }; + console.log(a.b + 3); + } + + function f1(){ + var a = { + b:{ + c:1 + }, + d:2 + }; + console.log(a.b + 3, a.d + 4, a.b.c + 5, a.d.c + 6); + } + } + expect: { + function f0(){ + console.log(4); + } + + function f1(){ + var a = { + b:{ + c:1 + }, + d:2 + }; + console.log(a.b + 3, 6, 6, 2..c + 6); + } + } +} + +unsafe_evaluate_object: { + options = { + evaluate : true, + reduce_vars : true, + unsafe : true + } + input: { + function f0(){ + var a = 1; + var b = {}; + b[a] = 2; + console.log(a + 3); + } + + function f1(){ + var a = { + b:1 + }; + a.b = 2; + console.log(a.b + 3); + } + } + expect: { + function f0(){ + var a = 1; + var b = {}; + b[a] = 2; + console.log(4); + } + + function f1(){ + var a = { + b:1 + }; + a.b = 2; + console.log(a.b + 3); + } + } +} + +unsafe_evaluate_array: { + options = { + evaluate : true, + reduce_vars : true, + unsafe : true + } + input: { + function f0(){ + var a = 1; + var b = []; + b[a] = 2; + console.log(a + 3); + } + + function f1(){ + var a = [1]; + a[2] = 3; + console.log(a.length); + } + + function f2(){ + var a = [1]; + a.push(2); + console.log(a.length); + } + } + expect: { + function f0(){ + var a = 1; + var b = []; + b[a] = 2; + console.log(4); + } + + function f1(){ + var a = [1]; + a[2] = 3; + console.log(a.length); + } + + function f2(){ + var a = [1]; + a.push(2); + console.log(a.length); + } + } +} + +unsafe_evaluate_equality: { + options = { + evaluate : true, + reduce_vars : true, + unsafe : true, + unused : true + } + input: { + function f0(){ + var a = {}; + console.log(a === a); + } + + function f1(){ + var a = []; + console.log(a === a); + } + + function f2(){ + var a = {a:1, b:2}; + var b = a; + var c = a; + console.log(b === c); + } + + function f3(){ + var a = [1, 2, 3]; + var b = a; + var c = a; + console.log(b === c); + } + } + expect: { + function f0(){ + console.log(true); + } + + function f1(){ + console.log(true); + } + + function f2(){ + console.log(true); + } + + function f3(){ + console.log(true); + } + } +} From 0610c020b1544820be9898a285ab6c9066490552 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 26 Jan 2017 19:16:50 +0800 Subject: [PATCH 6/8] optimise binary operands with evaluate() (#1427) - remove call to evaluate() in is_constant() and let nested optimize() does its job instead - reject RegExp in is_constant() and remove special case logic under collapse_vars - operands to conditionals optimisation are now always evaluate()-ed - throw error in constant_value() instead of returning undefined to catch possible bugs, similar to make_node_from_constant() - optimise binary boolean operators under `evaluate` instead of `conditionals` --- lib/compress.js | 119 +++++++++++++--------- test/compress/conditionals.js | 183 +++++---------------------------- test/compress/evaluate.js | 184 ++++++++++++++++++++++++++++++++++ test/compress/reduce_vars.js | 24 ++--- 4 files changed, 290 insertions(+), 220 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 5c019623..4e45df92 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -347,7 +347,7 @@ merge(Compressor.prototype, { if (ref.scope.uses_eval || ref.scope.uses_with) break; // Constant single use vars can be replaced in any scope. - if (!(var_decl.value instanceof AST_RegExp) && var_decl.value.is_constant(compressor)) { + if (var_decl.value.is_constant()) { var ctt = new TreeTransformer(function(node) { if (node === ref) return replace_var(node, ctt.parent(), true); @@ -1013,31 +1013,46 @@ merge(Compressor.prototype, { } return [ best_of(node, this), val ]; }); - AST_Node.DEFMETHOD("is_constant", function(compressor){ + var unaryPrefix = makePredicate("! ~ - +"); + AST_Node.DEFMETHOD("is_constant", function(){ // Accomodate when compress option evaluate=false - // as well as the common constant expressions !0 and !1 - return this instanceof AST_Constant - || (this instanceof AST_UnaryPrefix && this.operator == "!" - && this.expression instanceof AST_Constant) - || this.evaluate(compressor).length > 1; + // as well as the common constant expressions !0 and -1 + if (this instanceof AST_Constant) { + return !(this instanceof AST_RegExp); + } else { + return this instanceof AST_UnaryPrefix + && this.expression instanceof AST_Constant + && unaryPrefix(this.operator); + } }); // Obtain the constant value of an expression already known to be constant. - // Result only valid iff this.is_constant(compressor) is true. + // Result only valid iff this.is_constant() is true. AST_Node.DEFMETHOD("constant_value", function(compressor){ // Accomodate when option evaluate=false. - if (this instanceof AST_Constant) return this.value; - // Accomodate the common constant expressions !0 and !1 when option evaluate=false. - if (this instanceof AST_UnaryPrefix - && this.operator == "!" - && this.expression instanceof AST_Constant) { - return !this.expression.value; + if (this instanceof AST_Constant && !(this instanceof AST_RegExp)) { + return this.value; } - var result = this.evaluate(compressor) + // Accomodate the common constant expressions !0 and -1 when option evaluate=false. + if (this instanceof AST_UnaryPrefix + && this.expression instanceof AST_Constant) switch (this.operator) { + case "!": + return !this.expression.value; + case "~": + return ~this.expression.value; + case "-": + return -this.expression.value; + case "+": + return +this.expression.value; + default: + throw new Error(string_template("Cannot evaluate unary expression {value}", { + value: this.print_to_string() + })); + } + var result = this.evaluate(compressor); if (result.length > 1) { return result[1]; } - // should never be reached - return undefined; + throw new Error(string_template("Cannot evaluate constant [{file}:{line},{col}]", this.start)); }); def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); @@ -2419,6 +2434,16 @@ merge(Compressor.prototype, { var commutativeOperators = makePredicate("== === != !== * & | ^"); OPT(AST_Binary, function(self, compressor){ + var lhs = self.left.evaluate(compressor); + var rhs = self.right.evaluate(compressor); + if (lhs.length > 1 && lhs[0].is_constant() !== self.left.is_constant() + || rhs.length > 1 && rhs[0].is_constant() !== self.right.is_constant()) { + return make_node(AST_Binary, self, { + operator: self.operator, + left: lhs[0], + right: rhs[0] + }).optimize(compressor); + } function reverse(op, force) { if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) { if (op) self.operator = op; @@ -2491,32 +2516,6 @@ merge(Compressor.prototype, { } break; } - if (compressor.option("conditionals")) { - if (self.operator == "&&") { - var ll = self.left.evaluate(compressor); - if (ll.length > 1) { - if (ll[1]) { - compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]); - } else { - compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, ll[0]); - } - } - } - else if (self.operator == "||") { - var ll = self.left.evaluate(compressor); - if (ll.length > 1) { - if (ll[1]) { - compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, ll[0]); - } else { - compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); - return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]); - } - } - } - } if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) { case "&&": var ll = self.left.evaluate(compressor); @@ -2590,6 +2589,30 @@ merge(Compressor.prototype, { return self.left; } if (compressor.option("evaluate")) { + switch (self.operator) { + case "&&": + if (self.left.is_constant()) { + if (self.left.constant_value(compressor)) { + compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start); + return maintain_this_binding(compressor.parent(), self, self.right); + } else { + compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); + return maintain_this_binding(compressor.parent(), self, self.left); + } + } + break; + case "||": + if (self.left.is_constant()) { + if (self.left.constant_value(compressor)) { + compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start); + return maintain_this_binding(compressor.parent(), self, self.left); + } else { + compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); + return maintain_this_binding(compressor.parent(), self, self.right); + } + } + break; + } if (self.operator == "+") { if (self.left instanceof AST_Constant && self.right instanceof AST_Binary @@ -2816,14 +2839,14 @@ merge(Compressor.prototype, { }); } // y?1:1 --> 1 - if (consequent.is_constant(compressor) - && alternative.is_constant(compressor) + if (consequent.is_constant() + && alternative.is_constant() && consequent.equivalent_to(alternative)) { - var consequent_value = consequent.constant_value(compressor); + var consequent_value = consequent.evaluate(compressor)[0]; if (self.condition.has_side_effects(compressor)) { - return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]); + return AST_Seq.from_array([self.condition, consequent_value]); } else { - return make_node_from_constant(compressor, consequent_value, self); + return consequent_value; } } diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 35cb26f7..d88c5b90 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -635,166 +635,6 @@ ternary_boolean_alternative: { } } -conditional_and: { - options = { - conditionals: true, - evaluate : true - }; - input: { - var a; - // compress these - - a = true && condition; - a = 1 && console.log("a"); - a = 2 * 3 && 2 * condition; - a = 5 == 5 && condition + 3; - a = "string" && 4 - condition; - a = 5 + "" && condition / 5; - a = -4.5 && 6 << condition; - a = 6 && 7; - - a = false && condition; - a = NaN && console.log("b"); - a = 0 && console.log("c"); - a = undefined && 2 * condition; - a = null && condition + 3; - a = 2 * 3 - 6 && 4 - condition; - a = 10 == 7 && condition / 5; - a = !"string" && 6 % condition; - a = 0 && 7; - - // don't compress these - - a = condition && true; - a = console.log("a") && 2; - a = 4 - condition && "string"; - a = 6 << condition && -4.5; - - a = condition && false; - a = console.log("b") && NaN; - a = console.log("c") && 0; - a = 2 * condition && undefined; - a = condition + 3 && null; - - } - expect: { - var a; - - a = condition; - a = console.log("a"); - a = 2 * condition; - a = condition + 3; - a = 4 - condition; - a = condition / 5; - a = 6 << condition; - a = 7; - - a = false; - a = NaN; - a = 0; - a = void 0; - a = null; - a = 0; - a = false; - a = false; - a = 0; - - a = condition && true; - a = console.log("a") && 2; - a = 4 - condition && "string"; - a = 6 << condition && -4.5; - - a = condition && false; - a = console.log("b") && NaN; - a = console.log("c") && 0; - a = 2 * condition && void 0; - a = condition + 3 && null; - } -} - -conditional_or: { - options = { - conditionals: true, - evaluate : true - }; - input: { - var a; - // compress these - - a = true || condition; - a = 1 || console.log("a"); - a = 2 * 3 || 2 * condition; - a = 5 == 5 || condition + 3; - a = "string" || 4 - condition; - a = 5 + "" || condition / 5; - a = -4.5 || 6 << condition; - a = 6 || 7; - - a = false || condition; - a = 0 || console.log("b"); - a = NaN || console.log("c"); - a = undefined || 2 * condition; - a = null || condition + 3; - a = 2 * 3 - 6 || 4 - condition; - a = 10 == 7 || condition / 5; - a = !"string" || 6 % condition; - a = null || 7; - - a = console.log(undefined && condition || null); - a = console.log(undefined || condition && null); - - // don't compress these - - a = condition || true; - a = console.log("a") || 2; - a = 4 - condition || "string"; - a = 6 << condition || -4.5; - - a = condition || false; - a = console.log("b") || NaN; - a = console.log("c") || 0; - a = 2 * condition || undefined; - a = condition + 3 || null; - - } - expect: { - var a; - - a = true; - a = 1; - a = 6; - a = true; - a = "string"; - a = "5"; - a = -4.5; - a = 6; - - a = condition; - a = console.log("b"); - a = console.log("c"); - a = 2 * condition; - a = condition + 3; - a = 4 - condition; - a = condition / 5; - a = 6 % condition; - a = 7; - - a = console.log(null); - a = console.log(condition && null); - - a = condition || true; - a = console.log("a") || 2; - a = 4 - condition || "string"; - a = 6 << condition || -4.5; - - a = condition || false; - a = console.log("b") || NaN; - a = console.log("c") || 0; - a = 2 * condition || void 0; - a = condition + 3 || null; - } -} - trivial_boolean_ternary_expressions : { options = { conditionals: true, @@ -906,3 +746,26 @@ issue_1154: { function g6() { return g(), "number"; } } } + +no_evaluate: { + options = { + conditionals: true, + evaluate : false + } + input: { + function f(b) { + a = b ? !0 : !0; + a = b ? ~1 : ~1; + a = b ? -2 : -2; + a = b ? +3 : +3; + } + } + expect: { + function f(b) { + a = !0; + a = ~1; + a = -2; + a = +3; + } + } +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index c74c7b24..0ff157dc 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1,3 +1,187 @@ +and: { + options = { + evaluate: true + } + input: { + var a; + // compress these + + a = true && condition; + a = 1 && console.log("a"); + a = 2 * 3 && 2 * condition; + a = 5 == 5 && condition + 3; + a = "string" && 4 - condition; + a = 5 + "" && condition / 5; + a = -4.5 && 6 << condition; + a = 6 && 7; + + a = false && condition; + a = NaN && console.log("b"); + a = 0 && console.log("c"); + a = undefined && 2 * condition; + a = null && condition + 3; + a = 2 * 3 - 6 && 4 - condition; + a = 10 == 7 && condition / 5; + a = !"string" && 6 % condition; + a = 0 && 7; + + // don't compress these + + a = condition && true; + a = console.log("a") && 2; + a = 4 - condition && "string"; + a = 6 << condition && -4.5; + + a = condition && false; + a = console.log("b") && NaN; + a = console.log("c") && 0; + a = 2 * condition && undefined; + a = condition + 3 && null; + + } + expect: { + var a; + + a = condition; + a = console.log("a"); + a = 2 * condition; + a = condition + 3; + a = 4 - condition; + a = condition / 5; + a = 6 << condition; + a = 7; + + a = false; + a = NaN; + a = 0; + a = void 0; + a = null; + a = 0; + a = false; + a = false; + a = 0; + + a = condition && true; + a = console.log("a") && 2; + a = 4 - condition && "string"; + a = 6 << condition && -4.5; + + a = condition && false; + a = console.log("b") && NaN; + a = console.log("c") && 0; + a = 2 * condition && void 0; + a = condition + 3 && null; + } +} + +or: { + options = { + evaluate: true + } + input: { + var a; + // compress these + + a = true || condition; + a = 1 || console.log("a"); + a = 2 * 3 || 2 * condition; + a = 5 == 5 || condition + 3; + a = "string" || 4 - condition; + a = 5 + "" || condition / 5; + a = -4.5 || 6 << condition; + a = 6 || 7; + + a = false || condition; + a = 0 || console.log("b"); + a = NaN || console.log("c"); + a = undefined || 2 * condition; + a = null || condition + 3; + a = 2 * 3 - 6 || 4 - condition; + a = 10 == 7 || condition / 5; + a = !"string" || 6 % condition; + a = null || 7; + + a = console.log(undefined && condition || null); + a = console.log(undefined || condition && null); + + // don't compress these + + a = condition || true; + a = console.log("a") || 2; + a = 4 - condition || "string"; + a = 6 << condition || -4.5; + + a = condition || false; + a = console.log("b") || NaN; + a = console.log("c") || 0; + a = 2 * condition || undefined; + a = condition + 3 || null; + + } + expect: { + var a; + + a = true; + a = 1; + a = 6; + a = true; + a = "string"; + a = "5"; + a = -4.5; + a = 6; + + a = condition; + a = console.log("b"); + a = console.log("c"); + a = 2 * condition; + a = condition + 3; + a = 4 - condition; + a = condition / 5; + a = 6 % condition; + a = 7; + + a = console.log(null); + a = console.log(condition && null); + + a = condition || true; + a = console.log("a") || 2; + a = 4 - condition || "string"; + a = 6 << condition || -4.5; + + a = condition || false; + a = console.log("b") || NaN; + a = console.log("c") || 0; + a = 2 * condition || void 0; + a = condition + 3 || null; + } +} + +unary_prefix: { + options = { + evaluate: true + } + input: { + a = !0 && b; + a = !0 || b; + a = ~1 && b; + a = ~1 || b; + a = -2 && b; + a = -2 || b; + a = +3 && b; + a = +3 || b; + } + expect: { + a = b; + a = !0; + a = b; + a = -2; + a = b; + a = -2; + a = b; + a = 3; + } +} + negative_zero: { options = { evaluate: true } input: { diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index c401ac66..2301a92a 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -136,30 +136,30 @@ modified: { } function f2() { - var a = 1, b = 2, c = 3; + var b = 2, c = 3; b = c; - console.log(a + b); - console.log(b + c); + console.log(1 + b); + console.log(b + 3); console.log(4); - console.log(a + b + c); + console.log(1 + b + 3); } function f3() { - var a = 1, b = 2, c = 3; + var b = 2, c = 3; b *= c; - console.log(a + b); - console.log(b + c); + console.log(1 + b); + console.log(b + 3); console.log(4); - console.log(a + b + c); + console.log(1 + b + 3); } function f4() { - var a = 1, b = 2, c = 3; + var b = 2, c = 3; b = c; - console.log(a + b); + console.log(1 + b); console.log(b + c); - console.log(a + c); - console.log(a + b + c); + console.log(1 + c); + console.log(1 + b + c); } function f5(a) { From 1eaa211e0932105439d98d4f03a981f157f0a77c Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 26 Jan 2017 19:18:28 +0800 Subject: [PATCH 7/8] fix mangling collision with keep_fnames (#1431) * fix mangling collision with keep_fnames fixes #1423 * pass mangle options to figure_out_scope() bring command-line in line with minify() --- bin/uglifyjs | 6 +- lib/scope.js | 14 ++-- test/compress/issue-1431.js | 122 ++++++++++++++++++++++++++++++++ test/input/issue-1431/sample.js | 14 ++++ test/mocha/cli.js | 32 ++++++++- 5 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 test/compress/issue-1431.js create mode 100644 test/input/issue-1431/sample.js diff --git a/bin/uglifyjs b/bin/uglifyjs index 747fb151..8cb2f0df 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -428,10 +428,11 @@ async.eachLimit(files, 1, function (file, cb) { var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint var TL_CACHE = readNameCache("vars"); + if (MANGLE) MANGLE.cache = TL_CACHE; if (SCOPE_IS_NEEDED) { time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: screw_ie8, cache: TL_CACHE }); + TOPLEVEL.figure_out_scope(MANGLE || { screw_ie8: screw_ie8, cache: TL_CACHE }); if (ARGS.lint) { TOPLEVEL.scope_warnings(); } @@ -446,7 +447,7 @@ async.eachLimit(files, 1, function (file, cb) { if (SCOPE_IS_NEEDED) { time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: screw_ie8, cache: TL_CACHE }); + TOPLEVEL.figure_out_scope(MANGLE || { screw_ie8: screw_ie8, cache: TL_CACHE }); if (MANGLE && !TL_CACHE) { TOPLEVEL.compute_char_frequency(MANGLE); } @@ -454,7 +455,6 @@ async.eachLimit(files, 1, function (file, cb) { } if (MANGLE) time_it("mangle", function(){ - MANGLE.cache = TL_CACHE; TOPLEVEL.mangle_names(MANGLE); }); diff --git a/lib/scope.js b/lib/scope.js index ae792a0a..55d1eff1 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -233,7 +233,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ if (isModified(node, 0)) { sym.modified = true; } - node.reference(); + node.reference(options); return true; } }); @@ -264,13 +264,18 @@ AST_Lambda.DEFMETHOD("init_scope_vars", function(){ this.variables.set(symbol.name, def); }); -AST_SymbolRef.DEFMETHOD("reference", function() { +AST_SymbolRef.DEFMETHOD("reference", function(options) { var def = this.definition(); def.references.push(this); var s = this.scope; while (s) { push_uniq(s.enclosed, def); if (s === def.scope) break; + if (options.keep_fnames) { + s.variables.each(function(d) { + push_uniq(def.scope.enclosed, d); + }); + } s = s.parent_scope; } this.frame = this.scope.nesting - def.scope.nesting; @@ -338,11 +343,6 @@ AST_Function.DEFMETHOD("next_mangled", function(options, def){ } }); -AST_Scope.DEFMETHOD("references", function(sym){ - if (sym instanceof AST_Symbol) sym = sym.definition(); - return this.enclosed.indexOf(sym) < 0 ? null : sym; -}); - AST_Symbol.DEFMETHOD("unmangleable", function(options){ return this.definition().unmangleable(options); }); diff --git a/test/compress/issue-1431.js b/test/compress/issue-1431.js new file mode 100644 index 00000000..731ebba8 --- /dev/null +++ b/test/compress/issue-1431.js @@ -0,0 +1,122 @@ +level_one: { + options = { + keep_fnames: true + } + mangle = { + keep_fnames: true + } + input: { + function f(x) { + return function() { + function n(a) { + return a * a; + } + return x(n); + }; + } + } + expect: { + function f(r) { + return function() { + function n(n) { + return n * n; + } + return r(n); + }; + } + } +} + +level_two: { + options = { + keep_fnames: true + } + mangle = { + keep_fnames: true + } + input: { + function f(x) { + return function() { + function r(a) { + return a * a; + } + return function() { + function n(a) { + return a * a; + } + return x(n); + }; + }; + } + } + expect: { + function f(t) { + return function() { + function r(n) { + return n * n; + } + return function() { + function n(n) { + return n * n; + } + return t(n); + }; + }; + } + } +} + +level_three: { + options = { + keep_fnames: true + } + mangle = { + keep_fnames: true + } + input: { + function f(x) { + return function() { + function r(a) { + return a * a; + } + return [ + function() { + function t(a) { + return a * a; + } + return t; + }, + function() { + function n(a) { + return a * a; + } + return x(n); + } + ]; + }; + } + } + expect: { + function f(t) { + return function() { + function r(n) { + return n * n; + } + return [ + function() { + function t(n) { + return n * n; + } + return t; + }, + function() { + function n(n) { + return n * n; + } + return t(n); + } + ]; + }; + } + } +} diff --git a/test/input/issue-1431/sample.js b/test/input/issue-1431/sample.js new file mode 100644 index 00000000..32068cb2 --- /dev/null +++ b/test/input/issue-1431/sample.js @@ -0,0 +1,14 @@ +function f(x) { + return function() { + function n(a) { + return a * a; + } + return x(n); + }; +} + +function g(op) { + return op(1) + op(2); +} + +console.log(f(g)() == 5); \ No newline at end of file diff --git a/test/mocha/cli.js b/test/mocha/cli.js index bebd4d9d..a8de05c5 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -55,7 +55,7 @@ describe("bin/uglifyjs", function () { exec(command, function (err, stdout) { if (err) throw err; - assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" + + assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" + "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxHQUFJQSxLQUFNLFdBQ04sUUFBU0MsS0FBS0QsS0FDVixNQUFPQSxLQUdYLE1BQU9DIn0=\n"); done(); }); @@ -70,4 +70,34 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should work with --keep-fnames (mangle only)", function (done) { + var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n"); + done(); + }); + }); + it("Should work with --keep-fnames (mangle & compress)", function (done) { + var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(5==f(g)());\n"); + done(); + }); + }); + it("Should work with keep_fnames under mangler options", function (done) { + var command = uglifyjscmd + ' test/input/issue-1431/sample.js -m keep_fnames=true'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, "function f(r){return function(){function n(n){return n*n}return r(n)}}function g(n){return n(1)+n(2)}console.log(f(g)()==5);\n"); + done(); + }); + }); }); From 7f8d72d9d37396f2da05d5d824f74bd414c30119 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 26 Jan 2017 19:59:32 +0800 Subject: [PATCH 8/8] update test (#1441) improved reduce_vars & binary operands produce more optimal results --- test/compress/arrays.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/compress/arrays.js b/test/compress/arrays.js index 807ba206..77ef761a 100644 --- a/test/compress/arrays.js +++ b/test/compress/arrays.js @@ -76,6 +76,7 @@ constant_join_2: { for_loop: { options = { unsafe : true, + unused : true, evaluate : true, reduce_vars : true }; @@ -110,7 +111,7 @@ for_loop: { function f1() { var a = [1, 2, 3]; - for (var i = 0, len = 3; i < len; i++) + for (var i = 0; i < 3; i++) console.log(a[i]); }