From b2bc2e1173152c48096c032eb16921bfcbfae33e Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 7 Jul 2022 21:04:56 +0100 Subject: [PATCH] parse `export` & `import` statements correctly (#5550) fixes #5548 --- lib/parse.js | 6 ++-- test/compress.js | 2 +- test/compress/imports.js | 16 ----------- test/mocha/exports.js | 60 ++++++++++++++++++++++++++++++++++++++++ test/mocha/imports.js | 28 +++++++++++++++++++ 5 files changed, 93 insertions(+), 19 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 5ecf5326..c832758f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -815,7 +815,7 @@ function parse($TEXT, options) { } } - var statement = embed_tokens(function() { + var statement = embed_tokens(function(toplevel) { handle_regexp(); switch (S.token.type) { case "string": @@ -854,9 +854,11 @@ function parse($TEXT, options) { if (S.in_async) return simple_statement(); break; case "export": + if (!toplevel && options.module !== "") unexpected(); next(); return export_(); case "import": + if (!toplevel && options.module !== "") unexpected(); var token = peek(); if (!(token.type == "punc" && /^[(.]$/.test(token.value))) { next(); @@ -2563,7 +2565,7 @@ function parse($TEXT, options) { } S.input.push_directives_stack(); while (!is("eof")) - body.push(statement()); + body.push(statement(true)); S.input.pop_directives_stack(); var end = prev() || start; var toplevel = options.toplevel; diff --git a/test/compress.js b/test/compress.js index f4469df6..8d9a7348 100644 --- a/test/compress.js +++ b/test/compress.js @@ -69,7 +69,7 @@ function make_code(ast, options) { function parse_test(file) { var script = fs.readFileSync(file, "utf8"); try { - var ast = U.parse(script, { filename: file }); + var ast = U.parse(script, { filename: file, module: "" }); } catch (e) { console.error("Caught error while parsing tests in " + file); console.error(e); diff --git a/test/compress/imports.js b/test/compress/imports.js index 72cbbf69..c9b59e93 100644 --- a/test/compress/imports.js +++ b/test/compress/imports.js @@ -193,22 +193,6 @@ forbid_merge: { } } -merge_tail: { - options = { - conditionals: true, - } - input: { - if (console) - import "foo"; - else - import "foo"; - } - expect: { - console; - import "foo"; - } -} - issue_4708_1: { options = { imports: true, diff --git a/test/mocha/exports.js b/test/mocha/exports.js index fa448ff0..70d806e7 100644 --- a/test/mocha/exports.js +++ b/test/mocha/exports.js @@ -68,4 +68,64 @@ describe("export", function() { }, code); }); }); + it("Should reject `export` statement not under top-level scope", function() { + [ + "{ export {}; }", + "if (0) export var A;", + "function f() { export default 42; }", + ].forEach(function(code) { + assert.throws(function() { + UglifyJS.parse(code); + }, function(e) { + return e instanceof UglifyJS.JS_Parse_Error; + }, code); + }); + }); + it("Should compare `export` statements correctly", function() { + var stats = { + Declaration: [ + "export let A;", + "export const A = 42;", + "export var { A, B: [] } = C;", + "export function A() { return B(A); }", + "export async function* A({}, ...[]) { return B(A); }", + ], + Default: [ + "export default 42;", + "export default A => A(B);", + "export default class A extends B {}", + "export default (class A extends B {});", + "export default class A { static C = 42; }", + "export default class A extends B { static C = 42; }", + ], + Foreign: [ + "export * from 'path';", + "export {} from 'path';", + "export * as A from 'path';", + "export { default } from 'path';", + "export { A, B as C } from 'path';", + "export { A, default as C } from 'path';", + ], + References: [ + "export {};", + "export { A };", + "export { A as B };", + "export { A, B as C };", + "export { A as default };", + ], + }; + for (var k in stats) stats[k].forEach(function(c, i) { + var s = UglifyJS.parse(c); + assert.ok(s instanceof UglifyJS.AST_Toplevel, c); + assert.strictEqual(s.body.length, 1, c); + assert.strictEqual(s.body[0].TYPE, "Export" + k, c); + for (var l in stats) stats[l].forEach(function(d, j) { + var t = UglifyJS.parse(d); + assert.ok(t instanceof UglifyJS.AST_Toplevel, d); + assert.strictEqual(t.body.length, 1, d); + assert.strictEqual(t.body[0].TYPE, "Export" + l, d); + assert.strictEqual(s.equals(t), k === l && i === j, c + "\n" + d); + }); + }); + }); }); diff --git a/test/mocha/imports.js b/test/mocha/imports.js index e9b654cd..9ca23666 100644 --- a/test/mocha/imports.js +++ b/test/mocha/imports.js @@ -8,13 +8,16 @@ describe("import", function() { "import A;", "import {};", "import `path`;", + "{ import 'path'; }", "import from 'path';", + "if (0) import 'path';", "import * from 'path';", "import A as B from 'path';", "import { A }, B from 'path';", "import * as A, B from 'path';", "import * as A, {} from 'path';", "import { * as A } from 'path';", + "function f() { import 'path'; }", "import { 42 as A } from 'path';", "import { A-B as C } from 'path';", ].forEach(function(code) { @@ -25,4 +28,29 @@ describe("import", function() { }, code); }); }); + it("Should compare `import` statements correctly", function() { + [ + "import 'foo';", + "import 'path';", + "import A from 'path';", + "import { A } from 'path';", + "import * as A from 'path';", + "import A, { B } from 'path';", + "import A, * as B from 'path';", + "import { A as B } from 'path';", + "import A, { B, C as D } from 'path';", + ].forEach(function(c, i, stats) { + var s = UglifyJS.parse(c); + assert.ok(s instanceof UglifyJS.AST_Toplevel, c); + assert.strictEqual(s.body.length, 1, c); + assert.ok(s.body[0] instanceof UglifyJS.AST_Import, c); + stats.forEach(function(d, j) { + var t = UglifyJS.parse(d); + assert.ok(t instanceof UglifyJS.AST_Toplevel, d); + assert.strictEqual(t.body.length, 1, d); + assert.ok(t.body[0] instanceof UglifyJS.AST_Import, d); + assert.strictEqual(s.equals(t), i === j, c + "\n" + d); + }); + }); + }); });