From: Alex Lam S.L Date: Sun, 21 Feb 2021 05:01:56 +0000 (+0000) Subject: fix corner cases with `export default` (#4673) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=b726e364c1164b0c828d7be04bfcfa21774366a1;p=UglifyJS.git fix corner cases with `export default` (#4673) --- diff --git a/lib/ast.js b/lib/ast.js index b386b442..18169dfd 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -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); diff --git a/lib/compress.js b/lib/compress.js index da30cee1..7577437e 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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) { diff --git a/lib/output.js b/lib/output.js index 2950e3cb..29c85a42 100644 --- a/lib/output.js +++ b/lib/output.js @@ -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; diff --git a/lib/parse.js b/lib/parse.js index 39465d78..6106834b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -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": diff --git a/test/compress/exports.js b/test/compress/exports.js index 75ce5a43..24c1d8c0 100644 --- a/test/compress/exports.js +++ b/test/compress/exports.js @@ -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 }; }