From 96bf7fceabf1670ca07c08a04a3130e742775c8f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 19 Oct 2020 01:32:39 +0100 Subject: [PATCH] support `let` (#4227) --- README.md | 3 + lib/ast.js | 15 + lib/compress.js | 66 ++-- lib/output.js | 5 +- lib/parse.js | 16 +- lib/scope.js | 2 + test/compress/const.js | 110 +----- test/compress/let.js | 842 ++++++++++++++++++++++++++++++++++++++++ test/compress/varify.js | 236 +++++++++++ test/ufuzz/index.js | 43 +- 10 files changed, 1186 insertions(+), 152 deletions(-) create mode 100644 test/compress/let.js create mode 100644 test/compress/varify.js diff --git a/README.md b/README.md index 6c549768..ecc844b5 100644 --- a/README.md +++ b/README.md @@ -784,6 +784,9 @@ to be `false` and all symbol names will be omitted. - `unused` (default: `true`) -- drop unreferenced functions and variables (simple direct variable assignments do not count as references unless set to `"keep_assign"`) +- `varify` (default: `true`) -- convert block-scoped declaractions into `var` + whenever safe to do so + ## Mangle options - `eval` (default `false`) -- Pass `true` to mangle names visible in scopes diff --git a/lib/ast.js b/lib/ast.js index 5ce591cc..a87e9c58 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -754,6 +754,17 @@ var AST_Const = DEFNODE("Const", null, { }, }, AST_Definitions); +var AST_Let = DEFNODE("Let", null, { + $documentation: "A `let` statement", + _validate: function() { + this.definitions.forEach(function(node) { + if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]"); + if (!(node.name instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet"); + if (node.value != null) must_be_expression(node, "value"); + }); + }, +}, AST_Definitions); + var AST_Var = DEFNODE("Var", null, { $documentation: "A `var` statement", _validate: function() { @@ -1066,6 +1077,10 @@ var AST_SymbolConst = DEFNODE("SymbolConst", null, { $documentation: "Symbol defining a constant", }, AST_SymbolDeclaration); +var AST_SymbolLet = DEFNODE("SymbolLet", null, { + $documentation: "Symbol defining a lexical-scoped variable", +}, AST_SymbolDeclaration); + var AST_SymbolVar = DEFNODE("SymbolVar", null, { $documentation: "Symbol defining a variable", }, AST_SymbolDeclaration); diff --git a/lib/compress.js b/lib/compress.js index 015ee306..d4be93ee 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -97,6 +97,7 @@ function Compressor(options, false_by_default) { unsafe_regexp : false, unsafe_undefined: false, unused : !false_by_default, + varify : !false_by_default, }, true); var evaluate = this.options["evaluate"]; this.eval_threshold = /eager/.test(evaluate) ? 1 / 0 : +evaluate; @@ -488,7 +489,9 @@ merge(Compressor.prototype, { if (!(declare || all(def.orig, function(sym) { return !(sym instanceof AST_SymbolConst); }))) return false; - if (def.fixed === undefined) return true; + if (def.fixed === undefined) return declare || all(def.orig, function(sym) { + return !(sym instanceof AST_SymbolLet); + }); if (def.fixed === null && def.safe_ids) { def.safe_ids[def.id] = false; delete def.safe_ids; @@ -1616,7 +1619,7 @@ merge(Compressor.prototype, { if (side_effects && may_modify(node)) return true; var def = node.definition(); return (in_try || def.scope.resolve() !== scope) && !all(def.orig, function(sym) { - return !(sym instanceof AST_SymbolConst); + return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet); }); } if (node instanceof AST_This) return symbol_in_lvalues(node, parent); @@ -2177,7 +2180,7 @@ merge(Compressor.prototype, { var stat = statements[i]; if (stat instanceof AST_BlockStatement) { if (all(stat.body, function(stat) { - return !(stat instanceof AST_Const); + return !(stat instanceof AST_Const || stat instanceof AST_Let); })) { CHANGED = true; eliminate_spurious_blocks(stat.body); @@ -2513,7 +2516,7 @@ merge(Compressor.prototype, { var line = block.body[i]; if (line instanceof AST_Var && declarations_only(line)) { decls.push(line); - } else if (stat || line instanceof AST_Const) { + } else if (stat || line instanceof AST_Const || line instanceof AST_Let) { return false; } else { stat = line; @@ -2829,7 +2832,7 @@ merge(Compressor.prototype, { function push(node) { if (block) { block.push(node); - if (node instanceof AST_Const) block.required = true; + if (node instanceof AST_Const || node instanceof AST_Let) block.required = true; } else { target.push(node); } @@ -4106,7 +4109,7 @@ merge(Compressor.prototype, { def(AST_SymbolDeclaration, return_false); def(AST_SymbolRef, function(compressor) { return !(this.is_declared(compressor) && all(this.definition().orig, function(sym) { - return !(sym instanceof AST_SymbolConst); + return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet); })); }); def(AST_This, return_false); @@ -4360,7 +4363,7 @@ merge(Compressor.prototype, { return make_node(AST_EmptyStatement, node); case 1: var stat = node.body[0]; - if (!(stat instanceof AST_Const)) return stat; + if (!(stat instanceof AST_Const || stat instanceof AST_Let)) return stat; } return node; } @@ -4460,10 +4463,7 @@ merge(Compressor.prototype, { } if (node instanceof AST_Const) { node.definitions.forEach(function(defn) { - var def = defn.name.definition(); - references[def.id] = false; - def = def.redefined(); - if (def) references[def.id] = false; + references[defn.name.definition().id] = false; defn.value.walk(tw); }); return true; @@ -4516,6 +4516,13 @@ merge(Compressor.prototype, { pop(); return true; } + if (node instanceof AST_Let) { + node.definitions.forEach(function(defn) { + references[defn.name.definition().id] = false; + if (defn.value) defn.value.walk(tw); + }); + return true; + } if (node instanceof AST_Scope) { push(); segment.block = node; @@ -4618,7 +4625,10 @@ merge(Compressor.prototype, { if (!tail_refs) continue; if (head_refs.start.block !== tail_refs.start.block || !mergeable(head_refs, tail_refs) - || head_refs.start.loop && !mergeable(tail_refs, head_refs)) { + || head_refs.start.loop && !mergeable(tail_refs, head_refs) + || !all(tail_refs, function(sym) { + return sym.scope.find_variable(def.name) === def; + })) { skipped.unshift(tail); continue; } @@ -4715,7 +4725,7 @@ merge(Compressor.prototype, { if (!(sym instanceof AST_SymbolRef)) return; if (compressor.exposed(sym.definition())) return; if (!all(sym.definition().orig, function(sym) { - return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLambda); + return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLambda || sym instanceof AST_SymbolLet); })) return; return sym; }; @@ -4956,7 +4966,7 @@ merge(Compressor.prototype, { } } else if (compressor.option("functions") && !compressor.option("ie8") - && !(node instanceof AST_Const) + && !(node instanceof AST_Const || node instanceof AST_Let) && var_defs.length == 1 && sym.assignments == 0 && def.value instanceof AST_Function @@ -5100,7 +5110,7 @@ merge(Compressor.prototype, { return in_list ? List.skip : make_node(AST_EmptyStatement, node); case 1: var stat = node.body[0]; - if (!(stat instanceof AST_Const)) return stat; + if (!(stat instanceof AST_Const || stat instanceof AST_Let)) return stat; } else if (node instanceof AST_For) { // Certain combination of unused name + side effect leads to invalid AST: // https://github.com/mishoo/UglifyJS/issues/44 @@ -5665,7 +5675,7 @@ merge(Compressor.prototype, { return ref.fixed_value() === right; }) && all(def.orig, function(sym) { - return !(sym instanceof AST_SymbolConst); + return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet); }); } }); @@ -5883,7 +5893,7 @@ merge(Compressor.prototype, { }); function drop_symbol(ref) { return all(ref.definition().orig, function(sym) { - return !(sym instanceof AST_SymbolConst); + return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet); }); } def(AST_SymbolRef, function(compressor) { @@ -6623,6 +6633,12 @@ merge(Compressor.prototype, { }); }); + AST_Let.DEFMETHOD("remove_initializers", function() { + this.definitions.forEach(function(def) { + def.value = null; + }); + }); + AST_Var.DEFMETHOD("remove_initializers", function() { this.definitions.forEach(function(def) { def.value = null; @@ -6650,20 +6666,17 @@ merge(Compressor.prototype, { return make_sequence(this, assignments); }); - OPT(AST_Const, function(self, compressor) { - return all(self.definitions, function(defn) { + function varify(self, compressor) { + return compressor.option("varify") && all(self.definitions, function(defn) { var node = defn.name; if (!node.fixed_value()) return false; var def = node.definition(); if (compressor.exposed(def)) return false; var scope = def.scope.resolve(); - if (def.scope === scope) return true; - if (scope instanceof AST_Toplevel) return !scope.variables.has(node.name) && !scope.globals.has(node.name); - var s = def.scope; - do { + for (var s = def.scope; s !== scope;) { s = s.parent_scope; if (s.var_names()[node.name]) return false; - } while (s !== scope); + } return true; }) ? make_node(AST_Var, self, { definitions: self.definitions.map(function(defn) { @@ -6678,7 +6691,10 @@ merge(Compressor.prototype, { }); }) }) : self; - }); + } + + OPT(AST_Const, varify); + OPT(AST_Let, varify); function lift_sequence_in_expression(node, compressor) { var exp = node.expression; diff --git a/lib/output.js b/lib/output.js index 70bd74be..8068648e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -990,7 +990,7 @@ function OutputStream(options) { /* -----[ if ]----- */ function make_then(self, output) { var b = self.body; - if (output.option("braces") && !(b instanceof AST_Const) + if (output.option("braces") && !(b instanceof AST_Const || b instanceof AST_Let) || output.option("ie8") && b instanceof AST_Do) return make_block(b, output); // The squeezer replaces "block"-s that contain only a single @@ -1124,6 +1124,7 @@ function OutputStream(options) { }; } DEFPRINT(AST_Const, print_definitinos("const")); + DEFPRINT(AST_Let, print_definitinos("let")); DEFPRINT(AST_Var, print_definitinos("var")); function parenthesize_for_noin(node, output, noin) { @@ -1381,7 +1382,7 @@ function OutputStream(options) { }); function force_statement(stat, output) { - if (output.option("braces") && !(stat instanceof AST_Const)) { + if (output.option("braces") && !(stat instanceof AST_Const || stat instanceof AST_Let)) { make_block(stat, output); } else if (!stat || stat instanceof AST_EmptyStatement) { output.force_semicolon(); diff --git a/lib/parse.js b/lib/parse.js index 42914d7d..7b2c8887 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,7 +44,7 @@ "use strict"; -var KEYWORDS = "break case catch const continue debugger default delete do else finally for function if in instanceof new return switch throw try typeof var void while with"; +var KEYWORDS = "break case catch const continue debugger default delete do else finally for function if in instanceof let new return switch throw try typeof var void while with"; var KEYWORDS_ATOM = "false null true"; var RESERVED_WORDS = [ "abstract boolean byte char class double enum export extends final float goto implements import int interface let long native package private protected public short static super synchronized this throws transient volatile yield", @@ -880,6 +880,12 @@ function parse($TEXT, options) { next(); return if_(); + case "let": + next(); + var node = let_(); + semicolon(); + return node; + case "return": if (S.in_function == 0 && !options.bare_returns) croak("'return' outside of function"); @@ -1202,6 +1208,14 @@ function parse($TEXT, options) { }); }; + var let_ = function(no_in) { + return new AST_Let({ + start : prev(), + definitions : vardefs(AST_SymbolLet, no_in), + end : prev() + }); + }; + var var_ = function(no_in) { return new AST_Var({ start : prev(), diff --git a/lib/scope.js b/lib/scope.js index ef19a530..4abf4066 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -169,6 +169,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { } else if (node instanceof AST_SymbolLambda) { var def = defun.def_function(node, node.name == "arguments" ? undefined : defun); if (options.ie8) def.defun = defun.parent_scope.resolve(); + } else if (node instanceof AST_SymbolLet) { + scope.def_variable(node); } else if (node instanceof AST_SymbolVar) { defun.def_variable(node, null); entangle(defun, scope); diff --git a/test/compress/const.js b/test/compress/const.js index 3e6662cd..8bb9a94e 100644 --- a/test/compress/const.js +++ b/test/compress/const.js @@ -161,31 +161,6 @@ merge_vars_3: { expect_stdout: true } -reduce_merge_vars: { - options = { - merge_vars: true, - reduce_vars: true, - toplevel: true, - unused: true, - } - input: { - const a = console; - console.log(typeof a); - var b = typeof a; - console.log(b); - } - expect: { - var b = console; - console.log(typeof b); - b = typeof b; - console.log(b); - } - expect_stdout: [ - "object", - "object", - ] -} - use_before_init_1: { options = { reduce_vars: true, @@ -312,24 +287,6 @@ reduce_block_1: { expect_stdout: "object" } -reduce_block_1_toplevel: { - options = { - reduce_vars: true, - toplevel: true, - } - input: { - { - const a = typeof console; - console.log(a); - } - } - expect: { - var a = typeof console; - console.log(a); - } - expect_stdout: "object" -} - reduce_block_2: { options = { reduce_vars: true, @@ -373,7 +330,7 @@ reduce_block_2_toplevel: { expect_stdout: true } -hoist_props_1: { +hoist_props: { options = { hoist_props: true, reduce_vars: true, @@ -397,28 +354,6 @@ hoist_props_1: { expect_stdout: "PASS" } -hoist_props_2: { - options = { - hoist_props: true, - passes: 2, - reduce_vars: true, - toplevel: true, - } - input: { - { - const o = { - p: "PASS", - }; - console.log(o.p); - } - } - expect: { - var o_p = "PASS"; - console.log(o_p); - } - expect_stdout: "PASS" -} - loop_block_1: { options = { loops: true, @@ -559,29 +494,6 @@ dead_block_after_return: { expect_stdout: true } -const_to_var_scope_adjustment: { - options = { - conditionals: true, - inline: true, - reduce_vars: true, - toplevel: true, - unused: true, - } - input: { - for (var k in [ 42 ]) - console.log(function f() { - if (k) { - const a = 0; - } - }()); - } - expect: { - for (var k in [ 42 ]) - console.log(void (k && 0)); - } - expect_stdout: "undefined" -} - do_if_continue_1: { options = { if_return: true, @@ -687,7 +599,7 @@ legacy_scope: { expect_stdout: true } -issue_4191_1: { +issue_4191: { options = { functions: true, reduce_vars: true, @@ -708,24 +620,6 @@ issue_4191_1: { expect_stdout: true } -issue_4191_2: { - options = { - functions: true, - reduce_vars: true, - toplevel: true, - unused: true, - } - input: { - const a = function() {}; - console.log(typeof a, a()); - } - expect: { - function a() {}; - console.log(typeof a, a()); - } - expect_stdout: "function undefined" -} - issue_4193: { options = { dead_code: true, diff --git a/test/compress/let.js b/test/compress/let.js new file mode 100644 index 00000000..74e8fd81 --- /dev/null +++ b/test/compress/let.js @@ -0,0 +1,842 @@ +retain_block: { + options = {} + input: { + "use strict"; + { + let a = "FAIL"; + } + var a = "PASS"; + console.log(a); + } + expect: { + "use strict"; + { + let a = "FAIL"; + } + var a = "PASS"; + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +if_dead_branch: { + options = { + conditionals: true, + dead_code: true, + evaluate: true, + } + input: { + "use strict"; + console.log(function() { + if (0) { + let a = 0; + } + return typeof a; + }()); + } + expect: { + "use strict"; + console.log(function() { + 0; + { + let a; + } + return typeof a; + }()); + } + expect_stdout: "undefined" + node_version: ">=4" +} + +merge_vars_1: { + options = { + merge_vars: true, + toplevel: true, + } + input: { + "use strict"; + let a = console; + console.log(typeof a); + var b = typeof a; + console.log(b); + } + expect: { + "use strict"; + let a = console; + console.log(typeof a); + var b = typeof a; + console.log(b); + } + expect_stdout: [ + "object", + "object", + ] + node_version: ">=4" +} + +merge_vars_2: { + options = { + inline: true, + merge_vars: true, + toplevel: true, + } + input: { + "use strict"; + var a = 0; + (function() { + var b = function f() { + let c = a && f; + c.var += 0; + }(); + console.log(b); + })(1 && --a); + } + expect: { + "use strict"; + var a = 0; + 1 && --a, + b = function f() { + let c = a && f; + c.var += 0; + }(), + void console.log(b); + var b; + } + expect_stdout: "undefined" + node_version: ">=4" +} + +merge_vars_3: { + options = { + merge_vars: true, + toplevel: true, + } + input: { + "use strict"; + { + let a = 0; + var b = console; + console.log(typeof b); + } + var a = 1; + console.log(typeof a); + } + expect: { + "use strict"; + { + let a = 0; + var b = console; + console.log(typeof b); + } + var a = 1; + console.log(typeof a); + } + expect_stdout: [ + "object", + "number", + ] + node_version: ">=4" +} + +use_before_init_1: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + } + input: { + "use strict"; + a = "foo"; + let a = "bar"; + } + expect: { + "use strict"; + a = "foo"; + let a = "bar"; + } + expect_stdout: true + node_version: ">=4" +} + +use_before_init_2: { + options = { + toplevel: true, + unused: true, + } + input: { + "use strict"; + try { + a = "foo"; + } catch (e) { + console.log("PASS"); + } + let a = "bar"; + } + expect: { + "use strict"; + try { + a = "foo"; + } catch (e) { + console.log("PASS"); + } + let a = "bar"; + } + expect_stdout: "PASS" + node_version: ">=4" +} + +use_before_init_3: { + options = { + side_effects: true, + } + input: { + "use strict"; + try { + a; + } catch (e) { + console.log("PASS"); + } + let a = 42; + } + expect: { + "use strict"; + try { + a; + } catch (e) { + console.log("PASS"); + } + let a = 42; + } + expect_stdout: "PASS" + node_version: ">=4" +} + +use_before_init_4: { + options = { + reduce_vars: true, + } + input: { + "use strict"; + try { + console.log(a); + } catch (e) { + console.log("PASS"); + } + let a = "FAIL"; + } + expect: { + "use strict"; + try { + console.log(a); + } catch (e) { + console.log("PASS"); + } + let a = "FAIL"; + } + expect_stdout: "PASS" + node_version: ">=4" +} + +collapse_block: { + options = { + collapse_vars: true, + pure_getters: "strict", + unsafe: true, + } + input: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + } + expect: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + } + expect_stdout: "object" + node_version: ">=4" +} + +reduce_block_1: { + options = { + reduce_vars: true, + } + input: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + } + expect: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + } + expect_stdout: "object" + node_version: ">=4" +} + +reduce_block_2: { + options = { + reduce_vars: true, + } + input: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + console.log(typeof a); + } + expect: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + console.log(typeof a); + } + expect_stdout: [ + "object", + "undefined", + ] + node_version: ">=4" +} + +reduce_block_2_toplevel: { + options = { + reduce_vars: true, + toplevel: true, + } + input: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + console.log(typeof a); + } + expect: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + console.log(typeof a); + } + expect_stdout: [ + "object", + "undefined", + ] + node_version: ">=4" +} + +hoist_props: { + options = { + hoist_props: true, + reduce_vars: true, + } + input: { + "use strict"; + { + let o = { + p: "PASS", + }; + console.log(o.p); + } + } + expect: { + "use strict"; + { + let o = { + p: "PASS", + }; + console.log(o.p); + } + } + expect_stdout: "PASS" + node_version: ">=4" +} + +loop_block_1: { + options = { + loops: true, + } + input: { + "use strict"; + do { + let o = console; + console.log(typeof o.log); + } while (!console); + } + expect: { + "use strict"; + do { + let o = console; + console.log(typeof o.log); + } while (!console); + } + expect_stdout: "function" + node_version: ">=4" +} + +loop_block_2: { + options = { + loops: true, + } + input: { + "use strict"; + do { + let o = {}; + (function() { + console.log(typeof this, o.p++); + })(); + } while (!console); + } + expect: { + "use strict"; + do { + let o = {}; + (function() { + console.log(typeof this, o.p++); + })(); + } while (!console); + } + expect_stdout: "undefined NaN" + node_version: ">=4" +} + +do_continue: { + options = { + loops: true, + } + input: { + "use strict"; + try { + do { + { + let a = 0; + continue; + } + } while ([ A ]); + } catch (e) { + console.log("PASS"); + } + } + expect: { + "use strict"; + try { + do { + let a = 0; + continue; + } while ([ A ]); + } catch (e) { + console.log("PASS"); + } + } + expect_stdout: "PASS" + node_version: ">=4" +} + +dead_block_after_return: { + options = { + dead_code: true, + } + input: { + "use strict"; + (function(a) { + console.log(a); + return; + { + let a = "FAIL"; + } + })("PASS"); + } + expect: { + "use strict"; + (function(a) { + console.log(a); + return; + { + let a; + } + })("PASS"); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +do_if_continue_1: { + options = { + if_return: true, + } + input: { + "use strict"; + do { + if (console) { + console.log("PASS"); + { + let a = 0; + var b; + continue; + } + } + } while (b); + } + expect: { + "use strict"; + do { + if (!console); + else { + console.log("PASS"); + { + let a = 0; + var b; + } + } + } while (b); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +do_if_continue_2: { + options = { + if_return: true, + } + input: { + "use strict"; + do { + if (console) { + console.log("FAIL"); + { + let a = 0; + A = 0; + continue; + } + } + } while (A); + } + expect: { + "use strict"; + do { + if (!console); + else { + console.log("FAIL"); + { + let a = 0; + A = 0; + } + } + } while (A); + } + expect_stdout: ReferenceError("A is not defined") + node_version: ">=4" +} + +drop_unused: { + options = { + evaluate: true, + side_effects: true, + unused: true, + } + input: { + "use strict"; + function f(a) { + let b = a, c = b; + 0 && c.p++; + } + console.log(f()); + } + expect: { + "use strict"; + function f(a) { + let b = a; + b; + } + console.log(f()); + } + expect_stdout: "undefined" + node_version: ">=4" +} + +issue_4191: { + options = { + functions: true, + reduce_vars: true, + unused: true, + } + input: { + "use strict"; + { + let a = function() {}; + } + console.log(typeof a); + } + expect: { + "use strict"; + { + let a = function() {}; + } + console.log(typeof a); + } + expect_stdout: "undefined" + node_version: ">=4" +} + +issue_4197: { + options = { + collapse_vars: true, + } + input: { + "use strict"; + var a = 0; + try { + let b = function() { + a = 1; + b[1]; + }(); + } catch (e) { + console.log(a); + } + } + expect: { + "use strict"; + var a = 0; + try { + let b = function() { + a = 1; + b[1]; + }(); + } catch (e) { + console.log(a); + } + } + expect_stdout: "1" + node_version: ">=4" +} + +issue_4202: { + options = { + inline: true, + toplevel: true, + } + input: { + "use strict"; + { + let o = {}; + (function() { + function f() { + o.p = 42; + } + f(f); + })(); + console.log(o.p++); + } + } + expect: { + "use strict"; + { + let o = {}; + (function() { + function f() { + o.p = 42; + } + f(f); + })(); + console.log(o.p++); + } + } + expect_stdout: "42" + node_version: ">=4" +} + +issue_4207: { + options = { + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + "use strict"; + { + let a = function() {}; + console.log(a.length); + } + } + expect: { + "use strict"; + { + let a = function() {}; + console.log(a.length); + } + } + expect_stdout: "0" + node_version: ">=4" +} + +issue_4218: { + options = { + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + var a; + { + let a = function() {}; + var b = 0 * a; + } + console.log(typeof a, b); + } + expect: { + "use strict"; + var b = 0 * function() {}; + console.log(typeof void 0, b); + } + expect_stdout: "undefined NaN" + node_version: ">=4" +} + +issue_4210: { + options = { + reduce_vars: true, + varify: true, + } + input: { + "use strict"; + var a; + (function() { + try { + throw 42; + } catch (e) { + let a = typeof e; + console.log(a); + } finally { + return a = "foo"; + } + })(); + console.log(typeof a); + } + expect: { + "use strict"; + var a; + (function() { + try { + throw 42; + } catch (e) { + let a = typeof e; + console.log(a); + } finally { + return a = "foo"; + } + })(); + console.log(typeof a); + } + expect_stdout: [ + "number", + "string", + ] + node_version: ">=4" +} + +issue_4212_1: { + options = { + dead_code: true, + } + input: { + "use strict"; + console.log({ + get b() { + let a = 0; + return a /= 0; + } + }.b); + } + expect: { + "use strict"; + console.log({ + get b() { + let a = 0; + return a / 0; + } + }.b); + } + expect_stdout: "NaN" + node_version: ">=4" +} + +issue_4212_2: { + options = { + reduce_vars: true, + } + input: { + "use strict"; + console.log({ + get b() { + let a = 0; + return a /= 0; + } + }.b); + } + expect: { + "use strict"; + console.log({ + get b() { + let a = 0; + return a /= 0; + } + }.b); + } + expect_stdout: "NaN" + node_version: ">=4" +} + +skip_braces: { + beautify = { + beautify: true, + braces: true, + } + input: { + "use strict"; + if (console) + let a = console.log(typeof a); + } + expect_exact: [ + '"use strict";', + "", + "if (console) let a = console.log(typeof a);", + ] + expect_stdout: true + node_version: ">=4" +} + +issue_4225: { + options = { + side_effects: true, + } + input: { + "use strict"; + let a = void typeof b; + let b = 42; + console.log(a, b); + } + expect: { + "use strict"; + let a = void b; + let b = 42; + console.log(a, b); + } + expect_stdout: true + node_version: ">=4" +} diff --git a/test/compress/varify.js b/test/compress/varify.js new file mode 100644 index 00000000..1286fc67 --- /dev/null +++ b/test/compress/varify.js @@ -0,0 +1,236 @@ +reduce_merge_const: { + options = { + merge_vars: true, + reduce_vars: true, + toplevel: true, + unused: true, + varify: true, + } + input: { + const a = console; + console.log(typeof a); + var b = typeof a; + console.log(b); + } + expect: { + var b = console; + console.log(typeof b); + b = typeof b; + console.log(b); + } + expect_stdout: [ + "object", + "object", + ] +} + +reduce_merge_let: { + options = { + merge_vars: true, + reduce_vars: true, + toplevel: true, + unused: true, + varify: true, + } + input: { + "use strict"; + let a = console; + console.log(typeof a); + var b = typeof a; + console.log(b); + } + expect: { + "use strict"; + var b = console; + console.log(typeof b); + b = typeof b; + console.log(b); + } + expect_stdout: [ + "object", + "object", + ] + node_version: ">=4" +} + +reduce_block_const: { + options = { + reduce_vars: true, + toplevel: true, + varify: true, + } + input: { + { + const a = typeof console; + console.log(a); + } + } + expect: { + var a = typeof console; + console.log(a); + } + expect_stdout: "object" +} + +reduce_block_let: { + options = { + reduce_vars: true, + toplevel: true, + varify: true, + } + input: { + "use strict"; + { + let a = typeof console; + console.log(a); + } + } + expect: { + "use strict"; + var a = typeof console; + console.log(a); + } + expect_stdout: "object" + node_version: ">=4" +} + +hoist_props_const: { + options = { + hoist_props: true, + passes: 2, + reduce_vars: true, + toplevel: true, + varify: true, + } + input: { + { + const o = { + p: "PASS", + }; + console.log(o.p); + } + } + expect: { + var o_p = "PASS"; + console.log(o_p); + } + expect_stdout: "PASS" +} + +hoist_props_let: { + options = { + hoist_props: true, + passes: 2, + reduce_vars: true, + toplevel: true, + varify: true, + } + input: { + "use strict"; + { + let o = { + p: "PASS", + }; + console.log(o.p); + } + } + expect: { + "use strict"; + var o_p = "PASS"; + console.log(o_p); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +scope_adjustment_const: { + options = { + conditionals: true, + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + varify: true, + } + input: { + for (var k in [ 42 ]) + console.log(function f() { + if (k) { + const a = 0; + } + }()); + } + expect: { + for (var k in [ 42 ]) + console.log(void (k && 0)); + } + expect_stdout: "undefined" +} + +scope_adjustment_let: { + options = { + conditionals: true, + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + varify: true, + } + input: { + "use strict"; + for (var k in [ 42 ]) + console.log(function f() { + if (k) { + let a = 0; + } + }()); + } + expect: { + "use strict"; + for (var k in [ 42 ]) + console.log(void (k && 0)); + } + expect_stdout: "undefined" + node_version: ">=4" +} + +issue_4191_const: { + options = { + functions: true, + reduce_vars: true, + toplevel: true, + unused: true, + varify: true, + } + input: { + const a = function() {}; + console.log(typeof a, a()); + } + expect: { + function a() {}; + console.log(typeof a, a()); + } + expect_stdout: "function undefined" +} + +issue_4191_let: { + options = { + functions: true, + reduce_vars: true, + toplevel: true, + unused: true, + varify: true, + } + input: { + "use strict"; + let a = function() {}; + console.log(typeof a, a()); + } + expect: { + "use strict"; + function a() {}; + console.log(typeof a, a()); + } + expect_stdout: "function undefined" + node_version: ">=4" +} diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 09cad3b2..5f6a3de8 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -381,32 +381,43 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { var block_len = block_vars.length; var var_len = VAR_NAMES.length; var consts = []; + var lets = []; unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity"); while (!rng(block_vars.length > block_len ? 10 : 100)) { var name = createVarName(MANDATORY, DONT_STORE); - consts.push(name); + if (rng(2)) { + consts.push(name); + } else { + lets.push(name); + } block_vars.push(name); } unique_vars.length -= 6; fn(function() { - var s = []; - if (consts.length) { - var save = VAR_NAMES; - VAR_NAMES = VAR_NAMES.filter(function(name) { - return consts.indexOf(name) < 0; - }); - var len = VAR_NAMES.length; - s.push("const " + consts.map(function(name) { - var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow); - VAR_NAMES.push(name); - return name + " = " + value; - }).join(", ") + ";"); - VAR_NAMES = save.concat(VAR_NAMES.slice(len)); + if (rng(2)) { + return createDefinitions("const", consts) + "\n" + createDefinitions("let", lets) + "\n"; + } else { + return createDefinitions("let", lets) + "\n" + createDefinitions("const", consts) + "\n"; } - return s.join("\n"); }); block_vars.length = block_len; - if (consts.length) VAR_NAMES.splice(var_len, consts.length); + if (consts.length || lets.length) VAR_NAMES.splice(var_len, consts.length + lets.length); + + function createDefinitions(type, names) { + if (!names.length) return ""; + var save = VAR_NAMES; + VAR_NAMES = VAR_NAMES.filter(function(name) { + return names.indexOf(name) < 0; + }); + var len = VAR_NAMES.length; + var s = type + " " + names.map(function(name) { + var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow); + VAR_NAMES.push(name); + return name + " = " + value; + }).join(", ") + ";"; + VAR_NAMES = save.concat(VAR_NAMES.slice(len)); + return s; + } } function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { -- 2.34.1