upgrade AST<->ESTree translation (#4870)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 25 Apr 2021 20:23:52 +0000 (04:23 +0800)
committerGitHub <noreply@github.com>
Sun, 25 Apr 2021 20:23:52 +0000 (04:23 +0800)
fixes #968

bin/uglifyjs
lib/compress.js
lib/mozilla-ast.js
lib/parse.js
package.json
test/mozilla-ast.js
test/ufuzz/index.js
tools/node.js

index 682c6a5..325dacd 100755 (executable)
@@ -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);
index 6c47562..28ac7f4 100644 (file)
@@ -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]);
index 3ceaf58..09c5b10 100644 (file)
 "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) {
                 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) {
                 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) {
                 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",
             });
         },
     };
     };
 
     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");
     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,
+            }),
         };
     });
 
         return {
             type: "SwitchCase",
             test: to_moz(M.expression),
-            consequent: M.body.map(to_moz)
+            consequent: M.body.map(to_moz),
         };
     });
 
             block: to_moz_block(M),
             handler: to_moz(M.bcatch),
             guardedHandlers: [],
-            finalizer: to_moz(M.bfinally)
+            finalizer: to_moz(M.bfinally),
         };
     });
 
             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,
+            },
         };
     });
 
     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),
         };
     });
 
         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;
         };
     });
 
-    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: "-",
                 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 + "";
         });
     }
 
+    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" +
             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);
             }
         });
 
         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) {
         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;
     function to_moz_block(node) {
         return {
             type: "BlockStatement",
-            body: node.body.map(to_moz)
+            body: node.body.map(to_moz),
         };
     }
 
         }
         return {
             type: type,
-            body: body
+            body: body,
         };
     }
 })();
index cdd607a..415a20e 100644 (file)
@@ -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);
index e9ad500..0ce8167 100644 (file)
@@ -23,7 +23,7 @@
     "LICENSE"
   ],
   "devDependencies": {
-    "acorn": "~7.1.0",
+    "acorn": "~8.2.1",
     "semver": "~6.3.0"
   },
   "scripts": {
index 5f5ad54..0f6db3a 100644 (file)
@@ -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);
     });
 }
index b3650fa..8ccaefe 100644 (file)
@@ -2045,6 +2045,7 @@ function createVarName(maybe, dontStore) {
 if (require.main !== module) {
     exports.createTopLevelCode = createTopLevelCode;
     exports.num_iterations = num_iterations;
+    exports.verbose = verbose;
     return;
 }
 
index f1fb28b..d75fe01 100644 (file)
@@ -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) {