fix corner cases with `class` (#4723)
authorAlex Lam S.L <alexlamsl@gmail.com>
Tue, 2 Mar 2021 19:32:58 +0000 (19:32 +0000)
committerGitHub <noreply@github.com>
Tue, 2 Mar 2021 19:32:58 +0000 (03:32 +0800)
fixes #4720
fixes #4721
fixes #4722

lib/ast.js
lib/compress.js
test/compress/classes.js

index 1aee104..b035b5c 100644 (file)
@@ -874,6 +874,7 @@ var AST_ClassMethod = DEFNODE("ClassMethod", null, {
     $documentation: "A `class` method",
     _validate: function() {
         if (!(this.value instanceof AST_LambdaExpression)) throw new Error("value must be AST_LambdaExpression");
+        if (is_arrow(this.value)) throw new Error("value cannot be AST_Arrow or AST_AsyncArrow");
         if (this.value.name != null) throw new Error("name of class method's lambda must be null");
     },
 }, AST_ClassProperty);
index 7f64279..55ff50b 100644 (file)
@@ -409,6 +409,10 @@ merge(Compressor.prototype, {
         return node instanceof AST_Class || node instanceof AST_Lambda;
     }
 
+    function safe_for_extends(node) {
+        return node instanceof AST_Class || node instanceof AST_Defun || node instanceof AST_Function;
+    }
+
     function is_arguments(def) {
         return def.name == "arguments" && def.scope.uses_arguments;
     }
@@ -4815,7 +4819,7 @@ merge(Compressor.prototype, {
             var base = this.extends;
             if (base) {
                 if (base instanceof AST_SymbolRef) base = base.fixed_value();
-                if (!is_lambda(base) || is_arrow(base)) return true;
+                if (!safe_for_extends(base)) return true;
             }
             return any(this.properties, compressor);
         });
@@ -5037,7 +5041,7 @@ merge(Compressor.prototype, {
         });
         def(AST_Class, function(scope) {
             var base = this.extends;
-            if (base && (!is_lambda(base) || is_arrow(base))) return false;
+            if (base && !safe_for_extends(base)) return false;
             return all_constant(this.properties, scope);
         });
         def(AST_ClassProperty, function(scope) {
@@ -7310,7 +7314,7 @@ merge(Compressor.prototype, {
             var base = this.extends;
             if (base) {
                 if (base instanceof AST_SymbolRef) base = base.fixed_value();
-                base = !is_lambda(base) || is_arrow(base);
+                base = !safe_for_extends(base);
                 if (!base) exprs.unshift(this.extends);
             }
             exprs = trim(exprs, compressor, first_in_statement);
@@ -7320,10 +7324,6 @@ merge(Compressor.prototype, {
                 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: [],
@@ -7336,7 +7336,19 @@ merge(Compressor.prototype, {
                     expression: fn,
                 }));
             }
-            return make_sequence(this, exprs);
+            exprs = exprs.length ? make_sequence(this, exprs) : null;
+            if (!base) return exprs;
+            var node = make_node(AST_ClassExpression, this, this);
+            node.name = null;
+            node.properties = [];
+            if (exprs) node.properties.push(make_node(AST_ClassMethod, this, {
+                key: exprs,
+                value: make_node(AST_Function, this, {
+                    argnames: [],
+                    body: [],
+                }).init_vars(node),
+            }));
+            return node;
         });
         def(AST_Conditional, function(compressor) {
             var consequent = this.consequent.drop_side_effect_free(compressor);
@@ -10267,6 +10279,8 @@ merge(Compressor.prototype, {
                         single_use = false;
                     } else if (fixed.has_side_effects(compressor)) {
                         single_use = false;
+                    } else if (compressor.option("ie8") && fixed instanceof AST_Class) {
+                        single_use = false;
                     }
                     if (single_use) fixed.parent_scope = self.scope;
                 } else if (!fixed || !fixed.is_constant_expression() || fixed.drop_side_effect_free(compressor)) {
index 279cff6..f073acd 100644 (file)
@@ -1047,3 +1047,128 @@ issue_4705: {
     expect_stdout: "PASS"
     node_version: ">=12"
 }
+
+issue_4720: {
+    options = {
+        ie8: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        class A {
+            static p = function f() {};
+        }
+        console.log(typeof A.p, typeof f);
+    }
+    expect: {
+        class A {
+            static p = function f() {};
+        }
+        console.log(typeof A.p, typeof f);
+    }
+    expect_stdout: "function undefined"
+    node_version: ">=12"
+}
+
+issue_4721: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        "use strict";
+        var a = "foo";
+        try {
+            (class extends 42 {
+                [a = "bar"]() {}
+            })
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect: {
+        "use strict";
+        var a = "foo";
+        try {
+            (class extends 42 {
+                [a = "bar"]() {}
+            });
+        } catch (e) {
+            console.log(a);
+        }
+    }
+    expect_stdout: true
+    node_version: ">=4"
+}
+
+issue_4722_1: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        "use strict";
+        try {
+            (class extends function*() {} {});
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        "use strict";
+        try {
+            (class extends function*() {} {});
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=4"
+}
+
+issue_4722_2: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        "use strict";
+        try {
+            (class extends async function() {} {});
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        "use strict";
+        try {
+            (class extends async function() {} {});
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=8"
+}
+
+issue_4722_3: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        "use strict";
+        try {
+            (class extends async function*() {} {});
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        "use strict";
+        try {
+            (class extends async function*() {} {});
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+    node_version: ">=10"
+}