From 324587f76980114e030c59165a60bd285ca05be0 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 26 Apr 2021 04:23:52 +0800 Subject: [PATCH] upgrade AST<->ESTree translation (#4870) fixes #968 --- bin/uglifyjs | 15 +- lib/compress.js | 4 + lib/mozilla-ast.js | 900 ++++++++++++++++++++++++++++++++++++-------- lib/parse.js | 2 +- package.json | 2 +- test/mozilla-ast.js | 37 +- test/ufuzz/index.js | 1 + tools/node.js | 7 +- 8 files changed, 796 insertions(+), 172 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 682c6a5a4e..325dacdde3 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -235,7 +235,7 @@ if (options.mangle && options.mangle.properties) { }); } } -if (output == "ast") options.output = { +if (output == "ast" || output == "spidermonkey") options.output = { ast: true, code: false, }; @@ -313,9 +313,11 @@ function run() { if (options.parse.acorn) { files = convert_ast(function(toplevel, name) { return require("acorn").parse(files[name], { + ecmaVersion: "latest", locations: true, program: toplevel, - sourceFile: name + sourceFile: name, + sourceType: "module", }); }); } else if (options.parse.spidermonkey) { @@ -409,14 +411,7 @@ function run() { return value; }, 2)); } else if (output == "spidermonkey") { - print(JSON.stringify(UglifyJS.minify(result.code, { - compress: false, - mangle: false, - output: { - ast: true, - code: false - }, - }).ast.to_mozilla_ast(), null, 2)); + print(JSON.stringify(result.ast.to_mozilla_ast(), null, 2)); } else if (output) { fs.writeFileSync(output, result.code); if (result.map) fs.writeFileSync(output + ".map", result.map); diff --git a/lib/compress.js b/lib/compress.js index 6c47562919..28ac7f48c2 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -6562,6 +6562,10 @@ merge(Compressor.prototype, { } return insert_statements(body, node, in_list); } + if (node instanceof AST_Import) { + if (node.properties && node.properties == 0) node.properties = null; + return node; + } if (node instanceof AST_Sequence) { if (node.expressions.length > 1) return; return maintain_this_binding(compressor, tt.parent(), node, node.expressions[0]); diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 3ceaf584d1..09c5b105c5 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -44,53 +44,161 @@ "use strict"; (function() { - function normalize_directives(body) { - var in_directive = true; - for (var i = 0; i < body.length; i++) { - if (in_directive && body[i] instanceof AST_Statement && body[i].body instanceof AST_String) { - body[i] = new AST_Directive({ - start: body[i].start, - end: body[i].end, - value: body[i].body.value - }); - } else if (in_directive && !(body[i] instanceof AST_Statement && body[i].body instanceof AST_String)) { - in_directive = false; - } - } - return body; - } - var MOZ_TO_ME = { Program: function(M) { return new AST_Toplevel({ start: my_start_token(M), end: my_end_token(M), - body: normalize_directives(M.body.map(from_moz)) + body: normalize_directives(M.body.map(from_moz)), }); }, + ArrowFunctionExpression: function(M) { + var argnames = [], rest = null; + M.params.forEach(function(param) { + if (param.type == "RestElement") { + rest = from_moz(param.argument); + } else { + argnames.push(from_moz(param)); + } + }); + var fn = new (M.async ? AST_AsyncArrow : AST_Arrow)({ + start: my_start_token(M), + end: my_end_token(M), + argnames: argnames, + rest: rest, + }); + var node = from_moz(M.body); + if (node instanceof AST_BlockStatement) { + fn.body = normalize_directives(node.body); + fn.value = null; + } else { + fn.body = []; + fn.value = node; + } + return fn; + }, FunctionDeclaration: function(M) { - return new AST_Defun({ + var ctor; + if (M.async) { + ctor = M.generator ? AST_AsyncGeneratorDefun : AST_AsyncDefun; + } else { + ctor = M.generator ? AST_GeneratorDefun : AST_Defun; + } + var argnames = [], rest = null; + M.params.forEach(function(param) { + if (param.type == "RestElement") { + rest = from_moz(param.argument); + } else { + argnames.push(from_moz(param)); + } + }); + return new ctor({ start: my_start_token(M), end: my_end_token(M), name: from_moz(M.id), - argnames: M.params.map(from_moz), - body: normalize_directives(from_moz(M.body).body) + argnames: argnames, + rest: rest, + body: normalize_directives(from_moz(M.body).body), }); }, FunctionExpression: function(M) { - return new AST_Function({ + var ctor; + if (M.async) { + ctor = M.generator ? AST_AsyncGeneratorFunction : AST_AsyncFunction; + } else { + ctor = M.generator ? AST_GeneratorFunction : AST_Function; + } + var argnames = [], rest = null; + M.params.forEach(function(param) { + if (param.type == "RestElement") { + rest = from_moz(param.argument); + } else { + argnames.push(from_moz(param)); + } + }); + return new ctor({ start: my_start_token(M), end: my_end_token(M), name: from_moz(M.id), - argnames: M.params.map(from_moz), - body: normalize_directives(from_moz(M.body).body) + argnames: argnames, + rest: rest, + body: normalize_directives(from_moz(M.body).body), }); }, - ExpressionStatement: function(M) { - return new AST_SimpleStatement({ + ClassDeclaration: function(M) { + return new AST_DefClass({ start: my_start_token(M), end: my_end_token(M), - body: from_moz(M.expression) + name: from_moz(M.id), + extends: from_moz(M.superClass), + properties: M.body.body.map(from_moz), + }); + }, + ClassExpression: function(M) { + return new AST_ClassExpression({ + 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), + }); + }, + MethodDefinition: function(M) { + var key = M.key, internal = false; + if (M.computed) { + key = from_moz(key); + } else if (key.type == "PrivateIdentifier") { + internal = true; + key = "#" + key.name; + } else { + key = read_name(key); + } + var ctor = AST_ClassMethod, value = from_moz(M.value); + switch (M.kind) { + case "get": + ctor = AST_ClassGetter; + value = new AST_Accessor(value); + break; + case "set": + ctor = AST_ClassSetter; + value = new AST_Accessor(value); + break; + } + return new ctor({ + start: my_start_token(M), + end: my_end_token(M), + key: key, + private: internal, + static: M.static, + value: value, + }); + }, + PropertyDefinition: function(M) { + var key = M.key, internal = false; + if (M.computed) { + key = from_moz(key); + } else if (key.type == "PrivateIdentifier") { + internal = true; + key = "#" + key.name; + } else { + key = read_name(key); + } + return new AST_ClassField({ + start: my_start_token(M), + end: my_end_token(M), + key: key, + private: internal, + static: M.static, + value: from_moz(M.value), + }); + }, + ForOfStatement: function(M) { + return new (M.await ? AST_ForAwaitOf : AST_ForOf)({ + start: my_start_token(M), + end: my_end_token(M), + init: from_moz(M.left), + object: from_moz(M.right), + body: from_moz(M.body), }); }, TryStatement: function(M) { @@ -103,54 +211,86 @@ end : my_end_token(M), body : from_moz(M.block).body, bcatch : from_moz(handlers[0]), - bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null + bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null, }); }, Property: function(M) { - var key = M.key; + var key = M.computed ? from_moz(M.key) : read_name(M.key); var args = { - start : my_start_token(key), - end : my_end_token(M.value), - key : "" + key[key.type == "Identifier" ? "name" : "value"], - value : from_moz(M.value) + start: my_start_token(M), + end: my_end_token(M), + key: key, + value: from_moz(M[M.shorthand ? "key" : "value"]), }; - if (M.kind == "init") return new AST_ObjectKeyVal(args); + if (M.kind == "init") return new (M.method ? AST_ObjectMethod : AST_ObjectKeyVal)(args); 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); }, ArrayExpression: function(M) { return new AST_Array({ - start : my_start_token(M), - end : my_end_token(M), - elements : M.elements.map(function(elem) { + start: my_start_token(M), + end: my_end_token(M), + elements: M.elements.map(function(elem) { return elem === null ? new AST_Hole() : from_moz(elem); - }) + }), }); }, - ObjectExpression: function(M) { - return new AST_Object({ - start : my_start_token(M), - end : my_end_token(M), - properties : M.properties.map(function(prop) { - prop.type = "Property"; - return from_moz(prop) - }) + ArrayPattern: function(M) { + var elements = [], rest = null; + M.elements.forEach(function(el) { + if (el === null) { + elements.push(new AST_Hole()); + } else if (el.type == "RestElement") { + rest = from_moz(el.argument); + } else { + elements.push(from_moz(el)); + } + }); + return new AST_DestructuredArray({ + start: my_start_token(M), + end: my_end_token(M), + elements: elements, + rest: rest, }); }, - SequenceExpression: function(M) { - return new AST_Sequence({ - start : my_start_token(M), - end : my_end_token(M), - expressions: M.expressions.map(from_moz) + ObjectPattern: function(M) { + var props = [], rest = null; + M.properties.forEach(function(prop) { + if (prop.type == "RestElement") { + rest = from_moz(prop.argument); + } else { + props.push(new AST_DestructuredKeyVal(from_moz(prop))); + } + }); + return new AST_DestructuredObject({ + start: my_start_token(M), + end: my_end_token(M), + properties: props, + rest: rest, }); }, MemberExpression: function(M) { return new (M.computed ? AST_Sub : AST_Dot)({ - start : my_start_token(M), - end : my_end_token(M), - property : M.computed ? from_moz(M.property) : M.property.name, - expression : from_moz(M.object) + start: my_start_token(M), + end: my_end_token(M), + expression: from_moz(M.object), + property: M.computed ? from_moz(M.property) : M.property.name, + }); + }, + MetaProperty: function(M) { + var expr = from_moz(M.meta); + var prop = read_name(M.property); + if (expr.name == "new" && prop == "target") return new AST_NewTarget({ + start: my_start_token(M), + end: my_end_token(M), + name: "new.target", + }); + return new AST_Dot({ + start: my_start_token(M), + end: my_end_token(M), + expression: expr, + property: prop, }); }, SwitchCase: function(M) { @@ -158,21 +298,123 @@ start : my_start_token(M), end : my_end_token(M), expression : from_moz(M.test), - body : M.consequent.map(from_moz) + body : M.consequent.map(from_moz), + }); + }, + ExportAllDeclaration: function(M) { + var alias = M.exported ? read_name(M.exported) : "*"; + return new AST_ExportForeign({ + start: my_start_token(M), + end: my_end_token(M), + aliases: [ alias ], + keys: [ "*" ], + path: M.source.value, + }); + }, + ExportDefaultDeclaration: function(M) { + var decl = from_moz(M.declaration); + if (!decl.name) switch (decl.CTOR) { + case AST_AsyncDefun: + decl = new AST_AsyncFunction(decl); + break; + case AST_AsyncGeneratorDefun: + decl = new AST_AsyncGeneratorFunction(decl); + break; + case AST_DefClass: + decl = new AST_ClassExpression(decl); + break; + case AST_Defun: + decl = new AST_Function(decl); + break; + case AST_GeneratorDefun: + decl = new AST_GeneratorFunction(decl); + break; + } + return new AST_ExportDefault({ + start: my_start_token(M), + end: my_end_token(M), + body: decl, + }); + }, + ExportNamedDeclaration: function(M) { + if (M.declaration) return new AST_ExportDeclaration({ + start: my_start_token(M), + end: my_end_token(M), + body: from_moz(M.declaration), + }); + if (M.source) { + var aliases = [], keys = []; + M.specifiers.forEach(function(prop) { + aliases.push(read_name(prop.exported)); + keys.push(read_name(prop.local)); + }); + return new AST_ExportForeign({ + start: my_start_token(M), + end: my_end_token(M), + aliases: aliases, + keys: keys, + path: M.source.value, + }); + } + return new AST_ExportReferences({ + start: my_start_token(M), + end: my_end_token(M), + properties: M.specifiers.map(function(prop) { + var sym = new AST_SymbolExport(from_moz(prop.local)); + sym.alias = read_name(prop.exported); + return sym; + }), + }); + }, + ImportDeclaration: function(M) { + var all = null, def = null, props = null; + M.specifiers.forEach(function(prop) { + var sym = new AST_SymbolImport(from_moz(prop.local)); + switch (prop.type) { + case "ImportDefaultSpecifier": + def = sym; + def.key = ""; + break; + case "ImportNamespaceSpecifier": + all = sym; + all.key = "*"; + break; + default: + sym.key = prop.imported.name || syn.name; + if (!props) props = []; + props.push(sym); + break; + } + }); + return new AST_Import({ + start: my_start_token(M), + end: my_end_token(M), + all: all, + default: def, + properties: props, + path: M.source.value, }); }, VariableDeclaration: function(M) { - return new AST_Var({ - start : my_start_token(M), - end : my_end_token(M), - definitions : M.declarations.map(from_moz) + return new ({ + const: AST_Const, + let: AST_Let, + }[M.kind] || AST_Var)({ + start: my_start_token(M), + end: my_end_token(M), + definitions: M.declarations.map(from_moz), }); }, Literal: function(M) { - var val = M.value, args = { - start : my_start_token(M), - end : my_end_token(M) + var args = { + start: my_start_token(M), + end: my_end_token(M), }; + if (M.bigint) { + args.value = M.bigint.toLowerCase() + "n"; + return new AST_BigInt(args); + } + var val = M.value; if (val === null) return new AST_Null(args); var rx = M.regex; if (rx && rx.pattern) { @@ -196,25 +438,84 @@ return new (val ? AST_True : AST_False)(args); } }, + TemplateLiteral: function(M) { + return new AST_Template({ + start: my_start_token(M), + end: my_end_token(M), + expressions: M.expressions.map(from_moz), + strings: M.quasis.map(function(el) { + return el.value.raw; + }), + }); + }, + TaggedTemplateExpression: function(M) { + var tmpl = from_moz(M.quasi); + tmpl.start = my_start_token(M); + tmpl.end = my_end_token(M); + tmpl.tag = from_moz(M.tag); + return tmpl; + }, 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 ? AST_SymbolVar - : p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg) - : p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) - : p.type == "CatchClause" ? AST_SymbolCatch - : p.type == "BreakStatement" || p.type == "ContinueStatement" ? AST_LabelRef - : AST_SymbolRef)({ - start : my_start_token(M), - end : my_end_token(M), - name : M.name - }); + var p, level = FROM_MOZ_STACK.length - 1; + do { + p = FROM_MOZ_STACK[--level]; + } while (p.type == "ArrayPattern" + || p.type == "AssignmentPattern" && p.left === FROM_MOZ_STACK[level + 1] + || p.type == "ObjectPattern" + || p.type == "Property" && p[p.shorthand ? "key" : "value"] === FROM_MOZ_STACK[level + 1] + || p.type == "VariableDeclarator" && p.id === FROM_MOZ_STACK[level + 1]); + var ctor = AST_SymbolRef; + switch (p.type) { + case "ArrowFunctionExpression": + if (p.body !== FROM_MOZ_STACK[level + 1]) ctor = AST_SymbolFunarg; + break; + case "BreakStatement": + case "ContinueStatement": + ctor = AST_LabelRef; + break; + case "CatchClause": + ctor = AST_SymbolCatch; + break; + case "ClassDeclaration": + if (p.id === FROM_MOZ_STACK[level + 1]) ctor = AST_SymbolDefClass; + break; + case "ClassExpression": + if (p.id === FROM_MOZ_STACK[level + 1]) ctor = AST_SymbolClass; + break; + case "FunctionDeclaration": + ctor = p.id === FROM_MOZ_STACK[level + 1] ? AST_SymbolDefun : AST_SymbolFunarg; + break; + case "FunctionExpression": + ctor = p.id === FROM_MOZ_STACK[level + 1] ? AST_SymbolLambda : AST_SymbolFunarg; + break; + case "LabeledStatement": + ctor = AST_Label; + break; + case "VariableDeclaration": + ctor = { + const: AST_SymbolConst, + let: AST_SymbolLet, + }[p.kind] || AST_SymbolVar; + break; + } + return new ctor({ + start: my_start_token(M), + end: my_end_token(M), + name: M.name, + }); + }, + Super: function(M) { + return new AST_Super({ + start: my_start_token(M), + end: my_end_token(M), + name: "super", + }); }, ThisExpression: function(M) { return new AST_This({ - start : my_start_token(M), - end : my_end_token(M), - name : "this", + start: my_start_token(M), + end: my_end_token(M), + name: "this", }); }, }; @@ -232,6 +533,7 @@ }; map("EmptyStatement", AST_EmptyStatement); + map("ExpressionStatement", AST_SimpleStatement, "expression>body"); map("BlockStatement", AST_BlockStatement, "body@body"); map("IfStatement", AST_If, "test>condition, consequent>body, alternate>alternative"); map("LabeledStatement", AST_LabeledStatement, "label>label, body>body"); @@ -252,46 +554,145 @@ 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"); + map("AssignmentPattern", AST_DefaultValue, "left>name, right>value"); map("ConditionalExpression", AST_Conditional, "test>condition, consequent>consequent, alternate>alternative"); map("NewExpression", AST_New, "callee>expression, arguments@args"); map("CallExpression", AST_Call, "callee>expression, arguments@args"); + map("SequenceExpression", AST_Sequence, "expressions@expressions"); + map("SpreadElement", AST_Spread, "argument>expression"); + map("ObjectExpression", AST_Object, "properties@properties"); + map("AwaitExpression", AST_Await, "argument>expression"); + map("YieldExpression", AST_Yield, "argument>expression, delegate=nested"); def_to_moz(AST_Toplevel, function To_Moz_Program(M) { return to_moz_scope("Program", M); }); - def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) { + def_to_moz(AST_LambdaDefinition, function To_Moz_FunctionDeclaration(M) { + var params = M.argnames.map(to_moz); + if (M.rest) params.push({ + type: "RestElement", + argument: to_moz(M.rest), + }); return { type: "FunctionDeclaration", id: to_moz(M.name), - params: M.argnames.map(to_moz), - body: to_moz_scope("BlockStatement", M) - } + async: is_async(M), + generator: is_generator(M), + params: params, + body: to_moz_scope("BlockStatement", M), + }; }); - def_to_moz(AST_Function, function To_Moz_FunctionExpression(M) { + def_to_moz(AST_Lambda, function To_Moz_FunctionExpression(M) { + var params = M.argnames.map(to_moz); + if (M.rest) params.push({ + type: "RestElement", + argument: to_moz(M.rest), + }); + if (is_arrow(M)) return { + type: "ArrowFunctionExpression", + async: is_async(M), + params: params, + body: M.value ? to_moz(M.value) : to_moz_scope("BlockStatement", M), + }; return { type: "FunctionExpression", id: to_moz(M.name), - params: M.argnames.map(to_moz), - body: to_moz_scope("BlockStatement", M) - } + async: is_async(M), + generator: is_generator(M), + params: params, + body: to_moz_scope("BlockStatement", M), + }; }); - def_to_moz(AST_Directive, function To_Moz_Directive(M) { + def_to_moz(AST_DefClass, function To_Moz_ClassDeclaration(M) { return { - type: "ExpressionStatement", - expression: { + type: "ClassDeclaration", + id: to_moz(M.name), + superClass: to_moz(M.extends), + body: { + type: "ClassBody", + body: M.properties.map(to_moz), + }, + }; + }); + + def_to_moz(AST_ClassExpression, function To_Moz_ClassExpression(M) { + return { + type: "ClassExpression", + id: to_moz(M.name), + superClass: to_moz(M.extends), + body: { + type: "ClassBody", + body: M.properties.map(to_moz), + }, + }; + }); + + function To_Moz_MethodDefinition(kind) { + return function(M) { + var computed = M.key instanceof AST_Node; + var key = computed ? to_moz(M.key) : M.private ? { + type: "PrivateIdentifier", + name: M.key.slice(1), + } : { type: "Literal", - value: M.value - } + value: M.key, + }; + return { + type: "MethodDefinition", + kind: kind, + computed: computed, + key: key, + static: M.static, + value: to_moz(M.value), + }; + }; + } + def_to_moz(AST_ClassGetter, To_Moz_MethodDefinition("get")); + def_to_moz(AST_ClassSetter, To_Moz_MethodDefinition("set")); + def_to_moz(AST_ClassMethod, To_Moz_MethodDefinition("method")); + + def_to_moz(AST_ClassField, function To_Moz_PropertyDefinition(M) { + var computed = M.key instanceof AST_Node; + var key = computed ? to_moz(M.key) : M.private ? { + type: "PrivateIdentifier", + name: M.key.slice(1), + } : { + type: "Literal", + value: M.key, + }; + return { + type: "PropertyDefinition", + computed: computed, + key: key, + static: M.static, + value: to_moz(M.value), }; }); - def_to_moz(AST_SimpleStatement, function To_Moz_ExpressionStatement(M) { + function To_Moz_ForOfStatement(is_await) { + return function(M) { + return { + type: "ForOfStatement", + await: is_await, + left: to_moz(M.init), + right: to_moz(M.object), + body: to_moz(M.body), + }; + }; + } + def_to_moz(AST_ForAwaitOf, To_Moz_ForOfStatement(true)); + def_to_moz(AST_ForOf, To_Moz_ForOfStatement(false)); + + def_to_moz(AST_Directive, function To_Moz_Directive(M) { return { type: "ExpressionStatement", - expression: to_moz(M.body) + expression: set_moz_loc(M, { + type: "Literal", + value: M.value, + }), }; }); @@ -299,7 +700,7 @@ return { type: "SwitchCase", test: to_moz(M.expression), - consequent: M.body.map(to_moz) + consequent: M.body.map(to_moz), }; }); @@ -309,7 +710,7 @@ block: to_moz_block(M), handler: to_moz(M.bcatch), guardedHandlers: [], - finalizer: to_moz(M.bfinally) + finalizer: to_moz(M.bfinally), }; }); @@ -318,32 +719,132 @@ type: "CatchClause", param: to_moz(M.argname), guard: null, - body: to_moz_block(M) + body: to_moz_block(M), }; }); - def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) { + def_to_moz(AST_ExportDeclaration, function To_Moz_ExportNamedDeclaration_declaration(M) { return { - type: "VariableDeclaration", - kind: "var", - declarations: M.definitions.map(to_moz) + type: "ExportNamedDeclaration", + declaration: to_moz(M.body), }; }); - def_to_moz(AST_Sequence, function To_Moz_SequenceExpression(M) { + def_to_moz(AST_ExportDefault, function To_Moz_ExportDefaultDeclaration(M) { + var decl = to_moz(M.body); + switch (decl.type) { + case "ClassExpression": + decl.type = "ClassDeclaration"; + break; + case "FunctionExpression": + decl.type = "FunctionDeclaration"; + break; + } return { - type: "SequenceExpression", - expressions: M.expressions.map(to_moz) + type: "ExportDefaultDeclaration", + declaration: decl, + }; + }); + + def_to_moz(AST_ExportForeign, function To_Moz_ExportAllDeclaration_ExportNamedDeclaration(M) { + if (M.keys[0] == "*") return { + type: "ExportAllDeclaration", + exported: M.aliases[0] == "*" ? null : { + type: "Identifier", + name: M.aliases[0], + }, + source: { + type: "Literal", + value: M.path, + }, + }; + var specifiers = []; + for (var i = 0; i < M.aliases.length; i++) { + specifiers.push({ + type: "ExportSpecifier", + exported: { + type: "Identifier", + name: M.aliases[i], + }, + local: { + type: "Identifier", + name: M.keys[i], + }, + }); + } + return { + type: "ExportNamedDeclaration", + specifiers: specifiers, + source: { + type: "Literal", + value: M.path, + }, + }; + }); + + def_to_moz(AST_ExportReferences, function To_Moz_ExportNamedDeclaration_specifiers(M) { + return { + type: "ExportNamedDeclaration", + specifiers: M.properties.map(function(prop) { + return { + type: "ExportSpecifier", + local: to_moz(prop), + exported: { + type: "Identifier", + name: prop.alias, + }, + }; + }), + }; + }); + + def_to_moz(AST_Import, function To_Moz_ImportDeclaration(M) { + var specifiers = M.properties ? M.properties.map(function(prop) { + return { + type: "ImportSpecifier", + local: to_moz(prop), + imported: { + type: "Identifier", + name: prop.key, + }, + }; + }) : []; + if (M.all) specifiers.unshift({ + type: "ImportNamespaceSpecifier", + local: to_moz(M.all), + }); + if (M.default) specifiers.unshift({ + type: "ImportDefaultSpecifier", + local: to_moz(M.default), + }); + return { + type: "ImportDeclaration", + specifiers: specifiers, + source: { + type: "Literal", + value: M.path, + }, + }; + }); + + def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) { + return { + type: "VariableDeclaration", + kind: M.TYPE.toLowerCase(), + declarations: M.definitions.map(to_moz), }; }); def_to_moz(AST_PropAccess, function To_Moz_MemberExpression(M) { - var isComputed = M instanceof AST_Sub; + var computed = M instanceof AST_Sub; return { type: "MemberExpression", object: to_moz(M.expression), - computed: isComputed, - property: isComputed ? to_moz(M.property) : {type: "Identifier", name: M.property} + computed: computed, + property: computed ? to_moz(M.property) : { + type: "Identifier", + name: M.property, + }, }; }); @@ -368,37 +869,70 @@ def_to_moz(AST_Array, function To_Moz_ArrayExpression(M) { return { type: "ArrayExpression", - elements: M.elements.map(to_moz) + elements: M.elements.map(to_moz), }; }); - def_to_moz(AST_Object, function To_Moz_ObjectExpression(M) { + def_to_moz(AST_DestructuredArray, function To_Moz_ArrayPattern(M) { + var elements = M.elements.map(to_moz); + if (M.rest) elements.push({ + type: "RestElement", + argument: to_moz(M.rest), + }); return { - type: "ObjectExpression", - properties: M.properties.map(to_moz) + type: "ArrayPattern", + elements: elements, + }; + }); + + def_to_moz(AST_DestructuredKeyVal, function To_Moz_Property(M) { + var computed = M.key instanceof AST_Node; + var key = computed ? to_moz(M.key) : { + type: "Literal", + value: M.key, + }; + return { + type: "Property", + kind: "init", + computed: computed, + key: key, + value: to_moz(M.value), + }; + }); + + def_to_moz(AST_DestructuredObject, function To_Moz_ObjectPattern(M) { + var props = M.properties.map(to_moz); + if (M.rest) props.push({ + type: "RestElement", + argument: to_moz(M.rest), + }); + return { + type: "ObjectPattern", + properties: props, }; }); def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) { - var key = { + var computed = M.key instanceof AST_Node; + var key = computed ? to_moz(M.key) : { type: "Literal", - value: M.key + value: M.key, }; var kind; if (M instanceof AST_ObjectKeyVal) { kind = "init"; - } else - if (M instanceof AST_ObjectGetter) { + } else if (M instanceof AST_ObjectGetter) { kind = "get"; - } else - if (M instanceof AST_ObjectSetter) { + } else if (M instanceof AST_ObjectSetter) { kind = "set"; } return { type: "Property", kind: kind, + computed: computed, + method: M instanceof AST_ObjectMethod, key: key, - value: to_moz(M.value) + value: to_moz(M.value), }; }); @@ -406,14 +940,32 @@ var def = M.definition(); return { type: "Identifier", - name: def && def.mangled_name || M.name + name: def && def.mangled_name || M.name, }; }); + def_to_moz(AST_Super, function To_Moz_Super() { + return { type: "Super" }; + }); + def_to_moz(AST_This, function To_Moz_ThisExpression() { return { type: "ThisExpression" }; }); + def_to_moz(AST_NewTarget, function To_Moz_MetaProperty() { + return { + type: "MetaProperty", + meta: { + type: "Identifier", + name: "new", + }, + property: { + type: "Identifier", + name: "target", + }, + }; + }); + def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) { var flags = M.value.toString().match(/[gimuy]*$/)[0]; var value = "/" + M.value.raw_source + "/" + flags; @@ -428,9 +980,18 @@ }; }); - def_to_moz(AST_Constant, function To_Moz_Literal(M) { + def_to_moz(AST_BigInt, function To_Moz_BigInt(M) { var value = M.value; - if (typeof value === 'number' && (value < 0 || (value === 0 && 1 / value < 0))) { + return { + type: "Literal", + bigint: value.slice(0, -1), + raw: value, + }; + }); + + function To_Moz_Literal(M) { + var value = M.value; + if (typeof value === "number" && (value < 0 || (value === 0 && 1 / value < 0))) { return { type: "UnaryExpression", operator: "-", @@ -438,33 +999,68 @@ argument: { type: "Literal", value: -value, - raw: M.start.raw - } + raw: M.start.raw, + }, }; } return { type: "Literal", value: value, - raw: M.start.raw + raw: M.start.raw, }; - }); + } + def_to_moz(AST_Boolean, To_Moz_Literal); + def_to_moz(AST_Constant, To_Moz_Literal); + def_to_moz(AST_Null, To_Moz_Literal); def_to_moz(AST_Atom, function To_Moz_Atom(M) { return { type: "Identifier", - name: String(M.value) + name: String(M.value), }; }); - AST_Boolean.DEFMETHOD("to_mozilla_ast", AST_Constant.prototype.to_mozilla_ast); - AST_Null.DEFMETHOD("to_mozilla_ast", AST_Constant.prototype.to_mozilla_ast); - AST_Hole.DEFMETHOD("to_mozilla_ast", function To_Moz_ArrayHole() { return null }); + def_to_moz(AST_Template, function To_Moz_TemplateLiteral_TaggedTemplateExpression(M) { + var last = M.strings.length - 1; + var tmpl = { + type: "TemplateLiteral", + expressions: M.expressions.map(to_moz), + quasis: M.strings.map(function(str, index) { + return { + type: "TemplateElement", + tail: index == last, + value: { raw: str }, + }; + }), + }; + if (!M.tag) return tmpl; + return { + type: "TaggedTemplateExpression", + tag: to_moz(M.tag), + quasi: tmpl, + }; + }); AST_Block.DEFMETHOD("to_mozilla_ast", AST_BlockStatement.prototype.to_mozilla_ast); - AST_Lambda.DEFMETHOD("to_mozilla_ast", AST_Function.prototype.to_mozilla_ast); + AST_Hole.DEFMETHOD("to_mozilla_ast", return_null); + AST_Node.DEFMETHOD("to_mozilla_ast", function() { + throw new Error("Cannot convert AST_" + this.TYPE); + }); /* -----[ tools ]----- */ + function normalize_directives(body) { + for (var i = 0; i < body.length; i++) { + var stat = body[i]; + if (!(stat instanceof AST_SimpleStatement)) break; + var node = stat.body; + if (!(node instanceof AST_String)) break; + if (stat.start.pos !== node.start.pos) break; + body[i] = new AST_Directive(node); + } + return body; + } + function raw_token(moznode) { if (moznode.type == "Literal") { return moznode.raw != null ? moznode.raw : moznode.value + ""; @@ -501,6 +1097,10 @@ }); } + function read_name(M) { + return "" + M[M.type == "Identifier" ? "name" : "value"]; + } + function map(moztype, mytype, propmap) { var moz_to_me = "function From_Moz_" + moztype + "(M){\n"; moz_to_me += "return new U2." + mytype.name + "({\n" + @@ -518,24 +1118,24 @@ moz_to_me += ",\n" + my + ": "; me_to_moz += ",\n" + moz + ": "; switch (how) { - case "@": - moz_to_me += "M." + moz + ".map(from_moz)"; - me_to_moz += "M." + my + ".map(to_moz)"; - break; - case ">": - moz_to_me += "from_moz(M." + moz + ")"; - me_to_moz += "to_moz(M." + my + ")"; - break; - case "=": - moz_to_me += "M." + moz; - me_to_moz += "M." + my; - break; - case "%": - moz_to_me += "from_moz(M." + moz + ").body"; - me_to_moz += "to_moz_block(M)"; - break; - default: - throw new Error("Can't understand operator in propmap: " + prop); + case "@": + moz_to_me += "M." + moz + ".map(from_moz)"; + me_to_moz += "M." + my + ".map(to_moz)"; + break; + case ">": + moz_to_me += "from_moz(M." + moz + ")"; + me_to_moz += "to_moz(M." + my + ")"; + break; + case "=": + moz_to_me += "M." + moz; + me_to_moz += "M." + my; + break; + case "%": + moz_to_me += "from_moz(M." + moz + ").body"; + me_to_moz += "to_moz_block(M)"; + break; + default: + throw new Error("Can't understand operator in propmap: " + prop); } }); @@ -588,7 +1188,7 @@ return ast; }; - function set_moz_loc(mynode, moznode, myparent) { + function set_moz_loc(mynode, moznode) { var start = mynode.start; var end = mynode.end; if (start.pos != null && end.endpos != null) { @@ -597,7 +1197,7 @@ if (start.line) { moznode.loc = { start: {line: start.line, column: start.col}, - end: end.endline ? {line: end.endline, column: end.endcol} : null + end: end.endline ? {line: end.endline, column: end.endcol} : null, }; if (start.file) { moznode.loc.source = start.file; @@ -619,7 +1219,7 @@ function to_moz_block(node) { return { type: "BlockStatement", - body: node.body.map(to_moz) + body: node.body.map(to_moz), }; } @@ -630,7 +1230,7 @@ } return { type: type, - body: body + body: body, }; } })(); diff --git a/lib/parse.js b/lib/parse.js index cdd607a2dd..415a20ee6b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -2526,7 +2526,7 @@ function parse($TEXT, options) { while (!is("eof")) body.push(statement()); S.input.pop_directives_stack(); - var end = prev(); + var end = prev() || start; var toplevel = options.toplevel; if (toplevel) { toplevel.body = toplevel.body.concat(body); diff --git a/package.json b/package.json index e9ad500e55..0ce8167add 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "LICENSE" ], "devDependencies": { - "acorn": "~7.1.0", + "acorn": "~8.2.1", "semver": "~6.3.0" }, "scripts": { diff --git a/test/mozilla-ast.js b/test/mozilla-ast.js index 5f5ad541d9..0f6db3ac02 100644 --- a/test/mozilla-ast.js +++ b/test/mozilla-ast.js @@ -11,7 +11,7 @@ function try_beautify(code) { mangle: false, output: { beautify: true, - braces: true + braces: true, } }); if (beautified.error) { @@ -35,12 +35,18 @@ function validate(ast) { return UglifyJS.minify(ast, { compress: false, mangle: false, + validate: true, }); } +function fuzzy(code) { + return code.replace(/\bimport\s*\{\s*\}\s*from\s*(['"])/g, "import$1") + .replace(/\b(import\b.*?)\s*,\s*\{\s*\}\s*(from\s*['"])/g, "$1 $2"); +} + function test(original, estree, description) { var transformed = validate(UglifyJS.AST_Node.from_mozilla_ast(estree)); - if (transformed.error || original !== transformed.code) { + if (transformed.error || original !== transformed.code && fuzzy(original) !== fuzzy(transformed.code)) { console.log("//============================================================="); console.log("// !!!!!! Failed... round", round); console.log("// original code"); @@ -72,19 +78,36 @@ for (var round = 1; round <= num_iterations; round++) { compress: false, mangle: false, output: { - ast: true - } + ast: true, + }, }); - var ok = test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()"); + var ok = true; try { - ok = test(uglified.code, acorn.parse(input), "acorn.parse()") && ok; + var estree = uglified.ast.to_mozilla_ast(); } catch (e) { + ok = false; console.log("//============================================================="); - console.log("// acorn parser failed... round", round); + console.log("// AST_Node.to_mozilla_ast() failed... round", round); console.log(e); console.log("// original code"); console.log(input); } + if (ok) ok = test(uglified.code, estree, "AST_Node.to_mozilla_ast()"); + if (ok) try { + ok = test(uglified.code, acorn.parse(input, { + ecmaVersion: "latest", + locations: true, + sourceType: "module", + }), "acorn.parse()"); + } catch (e) { + if (ufuzz.verbose) { + console.log("//============================================================="); + console.log("// acorn parser failed... round", round); + console.log(e); + console.log("// original code"); + console.log(input); + } + } if (!ok) process.exit(1); }); } diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index b3650fa55e..8ccaefe0df 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -2045,6 +2045,7 @@ function createVarName(maybe, dontStore) { if (require.main !== module) { exports.createTopLevelCode = createTopLevelCode; exports.num_iterations = num_iterations; + exports.verbose = verbose; return; } diff --git a/tools/node.js b/tools/node.js index f1fb28ba17..d75fe013ff 100644 --- a/tools/node.js +++ b/tools/node.js @@ -56,6 +56,9 @@ if (+process.env["UGLIFY_BUG_REPORT"]) exports.minify = function(files, options) function describe_ast() { var out = OutputStream({ beautify: true }); + doitem(AST_Node); + return out.get() + "\n"; + function doitem(ctor) { out.print("AST_" + ctor.TYPE); var props = ctor.SELF_PROPS.filter(function(prop) { @@ -86,9 +89,7 @@ function describe_ast() { }); }); } - }; - doitem(AST_Node); - return out + "\n"; + } } function infer_options(options) {