diff options
-rw-r--r-- | lib/compress.js | 75 | ||||
-rw-r--r-- | test/compress/classes.js | 196 |
2 files changed, 247 insertions, 24 deletions
diff --git a/lib/compress.js b/lib/compress.js index 6b9d7e7c..2d826697 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -405,6 +405,10 @@ merge(Compressor.prototype, { } } + function is_lambda(node) { + return node instanceof AST_Class || node instanceof AST_Lambda; + } + function is_arguments(def) { return def.name == "arguments" && def.scope.uses_arguments; } @@ -600,10 +604,7 @@ merge(Compressor.prototype, { function is_immutable(value) { if (!value) return false; - return value.is_constant() - || value instanceof AST_Class - || value instanceof AST_Lambda - || value instanceof AST_ObjectIdentity; + return value.is_constant() || is_lambda(value) || value instanceof AST_ObjectIdentity; } function has_escaped(d, node, parent) { @@ -959,6 +960,7 @@ merge(Compressor.prototype, { } } node.properties.filter(function(prop) { + reset_flags(prop); if (prop.key instanceof AST_Node) prop.key.walk(tw); return prop.value; }).forEach(function(prop) { @@ -1154,7 +1156,7 @@ merge(Compressor.prototype, { d.recursive_refs++; } else if (value && ref_once(compressor, d)) { d.in_loop = tw.loop_ids[d.id] !== tw.in_loop; - d.single_use = value instanceof AST_Lambda + d.single_use = is_lambda(value) && !value.pinned() && (!d.in_loop || tw.parent() instanceof AST_Call) || !d.in_loop @@ -3597,7 +3599,7 @@ merge(Compressor.prototype, { if (!is_strict(compressor)) return false; var exp = this.expression; if (exp instanceof AST_SymbolRef) exp = exp.fixed_value(); - return !(this.property == "prototype" && (exp instanceof AST_Class || exp instanceof AST_Lambda)); + return !(this.property == "prototype" && is_lambda(exp)); }); def(AST_Lambda, return_false); def(AST_Null, return_true); @@ -4806,7 +4808,12 @@ merge(Compressor.prototype, { || any(this.body, compressor); }); def(AST_Class, function(compressor) { - return this.extends || any(this.properties, compressor); + var base = this.extends; + if (base) { + if (base instanceof AST_SymbolRef) base = base.fixed_value(); + if (!is_lambda(base) || is_arrow(base)) return true; + } + return any(this.properties, compressor); }); def(AST_ClassProperty, function(compressor) { return this.key instanceof AST_Node && this.key.has_side_effects(compressor) @@ -5009,9 +5016,9 @@ merge(Compressor.prototype, { // determine if expression is constant (function(def) { - function all_constant(list) { + function all_constant(list, scope) { for (var i = list.length; --i >= 0;) - if (!list[i].is_constant_expression()) + if (!list[i].is_constant_expression(scope)) return false; return true; } @@ -5024,11 +5031,13 @@ merge(Compressor.prototype, { && this.right.is_constant_expression() && (this.operator != "in" || is_object(this.right)); }); - def(AST_Class, function() { - return !this.extends && all_constant(this.properties); + def(AST_Class, function(scope) { + var base = this.extends; + if (base && (!is_lambda(base) || is_arrow(base))) return false; + return all_constant(this.properties, scope); }); - def(AST_ClassProperty, function() { - return typeof this.key == "string" && (!this.value || this.value.is_constant_expression()); + def(AST_ClassProperty, function(scope) { + return typeof this.key == "string" && (!this.value || this.value.is_constant_expression(scope)); }); def(AST_Constant, return_true); def(AST_Lambda, function(scope) { @@ -7289,18 +7298,28 @@ merge(Compressor.prototype, { return self; }); def(AST_Class, function(compressor, first_in_statement) { - if (this.extends) return this; var exprs = [], values = []; this.properties.forEach(function(prop) { if (prop.key instanceof AST_Node) exprs.push(prop.key); if (prop instanceof AST_ClassField && prop.static && prop.value) values.push(prop.value); }); + var base = this.extends; + if (base) { + if (base instanceof AST_SymbolRef) base = base.fixed_value(); + base = !is_lambda(base) || is_arrow(base); + if (!base) exprs.unshift(this.extends); + } exprs = trim(exprs, compressor, first_in_statement); + if (exprs) first_in_statement = false; values = trim(values, compressor, first_in_statement); if (!exprs) { - if (!values) return null; + if (!base && !values) return null; exprs = []; } + if (base) exprs.unshift(make_node(AST_ClassExpression, this, { + extends: this.extends, + properties: [], + })); if (values) { var fn = make_node(AST_Arrow, this, { argnames: [], @@ -10187,7 +10206,7 @@ merge(Compressor.prototype, { function recursive_ref(compressor, def) { var level = 0, node = compressor.self(); do { - if (node instanceof AST_Lambda && node.name && node.name.definition() === def) return node; + if (is_lambda(node) && node.name && node.name.definition() === def) return node; } while (node = compressor.parent(level++)); } @@ -10211,7 +10230,7 @@ merge(Compressor.prototype, { var fixed = self.fixed_value(); var single_use = def.single_use && !(parent instanceof AST_Call && parent.is_expr_pure(compressor)); if (single_use) { - if (fixed instanceof AST_Lambda) { + if (is_lambda(fixed)) { if ((def.scope !== self.scope.resolve() || def.in_loop) && (!compressor.option("reduce_funcs") || def.escaped.depth == 1 || fixed.inlined)) { single_use = false; @@ -10232,6 +10251,8 @@ merge(Compressor.prototype, { } else if (fixed.name && (fixed.name.name == "await" && is_async(fixed) || fixed.name.name == "yield" && is_generator(fixed))) { single_use = false; + } else if (fixed.has_side_effects(compressor)) { + single_use = false; } if (single_use) fixed.parent_scope = self.scope; } else if (!fixed || !fixed.is_constant_expression() || fixed.drop_side_effect_free(compressor)) { @@ -10244,7 +10265,7 @@ merge(Compressor.prototype, { fixed.single_use = true; if (fixed instanceof AST_DefClass) fixed = to_class_expr(fixed); if (fixed instanceof AST_LambdaDefinition) fixed = to_func_expr(fixed); - if (fixed instanceof AST_Lambda) { + if (is_lambda(fixed)) { var scope = self.scope.resolve(); fixed.enclosed.forEach(function(def) { if (fixed.variables.has(def.name)) return; @@ -10259,11 +10280,19 @@ merge(Compressor.prototype, { var defun_def = value.name.definition(); var lambda_def = value.variables.get(value.name.name); var name = lambda_def && lambda_def.orig[0]; - if (!(name instanceof AST_SymbolLambda)) { - name = make_node(AST_SymbolLambda, value.name, value.name); + var def_fn_name, symbol_type; + if (value instanceof AST_Class) { + def_fn_name = "def_function"; + symbol_type = AST_SymbolClass; + } else { + def_fn_name = "def_variable"; + symbol_type = AST_SymbolLambda; + } + if (!(name instanceof symbol_type)) { + name = make_node(symbol_type, value.name, value.name); name.scope = value; value.name = name; - lambda_def = value.def_function(name); + lambda_def = value[def_fn_name](name); lambda_def.recursive_refs = def.recursive_refs; } value.walk(new TreeWalker(function(node) { @@ -10275,13 +10304,13 @@ merge(Compressor.prototype, { } else { def.single_use = false; var fn = node.fixed_value(); - if (!(fn instanceof AST_Lambda)) return; + if (!is_lambda(fn)) return; if (!fn.name) return; if (fn.name.definition() !== def) return; if (def.scope !== fn.name.scope) return; if (fixed.variables.get(fn.name.name) !== def) return; fn.name = fn.name.clone(); - var value_def = value.variables.get(fn.name.name) || value.def_function(fn.name); + var value_def = value.variables.get(fn.name.name) || value[def_fn_name](fn.name); node.thedef = value_def; value_def.references.push(node); } diff --git a/test/compress/classes.js b/test/compress/classes.js index 386e3725..279cff65 100644 --- a/test/compress/classes.js +++ b/test/compress/classes.js @@ -269,6 +269,43 @@ block_scoped: { node_version: ">=4" } +drop_extends: { + options = { + inline: true, + passes: 2, + pure_getters: "strict", + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + "use strict"; + try { + (function() { + var f = () => {}; + class A extends f { + get p() {} + } + A.q = 42; + return class B extends A {}; + })(); + } catch (e) { + console.log("PASS"); + } + } + expect: { + "use strict"; + try { + (class extends (() => {}) {}); + } catch (e) { + console.log("PASS"); + } + } + expect_stdout: "PASS" + node_version: ">=4" +} + keep_extends: { options = { toplevel: true, @@ -370,7 +407,7 @@ static_side_effects: { node_version: ">=12" } -single_use: { +single_use_1: { options = { reduce_vars: true, toplevel: true, @@ -389,6 +426,163 @@ single_use: { node_version: ">=4" } +single_use_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + class A { + f(a) { + console.log(a); + } + } + new A().f("PASS"); + } + expect: { + "use strict"; + new class { + f(a) { + console.log(a); + } + }().f("PASS"); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +single_use_3: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + class A { + f() { + return A; + } + } + console.log(typeof new A().f()); + } + expect: { + "use strict"; + console.log(typeof new class A { + f() { + return A; + } + }().f()); + } + expect_stdout: "function" + node_version: ">=4" +} + +single_use_4: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + console.log(new class A { + f() { + return typeof A; + } + }().f()); + } + expect: { + "use strict"; + console.log(new class A { + f() { + return typeof A; + } + }().f()); + } + expect_stdout: "function" + node_version: ">=4" +} + +single_use_5: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + console.log("foo"); + } + (function() { + "use strict"; + class A extends f { + f() { + console.log("bar"); + } + } + console.log("baz"); + new A().f(); + })(); + } + expect: { + function f() { + console.log("foo"); + } + (function() { + "use strict"; + class A extends f { + f() { + console.log("bar"); + } + } + console.log("baz"); + new A().f(); + })(); + } + expect_stdout: [ + "baz", + "foo", + "bar", + ] + node_version: ">=4" +} + +single_use_6: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + class A { + [(console.log("foo"), "f")]() { + console.log("bar"); + } + } + console.log("baz"); + new A().f(); + } + expect: { + "use strict"; + class A { + [(console.log("foo"), "f")]() { + console.log("bar"); + } + } + console.log("baz"); + new A().f(); + } + expect_stdout: [ + "foo", + "baz", + "bar", + ] + node_version: ">=4" +} + collapse_non_strict: { options = { collapse_vars: true, |