enhance `reduce_vars` & `side_effects` (#4712)
authorAlex Lam S.L <alexlamsl@gmail.com>
Tue, 2 Mar 2021 05:43:10 +0000 (05:43 +0000)
committerGitHub <noreply@github.com>
Tue, 2 Mar 2021 05:43:10 +0000 (13:43 +0800)
lib/compress.js
test/compress/classes.js

index 6b9d7e7..2d82669 100644 (file)
@@ -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);
                         }
index 386e372..279cff6 100644 (file)
@@ -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,