From e6dd471f8fff84c878153008139150a090c5ba19 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 19 Dec 2020 12:28:38 +0800 Subject: [PATCH] support destructuring of `catch` variable (#4412) --- lib/ast.js | 10 ++--- lib/compress.js | 76 +++++++++++++++++++---------------- lib/parse.js | 2 +- test/compress/destructured.js | 40 ++++++++++++++++++ test/ufuzz/index.js | 42 ++++++++++++++----- 5 files changed, 120 insertions(+), 50 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index fcbfb710..e966cefa 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -786,7 +786,7 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", { var AST_Catch = DEFNODE("Catch", "argname", { $documentation: "A `catch` node; only makes sense as part of a `try` statement", $propdoc: { - argname: "[AST_SymbolCatch?] symbol for the exception, or null if not present", + argname: "[(AST_Destructured|AST_SymbolCatch)?] symbol for the exception, or null if not present", }, walk: function(visitor) { var node = this; @@ -796,9 +796,9 @@ var AST_Catch = DEFNODE("Catch", "argname", { }); }, _validate: function() { - if (this.argname != null) { - if (!(this.argname instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch"); - } + if (this.argname != null) validate_destructured(this.argname, function(node) { + if (!(node instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch"); + }); }, }, AST_Block); @@ -868,7 +868,7 @@ var AST_Var = DEFNODE("Var", null, { var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", $propdoc: { - name: "[AST_SymbolVar] name of the variable", + name: "[AST_Destructured|AST_SymbolVar] name of the variable", value: "[AST_Node?] initializer, or null of there's no initializer" }, walk: function(visitor) { diff --git a/lib/compress.js b/lib/compress.js index f33b97bc..1aab3680 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -4949,11 +4949,13 @@ merge(Compressor.prototype, { walk_body(node, tw); pop(); if (node.bcatch) { - if (node.bcatch.argname) { - var def = node.bcatch.argname.definition(); - references[def.id] = false; - if (def = def.redefined()) references[def.id] = false; - } + if (node.bcatch.argname) node.bcatch.argname.mark_symbol(function(node) { + if (node instanceof AST_SymbolCatch) { + var def = node.definition(); + references[def.id] = false; + if (def = def.redefined()) references[def.id] = false; + } + }, tw); push(); if (node.bfinally) segment.block = node.bcatch; walk_body(node.bcatch, tw); @@ -5317,6 +5319,34 @@ merge(Compressor.prototype, { var unused_fn_names = []; var calls_to_drop_args = []; var fns_with_marked_args = []; + var trimmer = new TreeTransformer(function(node) { + if (node instanceof AST_DestructuredArray) { + var trim = true; + for (var i = node.elements.length; --i >= 0;) { + var sym = node.elements[i]; + if (!(sym instanceof AST_SymbolDeclaration)) { + node.elements[i] = sym.transform(trimmer); + trim = false; + } else if (sym.definition().id in in_use_ids) { + trim = false; + } else if (trim) { + node.elements.pop(); + } else { + node.elements[i] = make_node(AST_Hole, sym); + } + } + return node; + } + if (node instanceof AST_DestructuredKeyVal) { + if (!(node.value instanceof AST_SymbolDeclaration)) { + node.value = node.value.transform(trimmer); + return node; + } + if (typeof node.key != "string") return node; + if (node.value.definition().id in in_use_ids) return node; + return List.skip; + } + }); var tt = new TreeTransformer(function(node, descend, in_list) { var parent = tt.parent(); if (drop_vars) { @@ -5383,34 +5413,7 @@ merge(Compressor.prototype, { for (var a = node.argnames, i = a.length; --i >= 0;) { var sym = a[i]; if (sym instanceof AST_Destructured) { - sym.transform(new TreeTransformer(function(node) { - if (node instanceof AST_DestructuredArray) { - var trim = true; - for (var i = node.elements.length; --i >= 0;) { - var sym = node.elements[i]; - if (!(sym instanceof AST_SymbolFunarg)) { - node.elements[i] = sym.transform(this); - trim = false; - } else if (sym.definition().id in in_use_ids) { - trim = false; - } else if (trim) { - node.elements.pop(); - } else { - node.elements[i] = make_node(AST_Hole, sym); - } - } - return node; - } - if (node instanceof AST_DestructuredKeyVal) { - if (!(node.value instanceof AST_SymbolFunarg)) { - node.value = node.value.transform(this); - return node; - } - if (typeof node.key != "string") return node; - if (node.value.definition().id in in_use_ids) return node; - return List.skip; - } - })); + sym.transform(trimmer); trim = false; continue; } @@ -5429,6 +5432,9 @@ merge(Compressor.prototype, { fns_with_marked_args.push(node); } } + if (node instanceof AST_Catch && node.argname instanceof AST_Destructured) { + node.argname.transform(trimmer); + } if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) { // place uninitialized names at the start var body = [], head = [], tail = []; @@ -7263,7 +7269,9 @@ merge(Compressor.prototype, { self.body = tighten_body(self.body, compressor); if (compressor.option("dead_code")) { if (has_declarations_only(self) - && !(self.bcatch && self.bcatch.argname && !can_drop_symbol(self.bcatch.argname))) { + && !(self.bcatch && self.bcatch.argname && self.bcatch.argname.match_symbol(function(node) { + return node instanceof AST_SymbolCatch && !can_drop_symbol(node); + }, true))) { var body = []; if (self.bcatch) { extract_declarations_from_unreachable_code(compressor, self.bcatch, body); diff --git a/lib/parse.js b/lib/parse.js index f760d0de..3b7d15ca 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1216,7 +1216,7 @@ function parse($TEXT, options) { var name = null; if (is("punc", "(")) { next(); - name = as_symbol(AST_SymbolCatch); + name = maybe_destructured(AST_SymbolCatch); expect(")"); } bcatch = new AST_Catch({ diff --git a/test/compress/destructured.js b/test/compress/destructured.js index 8e1cf68c..4c36e451 100644 --- a/test/compress/destructured.js +++ b/test/compress/destructured.js @@ -750,6 +750,46 @@ simple_var: { node_version: ">=6" } +drop_catch: { + options = { + dead_code: true, + } + input: { + try {} catch ({ + [console.log("FAIL")]: e, + }) {} finally { + console.log("PASS"); + } + } + expect: { + console.log("PASS"); + } + expect_stdout: "PASS" + node_version: ">=6" +} + +drop_catch_var: { + options = { + unused: true, + } + input: { + try { + throw new Error("PASS"); + } catch ({ name, message }) { + console.log(message); + } + } + expect: { + try { + throw new Error("PASS"); + } catch ({ message }) { + console.log(message); + } + } + expect_stdout: "PASS" + node_version: ">=6" +} + collapse_vars_1: { options = { collapse_vars: true, diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index eb38a759..848b22bc 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -858,22 +858,44 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn if (n !== 1) { // the catch var should only be accessible in the catch clause... // we have to do go through some trouble here to prevent leaking it - var nameLenBefore = VAR_NAMES.length; mayCreateBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { - if (SUPPORT.catch_omit_var && rng(20) == 0) { + var block_len = block_vars.length; + var nameLenBefore = VAR_NAMES.length; + var unique_len = unique_vars.length; + var offset = SUPPORT.catch_omit_var ? 0 : SUPPORT.destructuring ? 1 : 2; + switch (offset + rng(20 - offset)) { + case 0: s += " catch { "; - } else { - var catchName = createVarName(MANDATORY); - if (!catch_redef) unique_vars.push(catchName); - s += " catch (" + catchName + ") { "; + break; + case 1: + var name = createVarName(MANDATORY); + block_vars.push(name); + var message = createVarName(MANDATORY); + block_vars.push(message); + if (SUPPORT.computed_key && rng(10) == 0) { + s += " catch ({ message: " + message + ", "; + addAvoidVars([ name ]); + s += "[" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + "]: " + name; + removeAvoidVars([ name ]); + s += " }) { "; + } else { + s += " catch ({ name: " + name + ", message: " + message + " }) { "; + } + break; + default: + var name = createVarName(MANDATORY); + if (!catch_redef) unique_vars.push(name); + s += " catch (" + name + ") { "; + break; } - var freshCatchName = VAR_NAMES.length !== nameLenBefore; + var catches = VAR_NAMES.length - nameLenBefore; s += defns() + "\n"; s += _createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth); s += " }"; - // remove catch name - if (!catch_redef) unique_vars.pop(); - if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1); + // remove catch variables + block_vars.length = block_len; + if (catches > 0) VAR_NAMES.splice(nameLenBefore, catches); + unique_vars.length = unique_len; }); } if (n !== 0) s += " finally { " + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + " }"; -- 2.34.1