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() + ); + }); });