From e7b9b4aafbc0ee1196de0a8b76c1015e53181056 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 9 Jun 2024 00:57:15 +0300 Subject: [PATCH] workaround `function` declaration quirks in ES6+ (#5833) fixes #1666 --- lib/compress.js | 41 ++++++++++- test/compress/blocks.js | 42 +++++++++++ test/compress/collapse_vars.js | 112 +++++++++++++++++++++++++++++ test/compress/reduce_vars.js | 124 +++++++++++++++++++++++++++++++++ 4 files changed, 317 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 6de1ec4c06a..5cf2a2e6613 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1375,6 +1375,11 @@ Compressor.prototype.compress = function(node) { var d = this.definition(); if (!d.first_decl && d.references.length == 0) d.first_decl = this; }); + def(AST_SymbolDefun, function() { + var d = this.definition(); + if (!d.first_decl && d.references.length == 0) d.first_decl = this; + if (d.orig.length > 1 && d.scope.resolve() !== this.scope) d.fixed = false; + }); def(AST_SymbolImport, function() { var d = this.definition(); d.first_decl = this; @@ -1893,6 +1898,7 @@ Compressor.prototype.compress = function(node) { if (stat instanceof AST_LambdaDefinition) { var def = stat.name.definition(); var scope = stat.name.scope; + if (def.orig.length > 1 && def.scope.resolve() !== scope) return false; return def.scope === scope || all(def.references, function(ref) { var s = ref.scope; do { @@ -2405,6 +2411,7 @@ Compressor.prototype.compress = function(node) { } var read_toplevel = false; var modify_toplevel = false; + var defun_scopes = get_defun_scopes(lhs); // Locate symbols which may execute code outside of scanning range var enclosed = new Dictionary(); var well_defined = true; @@ -2535,8 +2542,11 @@ Compressor.prototype.compress = function(node) { if (parent instanceof AST_For) { if (node !== parent.init) return true; } - if (node instanceof AST_Assign) { - return node.operator != "=" && lhs.equals(node.left); + if (node instanceof AST_Assign) return node.operator != "=" && lhs.equals(node.left); + if (node instanceof AST_BlockStatement) { + return defun_scopes && !all(defun_scopes, function(scope) { + return node !== scope; + }); } if (node instanceof AST_Call) { if (!(lhs instanceof AST_PropAccess)) return false; @@ -3437,6 +3447,33 @@ Compressor.prototype.compress = function(node) { } } + function get_defun_scopes(lhs) { + if (!(lhs instanceof AST_SymbolDeclaration + || lhs instanceof AST_SymbolRef + || lhs instanceof AST_Destructured)) return; + var scopes = []; + lhs.mark_symbol(function(node) { + if (node instanceof AST_Symbol) { + var def = node.definition(); + var scope = def.scope.resolve(); + var found = false; + var avoid = def.orig.reduce(function(scopes, sym) { + if (sym instanceof AST_SymbolDefun) { + if (sym.scope !== scope) push_uniq(scopes, sym.scope); + } else { + found = true; + } + return scopes; + }, []); + if (found) avoid.forEach(function(scope) { + push_uniq(scopes, scope); + }); + } + }); + if (scopes.length == 0) return; + return scopes; + } + function is_lhs_local(lhs) { var sym = root_expr(lhs); if (!(sym instanceof AST_SymbolRef)) return false; diff --git a/test/compress/blocks.js b/test/compress/blocks.js index 8372adf2797..0801cc11b93 100644 --- a/test/compress/blocks.js +++ b/test/compress/blocks.js @@ -47,3 +47,45 @@ keep_some_blocks: { } else stuff(); } } + +issue_1666: { + input: { + var a = 42; + { + function a() {} + a(); + } + console.log("PASS"); + } + expect: { + var a = 42; + { + function a() {} + a(); + } + console.log("PASS"); + } + expect_stdout: true +} + +issue_1666_strict: { + input: { + "use strict"; + var a = 42; + { + function a() {} + a(); + } + console.log("PASS"); + } + expect: { + "use strict"; + var a = 42; + { + function a() {} + a(); + } + console.log("PASS"); + } + expect_stdout: true +} diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 45d26ea3131..bad26eadd4d 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -10172,3 +10172,115 @@ issue_5779: { } expect_stdout: "PASS" } + +issue_1666: { + options = { + collapse_vars: true, + } + input: { + var x = 42; + { + x(); + function x() { + console.log("foo"); + } + } + console.log(typeof x); + } + expect: { + var x = 42; + { + x(); + function x() { + console.log("foo"); + } + } + console.log(typeof x); + } + expect_stdout: true +} + +issue_1666_strict: { + options = { + collapse_vars: true, + } + input: { + "use strict"; + var x = 42; + { + x(); + function x() { + console.log("foo"); + } + } + console.log(typeof x); + } + expect: { + "use strict"; + var x = 42; + { + x(); + function x() { + console.log("foo"); + } + } + console.log(typeof x); + } + expect_stdout: true +} + +issue_1666_undefined: { + options = { + collapse_vars: true, + } + input: { + var undefined = 42; + { + undefined(); + function undefined() { + console.log("foo"); + } + } + console.log(typeof undefined); + } + expect: { + var undefined = 42; + { + undefined(); + function undefined() { + console.log("foo"); + } + } + console.log(typeof undefined); + } + expect_stdout: true +} + +issue_1666_undefined_strict: { + options = { + collapse_vars: true, + } + input: { + "use strict"; + var undefined = 42; + { + undefined(); + function undefined() { + console.log("foo"); + } + } + console.log(typeof undefined); + } + expect: { + "use strict"; + var undefined = 42; + { + undefined(); + function undefined() { + console.log("foo"); + } + } + console.log(typeof undefined); + } + expect_stdout: true +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 674d98c312d..aa9c45aba82 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -8222,3 +8222,127 @@ issue_5777_2: { } expect_stdout: "PASS" } + +issue_1666: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var x = 42; + { + x(); + function x() { + console.log("foo"); + } + } + console.log(typeof x); + } + expect: { + var x = 42; + { + x(); + function x() { + console.log("foo"); + } + } + console.log(typeof x); + } + expect_stdout: true +} + +issue_1666_strict: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + var x = 42; + { + x(); + function x() { + console.log("foo"); + } + } + console.log(typeof x); + } + expect: { + "use strict"; + var x = 42; + { + x(); + function x() { + console.log("foo"); + } + } + console.log(typeof x); + } + expect_stdout: true +} + +issue_1666_undefined: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var undefined = 42; + { + undefined(); + function undefined() { + console.log("foo"); + } + } + console.log(typeof undefined); + } + expect: { + var undefined = 42; + { + undefined(); + function undefined() { + console.log("foo"); + } + } + console.log(typeof undefined); + } + expect_stdout: true +} + +issue_1666_undefined_strict: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + var undefined = 42; + { + undefined(); + function undefined() { + console.log("foo"); + } + } + console.log(typeof undefined); + } + expect: { + "use strict"; + var undefined = 42; + { + undefined(); + function undefined() { + console.log("foo"); + } + } + console.log(typeof undefined); + } + expect_stdout: true +}