fix corner cases with `export default` (#4673)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 21 Feb 2021 05:01:56 +0000 (05:01 +0000)
committerGitHub <noreply@github.com>
Sun, 21 Feb 2021 05:01:56 +0000 (13:01 +0800)
lib/ast.js
lib/compress.js
lib/output.js
lib/parse.js
test/compress/exports.js

index b386b44..18169df 100644 (file)
@@ -1060,7 +1060,7 @@ var AST_ExportDeclaration = DEFNODE("ExportDeclaration", "body", {
 var AST_ExportDefault = DEFNODE("ExportDefault", "body", {
     $documentation: "An `export default` statement",
     $propdoc: {
-        body: "[AST_Node] an expression node (should not be instanceof AST_Statement)",
+        body: "[AST_Node] the default export",
     },
     walk: function(visitor) {
         var node = this;
@@ -1069,7 +1069,11 @@ var AST_ExportDefault = DEFNODE("ExportDefault", "body", {
         });
     },
     _validate: function() {
-        must_be_expression(this, "body");
+        if (this.body instanceof AST_Lambda && this.body.name) {
+            if (!(this.body instanceof AST_LambdaDefinition)) throw new Error("body must be AST_LambdaDefinition");
+        } else {
+            must_be_expression(this, "body");
+        }
     },
 }, AST_Statement);
 
index da30cee..7577437 100644 (file)
@@ -5537,6 +5537,27 @@ merge(Compressor.prototype, {
         }
     }
 
+    function to_func_expr(defun, drop_name) {
+        var ctor;
+        switch (defun.CTOR) {
+          case AST_AsyncDefun:
+            ctor = AST_AsyncFunction;
+            break;
+          case AST_AsyncGeneratorDefun:
+            ctor = AST_AsyncGeneratorFunction;
+            break;
+          case AST_Defun:
+            ctor = AST_Function;
+            break;
+          case AST_GeneratorDefun:
+            ctor = AST_GeneratorFunction;
+            break;
+        }
+        var fn = make_node(ctor, defun, defun);
+        fn.name = drop_name ? null : make_node(AST_SymbolLambda, defun.name, defun.name);
+        return fn;
+    }
+
     AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
         if (!compressor.option("unused")) return;
         var self = this;
@@ -5632,7 +5653,7 @@ merge(Compressor.prototype, {
                         in_use.push(def);
                     }
                     initializations.add(def.id, node);
-                    return true; // don't go in nested scopes
+                    if (!(tw.parent() instanceof AST_ExportDefault)) return true;
                 }
                 if (node instanceof AST_Definitions) {
                     node.definitions.forEach(function(defn) {
@@ -5828,6 +5849,7 @@ merge(Compressor.prototype, {
                     if (!(def.id in in_use_ids)) {
                         log(node.name, "Dropping unused function {name}");
                         def.eliminated++;
+                        if (parent instanceof AST_ExportDefault) return to_func_expr(node, true);
                         return in_list ? List.skip : make_node(AST_EmptyStatement, node);
                     }
                 }
@@ -9973,25 +9995,7 @@ merge(Compressor.prototype, {
                 def.single_use = false;
                 fixed._squeezed = true;
                 fixed.single_use = true;
-                if (fixed instanceof AST_LambdaDefinition) {
-                    var ctor;
-                    switch (fixed.CTOR) {
-                      case AST_AsyncDefun:
-                        ctor = AST_AsyncFunction;
-                        break;
-                      case AST_AsyncGeneratorDefun:
-                        ctor = AST_AsyncGeneratorFunction;
-                        break;
-                      case AST_Defun:
-                        ctor = AST_Function;
-                        break;
-                      case AST_GeneratorDefun:
-                        ctor = AST_GeneratorFunction;
-                        break;
-                    }
-                    fixed = make_node(ctor, fixed, fixed);
-                    fixed.name = make_node(AST_SymbolLambda, fixed.name, fixed.name);
-                }
+                if (fixed instanceof AST_LambdaDefinition) fixed = to_func_expr(fixed);
                 if (fixed instanceof AST_Lambda) {
                     var scope = self.scope.resolve();
                     fixed.enclosed.forEach(function(def) {
index 2950e3c..29c85a4 100644 (file)
@@ -663,14 +663,11 @@ function OutputStream(options) {
     // the first token to appear in a statement.
     function needs_parens_function(output) {
         if (!output.has_parens() && first_in_statement(output)) return true;
-        if (output.option("webkit")) {
-            var p = output.parent();
-            if (p instanceof AST_PropAccess && p.expression === this) return true;
-        }
-        if (output.option("wrap_iife")) {
-            var p = output.parent();
-            if (p instanceof AST_Call && p.expression === this) return true;
-        }
+        var p = output.parent();
+        // export default (function() {})()
+        if (p && p.TYPE == "Call" && output.parent(1) instanceof AST_ExportDefault) return true;
+        if (output.option("webkit") && p instanceof AST_PropAccess && p.expression === this) return true;
+        if (output.option("wrap_iife") && p instanceof AST_Call && p.expression === this) return true;
     }
     PARENS(AST_AsyncFunction, needs_parens_function);
     PARENS(AST_AsyncGeneratorFunction, needs_parens_function);
@@ -1018,7 +1015,7 @@ function OutputStream(options) {
         output.print("default");
         output.space();
         this.body.print(output);
-        output.semicolon();
+        if (!(this.body instanceof AST_Lambda) || is_arrow(this.body)) output.semicolon();
     });
     DEFPRINT(AST_ExportForeign, function(output) {
         var self = this;
index 39465d7..6106834 100644 (file)
@@ -1351,13 +1351,46 @@ function parse($TEXT, options) {
         }
         if (is("keyword", "default")) {
             next();
-            var body = expression();
-            semicolon();
+            var start = S.token;
+            var body = export_default_decl();
+            if (body) {
+                body.start = start;
+                body.end = prev();
+            } else {
+                body = expression();
+                semicolon();
+            }
             return new AST_ExportDefault({ body: body });
         }
         return new AST_ExportDeclaration({ body: export_decl() });
     }
 
+    function maybe_named(def, exp) {
+        var node = function_(exp);
+        if (node.name) {
+            node = new def(node);
+            node.name = new AST_SymbolDefun(node.name);
+        }
+        return node;
+    }
+
+    function export_default_decl() {
+        switch (S.token.value) {
+          case "async":
+            if (!is_token(peek(), "keyword", "function")) return;
+            next();
+            next();
+            if (!is("operator", "*")) return maybe_named(AST_AsyncDefun, AST_AsyncFunction);
+            next();
+            return maybe_named(AST_AsyncGeneratorDefun, AST_AsyncGeneratorFunction);
+          case "function":
+            next();
+            if (!is("operator", "*")) return maybe_named(AST_Defun, AST_Function);
+            next();
+            return maybe_named(AST_GeneratorDefun, AST_GeneratorFunction);
+        }
+    }
+
     var export_decl = embed_tokens(function() {
         switch (S.token.value) {
           case "async":
index 75ce5a4..24c1d8c 100644 (file)
@@ -32,7 +32,25 @@ defaults: {
         export default function*(a, b) {};
         export default async function f({ c }, ...[ d ]) {};
     }
-    expect_exact: "export default 42;export default(x,y)=>x*x;export default function*(a,b){};export default async function f({c:c},...[d]){};"
+    expect_exact: "export default 42;export default(x,y)=>x*x;export default function*(a,b){}export default async function f({c:c},...[d]){}"
+}
+
+defaults_parenthesis_1: {
+    input: {
+        export default function() {
+            console.log("FAIL");
+        }(console.log("PASS"));
+    }
+    expect_exact: 'export default function(){console.log("FAIL")}console.log("PASS");'
+}
+
+defaults_parenthesis_2: {
+    input: {
+        export default (async function() {
+            console.log("PASS");
+        })();
+    }
+    expect_exact: 'export default(async function(){console.log("PASS")})();'
 }
 
 foreign: {
@@ -108,8 +126,8 @@ mangle: {
             t(o, f);
         }
         export default t;
-        export default async function t(o, ...{ [c]: e}) {
-            (await o)(t, e);
+        export default async function e(t, ...{ [c]: o}) {
+            (await t)(e, o);
         }
     }
 }
@@ -137,8 +155,8 @@ mangle_rename: {
             t(o, f);
         }
         export default t;
-        export default async function t(o, ...{ [c]: e}) {
-            (await o)(t, e);
+        export default async function e(t, ...{ [c]: o}) {
+            (await t)(e, o);
         }
     }
 }
@@ -171,8 +189,8 @@ hoist_exports: {
             t(a, c);
         }
         export default 42;
-        export default async function t(a, ...{ [o]: f }) {
-            (await a)(t, f);
+        export default async function e(t, ...{ [o]: a }) {
+            (await t)(e, a);
         };
         export { f as bbb, o as ccc, c as fff };
     }