From 9f7246305c9b2ef20c9a60689e17eda885223145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Mon, 19 Mar 2018 16:33:35 +0000 Subject: [PATCH 1/9] Attempt to close #968 by implementing the mozilla tree for ES6 code --- lib/ast.js | 2 +- lib/mozilla-ast.js | 324 ++++++++++++++++++++++++++++++- lib/parse.js | 11 +- test/input/spidermonkey/input.js | 57 ++++++ test/mocha/spidermonkey.js | 12 ++ 5 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 test/input/spidermonkey/input.js diff --git a/lib/ast.js b/lib/ast.js index 8700b4ba..f921a71c 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -643,7 +643,7 @@ var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", { this.name._walk(visitor); }); } -}) +}); var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", { $documentation: "An `import` statement", diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 41ba04f1..40423ddf 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -71,12 +71,29 @@ body: normalize_directives(M.body.map(from_moz)) }); }, + ArrayPattern: function(M) { + return new AST_Destructuring({ + start: my_start_token(M), + end: my_end_token(M), + names: M.elements.map(from_moz), + is_array: true + }); + }, + ObjectPattern: function(M) { + return new AST_Destructuring({ + start: my_start_token(M), + end: my_end_token(M), + names: M.properties.map(from_moz), + }); + }, FunctionDeclaration: function(M) { return new AST_Defun({ start: my_start_token(M), end: my_end_token(M), name: from_moz(M.id), argnames: M.params.map(from_moz), + is_generator: M.generator, + async: M.async, body: normalize_directives(from_moz(M.body).body) }); }, @@ -86,9 +103,19 @@ end: my_end_token(M), name: from_moz(M.id), argnames: M.params.map(from_moz), + is_generator: M.generator, + async: M.async, body: normalize_directives(from_moz(M.body).body) }); }, + ArrowFunctionExpression: function(M) { + return new AST_Arrow({ + start: my_start_token(M), + end: my_end_token(M), + argnames: M.params.map(from_moz), + body: from_moz(M.body) + }); + }, ExpressionStatement: function(M) { return new AST_SimpleStatement({ start: my_start_token(M), @@ -117,6 +144,16 @@ key : key.type == "Identifier" ? key.name : key.value, value : from_moz(M.value) }; + if (M.method) { + args.is_generator = M.value.generator; + args.async = M.value.async; + if (key.type == "Identifier") { + args.key = new AST_SymbolMethod({ name: args.key }); + } else { + args.key = from_moz(M.key); + } + return new AST_ConciseMethod(args); + } if (M.kind == "init") return new AST_ObjectKeyVal(args); args.key = new AST_SymbolMethod({ name: args.key @@ -124,6 +161,30 @@ args.value = new AST_Accessor(args.value); if (M.kind == "get") return new AST_ObjectGetter(args); if (M.kind == "set") return new AST_ObjectSetter(args); + if (M.kind == "method") { + args.async = M.value.async; + args.is_generator = M.value.generator; + args.quote = M.computed ? "\"" : null; + return new AST_ConciseMethod(args); + } + }, + MethodDefinition: function(M) { + var args = { + start : my_start_token(M), + end : my_end_token(M), + key : from_moz(M.key), + value : from_moz(M.value), + static : M.static, + }; + if (M.kind == "get") { + return new AST_ObjectGetter(args); + } + if (M.kind == "set") { + return new AST_ObjectSetter(args); + } + args.is_generator = M.value.generator; + args.async = M.value.async; + return new AST_ConciseMethod(args); }, ArrayExpression: function(M) { return new AST_Array({ @@ -168,12 +229,81 @@ }); }, VariableDeclaration: function(M) { - return new (M.kind === "const" ? AST_Const : AST_Var)({ + return new (M.kind === "const" ? AST_Const : + M.kind === "let" ? AST_Let : AST_Var)({ start : my_start_token(M), end : my_end_token(M), definitions : M.declarations.map(from_moz) }); }, + + ImportDeclaration: function(M) { + var imported_name = null; + var imported_names = null; + M.specifiers.forEach(function (specifier) { + if (specifier.type === "ImportSpecifier") { + if (!imported_names) { imported_names = []; } + imported_names.push(new AST_NameMapping({ + start: my_start_token(specifier), + end: my_end_token(specifier), + foreign_name: from_moz(specifier.imported), + name: from_moz(specifier.local) + })); + } else if (specifier.type === "ImportDefaultSpecifier") { + imported_name = from_moz(specifier.local); + } else if (specifier.type === "ImportNamespaceSpecifier") { + if (!imported_names) { imported_names = []; } + imported_names.push(new AST_NameMapping({ + start: my_start_token(specifier), + end: my_end_token(specifier), + foreign_name: new AST_SymbolImportForeign({ name: "*" }), + name: from_moz(specifier.local) + })); + } + }); + return new AST_Import({ + start : my_start_token(M), + end : my_end_token(M), + imported_name: imported_name, + imported_names : imported_names, + module_name : from_moz(M.source) + }) + }, + ExportAllDeclaration: function(M) { + return new AST_Export({ + start: my_start_token(M), + end: my_end_token(M), + exported_names: [ + new AST_NameMapping({ + name: new AST_SymbolExportForeign({ name: "*" }), + foreign_name: new AST_SymbolExportForeign({ name: "*" }) + }) + ], + module_name: from_moz(M.source) + }); + }, + ExportNamedDeclaration: function(M) { + return new AST_Export({ + start: my_start_token(M), + end: my_end_token(M), + exported_definition: from_moz(M.declaration), + exported_names: M.specifiers && M.specifiers.map(function (specifier) { + return new AST_NameMapping({ + foreign_name: from_moz(specifier.exported), + name: from_moz(specifier.local) + }) + }), + module_name: from_moz(M.source) + }); + }, + ExportDefaultDeclaration: function(M) { + return new AST_Export({ + start: my_start_token(M), + end: my_end_token(M), + exported_value: from_moz(M.declaration), + is_default: true + }); + }, Literal: function(M) { var val = M.value, args = { start : my_start_token(M), @@ -201,12 +331,26 @@ return new AST_RegExp(args); } }, + MetaProperty: function(M) { + if (M.meta.name === "new" && M.property.name === "target") { + return new AST_NewTarget({ + start: my_start_token(M), + end: my_end_token(M) + }); + } + }, Identifier: function(M) { var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; return new ( p.type == "LabeledStatement" ? AST_Label - : p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar) + : p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : p.kind == "let" ? AST_SymbolLet : AST_SymbolVar) + : /Import.*Specifier/.test(p.type) ? (p.local === M ? AST_SymbolImport : AST_SymbolImportForeign) + : p.type == "ExportSpecifier" ? (p.local === M ? AST_SymbolExport : AST_SymbolExportForeign) : p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg) : p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) + : p.type == "ArrowFunctionExpression" ? (p.params.indexOf(M) !== -1) ? AST_SymbolFunarg : AST_SymbolRef + : p.type == "ClassExpression" ? (p.id === M ? AST_SymbolClass : AST_SymbolRef) + : p.type == "ClassDeclaration" ? (p.id === M ? AST_SymbolDefClass : AST_SymbolRef) + : p.type == "MethodDefinition" ? AST_SymbolMethod : p.type == "CatchClause" ? AST_SymbolCatch : p.type == "BreakStatement" || p.type == "ContinueStatement" ? AST_LabelRef : AST_SymbolRef)({ @@ -229,6 +373,17 @@ }); }; + MOZ_TO_ME.ClassDeclaration = + MOZ_TO_ME.ClassExpression = function From_Moz_Class(M) { + return new AST_Class({ + start : my_start_token(M), + end : my_end_token(M), + name : from_moz(M.id), + extends : from_moz(M.superClass), + properties: M.body.body.map(from_moz) + }); + }; + map("EmptyStatement", AST_EmptyStatement); map("BlockStatement", AST_BlockStatement, "body@body"); map("IfStatement", AST_If, "test>condition, consequent>body, alternate>alternative"); @@ -243,6 +398,9 @@ map("DoWhileStatement", AST_Do, "test>condition, body>body"); map("ForStatement", AST_For, "init>init, test>condition, update>step, body>body"); map("ForInStatement", AST_ForIn, "left>init, right>object, body>body"); + map("ForOfStatement", AST_ForOf, "left>init, right>object, body>body"); + map("AwaitExpression", AST_Await, "argument>expression"); + map("RestElement", AST_Expansion, "argument>expression"); map("DebuggerStatement", AST_Debugger); map("VariableDeclarator", AST_VarDef, "id>name, init>value"); map("CatchClause", AST_Catch, "param>argname, body%body"); @@ -264,19 +422,50 @@ type: "FunctionDeclaration", id: to_moz(M.name), params: M.argnames.map(to_moz), + generator: M.is_generator, + async: M.async, body: to_moz_scope("BlockStatement", M) } }); - def_to_moz(AST_Function, function To_Moz_FunctionExpression(M) { + def_to_moz(AST_Function, function To_Moz_FunctionExpression(M, parent) { + var is_generator = parent.is_generator !== undefined ? + parent.is_generator : M.is_generator return { type: "FunctionExpression", id: to_moz(M.name), params: M.argnames.map(to_moz), + generator: is_generator, + async: M.async, body: to_moz_scope("BlockStatement", M) } }); + def_to_moz(AST_Arrow, function To_Moz_ArrowFunctionExpression(M) { + var body = M.body instanceof Array ? { + type: "BlockStatement", + body: M.body.map(to_moz) + } : to_moz(M.body); + return { + type: "ArrowFunctionExpression", + params: M.argnames.map(to_moz), + body: body + } + }); + + def_to_moz(AST_Destructuring, function To_Moz_ObjectPattern(M) { + if (M.is_array) { + return { + type: "ArrayPattern", + elements: M.names.map(to_moz) + } + } + return { + type: "ObjectPattern", + properties: M.names.map(to_moz) + }; + }); + def_to_moz(AST_Directive, function To_Moz_Directive(M) { return { type: "ExpressionStatement", @@ -324,11 +513,64 @@ def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) { return { type: "VariableDeclaration", - kind: M instanceof AST_Const ? "const" : "var", + kind: + M instanceof AST_Const ? "const" : + M instanceof AST_Let ? "let" : "var", declarations: M.definitions.map(to_moz) }; }); + def_to_moz(AST_Export, function To_Moz_ExportDeclaration(M) { + if (M.exported_names) { + if (M.exported_names[0].name.name === "*") { + return { + type: "ExportAllDeclaration", + source: to_moz(M.module_name) + }; + } + return { + type: "ExportNamedDeclaration", + specifiers: M.exported_names.map(function (name_mapping) { + return { + type: "ExportSpecifier", + exported: to_moz(name_mapping.foreign_name), + local: to_moz(name_mapping.name) + }; + }), + declaration: to_moz(M.exported_definition), + source: to_moz(M.module_name) + }; + } + return { + type: M.is_default ? "ExportDefaultDeclaration" : "ExportNamedDeclaration", + declaration: to_moz(M.is_default ? M.exported_value : M.exported_definition) + }; + }); + + def_to_moz(AST_Import, function To_Moz_ImportDeclaration(M) { + var specifiers = []; + if (M.imported_name) { + specifiers.push({ + type: "ImportDefaultSpecifier", + local: to_moz(M.imported_name) + }); + } + if (M.imported_names) { + M.imported_names.forEach(function(name_mapping) { + specifiers.push({ + type: "ImportSpecifier", + local: to_moz(name_mapping.name), + imported: to_moz(name_mapping.foreign_name) + }); + }); + } + return { + type: "ImportDeclaration", + specifiers: specifiers, + source: to_moz(M.module_name) + } + }); + def_to_moz(AST_Sequence, function To_Moz_SequenceExpression(M) { return { type: "SequenceExpression", @@ -378,7 +620,7 @@ }; }); - def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) { + def_to_moz(AST_ObjectProperty, function To_Moz_Property(M, parent) { var key = { type: "Literal", value: M.key instanceof AST_SymbolMethod ? M.key.name : M.key @@ -393,6 +635,16 @@ if (M instanceof AST_ObjectSetter) { kind = "set"; } + if (parent instanceof AST_Class) { + return { + type: "MethodDefinition", + computed: !(M.key instanceof AST_Symbol), + kind: kind, + static: M.static, + key: to_moz(M.key), + value: to_moz(M.value) + }; + } return { type: "Property", kind: kind, @@ -401,6 +653,55 @@ }; }); + def_to_moz(AST_ConciseMethod, function To_Moz_MethodDefinition(M, parent) { + if (parent instanceof AST_Object) { + return { + type: "Property", + computed: !(M.key instanceof AST_Symbol), + kind: "init", + method: true, + shorthand: false, + key: to_moz(M.key), + value: to_moz(M.value) + }; + } + return { + type: "MethodDefinition", + computed: !(M.key instanceof AST_Symbol), + kind: M.key === "constructor" ? "constructor" : "method", + static: M.static, + key: to_moz(M.key), + value: to_moz(M.value) + }; + }); + + def_to_moz(AST_Class, function To_Moz_Class(M) { + var type = M instanceof AST_ClassExpression ? "ClassExpression" : "ClassDeclaration"; + return { + type: type, + superClass: to_moz(M.extends), + id: M.name ? to_moz(M.name) : null, + body: { + type: "ClassBody", + body: M.properties.map(to_moz) + } + } + }); + + def_to_moz(AST_NewTarget, function To_Moz_MetaProperty(M) { + return { + type: "MetaProperty", + meta: { + type: "Identifier", + name: "new" + }, + property: { + type: "Identifier", + name: "target" + } + }; + }); + def_to_moz(AST_Symbol, function To_Moz_Identifier(M) { var def = M.definition(); return { @@ -586,13 +887,20 @@ }; function def_to_moz(mytype, handler) { - mytype.DEFMETHOD("to_mozilla_ast", function() { - return set_moz_loc(this, handler(this)); + mytype.DEFMETHOD("to_mozilla_ast", function(parent) { + return set_moz_loc(this, handler(this, parent)); }); }; + var TO_MOZ_STACK = null; + function to_moz(node) { - return node != null ? node.to_mozilla_ast() : null; + if (TO_MOZ_STACK === null) { TO_MOZ_STACK = []; } + TO_MOZ_STACK.push(node); + var ast = node != null ? node.to_mozilla_ast(TO_MOZ_STACK[TO_MOZ_STACK.length - 2]) : null; + TO_MOZ_STACK.pop(); + if (TO_MOZ_STACK.length === 0) { TO_MOZ_STACK = null; } + return ast }; function to_moz_block(node) { diff --git a/lib/parse.js b/lib/parse.js index d8cc9ac4..ddf09001 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1294,9 +1294,14 @@ function parse($TEXT, options) { var body = _function_body(is("punc", "{"), false, is_async); + var end = + body instanceof Array && body.length ? body[body.length - 1].end : + body instanceof Array ? start : + body.end; + return new AST_Arrow({ start : start, - end : body.end, + end : end, async : is_async, argnames : argnames, body : body @@ -1709,6 +1714,8 @@ function parse($TEXT, options) { } // the await expression is parsed as a unary expression in Babel return new AST_Await({ + start: prev(), + end: S.token, expression : maybe_unary(true), }); } @@ -2478,7 +2485,7 @@ function parse($TEXT, options) { next(); if (is_import && is("name", "as")) { next(); // The "as" word - name = as_symbol(AST_SymbolImportForeign); + name = as_symbol(is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign); } names = [map_nameAsterisk(is_import, name)]; } diff --git a/test/input/spidermonkey/input.js b/test/input/spidermonkey/input.js new file mode 100644 index 00000000..3cf63fdc --- /dev/null +++ b/test/input/spidermonkey/input.js @@ -0,0 +1,57 @@ +import "mod-name"; +import Foo from "bar"; +import * as Food from "food" +import { Bar, Baz } from "lel"; +import Bar, { Foo } from "lel"; +import { Bar as kex, Baz as food } from "lel"; + +const x = 0b01; +let y = 6; + +export default x; +export const z = 4; +export function fun() {} +export * from "a.js"; +export {A} from "a.js"; +export {A, B} from "a.js"; +export {C}; + +(a, [b], {c:foo = 3}, ...d) => null; +() => {} + +async function f() { } +function*gen() { } + +class Class extends Object { + constructor(...args) { + } + foo() {} +} + +x = class { + static staticMethod() {} + static get foo() {} + static set bar() {} + get x() {} + set x(value) {} + static() { /* "static" can be a method name! */ } + get() { /* "get" can be a method name! */ } + set() { /* "set" can be a method name! */ } + *bar() {} + static *baz() {} + *['constructor']() {} + static ['constructor']() {} +} + +y = { + get x() {}, + set x(value) {}, + bar() {}, + *bar() {}, + *['constructor']() {} +} +console.log(new.target); +console.log([10, ...[], 20, ...[30, 40], 50]["length"]); +var { w: w1, ...V } = { w: 7, x: 1, y: 2 }; +for (const x of y) {} +async function f1() { await x + y; } diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js index 709b89e5..381b7dd2 100644 --- a/test/mocha/spidermonkey.js +++ b/test/mocha/spidermonkey.js @@ -1,4 +1,5 @@ var assert = require("assert"); +var fs = require("fs"); var exec = require("child_process").exec; var uglify = require("../node"); @@ -112,4 +113,15 @@ describe("spidermonkey export/import sanity test", function() { assert.strictEqual(counter_strings, tests[i].strings, "String count mismatch for test " + tests[i].input); } }); + + it("should output and parse ES6 code correctly", function() { + var code = fs.readFileSync("test/input/spidermonkey/input.js", "utf-8"); + var uglify_ast = uglify.parse(code); + var moz_ast = uglify_ast.to_mozilla_ast(); + var from_moz_ast = uglify.AST_Node.from_mozilla_ast(moz_ast); + assert.strictEqual( + from_moz_ast.print_to_string(), + uglify_ast.print_to_string() + ); + }); }); From 0c924fca64419801298b8abfe89a8df7c3b92fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Tue, 20 Mar 2018 20:27:06 +0000 Subject: [PATCH 2/9] further AST input/output --- lib/mozilla-ast.js | 94 ++++++++++++++++++++++++++----- test/input/spidermonkey/input.js | 97 +++++++++++++++++++++++++++++--- test/mocha/spidermonkey.js | 12 ++++ 3 files changed, 180 insertions(+), 23 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 40423ddf..173c6a15 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -75,7 +75,12 @@ return new AST_Destructuring({ start: my_start_token(M), end: my_end_token(M), - names: M.elements.map(from_moz), + names: M.elements.map(function(elm) { + if (elm === null) { + return new AST_Hole(); + } + return from_moz(elm); + }), is_array: true }); }, @@ -86,6 +91,29 @@ names: M.properties.map(from_moz), }); }, + AssignmentPattern: function(M) { + return new AST_Binary({ + start: my_start_token(M), + end: my_end_token(M), + left: from_moz(M.left), + operator: "=", + right: from_moz(M.right) + }); + }, + SpreadElement: function(M) { + return new AST_Expansion({ + start: my_start_token(M), + end: my_end_token(M), + expression: from_moz(M.argument) + }); + }, + RestElement: function(M) { + return new AST_Expansion({ + start: my_start_token(M), + end: my_end_token(M), + expression: from_moz(M.argument) + }); + }, FunctionDeclaration: function(M) { return new AST_Defun({ start: my_start_token(M), @@ -113,7 +141,8 @@ start: my_start_token(M), end: my_end_token(M), argnames: M.params.map(from_moz), - body: from_moz(M.body) + body: from_moz(M.body), + async: M.async, }); }, ExpressionStatement: function(M) { @@ -147,14 +176,19 @@ if (M.method) { args.is_generator = M.value.generator; args.async = M.value.async; - if (key.type == "Identifier") { + if (!M.computed) { args.key = new AST_SymbolMethod({ name: args.key }); } else { args.key = from_moz(M.key); } return new AST_ConciseMethod(args); } - if (M.kind == "init") return new AST_ObjectKeyVal(args); + if (M.kind == "init") { + if (key.type != "Identifier") { + args.key = from_moz(key); + } + return new AST_ObjectKeyVal(args); + } args.key = new AST_SymbolMethod({ name: args.key }); @@ -172,7 +206,7 @@ var args = { start : my_start_token(M), end : my_end_token(M), - key : from_moz(M.key), + key : M.computed ? from_moz(M.key) : new AST_SymbolMethod({ name: M.key.name || M.key.value }), value : from_moz(M.value), static : M.static, }; @@ -287,12 +321,12 @@ start: my_start_token(M), end: my_end_token(M), exported_definition: from_moz(M.declaration), - exported_names: M.specifiers && M.specifiers.map(function (specifier) { + exported_names: M.specifiers && M.specifiers.length ? M.specifiers.map(function (specifier) { return new AST_NameMapping({ foreign_name: from_moz(specifier.exported), name: from_moz(specifier.local) }) - }), + }) : null, module_name: from_moz(M.source) }); }, @@ -350,7 +384,7 @@ : p.type == "ArrowFunctionExpression" ? (p.params.indexOf(M) !== -1) ? AST_SymbolFunarg : AST_SymbolRef : p.type == "ClassExpression" ? (p.id === M ? AST_SymbolClass : AST_SymbolRef) : p.type == "ClassDeclaration" ? (p.id === M ? AST_SymbolDefClass : AST_SymbolRef) - : p.type == "MethodDefinition" ? AST_SymbolMethod + : p.type == "MethodDefinition" ? (p.computed ? AST_SymbolRef : AST_SymbolMethod) : p.type == "CatchClause" ? AST_SymbolCatch : p.type == "BreakStatement" || p.type == "ContinueStatement" ? AST_LabelRef : AST_SymbolRef)({ @@ -400,7 +434,6 @@ map("ForInStatement", AST_ForIn, "left>init, right>object, body>body"); map("ForOfStatement", AST_ForOf, "left>init, right>object, body>body"); map("AwaitExpression", AST_Await, "argument>expression"); - map("RestElement", AST_Expansion, "argument>expression"); map("DebuggerStatement", AST_Debugger); map("VariableDeclarator", AST_VarDef, "id>name, init>value"); map("CatchClause", AST_Catch, "param>argname, body%body"); @@ -417,6 +450,13 @@ return to_moz_scope("Program", M); }); + def_to_moz(AST_Expansion, function To_Moz_Spread(M, parent) { + return { + type: to_moz_in_destructuring() ? "RestElement" : "SpreadElement", + argument: to_moz(M.expression) + }; + }); + def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) { return { type: "FunctionDeclaration", @@ -449,6 +489,7 @@ return { type: "ArrowFunctionExpression", params: M.argnames.map(to_moz), + async: M.async, body: body } }); @@ -598,6 +639,13 @@ }); def_to_moz(AST_Binary, function To_Moz_BinaryExpression(M) { + if (to_moz_in_destructuring()) { + return { + type: "AssignmentPattern", + left: to_moz(M.left), + right: to_moz(M.right) + }; + } return { type: M.operator == "&&" || M.operator == "||" ? "LogicalExpression" : "BinaryExpression", left: to_moz(M.left), @@ -621,10 +669,16 @@ }); def_to_moz(AST_ObjectProperty, function To_Moz_Property(M, parent) { - var key = { - type: "Literal", - value: M.key instanceof AST_SymbolMethod ? M.key.name : M.key + var key = M.key instanceof AST_Node ? to_moz(M.key) : { + type: "Identifier", + value: M.key }; + if (typeof M.key === "string") { + key = { + type: "Identifier", + name: M.key + }; + } var kind; if (M instanceof AST_ObjectKeyVal) { kind = "init"; @@ -638,7 +692,7 @@ if (parent instanceof AST_Class) { return { type: "MethodDefinition", - computed: !(M.key instanceof AST_Symbol), + computed: !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef, kind: kind, static: M.static, key: to_moz(M.key), @@ -657,7 +711,7 @@ if (parent instanceof AST_Object) { return { type: "Property", - computed: !(M.key instanceof AST_Symbol), + computed: !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef, kind: "init", method: true, shorthand: false, @@ -667,7 +721,7 @@ } return { type: "MethodDefinition", - computed: !(M.key instanceof AST_Symbol), + computed: !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef, kind: M.key === "constructor" ? "constructor" : "method", static: M.static, key: to_moz(M.key), @@ -903,6 +957,16 @@ return ast }; + function to_moz_in_destructuring() { + var i = TO_MOZ_STACK.length; + while (i--) { + if (TO_MOZ_STACK[i] instanceof AST_Destructuring) { + return true; + } + } + return false; + }; + function to_moz_block(node) { return { type: "BlockStatement", diff --git a/test/input/spidermonkey/input.js b/test/input/spidermonkey/input.js index 3cf63fdc..9108aa0d 100644 --- a/test/input/spidermonkey/input.js +++ b/test/input/spidermonkey/input.js @@ -2,8 +2,8 @@ import "mod-name"; import Foo from "bar"; import * as Food from "food" import { Bar, Baz } from "lel"; -import Bar, { Foo } from "lel"; -import { Bar as kex, Baz as food } from "lel"; +import Bar1, { Foo2 } from "lel"; +import { Bar2 as kex, Baz as food } from "lel"; const x = 0b01; let y = 6; @@ -13,7 +13,7 @@ export const z = 4; export function fun() {} export * from "a.js"; export {A} from "a.js"; -export {A, B} from "a.js"; +export {A1, B1} from "a.js"; export {C}; (a, [b], {c:foo = 3}, ...d) => null; @@ -31,16 +31,24 @@ class Class extends Object { x = class { static staticMethod() {} static get foo() {} - static set bar() {} + static set bar(value) {} get x() {} set x(value) {} - static() { /* "static" can be a method name! */ } - get() { /* "get" can be a method name! */ } - set() { /* "set" can be a method name! */ } + static() { + // "static" can be a method name! + } + get() { + // "get" can be a method name! + } + async set() { + // "set" can be a method name! + } *bar() {} static *baz() {} *['constructor']() {} static ['constructor']() {} + [a]() {} + "%"(){} } y = { @@ -50,8 +58,81 @@ y = { *bar() {}, *['constructor']() {} } -console.log(new.target); +function f () { + console.log(new.target); +} console.log([10, ...[], 20, ...[30, 40], 50]["length"]); var { w: w1, ...V } = { w: 7, x: 1, y: 2 }; for (const x of y) {} async function f1() { await x + y; } + +// arrow.js + +var foo = ([]) => "foo"; +var bar = ({}) => "bar"; +var with_default = (foo = "default") => foo; +var object_with_default = ({foo = "default", bar: baz = "default"}) => foo; +var array_after_spread = (...[foo]) => foo; +var array_after_spread = (...{foo}) => foo; +var computed = ({ [compute()]: x }) => {}; +var array_hole = ([, , ...x] = [1, 2]) => {}; +var object_trailing_elision = ({foo,}) => {}; +var spread_empty_array = (...[]) => "foo"; +var spread_empty_object = (...{}) => "foo"; + +// async.js + +async (x) => await x + +// destructuring.js + +var [aa, bb] = cc; +var [aa, [bb, cc]] = dd; +var [,[,,,,,],,,zz,] = xx; // Trailing comma +var [,,zzz,,] = xxx; // Trailing comma after hole + +var {aa, bb} = {aa:1, bb:2}; +var {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}}; + +for (const [x,y] in pairs); +for (const [a] = 0;;); +for (const {c} of cees); + +// object.js + +var a = { + get, + set: "foo", + get bar() { + return this.get; + }, + get 5() { + return "five"; + }, + get 0xf55() { + return "f five five"; + }, + get "five"() { + return 5; + }, + set one(value) { + this._one = value; + }, + set 9(value) { + this._nine = value; + }, + set 0b1010(value) { + this._ten = value; + }, + set "eleven"(value) { + this._eleven = value; + }, + *"%"() { + return 2; + }, + *["%"]() { + return 2; + }, + [a]() {} +}; + diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js index 381b7dd2..76c5b674 100644 --- a/test/mocha/spidermonkey.js +++ b/test/mocha/spidermonkey.js @@ -1,6 +1,7 @@ var assert = require("assert"); var fs = require("fs"); var exec = require("child_process").exec; +var acorn = require("acorn"); var uglify = require("../node"); describe("spidermonkey export/import sanity test", function() { @@ -124,4 +125,15 @@ describe("spidermonkey export/import sanity test", function() { uglify_ast.print_to_string() ); }); + + it("should be capable of importing from acorn", function() { + var code = fs.readFileSync("test/input/spidermonkey/input.js", "utf-8"); + var uglify_ast = uglify.parse(code); + var moz_ast = acorn.parse(code, {sourceType: 'module', ecmaVersion: 9}); + var from_moz_ast = uglify.AST_Node.from_mozilla_ast(moz_ast); + assert.strictEqual( + from_moz_ast.print_to_string(), + uglify_ast.print_to_string() + ); + }); }); From a69b51e25d94756c790f5f58d8f59ed48d70b6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Tue, 20 Mar 2018 20:48:28 +0000 Subject: [PATCH 3/9] template string mozilla ASTs --- lib/ast.js | 2 +- lib/mozilla-ast.js | 62 ++++++++++++++++++++++++++++++++ lib/parse.js | 3 +- test/input/spidermonkey/input.js | 5 +++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index f921a71c..da654606 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -445,7 +445,7 @@ var AST_PrefixedTemplateString = DEFNODE("PrefixedTemplateString", "template_str this.prefix._walk(visitor); this.template_string._walk(visitor); } -}) +}); var AST_TemplateString = DEFNODE("TemplateString", "segments", { $documentation: "A template string literal", diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 173c6a15..ec8bc87d 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -114,6 +114,36 @@ expression: from_moz(M.argument) }); }, + TemplateElement: function(M) { + return new AST_TemplateSegment({ + start: my_start_token(M), + end: my_end_token(M), + value: M.value.cooked, + raw: M.value.raw + }); + }, + TemplateLiteral: function(M) { + var segments = []; + for (var i = 0; i < M.quasis.length; i++) { + segments.push(from_moz(M.quasis[i])); + if (M.expressions[i]) { + segments.push(from_moz(M.expressions[i])); + } + } + return new AST_TemplateString({ + start: my_start_token(M), + end: my_end_token(M), + segments: segments + }); + }, + TaggedTemplateExpression: function(M) { + return new AST_PrefixedTemplateString({ + start: my_start_token(M), + end: my_end_token(M), + template_string: from_moz(M.quasi), + prefix: from_moz(M.tag) + }); + }, FunctionDeclaration: function(M) { return new AST_Defun({ start: my_start_token(M), @@ -457,6 +487,38 @@ }; }); + def_to_moz(AST_PrefixedTemplateString, function To_Moz_TaggedTemplateExpression(M) { + return { + type: "TaggedTemplateExpression", + tag: to_moz(M.prefix), + quasi: to_moz(M.template_string) + }; + }); + + def_to_moz(AST_TemplateString, function To_Moz_TemplateLiteral(M) { + var quasis = []; + var expressions = []; + for (var i = 0; i < M.segments.length; i++) { + if (i % 2 !== 0) { + expressions.push(to_moz(M.segments[i])); + } else { + quasis.push({ + type: "TemplateElement", + value: { + raw: M.segments[i].raw, + cooked: M.segments[i].value + }, + tail: i === M.segments.length - 1 + }); + } + } + return { + type: "TemplateLiteral", + quasis: quasis, + expressions: expressions + }; + }); + def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) { return { type: "FunctionDeclaration", diff --git a/lib/parse.js b/lib/parse.js index ddf09001..78640dc5 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -2693,7 +2693,8 @@ function parse($TEXT, options) { return subscripts(new AST_PrefixedTemplateString({ start: start, prefix: expr, - template_string: template_string() + template_string: template_string(), + end: prev() }), allow_calls); } return expr; diff --git a/test/input/spidermonkey/input.js b/test/input/spidermonkey/input.js index 9108aa0d..033bbfa4 100644 --- a/test/input/spidermonkey/input.js +++ b/test/input/spidermonkey/input.js @@ -66,6 +66,11 @@ var { w: w1, ...V } = { w: 7, x: 1, y: 2 }; for (const x of y) {} async function f1() { await x + y; } +``; +`x`; +`x${1}`; +String.raw`\n`; + // arrow.js var foo = ([]) => "foo"; From 2655e84f0d7e367e4aff6cfd7503eb4e60826fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Wed, 21 Mar 2018 20:28:40 +0000 Subject: [PATCH 4/9] implement AST_Yield spidermonkey AST --- lib/mozilla-ast.js | 1 + lib/parse.js | 5 ++++- test/input/spidermonkey/input.js | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index ec8bc87d..275047b1 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -464,6 +464,7 @@ map("ForInStatement", AST_ForIn, "left>init, right>object, body>body"); map("ForOfStatement", AST_ForOf, "left>init, right>object, body>body"); map("AwaitExpression", AST_Await, "argument>expression"); + map("YieldExpression", AST_Yield, "argument>expression, delegate=is_star"); map("DebuggerStatement", AST_Debugger); map("VariableDeclarator", AST_VarDef, "id>name, init>value"); map("CatchClause", AST_Catch, "param>argname, body%body"); diff --git a/lib/parse.js b/lib/parse.js index 78640dc5..313d890f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1726,6 +1726,7 @@ function parse($TEXT, options) { croak("Unexpected yield expression outside generator function", S.prev.line, S.prev.col, S.prev.pos); } + var start = S.token; var star = false; var has_expression = true; @@ -1749,8 +1750,10 @@ function parse($TEXT, options) { } return new AST_Yield({ + start : start, is_star : star, - expression : has_expression ? expression() : null + expression : has_expression ? expression() : null, + end : prev() }); } diff --git a/test/input/spidermonkey/input.js b/test/input/spidermonkey/input.js index 033bbfa4..7cdc7c19 100644 --- a/test/input/spidermonkey/input.js +++ b/test/input/spidermonkey/input.js @@ -17,10 +17,13 @@ export {A1, B1} from "a.js"; export {C}; (a, [b], {c:foo = 3}, ...d) => null; -() => {} +() => {}; async function f() { } -function*gen() { } +function*gen() { + yield 1; + yield* 2; +} class Class extends Object { constructor(...args) { From d310bcdf2ac99486b86e98f2fbe35d24b519218e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Thu, 22 Mar 2018 21:22:25 +0000 Subject: [PATCH 5/9] test feeding spidermonkey AST through escodegen --- lib/mozilla-ast.js | 20 +++++++++++++++++--- lib/output.js | 4 +++- lib/parse.js | 4 +++- package.json | 1 + test/input/spidermonkey/input.js | 2 +- test/mocha/spidermonkey.js | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 275047b1..5848ecc1 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -659,7 +659,12 @@ local: to_moz(M.imported_name) }); } - if (M.imported_names) { + if (M.imported_names && M.imported_names[0].foreign_name.name === '*') { + specifiers.push({ + type: "ImportNamespaceSpecifier", + local: to_moz(M.imported_names[0].name) + }); + } else if (M.imported_names) { M.imported_names.forEach(function(name_mapping) { specifiers.push({ type: "ImportSpecifier", @@ -743,8 +748,10 @@ }; } var kind; + var computed = typeof M.key === "string" ? false : !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef; if (M instanceof AST_ObjectKeyVal) { kind = "init"; + computed = typeof M.key !== "string"; } else if (M instanceof AST_ObjectGetter) { kind = "get"; @@ -755,7 +762,7 @@ if (parent instanceof AST_Class) { return { type: "MethodDefinition", - computed: !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef, + computed: computed, kind: kind, static: M.static, key: to_moz(M.key), @@ -764,6 +771,7 @@ } return { type: "Property", + computed: computed, kind: kind, key: key, value: to_moz(M.value) @@ -819,7 +827,13 @@ }; }); - def_to_moz(AST_Symbol, function To_Moz_Identifier(M) { + def_to_moz(AST_Symbol, function To_Moz_Identifier(M, parent) { + if (M instanceof AST_SymbolMethod && parent.quote) { + return { + type: "Literal", + value: M.name + }; + } var def = M.definition(); return { type: "Identifier", diff --git a/lib/output.js b/lib/output.js index e4987179..4abe95c0 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1455,7 +1455,9 @@ function OutputStream(options) { output.space(); self.module_name.print(output); } - output.semicolon(); + if (!self.exported_definition) { + output.semicolon(); + } }); function parenthesize_for_noin(node, output, noin) { diff --git a/lib/parse.js b/lib/parse.js index 313d890f..febd79db 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1159,7 +1159,9 @@ function parse($TEXT, options) { case "export": if (!is_token(peek(), "punc", "(")) { next(); - return export_(); + var node = export_(); + semicolon(); + return node; } } } diff --git a/package.json b/package.json index eb8e902d..c35fd034 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ }, "devDependencies": { "acorn": "~5.4.1", + "escodegen": "^1.9.1", "mocha": "~3.5.1", "semver": "~5.5.0" }, diff --git a/test/input/spidermonkey/input.js b/test/input/spidermonkey/input.js index 7cdc7c19..e4de54ec 100644 --- a/test/input/spidermonkey/input.js +++ b/test/input/spidermonkey/input.js @@ -67,7 +67,7 @@ function f () { console.log([10, ...[], 20, ...[30, 40], 50]["length"]); var { w: w1, ...V } = { w: 7, x: 1, y: 2 }; for (const x of y) {} -async function f1() { await x + y; } +async function f1() { await x; } ``; `x`; diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js index 76c5b674..6f80a90e 100644 --- a/test/mocha/spidermonkey.js +++ b/test/mocha/spidermonkey.js @@ -2,6 +2,7 @@ var assert = require("assert"); var fs = require("fs"); var exec = require("child_process").exec; var acorn = require("acorn"); +var escodegen = require("escodegen"); var uglify = require("../node"); describe("spidermonkey export/import sanity test", function() { @@ -136,4 +137,35 @@ describe("spidermonkey export/import sanity test", function() { uglify_ast.print_to_string() ); }); + + it("should produce an AST compatible with escodegen", function() { + var code = fs.readFileSync("test/input/spidermonkey/input.js", "utf-8"); + var uglify_ast = uglify.parse(code); + var moz_ast = uglify_ast.to_mozilla_ast(); + assert.strictEqual( + escodegen.generate(moz_ast, { + format: { + indent: { + style: "", + }, + newline: "", + space: "", + quotes: "double" + } + }) + .replace(/;}/g, "}") + .replace(/var {/g, "var{") + .replace(/var \[/g, "var[") + .replace(/const {/g, "const{") + .replace(/const \[/g, "const[") + .replace(/get \"/g, "get\"") + .replace(/set \"/g, "set\"") + .replace(/\[object Object\].\[object Object\]/g, "new.target") // escodegen issue + .replace(/\(await x\)/, "await x") + , + uglify_ast.print_to_string({ + keep_quoted_props: true + }) + ); + }); }); From b288181a699bfb9cf0c96b1f023252efc0b1da68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Fri, 23 Mar 2018 18:53:05 +0000 Subject: [PATCH 6/9] fix: update exports tests --- lib/parse.js | 2 +- test/compress/export.js | 12 +++++------ test/compress/harmony.js | 6 +++--- test/compress/issue-2001.js | 40 ++++++++++++++++++------------------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index febd79db..414a47ac 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1160,7 +1160,7 @@ function parse($TEXT, options) { if (!is_token(peek(), "punc", "(")) { next(); var node = export_(); - semicolon(); + if (can_insert_semicolon()) semicolon(); return node; } } diff --git a/test/compress/export.js b/test/compress/export.js index 783fa80a..6fcc46b0 100644 --- a/test/compress/export.js +++ b/test/compress/export.js @@ -309,7 +309,7 @@ export_default_anonymous_generator: { yield foo(); } } - expect_exact: "export default function*(){yield foo()};" + expect_exact: "export default function*(){yield foo()}" } export_default_anonymous_async_function: { @@ -326,7 +326,7 @@ export_default_anonymous_async_function: { return await foo(); } } - expect_exact: "export default async function(){return await foo()};" + expect_exact: "export default async function(){return await foo()}" } export_default_async_arrow_function: { @@ -358,7 +358,7 @@ export_default_named_generator: { yield foo(); } } - expect_exact: "export default function*gen(){yield foo()};" + expect_exact: "export default function*gen(){yield foo()}" } export_default_named_async_function: { @@ -375,7 +375,7 @@ export_default_named_async_function: { return await foo(); } } - expect_exact: "export default async function bar(){return await foo()};" + expect_exact: "export default async function bar(){return await foo()}" } export_default_anonymous_class: { @@ -426,7 +426,7 @@ export_default_anonymous_generator_not_call: { export default function*(){}(foo); } // agrees with `acorn` and `babylon 7` - expect_exact: "export default function*(){};foo;" + expect_exact: "export default function*(){}foo;" } export_default_anonymous_async_function_not_call: { @@ -442,5 +442,5 @@ export_default_anonymous_async_function_not_call: { export default async function(){}(foo); } // agrees with `acorn` and `babylon 7` - expect_exact: "export default async function(){};foo;" + expect_exact: "export default async function(){}foo;" } diff --git a/test/compress/harmony.js b/test/compress/harmony.js index b85214ec..93025177 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -218,7 +218,7 @@ export_statement: { export function d() {}; export class e {}; } - expect_exact: "export default 3;export var a=4;export let b=6;export const c=6;export function d(){};export class e{};" + expect_exact: "export default 3;export var a=4;export let b=6;export const c=6;export function d(){}export class e{}" } export_default_object_expression: { @@ -680,7 +680,7 @@ export_default_function_decl: { export default function Foo() {}; export function Far() {}; } - expect_exact: "export default function Foo(){};export function Far(){};" + expect_exact: "export default function Foo(){}export function Far(){}" } export_default_class_decl: { @@ -694,7 +694,7 @@ export_default_class_decl: { export default class Car {}; export class Cab {}; } - expect_exact: "export default class Car{};export class Cab{};" + expect_exact: "export default class Car{}export class Cab{}" } object_rest_spread: { diff --git a/test/compress/issue-2001.js b/test/compress/issue-2001.js index f2c9504b..4196f6c3 100644 --- a/test/compress/issue-2001.js +++ b/test/compress/issue-2001.js @@ -5,9 +5,9 @@ export_func_1: { unused: true, } input: { - export function f(){}; + export function f(){} } - expect_exact: "export function f(){};" + expect_exact: "export function f(){}" } export_func_2: { @@ -20,7 +20,7 @@ export_func_2: { input: { export function f(){}(1); } - expect_exact: "export function f(){};1;" + expect_exact: "export function f(){}1;" } export_func_3: { @@ -33,7 +33,7 @@ export_func_3: { input: { export function f(){}(1); } - expect_exact: "export function f(){};" + expect_exact: "export function f(){}" } export_default_func_1: { @@ -43,9 +43,9 @@ export_default_func_1: { unused: true, } input: { - export default function f(){}; + export default function f(){} } - expect_exact: "export default function f(){};" + expect_exact: "export default function f(){}" } export_default_func_2: { @@ -58,7 +58,7 @@ export_default_func_2: { input: { export default function f(){}(1); } - expect_exact: "export default function f(){};1;" + expect_exact: "export default function f(){}1;" } export_default_func_3: { @@ -71,7 +71,7 @@ export_default_func_3: { input: { export default function f(){}(1); } - expect_exact: "export default function f(){};" + expect_exact: "export default function f(){}" } export_class_1: { @@ -83,7 +83,7 @@ export_class_1: { input: { export class C {}; } - expect_exact: "export class C{};" + expect_exact: "export class C{}" } export_class_2: { @@ -96,7 +96,7 @@ export_class_2: { input: { export class C {}(1); } - expect_exact: "export class C{};1;" + expect_exact: "export class C{}1;" } export_class_3: { @@ -109,7 +109,7 @@ export_class_3: { input: { export class C {}(1); } - expect_exact: "export class C{};" + expect_exact: "export class C{}" } export_default_class_1: { @@ -121,7 +121,7 @@ export_default_class_1: { input: { export default class C {}; } - expect_exact: "export default class C{};" + expect_exact: "export default class C{}" } export_default_class_2: { @@ -134,7 +134,7 @@ export_default_class_2: { input: { export default class C {}(1); } - expect_exact: "export default class C{};1;" + expect_exact: "export default class C{}1;" } export_default_class_3: { @@ -147,7 +147,7 @@ export_default_class_3: { input: { export default class C {}(1); } - expect_exact: "export default class C{};" + expect_exact: "export default class C{}" } export_mangle_1: { @@ -159,7 +159,7 @@ export_mangle_1: { return one - two; }; } - expect_exact: "export function foo(o,n){return o-n};" + expect_exact: "export function foo(o,n){return o-n}" } export_mangle_2: { @@ -171,7 +171,7 @@ export_mangle_2: { return one - two; }; } - expect_exact: "export default function foo(o,t){return o-t};" + expect_exact: "export default function foo(o,t){return o-t}" } export_mangle_3: { @@ -190,7 +190,7 @@ export_mangle_3: { } }; } - expect_exact: "export class C{go(r,e){return r-e+r}};" + expect_exact: "export class C{go(r,e){return r-e+r}}" } export_mangle_4: { @@ -209,7 +209,7 @@ export_mangle_4: { } }; } - expect_exact: "export default class C{go(e,r){return e-r+e}};" + expect_exact: "export default class C{go(e,r){return e-r+e}}" } export_mangle_5: { @@ -276,8 +276,8 @@ export_default_func_ref: { unused: true, } input: { - export default function f(){}; + export default function f(){} f(); } - expect_exact: "export default function f(){};f();" + expect_exact: "export default function f(){}f();" } From 0150e02dc49bed2357de5fff33898e7c0f04e3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 24 Mar 2018 18:47:55 +0000 Subject: [PATCH 7/9] fix handling of regex in node 9 --- lib/mozilla-ast.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 5848ecc1..3ad26132 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -374,6 +374,17 @@ end : my_end_token(M) }; if (val === null) return new AST_Null(args); + var rx = M.regex; + if (rx && rx.pattern) { + // RegExpLiteral as per ESTree AST spec + args.value = new RegExp(rx.pattern, rx.flags); + args.value.raw_source = rx.pattern; + return new AST_RegExp(args); + } else if (rx) { + // support legacy RegExp + args.value = M.regex && M.raw ? M.raw : val; + return new AST_RegExp(args); + } switch (typeof val) { case "string": args.value = val; @@ -383,16 +394,6 @@ return new AST_Number(args); case "boolean": return new (val ? AST_True : AST_False)(args); - default: - var rx = M.regex; - if (rx && rx.pattern) { - // RegExpLiteral as per ESTree AST spec - args.value = new RegExp(rx.pattern, rx.flags).toString(); - } else { - // support legacy RegExp - args.value = M.regex && M.raw ? M.raw : val; - } - return new AST_RegExp(args); } }, MetaProperty: function(M) { @@ -842,14 +843,15 @@ }); def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) { - var value = M.value; + var flags = M.value.toString().match(/[gimuy]*$/)[0]; + var value = "/" + M.value.raw_source + "/" + flags; return { type: "Literal", value: value, - raw: value.toString(), + raw: value, regex: { - pattern: value.source, - flags: value.toString().match(/[gimuy]*$/)[0] + pattern: M.value.raw_source, + flags: flags } }; }); From ca926600dd66188f867e6097c662e64771f566a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 24 Mar 2018 18:59:14 +0000 Subject: [PATCH 8/9] run mozilla AST I/O tests against all existing compress tests --- lib/mozilla-ast.js | 38 ++++++++++++++++++++++++++++---------- lib/output.js | 8 +++++++- lib/parse.js | 5 +++-- test/compress/export.js | 4 ++-- test/compress/harmony.js | 4 ++-- test/run-tests.js | 10 ++++++++++ 6 files changed, 52 insertions(+), 17 deletions(-) diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 3ad26132..1676ee37 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -89,6 +89,7 @@ start: my_start_token(M), end: my_end_token(M), names: M.properties.map(from_moz), + is_array: false }); }, AssignmentPattern: function(M) { @@ -198,11 +199,14 @@ Property: function(M) { var key = M.key; var args = { - start : my_start_token(key), + start : my_start_token(key || M.value), end : my_end_token(M.value), key : key.type == "Identifier" ? key.name : key.value, value : from_moz(M.value) }; + if (M.computed) { + args.key = from_moz(M.key); + } if (M.method) { args.is_generator = M.value.generator; args.async = M.value.async; @@ -214,14 +218,16 @@ return new AST_ConciseMethod(args); } if (M.kind == "init") { - if (key.type != "Identifier") { + if (key.type != "Identifier" && key.type != "Literal") { args.key = from_moz(key); } return new AST_ObjectKeyVal(args); } - args.key = new AST_SymbolMethod({ - name: args.key - }); + if (typeof args.key === "string" || typeof args.key === "number") { + args.key = new AST_SymbolMethod({ + name: args.key + }); + } args.value = new AST_Accessor(args.value); if (M.kind == "get") return new AST_ObjectGetter(args); if (M.kind == "set") return new AST_ObjectSetter(args); @@ -264,6 +270,9 @@ start : my_start_token(M), end : my_end_token(M), properties : M.properties.map(function(prop){ + if (prop.type === "SpreadElement") { + return from_moz(prop); + } prop.type = "Property"; return from_moz(prop) }) @@ -414,6 +423,7 @@ : p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) : p.type == "ArrowFunctionExpression" ? (p.params.indexOf(M) !== -1) ? AST_SymbolFunarg : AST_SymbolRef : p.type == "ClassExpression" ? (p.id === M ? AST_SymbolClass : AST_SymbolRef) + : p.type == "Property" ? (p.key === M && p.computed || p.value === M ? AST_SymbolRef : AST_SymbolMethod) : p.type == "ClassDeclaration" ? (p.id === M ? AST_SymbolDefClass : AST_SymbolRef) : p.type == "MethodDefinition" ? (p.computed ? AST_SymbolRef : AST_SymbolMethod) : p.type == "CatchClause" ? AST_SymbolCatch @@ -440,7 +450,7 @@ MOZ_TO_ME.ClassDeclaration = MOZ_TO_ME.ClassExpression = function From_Moz_Class(M) { - return new AST_Class({ + return new (M.type === "ClassDeclaration" ? AST_DefClass : AST_ClassExpression)({ start : my_start_token(M), end : my_end_token(M), name : from_moz(M.id), @@ -471,6 +481,7 @@ map("CatchClause", AST_Catch, "param>argname, body%body"); map("ThisExpression", AST_This); + map("SuperExpression", AST_Super); map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right"); map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right"); map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right"); @@ -648,7 +659,7 @@ } return { type: M.is_default ? "ExportDefaultDeclaration" : "ExportNamedDeclaration", - declaration: to_moz(M.is_default ? M.exported_value : M.exported_definition) + declaration: to_moz(M.exported_value || M.exported_definition) }; }); @@ -708,7 +719,7 @@ }); def_to_moz(AST_Binary, function To_Moz_BinaryExpression(M) { - if (to_moz_in_destructuring()) { + if (M.operator == "=" && to_moz_in_destructuring()) { return { type: "AssignmentPattern", left: to_moz(M.left), @@ -742,6 +753,12 @@ type: "Identifier", value: M.key }; + if (typeof M.key === "number") { + key = { + type: "Literal", + value: Number(M.key) + }; + } if (typeof M.key === "string") { key = { type: "Identifier", @@ -749,10 +766,11 @@ }; } var kind; - var computed = typeof M.key === "string" ? false : !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef; + var string_or_num = typeof M.key === "string" || typeof M.key === "number"; + var computed = string_or_num ? false : !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef; if (M instanceof AST_ObjectKeyVal) { kind = "init"; - computed = typeof M.key !== "string"; + computed = !string_or_num; } else if (M instanceof AST_ObjectGetter) { kind = "get"; diff --git a/lib/output.js b/lib/output.js index 4abe95c0..9093665e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1455,7 +1455,13 @@ function OutputStream(options) { output.space(); self.module_name.print(output); } - if (!self.exported_definition) { + if (self.exported_value + && !(self.exported_value instanceof AST_Defun || + self.exported_value instanceof AST_Function || + self.exported_value instanceof AST_Class) + || self.module_name + || self.exported_names + ) { output.semicolon(); } }); diff --git a/lib/parse.js b/lib/parse.js index 414a47ac..c44088c8 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1160,7 +1160,7 @@ function parse($TEXT, options) { if (!is_token(peek(), "punc", "(")) { next(); var node = export_(); - if (can_insert_semicolon()) semicolon(); + if (is("punc", ";")) semicolon(); return node; } } @@ -2712,7 +2712,8 @@ function parse($TEXT, options) { next(); args.push(new AST_Expansion({ start: prev(), - expression: expression(false) + expression: expression(false), + end: prev() })); } else { args.push(expression(false)); diff --git a/test/compress/export.js b/test/compress/export.js index 6fcc46b0..66eed356 100644 --- a/test/compress/export.js +++ b/test/compress/export.js @@ -277,7 +277,7 @@ export_default_anonymous_function: { foo(); } } - expect_exact: "export default function(){foo()};" + expect_exact: "export default function(){foo()}" } export_default_arrow: { @@ -394,7 +394,7 @@ export_default_anonymous_class: { } }; } - expect_exact: "export default class{constructor(){foo()}};" + expect_exact: "export default class{constructor(){foo()}}" } export_default_anonymous_function_not_call: { diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 93025177..c7f105c0 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -254,7 +254,7 @@ export_default_anon_function: { console.log(1 + 2); } } - expect_exact: "export default function(){console.log(3)};" + expect_exact: "export default function(){console.log(3)}" } export_default_anon_class: { @@ -266,7 +266,7 @@ export_default_anon_class: { foo() { console.log(1 + 2); } } } - expect_exact: "export default class{foo(){console.log(3)}};" + expect_exact: "export default class{foo(){console.log(3)}}" } export_module_statement: { diff --git a/test/run-tests.js b/test/run-tests.js index deb1f954..05994d99 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -110,6 +110,16 @@ function run_compress_tests() { }); return false; } + var ast = input.to_mozilla_ast(); + var ast_as_string = U.AST_Node.from_mozilla_ast(ast).print_to_string(); + var input_string = input.print_to_string(); + if (input_string !== ast_as_string) { + log("!!! Mozilla AST I/O corrupted input\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n\n", { + input: input_string, + output: ast_as_string, + }); + return false; + } var options = U.defaults(test.options, { warnings: false }); From cb8285ce6a13784694ae6e548acb7b308475c575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sun, 25 Mar 2018 20:53:15 +0100 Subject: [PATCH 9/9] simplify escodegen test by adding an acorn round-trip --- test/mocha/spidermonkey.js | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js index 6f80a90e..d9d6f244 100644 --- a/test/mocha/spidermonkey.js +++ b/test/mocha/spidermonkey.js @@ -142,30 +142,15 @@ describe("spidermonkey export/import sanity test", function() { var code = fs.readFileSync("test/input/spidermonkey/input.js", "utf-8"); var uglify_ast = uglify.parse(code); var moz_ast = uglify_ast.to_mozilla_ast(); + var generated = escodegen.generate(moz_ast) + .replace(/\[object Object\].\[object Object\]/g, "new.target"); // escodegen issue + var parsed = acorn.parse(generated, { + sourceType: "module", + ecmaVersion: 9 + }); assert.strictEqual( - escodegen.generate(moz_ast, { - format: { - indent: { - style: "", - }, - newline: "", - space: "", - quotes: "double" - } - }) - .replace(/;}/g, "}") - .replace(/var {/g, "var{") - .replace(/var \[/g, "var[") - .replace(/const {/g, "const{") - .replace(/const \[/g, "const[") - .replace(/get \"/g, "get\"") - .replace(/set \"/g, "set\"") - .replace(/\[object Object\].\[object Object\]/g, "new.target") // escodegen issue - .replace(/\(await x\)/, "await x") - , - uglify_ast.print_to_string({ - keep_quoted_props: true - }) + uglify.AST_Node.from_mozilla_ast(parsed).print_to_string(), + uglify_ast.print_to_string() ); }); });