Index: frontend/node_modules/terser/lib/ast.js
===================================================================
--- frontend/node_modules/terser/lib/ast.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/ast.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3512 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    HOP,
+    MAP,
+    noop
+} from "./utils/index.js";
+import { parse } from "./parse.js";
+
+function DEFNODE(type, props, ctor, methods, base = AST_Node) {
+    if (!props) props = [];
+    else props = props.split(/\s+/);
+    var self_props = props;
+    if (base && base.PROPS)
+        props = props.concat(base.PROPS);
+    const proto = base && Object.create(base.prototype);
+    if (proto) {
+        ctor.prototype = proto;
+        ctor.BASE = base;
+    }
+    if (base) base.SUBCLASSES.push(ctor);
+    ctor.prototype.CTOR = ctor;
+    ctor.prototype.constructor = ctor;
+    ctor.PROPS = props || null;
+    ctor.SELF_PROPS = self_props;
+    ctor.SUBCLASSES = [];
+    if (type) {
+        ctor.prototype.TYPE = ctor.TYPE = type;
+    }
+    if (methods) for (let i in methods) if (HOP(methods, i)) {
+        if (i[0] === "$") {
+            ctor[i.substr(1)] = methods[i];
+        } else {
+            ctor.prototype[i] = methods[i];
+        }
+    }
+    ctor.DEFMETHOD = function(name, method) {
+        this.prototype[name] = method;
+    };
+    return ctor;
+}
+
+const has_tok_flag = (tok, flag) => Boolean(tok.flags & flag);
+const set_tok_flag = (tok, flag, truth) => {
+    if (truth) {
+        tok.flags |= flag;
+    } else {
+        tok.flags &= ~flag;
+    }
+};
+
+const TOK_FLAG_NLB          = 0b0001;
+const TOK_FLAG_QUOTE_SINGLE = 0b0010;
+const TOK_FLAG_QUOTE_EXISTS = 0b0100;
+const TOK_FLAG_TEMPLATE_END = 0b1000;
+
+class AST_Token {
+    constructor(type, value, line, col, pos, nlb, comments_before, comments_after, file) {
+        this.flags = (nlb ? 1 : 0);
+
+        this.type = type;
+        this.value = value;
+        this.line = line;
+        this.col = col;
+        this.pos = pos;
+        this.comments_before = comments_before;
+        this.comments_after = comments_after;
+        this.file = file;
+
+        Object.seal(this);
+    }
+
+    // Return a string summary of the token for node.js console.log
+    [Symbol.for("nodejs.util.inspect.custom")](_depth, options) {
+        const special = str => options.stylize(str, "special");
+        const quote = typeof this.value === "string" && this.value.includes("`") ? "'" : "`";
+        const value = `${quote}${this.value}${quote}`;
+        return `${special("[AST_Token")} ${value} at ${this.line}:${this.col}${special("]")}`;
+    }
+
+    get nlb() {
+        return has_tok_flag(this, TOK_FLAG_NLB);
+    }
+
+    set nlb(new_nlb) {
+        set_tok_flag(this, TOK_FLAG_NLB, new_nlb);
+    }
+
+    get quote() {
+        return !has_tok_flag(this, TOK_FLAG_QUOTE_EXISTS)
+            ? ""
+            : (has_tok_flag(this, TOK_FLAG_QUOTE_SINGLE) ? "'" : '"');
+    }
+
+    set quote(quote_type) {
+        set_tok_flag(this, TOK_FLAG_QUOTE_SINGLE, quote_type === "'");
+        set_tok_flag(this, TOK_FLAG_QUOTE_EXISTS, !!quote_type);
+    }
+
+    get template_end() {
+        return has_tok_flag(this, TOK_FLAG_TEMPLATE_END);
+    }
+
+    set template_end(new_template_end) {
+        set_tok_flag(this, TOK_FLAG_TEMPLATE_END, new_template_end);
+    }
+}
+
+var AST_Node = DEFNODE("Node", "start end", function AST_Node(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    _clone: function(deep) {
+        if (deep) {
+            var self = this.clone();
+            return self.transform(new TreeTransformer(function(node) {
+                if (node !== self) {
+                    return node.clone(true);
+                }
+            }));
+        }
+        return new this.CTOR(this);
+    },
+    clone: function(deep) {
+        return this._clone(deep);
+    },
+    $documentation: "Base class of all AST nodes",
+    $propdoc: {
+        start: "[AST_Token] The first token of this node",
+        end: "[AST_Token] The last token of this node"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this);
+    },
+    walk: function(visitor) {
+        return this._walk(visitor); // not sure the indirection will be any help
+    },
+    _children_backwards: () => {}
+}, null);
+
+/* -----[ statements ]----- */
+
+var AST_Statement = DEFNODE("Statement", null, function AST_Statement(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class of all statements",
+});
+
+var AST_Debugger = DEFNODE("Debugger", null, function AST_Debugger(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Represents a debugger statement",
+}, AST_Statement);
+
+var AST_Directive = DEFNODE("Directive", "value quote", function AST_Directive(props) {
+    if (props) {
+        this.value = props.value;
+        this.quote = props.quote;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Represents a directive, like \"use strict\";",
+    $propdoc: {
+        value: "[string] The value of this directive as a plain string (it's not an AST_String!)",
+        quote: "[string] the original quote character"
+    },
+}, AST_Statement);
+
+var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", function AST_SimpleStatement(props) {
+    if (props) {
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A statement consisting of an expression, i.e. a = 1 + 2",
+    $propdoc: {
+        body: "[AST_Node] an expression node (should not be instanceof AST_Statement)"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.body._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.body);
+    }
+}, AST_Statement);
+
+function walk_body(node, visitor) {
+    const body = node.body;
+    for (var i = 0, len = body.length; i < len; i++) {
+        body[i]._walk(visitor);
+    }
+}
+
+function clone_block_scope(deep) {
+    var clone = this._clone(deep);
+    if (this.block_scope) {
+        clone.block_scope = this.block_scope.clone();
+    }
+    return clone;
+}
+
+var AST_Block = DEFNODE("Block", "body block_scope", function AST_Block(props) {
+    if (props) {
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A body of statements (usually braced)",
+    $propdoc: {
+        body: "[AST_Statement*] an array of statements",
+        block_scope: "[AST_Scope] the block scope"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            walk_body(this, visitor);
+        });
+    },
+    _children_backwards(push) {
+        let i = this.body.length;
+        while (i--) push(this.body[i]);
+    },
+    clone: clone_block_scope
+}, AST_Statement);
+
+var AST_BlockStatement = DEFNODE("BlockStatement", null, function AST_BlockStatement(props) {
+    if (props) {
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A block statement",
+}, AST_Block);
+
+var AST_EmptyStatement = DEFNODE("EmptyStatement", null, function AST_EmptyStatement(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The empty statement (empty block or simply a semicolon)"
+}, AST_Statement);
+
+var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", function AST_StatementWithBody(props) {
+    if (props) {
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`",
+    $propdoc: {
+        body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"
+    }
+}, AST_Statement);
+
+var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", function AST_LabeledStatement(props) {
+    if (props) {
+        this.label = props.label;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Statement with a label",
+    $propdoc: {
+        label: "[AST_Label] a label definition"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.label._walk(visitor);
+            this.body._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.body);
+        push(this.label);
+    },
+    clone: function(deep) {
+        var node = this._clone(deep);
+        if (deep) {
+            var label = node.label;
+            var def = this.label;
+            node.walk(new TreeWalker(function(node) {
+                if (node instanceof AST_LoopControl
+                    && node.label && node.label.thedef === def) {
+                    node.label.thedef = label;
+                    label.references.push(node);
+                }
+            }));
+        }
+        return node;
+    }
+}, AST_StatementWithBody);
+
+var AST_IterationStatement = DEFNODE(
+    "IterationStatement",
+    "block_scope",
+    function AST_IterationStatement(props) {
+        if (props) {
+            this.block_scope = props.block_scope;
+            this.body = props.body;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "Internal class.  All loops inherit from it.",
+        $propdoc: {
+            block_scope: "[AST_Scope] the block scope for this iteration statement."
+        },
+        clone: clone_block_scope
+    },
+    AST_StatementWithBody
+);
+
+var AST_DWLoop = DEFNODE("DWLoop", "condition", function AST_DWLoop(props) {
+    if (props) {
+        this.condition = props.condition;
+        this.block_scope = props.block_scope;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for do/while statements",
+    $propdoc: {
+        condition: "[AST_Node] the loop condition.  Should not be instanceof AST_Statement"
+    }
+}, AST_IterationStatement);
+
+var AST_Do = DEFNODE("Do", null, function AST_Do(props) {
+    if (props) {
+        this.condition = props.condition;
+        this.block_scope = props.block_scope;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `do` statement",
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.body._walk(visitor);
+            this.condition._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.condition);
+        push(this.body);
+    }
+}, AST_DWLoop);
+
+var AST_While = DEFNODE("While", null, function AST_While(props) {
+    if (props) {
+        this.condition = props.condition;
+        this.block_scope = props.block_scope;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `while` statement",
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.condition._walk(visitor);
+            this.body._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.body);
+        push(this.condition);
+    },
+}, AST_DWLoop);
+
+var AST_For = DEFNODE("For", "init condition step", function AST_For(props) {
+    if (props) {
+        this.init = props.init;
+        this.condition = props.condition;
+        this.step = props.step;
+        this.block_scope = props.block_scope;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `for` statement",
+    $propdoc: {
+        init: "[AST_Node?] the `for` initialization code, or null if empty",
+        condition: "[AST_Node?] the `for` termination clause, or null if empty",
+        step: "[AST_Node?] the `for` update clause, or null if empty"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            if (this.init) this.init._walk(visitor);
+            if (this.condition) this.condition._walk(visitor);
+            if (this.step) this.step._walk(visitor);
+            this.body._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.body);
+        if (this.step) push(this.step);
+        if (this.condition) push(this.condition);
+        if (this.init) push(this.init);
+    },
+}, AST_IterationStatement);
+
+var AST_ForIn = DEFNODE("ForIn", "init object", function AST_ForIn(props) {
+    if (props) {
+        this.init = props.init;
+        this.object = props.object;
+        this.block_scope = props.block_scope;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `for ... in` statement",
+    $propdoc: {
+        init: "[AST_Node] the `for/in` initialization code",
+        object: "[AST_Node] the object that we're looping through"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.init._walk(visitor);
+            this.object._walk(visitor);
+            this.body._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.body);
+        if (this.object) push(this.object);
+        if (this.init) push(this.init);
+    },
+}, AST_IterationStatement);
+
+var AST_ForOf = DEFNODE("ForOf", "await", function AST_ForOf(props) {
+    if (props) {
+        this.await = props.await;
+        this.init = props.init;
+        this.object = props.object;
+        this.block_scope = props.block_scope;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `for ... of` statement",
+}, AST_ForIn);
+
+var AST_With = DEFNODE("With", "expression", function AST_With(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `with` statement",
+    $propdoc: {
+        expression: "[AST_Node] the `with` expression"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+            this.body._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.body);
+        push(this.expression);
+    },
+}, AST_StatementWithBody);
+
+/* -----[ scope and functions ]----- */
+
+var AST_Scope = DEFNODE(
+    "Scope",
+    "variables uses_with uses_eval parent_scope enclosed cname",
+    function AST_Scope(props) {
+        if (props) {
+            this.variables = props.variables;
+            this.uses_with = props.uses_with;
+            this.uses_eval = props.uses_eval;
+            this.parent_scope = props.parent_scope;
+            this.enclosed = props.enclosed;
+            this.cname = props.cname;
+            this.body = props.body;
+            this.block_scope = props.block_scope;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "Base class for all statements introducing a lexical scope",
+        $propdoc: {
+            variables: "[Map/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
+            uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
+            uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
+            parent_scope: "[AST_Scope?/S] link to the parent scope",
+            enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
+            cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
+        },
+        get_defun_scope: function() {
+            var self = this;
+            while (self.is_block_scope()) {
+                self = self.parent_scope;
+            }
+            return self;
+        },
+        clone: function(deep, toplevel) {
+            var node = this._clone(deep);
+            if (deep && this.variables && toplevel && !this._block_scope) {
+                node.figure_out_scope({}, {
+                    toplevel: toplevel,
+                    parent_scope: this.parent_scope
+                });
+            } else {
+                if (this.variables) node.variables = new Map(this.variables);
+                if (this.enclosed) node.enclosed = this.enclosed.slice();
+                if (this._block_scope) node._block_scope = this._block_scope;
+            }
+            return node;
+        },
+        pinned: function() {
+            return this.uses_eval || this.uses_with;
+        }
+    },
+    AST_Block
+);
+
+var AST_Toplevel = DEFNODE("Toplevel", "globals", function AST_Toplevel(props) {
+    if (props) {
+        this.globals = props.globals;
+        this.variables = props.variables;
+        this.uses_with = props.uses_with;
+        this.uses_eval = props.uses_eval;
+        this.parent_scope = props.parent_scope;
+        this.enclosed = props.enclosed;
+        this.cname = props.cname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The toplevel scope",
+    $propdoc: {
+        globals: "[Map/S] a map of name -> SymbolDef for all undeclared names",
+    },
+    wrap_commonjs: function(name) {
+        var body = this.body;
+        var wrapped_tl = "(function(exports){'$ORIG';})(typeof " + name + "=='undefined'?(" + name + "={}):" + name + ");";
+        wrapped_tl = parse(wrapped_tl);
+        wrapped_tl = wrapped_tl.transform(new TreeTransformer(function(node) {
+            if (node instanceof AST_Directive && node.value == "$ORIG") {
+                return MAP.splice(body);
+            }
+        }));
+        return wrapped_tl;
+    },
+    wrap_enclose: function(args_values) {
+        if (typeof args_values != "string") args_values = "";
+        var index = args_values.indexOf(":");
+        if (index < 0) index = args_values.length;
+        var body = this.body;
+        return parse([
+            "(function(",
+            args_values.slice(0, index),
+            '){"$ORIG"})(',
+            args_values.slice(index + 1),
+            ")"
+        ].join("")).transform(new TreeTransformer(function(node) {
+            if (node instanceof AST_Directive && node.value == "$ORIG") {
+                return MAP.splice(body);
+            }
+        }));
+    }
+}, AST_Scope);
+
+var AST_Expansion = DEFNODE("Expansion", "expression", function AST_Expansion(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "An expandible argument, such as ...rest, a splat, such as [1,2,...all], or an expansion in a variable declaration, such as var [first, ...rest] = list",
+    $propdoc: {
+        expression: "[AST_Node] the thing to be expanded"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression.walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.expression);
+    },
+});
+
+var AST_Lambda = DEFNODE(
+    "Lambda",
+    "name argnames uses_arguments is_generator async",
+    function AST_Lambda(props) {
+        if (props) {
+            this.name = props.name;
+            this.argnames = props.argnames;
+            this.uses_arguments = props.uses_arguments;
+            this.is_generator = props.is_generator;
+            this.async = props.async;
+            this.variables = props.variables;
+            this.uses_with = props.uses_with;
+            this.uses_eval = props.uses_eval;
+            this.parent_scope = props.parent_scope;
+            this.enclosed = props.enclosed;
+            this.cname = props.cname;
+            this.body = props.body;
+            this.block_scope = props.block_scope;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "Base class for functions",
+        $propdoc: {
+            name: "[AST_SymbolDeclaration?] the name of this function",
+            argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion|AST_DefaultAssign*] array of function arguments, destructurings, or expanding arguments",
+            uses_arguments: "[boolean/S] tells whether this function accesses the arguments array",
+            is_generator: "[boolean] is this a generator method",
+            async: "[boolean] is this method async",
+        },
+        args_as_names: function () {
+            var out = [];
+            for (var i = 0; i < this.argnames.length; i++) {
+                if (this.argnames[i] instanceof AST_Destructuring) {
+                    out.push(...this.argnames[i].all_symbols());
+                } else {
+                    out.push(this.argnames[i]);
+                }
+            }
+            return out;
+        },
+        _walk: function(visitor) {
+            return visitor._visit(this, function() {
+                if (this.name) this.name._walk(visitor);
+                var argnames = this.argnames;
+                for (var i = 0, len = argnames.length; i < len; i++) {
+                    argnames[i]._walk(visitor);
+                }
+                walk_body(this, visitor);
+            });
+        },
+        _children_backwards(push) {
+            let i = this.body.length;
+            while (i--) push(this.body[i]);
+
+            i = this.argnames.length;
+            while (i--) push(this.argnames[i]);
+
+            if (this.name) push(this.name);
+        },
+        is_braceless() {
+            return this.body[0] instanceof AST_Return && this.body[0].value;
+        },
+        // Default args and expansion don't count, so .argnames.length doesn't cut it
+        length_property() {
+            let length = 0;
+
+            for (const arg of this.argnames) {
+                if (arg instanceof AST_SymbolFunarg || arg instanceof AST_Destructuring) {
+                    length++;
+                }
+            }
+
+            return length;
+        }
+    },
+    AST_Scope
+);
+
+var AST_Accessor = DEFNODE("Accessor", null, function AST_Accessor(props) {
+    if (props) {
+        this.name = props.name;
+        this.argnames = props.argnames;
+        this.uses_arguments = props.uses_arguments;
+        this.is_generator = props.is_generator;
+        this.async = props.async;
+        this.variables = props.variables;
+        this.uses_with = props.uses_with;
+        this.uses_eval = props.uses_eval;
+        this.parent_scope = props.parent_scope;
+        this.enclosed = props.enclosed;
+        this.cname = props.cname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A setter/getter function.  The `name` property is always null."
+}, AST_Lambda);
+
+var AST_Function = DEFNODE("Function", null, function AST_Function(props) {
+    if (props) {
+        this.name = props.name;
+        this.argnames = props.argnames;
+        this.uses_arguments = props.uses_arguments;
+        this.is_generator = props.is_generator;
+        this.async = props.async;
+        this.variables = props.variables;
+        this.uses_with = props.uses_with;
+        this.uses_eval = props.uses_eval;
+        this.parent_scope = props.parent_scope;
+        this.enclosed = props.enclosed;
+        this.cname = props.cname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A function expression"
+}, AST_Lambda);
+
+var AST_Arrow = DEFNODE("Arrow", null, function AST_Arrow(props) {
+    if (props) {
+        this.name = props.name;
+        this.argnames = props.argnames;
+        this.uses_arguments = props.uses_arguments;
+        this.is_generator = props.is_generator;
+        this.async = props.async;
+        this.variables = props.variables;
+        this.uses_with = props.uses_with;
+        this.uses_eval = props.uses_eval;
+        this.parent_scope = props.parent_scope;
+        this.enclosed = props.enclosed;
+        this.cname = props.cname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "An ES6 Arrow function ((a) => b)"
+}, AST_Lambda);
+
+var AST_Defun = DEFNODE("Defun", null, function AST_Defun(props) {
+    if (props) {
+        this.name = props.name;
+        this.argnames = props.argnames;
+        this.uses_arguments = props.uses_arguments;
+        this.is_generator = props.is_generator;
+        this.async = props.async;
+        this.variables = props.variables;
+        this.uses_with = props.uses_with;
+        this.uses_eval = props.uses_eval;
+        this.parent_scope = props.parent_scope;
+        this.enclosed = props.enclosed;
+        this.cname = props.cname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A function definition"
+}, AST_Lambda);
+
+/* -----[ DESTRUCTURING ]----- */
+var AST_Destructuring = DEFNODE("Destructuring", "names is_array", function AST_Destructuring(props) {
+    if (props) {
+        this.names = props.names;
+        this.is_array = props.is_array;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A destructuring of several names. Used in destructuring assignment and with destructuring function argument names",
+    $propdoc: {
+        "names": "[AST_Node*] Array of properties or elements",
+        "is_array": "[Boolean] Whether the destructuring represents an object or array"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.names.forEach(function(name) {
+                name._walk(visitor);
+            });
+        });
+    },
+    _children_backwards(push) {
+        let i = this.names.length;
+        while (i--) push(this.names[i]);
+    },
+    all_symbols: function() {
+        var out = [];
+        walk(this, node => {
+            if (node instanceof AST_SymbolDeclaration) {
+                out.push(node);
+            }
+            if (node instanceof AST_Lambda) {
+                return true;
+            }
+        });
+        return out;
+    }
+});
+
+var AST_PrefixedTemplateString = DEFNODE(
+    "PrefixedTemplateString",
+    "template_string prefix",
+    function AST_PrefixedTemplateString(props) {
+        if (props) {
+            this.template_string = props.template_string;
+            this.prefix = props.prefix;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "A templatestring with a prefix, such as String.raw`foobarbaz`",
+        $propdoc: {
+            template_string: "[AST_TemplateString] The template string",
+            prefix: "[AST_Node] The prefix, which will get called."
+        },
+        _walk: function(visitor) {
+            return visitor._visit(this, function () {
+                this.prefix._walk(visitor);
+                this.template_string._walk(visitor);
+            });
+        },
+        _children_backwards(push) {
+            push(this.template_string);
+            push(this.prefix);
+        },
+    }
+);
+
+var AST_TemplateString = DEFNODE("TemplateString", "segments", function AST_TemplateString(props) {
+    if (props) {
+        this.segments = props.segments;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A template string literal",
+    $propdoc: {
+        segments: "[AST_Node*] One or more segments, starting with AST_TemplateSegment. AST_Node may follow AST_TemplateSegment, but each AST_Node must be followed by AST_TemplateSegment."
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.segments.forEach(function(seg) {
+                seg._walk(visitor);
+            });
+        });
+    },
+    _children_backwards(push) {
+        let i = this.segments.length;
+        while (i--) push(this.segments[i]);
+    }
+});
+
+var AST_TemplateSegment = DEFNODE("TemplateSegment", "value raw", function AST_TemplateSegment(props) {
+    if (props) {
+        this.value = props.value;
+        this.raw = props.raw;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A segment of a template string literal",
+    $propdoc: {
+        value: "Content of the segment",
+        raw: "Raw source of the segment",
+    }
+});
+
+/* -----[ JUMPS ]----- */
+
+var AST_Jump = DEFNODE("Jump", null, function AST_Jump(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)"
+}, AST_Statement);
+
+/** Base class for “exits” (`return` and `throw`) */
+var AST_Exit = DEFNODE("Exit", "value", function AST_Exit(props) {
+    if (props) {
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for “exits” (`return` and `throw`)",
+    $propdoc: {
+        value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, this.value && function() {
+            this.value._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        if (this.value) push(this.value);
+    },
+}, AST_Jump);
+
+var AST_Return = DEFNODE("Return", null, function AST_Return(props) {
+    if (props) {
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `return` statement"
+}, AST_Exit);
+
+var AST_Throw = DEFNODE("Throw", null, function AST_Throw(props) {
+    if (props) {
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `throw` statement"
+}, AST_Exit);
+
+var AST_LoopControl = DEFNODE("LoopControl", "label", function AST_LoopControl(props) {
+    if (props) {
+        this.label = props.label;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for loop control statements (`break` and `continue`)",
+    $propdoc: {
+        label: "[AST_LabelRef?] the label, or null if none",
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, this.label && function() {
+            this.label._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        if (this.label) push(this.label);
+    },
+}, AST_Jump);
+
+var AST_Break = DEFNODE("Break", null, function AST_Break(props) {
+    if (props) {
+        this.label = props.label;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `break` statement"
+}, AST_LoopControl);
+
+var AST_Continue = DEFNODE("Continue", null, function AST_Continue(props) {
+    if (props) {
+        this.label = props.label;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `continue` statement"
+}, AST_LoopControl);
+
+var AST_Await = DEFNODE("Await", "expression", function AST_Await(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "An `await` statement",
+    $propdoc: {
+        expression: "[AST_Node] the mandatory expression being awaited",
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.expression);
+    },
+});
+
+var AST_Yield = DEFNODE("Yield", "expression is_star", function AST_Yield(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.is_star = props.is_star;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `yield` statement",
+    $propdoc: {
+        expression: "[AST_Node?] the value returned or thrown by this statement; could be null (representing undefined) but only when is_star is set to false",
+        is_star: "[Boolean] Whether this is a yield or yield* statement"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, this.expression && function() {
+            this.expression._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        if (this.expression) push(this.expression);
+    }
+});
+
+/* -----[ IF ]----- */
+
+var AST_If = DEFNODE("If", "condition alternative", function AST_If(props) {
+    if (props) {
+        this.condition = props.condition;
+        this.alternative = props.alternative;
+        this.body = props.body;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `if` statement",
+    $propdoc: {
+        condition: "[AST_Node] the `if` condition",
+        alternative: "[AST_Statement?] the `else` part, or null if not present"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.condition._walk(visitor);
+            this.body._walk(visitor);
+            if (this.alternative) this.alternative._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        if (this.alternative) {
+            push(this.alternative);
+        }
+        push(this.body);
+        push(this.condition);
+    }
+}, AST_StatementWithBody);
+
+/* -----[ SWITCH ]----- */
+
+var AST_Switch = DEFNODE("Switch", "expression", function AST_Switch(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `switch` statement",
+    $propdoc: {
+        expression: "[AST_Node] the `switch` “discriminant”"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+            walk_body(this, visitor);
+        });
+    },
+    _children_backwards(push) {
+        let i = this.body.length;
+        while (i--) push(this.body[i]);
+        push(this.expression);
+    }
+}, AST_Block);
+
+var AST_SwitchBranch = DEFNODE("SwitchBranch", null, function AST_SwitchBranch(props) {
+    if (props) {
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for `switch` branches",
+}, AST_Block);
+
+var AST_Default = DEFNODE("Default", null, function AST_Default(props) {
+    if (props) {
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `default` switch branch",
+}, AST_SwitchBranch);
+
+var AST_Case = DEFNODE("Case", "expression", function AST_Case(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `case` switch branch",
+    $propdoc: {
+        expression: "[AST_Node] the `case` expression"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+            walk_body(this, visitor);
+        });
+    },
+    _children_backwards(push) {
+        let i = this.body.length;
+        while (i--) push(this.body[i]);
+        push(this.expression);
+    },
+}, AST_SwitchBranch);
+
+/* -----[ EXCEPTIONS ]----- */
+
+var AST_Try = DEFNODE("Try", "body bcatch bfinally", function AST_Try(props) {
+    if (props) {
+        this.body = props.body;
+        this.bcatch = props.bcatch;
+        this.bfinally = props.bfinally;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `try` statement",
+    $propdoc: {
+        body: "[AST_TryBlock] the try block",
+        bcatch: "[AST_Catch?] the catch block, or null if not present",
+        bfinally: "[AST_Finally?] the finally block, or null if not present"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.body._walk(visitor);
+            if (this.bcatch) this.bcatch._walk(visitor);
+            if (this.bfinally) this.bfinally._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        if (this.bfinally) push(this.bfinally);
+        if (this.bcatch) push(this.bcatch);
+        push(this.body);
+    },
+}, AST_Statement);
+
+var AST_TryBlock = DEFNODE("TryBlock", null, function AST_TryBlock(props) {
+    if (props) {
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The `try` block of a try statement"
+}, AST_Block);
+
+var AST_Catch = DEFNODE("Catch", "argname", function AST_Catch(props) {
+    if (props) {
+        this.argname = props.argname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `catch` node; only makes sense as part of a `try` statement",
+    $propdoc: {
+        argname: "[AST_SymbolCatch|AST_Destructuring|AST_Expansion|AST_DefaultAssign] symbol for the exception"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            if (this.argname) this.argname._walk(visitor);
+            walk_body(this, visitor);
+        });
+    },
+    _children_backwards(push) {
+        let i = this.body.length;
+        while (i--) push(this.body[i]);
+        if (this.argname) push(this.argname);
+    },
+}, AST_Block);
+
+var AST_Finally = DEFNODE("Finally", null, function AST_Finally(props) {
+    if (props) {
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `finally` node; only makes sense as part of a `try` statement"
+}, AST_Block);
+
+/* -----[ VAR/CONST ]----- */
+
+var AST_DefinitionsLike = DEFNODE("DefinitionsLike", "definitions", function AST_DefinitionsLike(props) {
+    if (props) {
+        this.definitions = props.definitions;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for variable definitions and `using`",
+    $propdoc: {
+        definitions: "[AST_VarDef*|AST_UsingDef*] array of variable definitions"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            var definitions = this.definitions;
+            for (var i = 0, len = definitions.length; i < len; i++) {
+                definitions[i]._walk(visitor);
+            }
+        });
+    },
+    _children_backwards(push) {
+        let i = this.definitions.length;
+        while (i--) push(this.definitions[i]);
+    },
+}, AST_Statement);
+
+var AST_Definitions = DEFNODE("Definitions", null, function AST_Definitions(props) {
+    if (props) {
+        this.definitions = props.definitions;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)",
+}, AST_DefinitionsLike);
+
+var AST_Var = DEFNODE("Var", null, function AST_Var(props) {
+    if (props) {
+        this.definitions = props.definitions;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `var` statement"
+}, AST_Definitions);
+
+var AST_Let = DEFNODE("Let", null, function AST_Let(props) {
+    if (props) {
+        this.definitions = props.definitions;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `let` statement"
+}, AST_Definitions);
+
+var AST_Const = DEFNODE("Const", null, function AST_Const(props) {
+    if (props) {
+        this.definitions = props.definitions;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `const` statement"
+}, AST_Definitions);
+
+var AST_Using = DEFNODE("Using", "await", function AST_Using(props) {
+    if (props) {
+        this.await = props.await;
+        this.definitions = props.definitions;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `using` statement",
+    $propdoc: {
+        await: "[boolean] Whether it's `await using`"
+    },
+}, AST_DefinitionsLike);
+
+var AST_VarDefLike = DEFNODE("VarDefLike", "name value", function AST_VarDefLike(props) {
+    if (props) {
+        this.name = props.name;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A name=value pair in a variable definition statement or `using`",
+    $propdoc: {
+        name: "[AST_Destructuring|AST_SymbolDeclaration] name of the variable",
+        value: "[AST_Node?] initializer, or null of there's no initializer"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.name._walk(visitor);
+            if (this.value) this.value._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        if (this.value) push(this.value);
+        push(this.name);
+    },
+    declarations_as_names() {
+        if (this.name instanceof AST_SymbolDeclaration) {
+            return [this.name];
+        } else {
+            return this.name.all_symbols();
+        }
+    }
+});
+
+var AST_VarDef = DEFNODE("VarDef", null, function AST_VarDef(props) {
+    if (props) {
+        this.name = props.name;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A variable declaration; only appears in a AST_Definitions node",
+}, AST_VarDefLike);
+
+var AST_UsingDef = DEFNODE("UsingDef", null, function AST_UsingDef(props) {
+    if (props) {
+        this.name = props.name;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Like VarDef but specific to AST_Using",
+}, AST_VarDefLike);
+
+var AST_NameMapping = DEFNODE("NameMapping", "foreign_name name", function AST_NameMapping(props) {
+    if (props) {
+        this.foreign_name = props.foreign_name;
+        this.name = props.name;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The part of the export/import statement that declare names from a module.",
+    $propdoc: {
+        foreign_name: "[AST_SymbolExportForeign|AST_SymbolImportForeign] The name being exported/imported (as specified in the module)",
+        name: "[AST_SymbolExport|AST_SymbolImport] The name as it is visible to this module."
+    },
+    _walk: function (visitor) {
+        return visitor._visit(this, function() {
+            this.foreign_name._walk(visitor);
+            this.name._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.name);
+        push(this.foreign_name);
+    },
+});
+
+var AST_Import = DEFNODE(
+    "Import",
+    "phase imported_name imported_names module_name attributes",
+    function AST_Import(props) {
+        if (props) {
+            this.phase = props.phase;
+            this.imported_name = props.imported_name;
+            this.imported_names = props.imported_names;
+            this.module_name = props.module_name;
+            this.attributes = props.attributes;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "An `import` statement",
+        $propdoc: {
+            phase: "[string?] Phase keyword: 'source', 'defer', or null.",
+            imported_name: "[AST_SymbolImport] The name of the variable holding the module's default export.",
+            imported_names: "[AST_NameMapping*] The names of non-default imported variables",
+            module_name: "[AST_String] String literal describing where this module came from",
+            attributes: "[AST_Object?] The import attributes (with {...})"
+        },
+        _walk: function(visitor) {
+            return visitor._visit(this, function() {
+                if (this.imported_name) {
+                    this.imported_name._walk(visitor);
+                }
+                if (this.imported_names) {
+                    this.imported_names.forEach(function(name_import) {
+                        name_import._walk(visitor);
+                    });
+                }
+                this.module_name._walk(visitor);
+            });
+        },
+        _children_backwards(push) {
+            push(this.module_name);
+            if (this.imported_names) {
+                let i = this.imported_names.length;
+                while (i--) push(this.imported_names[i]);
+            }
+            if (this.imported_name) push(this.imported_name);
+        },
+    }
+);
+
+var AST_ImportMeta = DEFNODE("ImportMeta", null, function AST_ImportMeta(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A reference to import.meta",
+});
+
+var AST_DynamicImport = DEFNODE(
+    "DynamicImport",
+    "phase args",
+    function AST_DynamicImport(props) {
+        if (props) {
+            this.phase = props.phase;
+            this.args = props.args;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "A phased dynamic import expression: `import.source(specifier [, options])` or `import.defer(specifier [, options])`. Plain `import(x)` continues to be parsed as an AST_Call with a synthetic `import` SymbolRef callee.",
+        $propdoc: {
+            phase: "[string] Phase keyword ('source' or 'defer').",
+            args: "[AST_Node*] specifier followed by optional options argument"
+        },
+        _walk: function(visitor) {
+            return visitor._visit(this, function() {
+                var args = this.args;
+                for (var i = 0, len = args.length; i < len; i++) {
+                    args[i]._walk(visitor);
+                }
+            });
+        },
+        _children_backwards(push) {
+            let i = this.args.length;
+            while (i--) push(this.args[i]);
+        },
+    }
+);
+
+var AST_Export = DEFNODE(
+    "Export",
+    "exported_definition exported_value is_default exported_names module_name attributes",
+    function AST_Export(props) {
+        if (props) {
+            this.exported_definition = props.exported_definition;
+            this.exported_value = props.exported_value;
+            this.is_default = props.is_default;
+            this.exported_names = props.exported_names;
+            this.module_name = props.module_name;
+            this.attributes = props.attributes;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "An `export` statement",
+        $propdoc: {
+            exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition",
+            exported_value: "[AST_Node?] An exported value",
+            exported_names: "[AST_NameMapping*?] List of exported names",
+            module_name: "[AST_String?] Name of the file to load exports from",
+            is_default: "[Boolean] Whether this is the default exported value of this module",
+            attributes: "[AST_Object?] The import attributes"
+        },
+        _walk: function (visitor) {
+            return visitor._visit(this, function () {
+                if (this.exported_definition) {
+                    this.exported_definition._walk(visitor);
+                }
+                if (this.exported_value) {
+                    this.exported_value._walk(visitor);
+                }
+                if (this.exported_names) {
+                    this.exported_names.forEach(function(name_export) {
+                        name_export._walk(visitor);
+                    });
+                }
+                if (this.module_name) {
+                    this.module_name._walk(visitor);
+                }
+            });
+        },
+        _children_backwards(push) {
+            if (this.module_name) push(this.module_name);
+            if (this.exported_names) {
+                let i = this.exported_names.length;
+                while (i--) push(this.exported_names[i]);
+            }
+            if (this.exported_value) push(this.exported_value);
+            if (this.exported_definition) push(this.exported_definition);
+        }
+    },
+    AST_Statement
+);
+
+/* -----[ OTHER ]----- */
+
+var AST_Call = DEFNODE(
+    "Call",
+    "expression args optional _annotations",
+    function AST_Call(props) {
+        if (props) {
+            this.expression = props.expression;
+            this.args = props.args;
+            this.optional = props.optional;
+            this._annotations = props._annotations;
+            this.start = props.start;
+            this.end = props.end;
+            this.initialize();
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "A function call expression",
+        $propdoc: {
+            expression: "[AST_Node] expression to invoke as function",
+            args: "[AST_Node*] array of arguments",
+            optional: "[boolean] whether this is an optional call (IE ?.() )",
+            _annotations: "[number] bitfield containing information about the call"
+        },
+        initialize() {
+            if (this._annotations == null) this._annotations = 0;
+        },
+        _walk(visitor) {
+            return visitor._visit(this, function() {
+                var args = this.args;
+                for (var i = 0, len = args.length; i < len; i++) {
+                    args[i]._walk(visitor);
+                }
+                this.expression._walk(visitor);  // TODO why do we need to crawl this last?
+            });
+        },
+        _children_backwards(push) {
+            let i = this.args.length;
+            while (i--) push(this.args[i]);
+            push(this.expression);
+        },
+    }
+);
+
+var AST_New = DEFNODE("New", null, function AST_New(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.args = props.args;
+        this.optional = props.optional;
+        this._annotations = props._annotations;
+        this.start = props.start;
+        this.end = props.end;
+        this.initialize();
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "An object instantiation.  Derives from a function call since it has exactly the same properties"
+}, AST_Call);
+
+var AST_Sequence = DEFNODE("Sequence", "expressions", function AST_Sequence(props) {
+    if (props) {
+        this.expressions = props.expressions;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A sequence expression (comma-separated expressions)",
+    $propdoc: {
+        expressions: "[AST_Node*] array of expressions (at least two)"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expressions.forEach(function(node) {
+                node._walk(visitor);
+            });
+        });
+    },
+    _children_backwards(push) {
+        let i = this.expressions.length;
+        while (i--) push(this.expressions[i]);
+    },
+});
+
+var AST_PropAccess = DEFNODE(
+    "PropAccess",
+    "expression property optional",
+    function AST_PropAccess(props) {
+        if (props) {
+            this.expression = props.expression;
+            this.property = props.property;
+            this.optional = props.optional;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`",
+        $propdoc: {
+            expression: "[AST_Node] the “container” expression",
+            property: "[AST_Node|string] the property to access.  For AST_Dot & AST_DotHash this is always a plain string, while for AST_Sub it's an arbitrary AST_Node",
+
+            optional: "[boolean] whether this is an optional property access (IE ?.)"
+        }
+    }
+);
+
+var AST_Dot = DEFNODE("Dot", "quote", function AST_Dot(props) {
+    if (props) {
+        this.quote = props.quote;
+        this.expression = props.expression;
+        this.property = props.property;
+        this.optional = props.optional;
+        this._annotations = props._annotations;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A dotted property access expression",
+    $propdoc: {
+        quote: "[string] the original quote character when transformed from AST_Sub",
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.expression);
+    },
+}, AST_PropAccess);
+
+var AST_DotHash = DEFNODE("DotHash", "", function AST_DotHash(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.property = props.property;
+        this.optional = props.optional;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A dotted property access to a private property",
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.expression);
+    },
+}, AST_PropAccess);
+
+var AST_Sub = DEFNODE("Sub", null, function AST_Sub(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.property = props.property;
+        this.optional = props.optional;
+        this._annotations = props._annotations;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Index-style property access, i.e. `a[\"foo\"]`",
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+            this.property._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.property);
+        push(this.expression);
+    },
+}, AST_PropAccess);
+
+var AST_Chain = DEFNODE("Chain", "expression", function AST_Chain(props) {
+    if (props) {
+        this.expression = props.expression;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A chain expression like a?.b?.(c)?.[d]",
+    $propdoc: {
+        expression: "[AST_Call|AST_Dot|AST_DotHash|AST_Sub] chain element."
+    },
+    _walk: function (visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.expression);
+    },
+});
+
+var AST_Unary = DEFNODE("Unary", "operator expression", function AST_Unary(props) {
+    if (props) {
+        this.operator = props.operator;
+        this.expression = props.expression;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for unary expressions",
+    $propdoc: {
+        operator: "[string] the operator",
+        expression: "[AST_Node] expression that this unary operator applies to"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.expression._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.expression);
+    },
+});
+
+var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, function AST_UnaryPrefix(props) {
+    if (props) {
+        this.operator = props.operator;
+        this.expression = props.expression;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Unary prefix expression, i.e. `typeof i` or `++i`"
+}, AST_Unary);
+
+var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, function AST_UnaryPostfix(props) {
+    if (props) {
+        this.operator = props.operator;
+        this.expression = props.expression;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Unary postfix expression, i.e. `i++`"
+}, AST_Unary);
+
+var AST_Binary = DEFNODE("Binary", "operator left right", function AST_Binary(props) {
+    if (props) {
+        this.operator = props.operator;
+        this.left = props.left;
+        this.right = props.right;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Binary expression, i.e. `a + b`",
+    $propdoc: {
+        left: "[AST_Node] left-hand side expression",
+        operator: "[string] the operator",
+        right: "[AST_Node] right-hand side expression"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.left._walk(visitor);
+            this.right._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.right);
+        push(this.left);
+    },
+});
+
+var AST_Conditional = DEFNODE(
+    "Conditional",
+    "condition consequent alternative",
+    function AST_Conditional(props) {
+        if (props) {
+            this.condition = props.condition;
+            this.consequent = props.consequent;
+            this.alternative = props.alternative;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`",
+        $propdoc: {
+            condition: "[AST_Node]",
+            consequent: "[AST_Node]",
+            alternative: "[AST_Node]"
+        },
+        _walk: function(visitor) {
+            return visitor._visit(this, function() {
+                this.condition._walk(visitor);
+                this.consequent._walk(visitor);
+                this.alternative._walk(visitor);
+            });
+        },
+        _children_backwards(push) {
+            push(this.alternative);
+            push(this.consequent);
+            push(this.condition);
+        },
+    }
+);
+
+var AST_Assign = DEFNODE("Assign", "logical", function AST_Assign(props) {
+    if (props) {
+        this.logical = props.logical;
+        this.operator = props.operator;
+        this.left = props.left;
+        this.right = props.right;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "An assignment expression — `a = b + 5`",
+    $propdoc: {
+        logical: "Whether it's a logical assignment"
+    }
+}, AST_Binary);
+
+var AST_DefaultAssign = DEFNODE("DefaultAssign", null, function AST_DefaultAssign(props) {
+    if (props) {
+        this.operator = props.operator;
+        this.left = props.left;
+        this.right = props.right;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A default assignment expression like in `(a = 3) => a`"
+}, AST_Binary);
+
+/* -----[ LITERALS ]----- */
+
+var AST_Array = DEFNODE("Array", "elements", function AST_Array(props) {
+    if (props) {
+        this.elements = props.elements;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "An array literal",
+    $propdoc: {
+        elements: "[AST_Node*] array of elements"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            var elements = this.elements;
+            for (var i = 0, len = elements.length; i < len; i++) {
+                elements[i]._walk(visitor);
+            }
+        });
+    },
+    _children_backwards(push) {
+        let i = this.elements.length;
+        while (i--) push(this.elements[i]);
+    },
+});
+
+var AST_Object = DEFNODE("Object", "properties", function AST_Object(props) {
+    if (props) {
+        this.properties = props.properties;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "An object literal",
+    $propdoc: {
+        properties: "[AST_ObjectProperty*] array of properties"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            var properties = this.properties;
+            for (var i = 0, len = properties.length; i < len; i++) {
+                properties[i]._walk(visitor);
+            }
+        });
+    },
+    _children_backwards(push) {
+        let i = this.properties.length;
+        while (i--) push(this.properties[i]);
+    },
+});
+
+/* -----[ OBJECT/CLASS PROPERTIES ]----- */
+
+/**
+ * Everything inside the curly braces of an object/class is a subclass of AST_ObjectProperty, except for AST_ClassStaticBlock.
+ **/
+var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", function AST_ObjectProperty(props) {
+    if (props) {
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+        this._annotations = props._annotations;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for literal object properties",
+    $propdoc: {
+        key: "[string|AST_Node] property name. For ObjectKeyVal this is a string. For getters, setters and computed property this is an AST_Node.",
+        value: "[AST_Node] property value.  For getters, setters and methods this is an AST_Accessor."
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            if (this.key instanceof AST_Node)
+                this.key._walk(visitor);
+            this.value._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.value);
+        if (this.key instanceof AST_Node) push(this.key);
+    },
+});
+
+var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", function AST_ObjectKeyVal(props) {
+    if (props) {
+        this.quote = props.quote;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+        this._annotations = props._annotations;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A key: value object property",
+    $propdoc: {
+        quote: "[string] the original quote character"
+    },
+    computed_key() {
+        return this.key instanceof AST_Node;
+    }
+}, AST_ObjectProperty);
+
+var AST_PrivateSetter = DEFNODE("PrivateSetter", "static", function AST_PrivateSetter(props) {
+    if (props) {
+        this.static = props.static;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $propdoc: {
+        static: "[boolean] whether this is a static private setter"
+    },
+    $documentation: "A private setter property",
+    computed_key() {
+        return false;
+    }
+}, AST_ObjectProperty);
+
+var AST_PrivateGetter = DEFNODE("PrivateGetter", "static", function AST_PrivateGetter(props) {
+    if (props) {
+        this.static = props.static;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $propdoc: {
+        static: "[boolean] whether this is a static private getter"
+    },
+    $documentation: "A private getter property",
+    computed_key() {
+        return false;
+    }
+}, AST_ObjectProperty);
+
+var AST_ObjectSetter = DEFNODE("ObjectSetter", "quote static", function AST_ObjectSetter(props) {
+    if (props) {
+        this.quote = props.quote;
+        this.static = props.static;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+        this._annotations = props._annotations;
+    }
+
+    this.flags = 0;
+}, {
+    $propdoc: {
+        quote: "[string|undefined] the original quote character, if any",
+        static: "[boolean] whether this is a static setter (classes only)"
+    },
+    $documentation: "An object setter property",
+    computed_key() {
+        return !(this.key instanceof AST_SymbolMethod);
+    }
+}, AST_ObjectProperty);
+
+var AST_ObjectGetter = DEFNODE("ObjectGetter", "quote static", function AST_ObjectGetter(props) {
+    if (props) {
+        this.quote = props.quote;
+        this.static = props.static;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+        this._annotations = props._annotations;
+    }
+
+    this.flags = 0;
+}, {
+    $propdoc: {
+        quote: "[string|undefined] the original quote character, if any",
+        static: "[boolean] whether this is a static getter (classes only)"
+    },
+    $documentation: "An object getter property",
+    computed_key() {
+        return !(this.key instanceof AST_SymbolMethod);
+    }
+}, AST_ObjectProperty);
+
+var AST_ConciseMethod = DEFNODE("ConciseMethod", "quote static", function AST_ConciseMethod(props) {
+    if (props) {
+        this.quote = props.quote;
+        this.static = props.static;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+        this._annotations = props._annotations;
+    }
+
+    this.flags = 0;
+}, {
+    $propdoc: {
+        quote: "[string|undefined] the original quote character, if any",
+        static: "[boolean] is this method static (classes only)",
+    },
+    $documentation: "An ES6 concise method inside an object or class",
+    computed_key() {
+        return !(this.key instanceof AST_SymbolMethod);
+    }
+}, AST_ObjectProperty);
+
+var AST_PrivateMethod = DEFNODE("PrivateMethod", "static", function AST_PrivateMethod(props) {
+    if (props) {
+        this.static = props.static;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A private class method inside a class",
+    $propdoc: {
+        static: "[boolean] is this a static private method",
+    },
+    computed_key() {
+        return false;
+    },
+}, AST_ObjectProperty);
+
+var AST_Class = DEFNODE("Class", "name extends properties", function AST_Class(props) {
+    if (props) {
+        this.name = props.name;
+        this.extends = props.extends;
+        this.properties = props.properties;
+        this.variables = props.variables;
+        this.uses_with = props.uses_with;
+        this.uses_eval = props.uses_eval;
+        this.parent_scope = props.parent_scope;
+        this.enclosed = props.enclosed;
+        this.cname = props.cname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $propdoc: {
+        name: "[AST_SymbolClass|AST_SymbolDefClass?] optional class name.",
+        extends: "[AST_Node]? optional parent class",
+        properties: "[AST_ObjectProperty|AST_ClassStaticBlock]* array of properties or static blocks"
+    },
+    $documentation: "An ES6 class",
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            if (this.name) {
+                this.name._walk(visitor);
+            }
+            if (this.extends) {
+                this.extends._walk(visitor);
+            }
+            this.properties.forEach((prop) => prop._walk(visitor));
+        });
+    },
+    _children_backwards(push) {
+        let i = this.properties.length;
+        while (i--) push(this.properties[i]);
+        if (this.extends) push(this.extends);
+        if (this.name) push(this.name);
+    },
+    /** go through the bits that are executed instantly, not when the class is `new`'d. Doesn't walk the name. */
+    visit_nondeferred_class_parts(visitor) {
+        if (this.extends) {
+            this.extends._walk(visitor);
+        }
+        this.properties.forEach((prop) => {
+            if (prop instanceof AST_ClassStaticBlock) {
+                prop._walk(visitor);
+                return;
+            }
+            if (prop.computed_key()) {
+                visitor.push(prop);
+                prop.key._walk(visitor);
+                visitor.pop();
+            }
+            if (
+                prop instanceof AST_ClassPrivateProperty && prop.static && prop.value
+                || prop instanceof AST_ClassProperty && prop.static && prop.value
+            ) {
+                visitor.push(prop);
+                prop.value._walk(visitor);
+                visitor.pop();
+            }
+        });
+    },
+    /** go through the bits that are executed later, when the class is `new`'d or a static method is called */
+    visit_deferred_class_parts(visitor) {
+        this.properties.forEach((prop) => {
+            if (
+                prop instanceof AST_ConciseMethod
+                || prop instanceof AST_PrivateMethod
+            ) {
+                prop.walk(visitor);
+            } else if (
+                prop instanceof AST_ClassProperty && !prop.static && prop.value
+                || prop instanceof AST_ClassPrivateProperty && !prop.static && prop.value
+            ) {
+                visitor.push(prop);
+                prop.value._walk(visitor);
+                visitor.pop();
+            }
+        });
+    },
+    is_self_referential: function() {
+        const this_id = this.name && this.name.definition().id;
+        let found = false;
+        let class_this = true;
+        this.visit_nondeferred_class_parts(new TreeWalker((node, descend) => {
+            if (found) return true;
+            if (node instanceof AST_This) return (found = class_this);
+            if (node instanceof AST_SymbolRef) return (found = node.definition().id === this_id);
+            if (node instanceof AST_Lambda && !(node instanceof AST_Arrow)) {
+                const class_this_save = class_this;
+                class_this = false;
+                descend();
+                class_this = class_this_save;
+                return true;
+            }
+        }));
+        return found;
+    },
+}, AST_Scope /* TODO a class might have a scope but it's not a scope */);
+
+var AST_ClassProperty = DEFNODE("ClassProperty", "static quote", function AST_ClassProperty(props) {
+    if (props) {
+        this.static = props.static;
+        this.quote = props.quote;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+        this._annotations = props._annotations;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A class property",
+    $propdoc: {
+        static: "[boolean] whether this is a static key",
+        quote: "[string] which quote is being used"
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            if (this.key instanceof AST_Node)
+                this.key._walk(visitor);
+            if (this.value instanceof AST_Node)
+                this.value._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        if (this.value instanceof AST_Node) push(this.value);
+        if (this.key instanceof AST_Node) push(this.key);
+    },
+    computed_key() {
+        return !(this.key instanceof AST_SymbolClassProperty);
+    }
+}, AST_ObjectProperty);
+
+var AST_ClassPrivateProperty = DEFNODE("ClassPrivateProperty", "", function AST_ClassPrivateProperty(props) {
+    if (props) {
+        this.static = props.static;
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A class property for a private property",
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            if (this.value instanceof AST_Node)
+                this.value._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        if (this.value instanceof AST_Node) push(this.value);
+    },
+    computed_key() {
+        return false;
+    },
+}, AST_ObjectProperty);
+
+var AST_PrivateIn = DEFNODE("PrivateIn", "key value", function AST_PrivateIn(props) {
+    if (props) {
+        this.key = props.key;
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "An `in` binop when the key is private, eg #x in this",
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            this.key._walk(visitor);
+            this.value._walk(visitor);
+        });
+    },
+    _children_backwards(push) {
+        push(this.value);
+        push(this.key);
+    },
+});
+
+var AST_DefClass = DEFNODE("DefClass", null, function AST_DefClass(props) {
+    if (props) {
+        this.name = props.name;
+        this.extends = props.extends;
+        this.properties = props.properties;
+        this.variables = props.variables;
+        this.uses_with = props.uses_with;
+        this.uses_eval = props.uses_eval;
+        this.parent_scope = props.parent_scope;
+        this.enclosed = props.enclosed;
+        this.cname = props.cname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A class definition",
+}, AST_Class);
+
+var AST_ClassStaticBlock = DEFNODE("ClassStaticBlock", "body block_scope", function AST_ClassStaticBlock (props) {
+    this.body = props.body;
+    this.block_scope = props.block_scope;
+    this.start = props.start;
+    this.end = props.end;
+}, {
+    $documentation: "A block containing statements to be executed in the context of the class",
+    $propdoc: {
+        body: "[AST_Statement*] an array of statements",
+    },
+    _walk: function(visitor) {
+        return visitor._visit(this, function() {
+            walk_body(this, visitor);
+        });
+    },
+    _children_backwards(push) {
+        let i = this.body.length;
+        while (i--) push(this.body[i]);
+    },
+    clone: clone_block_scope,
+    computed_key() {
+        return false;
+    },
+}, AST_Scope);
+
+var AST_ClassExpression = DEFNODE("ClassExpression", null, function AST_ClassExpression(props) {
+    if (props) {
+        this.name = props.name;
+        this.extends = props.extends;
+        this.properties = props.properties;
+        this.variables = props.variables;
+        this.uses_with = props.uses_with;
+        this.uses_eval = props.uses_eval;
+        this.parent_scope = props.parent_scope;
+        this.enclosed = props.enclosed;
+        this.cname = props.cname;
+        this.body = props.body;
+        this.block_scope = props.block_scope;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A class expression."
+}, AST_Class);
+
+var AST_Symbol = DEFNODE("Symbol", "scope name thedef", function AST_Symbol(props) {
+    if (props) {
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $propdoc: {
+        name: "[string] name of this symbol",
+        scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
+        thedef: "[SymbolDef/S] the definition of this symbol"
+    },
+    $documentation: "Base class for all symbols"
+});
+
+var AST_NewTarget = DEFNODE("NewTarget", null, function AST_NewTarget(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A reference to new.target"
+});
+
+var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", function AST_SymbolDeclaration(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)",
+}, AST_Symbol);
+
+var AST_SymbolVar = DEFNODE("SymbolVar", null, function AST_SymbolVar(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol defining a variable",
+}, AST_SymbolDeclaration);
+
+var AST_SymbolBlockDeclaration = DEFNODE(
+    "SymbolBlockDeclaration",
+    null,
+    function AST_SymbolBlockDeclaration(props) {
+        if (props) {
+            this.init = props.init;
+            this.scope = props.scope;
+            this.name = props.name;
+            this.thedef = props.thedef;
+            this.start = props.start;
+            this.end = props.end;
+        }
+
+        this.flags = 0;
+    },
+    {
+        $documentation: "Base class for block-scoped declaration symbols"
+    },
+    AST_SymbolDeclaration
+);
+
+var AST_SymbolConst = DEFNODE("SymbolConst", null, function AST_SymbolConst(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A constant declaration"
+}, AST_SymbolBlockDeclaration);
+
+var AST_SymbolUsing = DEFNODE("SymbolUsing", null, function AST_SymbolUsing(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A `using` declaration"
+}, AST_SymbolBlockDeclaration);
+
+var AST_SymbolLet = DEFNODE("SymbolLet", null, function AST_SymbolLet(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A block-scoped `let` declaration"
+}, AST_SymbolBlockDeclaration);
+
+var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, function AST_SymbolFunarg(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol naming a function argument",
+}, AST_SymbolVar);
+
+var AST_SymbolDefun = DEFNODE("SymbolDefun", null, function AST_SymbolDefun(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol defining a function",
+}, AST_SymbolDeclaration);
+
+var AST_SymbolMethod = DEFNODE("SymbolMethod", null, function AST_SymbolMethod(props) {
+    if (props) {
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol in an object defining a method",
+}, AST_Symbol);
+
+var AST_SymbolClassProperty = DEFNODE("SymbolClassProperty", null, function AST_SymbolClassProperty(props) {
+    if (props) {
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol for a class property",
+}, AST_Symbol);
+
+var AST_SymbolLambda = DEFNODE("SymbolLambda", null, function AST_SymbolLambda(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol naming a function expression",
+}, AST_SymbolDeclaration);
+
+var AST_SymbolDefClass = DEFNODE("SymbolDefClass", null, function AST_SymbolDefClass(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol naming a class's name in a class declaration. Lexically scoped to its containing scope, and accessible within the class."
+}, AST_SymbolBlockDeclaration);
+
+var AST_SymbolClass = DEFNODE("SymbolClass", null, function AST_SymbolClass(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol naming a class's name. Lexically scoped to the class."
+}, AST_SymbolDeclaration);
+
+var AST_SymbolCatch = DEFNODE("SymbolCatch", null, function AST_SymbolCatch(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol naming the exception in catch",
+}, AST_SymbolBlockDeclaration);
+
+var AST_SymbolImport = DEFNODE("SymbolImport", null, function AST_SymbolImport(props) {
+    if (props) {
+        this.init = props.init;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol referring to an imported name",
+}, AST_SymbolBlockDeclaration);
+
+var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", "quote", function AST_SymbolImportForeign(props) {
+    if (props) {
+        this.quote = props.quote;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A symbol imported from a module, but it is defined in the other module, and its real name is irrelevant for this module's purposes",
+}, AST_Symbol);
+
+var AST_Label = DEFNODE("Label", "references", function AST_Label(props) {
+    if (props) {
+        this.references = props.references;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+        this.initialize();
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol naming a label (declaration)",
+    $propdoc: {
+        references: "[AST_LoopControl*] a list of nodes referring to this label"
+    },
+    initialize: function() {
+        this.references = [];
+        this.thedef = this;
+    }
+}, AST_Symbol);
+
+var AST_SymbolRef = DEFNODE("SymbolRef", null, function AST_SymbolRef(props) {
+    if (props) {
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Reference to some symbol (not definition/declaration)",
+}, AST_Symbol);
+
+var AST_SymbolExport = DEFNODE("SymbolExport", "quote", function AST_SymbolExport(props) {
+    if (props) {
+        this.quote = props.quote;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Symbol referring to a name to export",
+}, AST_SymbolRef);
+
+var AST_SymbolExportForeign = DEFNODE("SymbolExportForeign", "quote", function AST_SymbolExportForeign(props) {
+    if (props) {
+        this.quote = props.quote;
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A symbol exported from this module, but it is used in the other module, and its real name is irrelevant for this module's purposes",
+}, AST_Symbol);
+
+var AST_LabelRef = DEFNODE("LabelRef", null, function AST_LabelRef(props) {
+    if (props) {
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Reference to a label symbol",
+}, AST_Symbol);
+
+var AST_SymbolPrivateProperty = DEFNODE("SymbolPrivateProperty", null, function AST_SymbolPrivateProperty(props) {
+    if (props) {
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A symbol that refers to a private property",
+}, AST_Symbol);
+
+var AST_This = DEFNODE("This", null, function AST_This(props) {
+    if (props) {
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The `this` symbol",
+}, AST_Symbol);
+
+var AST_Super = DEFNODE("Super", null, function AST_Super(props) {
+    if (props) {
+        this.scope = props.scope;
+        this.name = props.name;
+        this.thedef = props.thedef;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The `super` symbol",
+}, AST_This);
+
+var AST_Constant = DEFNODE("Constant", null, function AST_Constant(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for all constants",
+    getValue: function() {
+        return this.value;
+    }
+});
+
+var AST_String = DEFNODE("String", "value quote", function AST_String(props) {
+    if (props) {
+        this.value = props.value;
+        this.quote = props.quote;
+        this.start = props.start;
+        this.end = props.end;
+        this._annotations = props._annotations;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A string literal",
+    $propdoc: {
+        value: "[string] the contents of this string",
+        quote: "[string] the original quote character"
+    }
+}, AST_Constant);
+
+var AST_Number = DEFNODE("Number", "value raw", function AST_Number(props) {
+    if (props) {
+        this.value = props.value;
+        this.raw = props.raw;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A number literal",
+    $propdoc: {
+        value: "[number] the numeric value",
+        raw: "[string] numeric value as string"
+    }
+}, AST_Constant);
+
+var AST_BigInt = DEFNODE("BigInt", "value raw", function AST_BigInt(props) {
+    if (props) {
+        this.value = props.value;
+        this.raw = props.raw;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A big int literal",
+    $propdoc: {
+        value: "[string] big int value, represented as a string",
+        raw: "[string] the original format preserved"
+    }
+}, AST_Constant);
+
+var AST_RegExp = DEFNODE("RegExp", "value", function AST_RegExp(props) {
+    if (props) {
+        this.value = props.value;
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A regexp literal",
+    $propdoc: {
+        value: "[RegExp] the actual regexp",
+    }
+}, AST_Constant);
+
+var AST_Atom = DEFNODE("Atom", null, function AST_Atom(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for atoms",
+}, AST_Constant);
+
+var AST_Null = DEFNODE("Null", null, function AST_Null(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The `null` atom",
+    value: null
+}, AST_Atom);
+
+var AST_NaN = DEFNODE("NaN", null, function AST_NaN(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The impossible value",
+    value: 0/0
+}, AST_Atom);
+
+var AST_Undefined = DEFNODE("Undefined", null, function AST_Undefined(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The `undefined` value",
+    value: (function() {}())
+}, AST_Atom);
+
+var AST_Hole = DEFNODE("Hole", null, function AST_Hole(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "A hole in an array",
+    value: (function() {}())
+}, AST_Atom);
+
+var AST_Infinity = DEFNODE("Infinity", null, function AST_Infinity(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The `Infinity` value",
+    value: 1/0
+}, AST_Atom);
+
+var AST_Boolean = DEFNODE("Boolean", null, function AST_Boolean(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "Base class for booleans",
+}, AST_Atom);
+
+var AST_False = DEFNODE("False", null, function AST_False(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The `false` atom",
+    value: false
+}, AST_Boolean);
+
+var AST_True = DEFNODE("True", null, function AST_True(props) {
+    if (props) {
+        this.start = props.start;
+        this.end = props.end;
+    }
+
+    this.flags = 0;
+}, {
+    $documentation: "The `true` atom",
+    value: true
+}, AST_Boolean);
+
+/* -----[ Walk function ]---- */
+
+/**
+ * Walk nodes in depth-first search fashion.
+ * Callback can return `walk_abort` symbol to stop iteration.
+ * It can also return `true` to stop iteration just for child nodes.
+ * Iteration can be stopped and continued by passing the `to_visit` argument,
+ * which is given to the callback in the second argument.
+ **/
+function walk(node, cb, to_visit = [node]) {
+    const push = to_visit.push.bind(to_visit);
+    while (to_visit.length) {
+        const node = to_visit.pop();
+        const ret = cb(node, to_visit);
+
+        if (ret) {
+            if (ret === walk_abort) return true;
+            continue;
+        }
+
+        node._children_backwards(push);
+    }
+    return false;
+}
+
+/**
+ * Walks an AST node and its children.
+ *
+ * {cb} can return `walk_abort` to interrupt the walk.
+ *
+ * @param node
+ * @param cb {(node, info: { parent: (nth) => any }) => (boolean | undefined)}
+ *
+ * @returns {boolean} whether the walk was aborted
+ *
+ * @example
+ * const found_some_cond = walk_parent(my_ast_node, (node, { parent }) => {
+ *   if (some_cond(node, parent())) return walk_abort
+ * });
+ */
+function walk_parent(node, cb, initial_stack) {
+    const to_visit = [node];
+    const push = to_visit.push.bind(to_visit);
+    const stack = initial_stack ? initial_stack.slice() : [];
+    const parent_pop_indices = [];
+
+    let current;
+
+    const info = {
+        parent: (n = 0) => {
+            if (n === -1) {
+                return current;
+            }
+
+            // [ p1 p0 ] [ 1 0 ]
+            if (initial_stack && n >= stack.length) {
+                n -= stack.length;
+                return initial_stack[
+                    initial_stack.length - (n + 1)
+                ];
+            }
+
+            return stack[stack.length - (1 + n)];
+        },
+    };
+
+    while (to_visit.length) {
+        current = to_visit.pop();
+
+        while (
+            parent_pop_indices.length &&
+            to_visit.length == parent_pop_indices[parent_pop_indices.length - 1]
+        ) {
+            stack.pop();
+            parent_pop_indices.pop();
+        }
+
+        const ret = cb(current, info);
+
+        if (ret) {
+            if (ret === walk_abort) return true;
+            continue;
+        }
+
+        const visit_length = to_visit.length;
+
+        current._children_backwards(push);
+
+        // Push only if we're going to traverse the children
+        if (to_visit.length > visit_length) {
+            stack.push(current);
+            parent_pop_indices.push(visit_length - 1);
+        }
+    }
+
+    return false;
+}
+
+const walk_abort = Symbol("abort walk");
+
+/* -----[ TreeWalker ]----- */
+
+class TreeWalker {
+    constructor(callback) {
+        this.visit = callback;
+        this.stack = [];
+        this.directives = Object.create(null);
+    }
+
+    _visit(node, descend) {
+        this.push(node);
+        var ret = this.visit(node, descend ? function() {
+            descend.call(node);
+        } : noop);
+        if (!ret && descend) {
+            descend.call(node);
+        }
+        this.pop();
+        return ret;
+    }
+
+    parent(n) {
+        return this.stack[this.stack.length - 2 - (n || 0)];
+    }
+
+    push(node) {
+        if (node instanceof AST_Lambda) {
+            this.directives = Object.create(this.directives);
+        } else if (node instanceof AST_Directive && !this.directives[node.value]) {
+            this.directives[node.value] = node;
+        } else if (node instanceof AST_Class) {
+            this.directives = Object.create(this.directives);
+            if (!this.directives["use strict"]) {
+                this.directives["use strict"] = node;
+            }
+        }
+        this.stack.push(node);
+    }
+
+    pop() {
+        var node = this.stack.pop();
+        if (node instanceof AST_Lambda || node instanceof AST_Class) {
+            this.directives = Object.getPrototypeOf(this.directives);
+        }
+    }
+
+    self() {
+        return this.stack[this.stack.length - 1];
+    }
+
+    find_parent(type) {
+        var stack = this.stack;
+        for (var i = stack.length; --i >= 0;) {
+            var x = stack[i];
+            if (x instanceof type) return x;
+        }
+    }
+
+    is_within_loop() {
+        let i = this.stack.length - 1;
+        let child = this.stack[i];
+        while (i--) {
+            const node = this.stack[i];
+
+            if (node instanceof AST_Lambda) return false;
+            if (
+                node instanceof AST_IterationStatement
+                // exclude for-loop bits that only run once
+                && !((node instanceof AST_For) && child === node.init)
+                && !((node instanceof AST_ForIn || node instanceof AST_ForOf) && child === node.object)
+            ) {
+                return true;
+            }
+
+            child = node;
+        }
+
+        return false;
+    }
+
+    find_scope() {
+        var stack = this.stack;
+        for (var i = stack.length; --i >= 0;) {
+            const p = stack[i];
+            if (p instanceof AST_Toplevel) return p;
+            if (p instanceof AST_Lambda) return p;
+            if (p.block_scope) return p.block_scope;
+        }
+    }
+
+    has_directive(type) {
+        var dir = this.directives[type];
+        if (dir) return dir;
+        var node = this.stack[this.stack.length - 1];
+        if (node instanceof AST_Scope && node.body) {
+            for (var i = 0; i < node.body.length; ++i) {
+                var st = node.body[i];
+                if (!(st instanceof AST_Directive)) break;
+                if (st.value == type) return st;
+            }
+        }
+    }
+
+    loopcontrol_target(node) {
+        var stack = this.stack;
+        if (node.label) for (var i = stack.length; --i >= 0;) {
+            var x = stack[i];
+            if (x instanceof AST_LabeledStatement && x.label.name == node.label.name)
+                return x.body;
+        } else for (var i = stack.length; --i >= 0;) {
+            var x = stack[i];
+            if (x instanceof AST_IterationStatement
+                || node instanceof AST_Break && x instanceof AST_Switch)
+                return x;
+        }
+    }
+}
+
+// Tree transformer helpers.
+class TreeTransformer extends TreeWalker {
+    constructor(before, after) {
+        super();
+        this.before = before;
+        this.after = after;
+    }
+}
+
+const _PURE       = 0b00000001;
+const _INLINE     = 0b00000010;
+const _NOINLINE   = 0b00000100;
+const _KEY        = 0b00001000;
+const _MANGLEPROP = 0b00010000;
+
+export {
+    AST_Accessor,
+    AST_Array,
+    AST_Arrow,
+    AST_Assign,
+    AST_Atom,
+    AST_Await,
+    AST_BigInt,
+    AST_Binary,
+    AST_Block,
+    AST_BlockStatement,
+    AST_Boolean,
+    AST_Break,
+    AST_Call,
+    AST_Case,
+    AST_Catch,
+    AST_Chain,
+    AST_Class,
+    AST_ClassExpression,
+    AST_ClassPrivateProperty,
+    AST_PrivateIn,
+    AST_ClassProperty,
+    AST_ClassStaticBlock,
+    AST_ConciseMethod,
+    AST_Conditional,
+    AST_Const,
+    AST_Constant,
+    AST_Continue,
+    AST_Debugger,
+    AST_Default,
+    AST_DefaultAssign,
+    AST_DefClass,
+    AST_Definitions,
+    AST_DefinitionsLike,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Directive,
+    AST_Do,
+    AST_Dot,
+    AST_DotHash,
+    AST_DWLoop,
+    AST_EmptyStatement,
+    AST_Exit,
+    AST_Expansion,
+    AST_Export,
+    AST_False,
+    AST_Finally,
+    AST_For,
+    AST_ForIn,
+    AST_ForOf,
+    AST_Function,
+    AST_Hole,
+    AST_If,
+    AST_DynamicImport,
+    AST_Import,
+    AST_ImportMeta,
+    AST_Infinity,
+    AST_IterationStatement,
+    AST_Jump,
+    AST_Label,
+    AST_LabeledStatement,
+    AST_LabelRef,
+    AST_Lambda,
+    AST_Let,
+    AST_LoopControl,
+    AST_NameMapping,
+    AST_NaN,
+    AST_New,
+    AST_NewTarget,
+    AST_Node,
+    AST_Null,
+    AST_Number,
+    AST_Object,
+    AST_ObjectGetter,
+    AST_ObjectKeyVal,
+    AST_ObjectProperty,
+    AST_ObjectSetter,
+    AST_PrefixedTemplateString,
+    AST_PrivateGetter,
+    AST_PrivateMethod,
+    AST_PrivateSetter,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Return,
+    AST_Scope,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Statement,
+    AST_StatementWithBody,
+    AST_String,
+    AST_Sub,
+    AST_Super,
+    AST_Switch,
+    AST_SwitchBranch,
+    AST_Symbol,
+    AST_SymbolBlockDeclaration,
+    AST_SymbolCatch,
+    AST_SymbolClass,
+    AST_SymbolClassProperty,
+    AST_SymbolConst,
+    AST_SymbolDeclaration,
+    AST_SymbolDefClass,
+    AST_SymbolDefun,
+    AST_SymbolExport,
+    AST_SymbolExportForeign,
+    AST_SymbolFunarg,
+    AST_SymbolImport,
+    AST_SymbolImportForeign,
+    AST_SymbolLambda,
+    AST_SymbolLet,
+    AST_SymbolMethod,
+    AST_SymbolRef,
+    AST_SymbolUsing,
+    AST_SymbolVar,
+    AST_TemplateSegment,
+    AST_TemplateString,
+    AST_SymbolPrivateProperty,
+    AST_This,
+    AST_Throw,
+    AST_Token,
+    AST_Toplevel,
+    AST_True,
+    AST_Try,
+    AST_TryBlock,
+    AST_Unary,
+    AST_UnaryPostfix,
+    AST_UnaryPrefix,
+    AST_Undefined,
+    AST_Using,
+    AST_UsingDef,
+    AST_Var,
+    AST_VarDef,
+    AST_VarDefLike,
+    AST_While,
+    AST_With,
+    AST_Yield,
+
+    // Walkers
+    TreeTransformer,
+    TreeWalker,
+    walk,
+    walk_abort,
+    walk_body,
+    walk_parent,
+
+    // annotations
+    _INLINE,
+    _NOINLINE,
+    _PURE,
+    _KEY,
+    _MANGLEPROP,
+};
Index: frontend/node_modules/terser/lib/cli.js
===================================================================
--- frontend/node_modules/terser/lib/cli.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/cli.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,482 @@
+import { minify, _default_options } from "../main.js";
+import { parse } from "./parse.js";
+import {
+    AST_Assign,
+    AST_Array,
+    AST_Constant,
+    AST_Node,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Sequence,
+    AST_Symbol,
+    AST_Token,
+    walk
+} from "./ast.js";
+import { OutputStream } from "./output.js";
+
+export async function run_cli({ program, packageJson, fs, path }) {
+    const skip_keys = new Set([ "cname", "parent_scope", "scope", "uses_eval", "uses_with" ]);
+    var files = {};
+    var options = {
+        compress: false,
+        mangle: false
+    };
+    const default_options = await _default_options();
+    program.version(packageJson.name + " " + packageJson.version);
+    program.parseArgv = program.parse;
+    program.parse = undefined;
+
+    if (process.argv.includes("ast")) program.helpInformation = describe_ast;
+    else if (process.argv.includes("options")) program.helpInformation = function() {
+        var text = [];
+        for (var option in default_options) {
+            text.push("--" + (option === "sourceMap" ? "source-map" : option) + " options:");
+            text.push(format_object(default_options[option]));
+            text.push("");
+        }
+        return text.join("\n");
+    };
+
+    program.option("-p, --parse <options>", "Specify parser options.", parse_js());
+    program.option("-c, --compress [options]", "Enable compressor/specify compressor options.", parse_js());
+    program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js());
+    program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js());
+    program.option("-f, --format [options]", "Format options.", parse_js());
+    program.option("-b, --beautify [options]", "Alias for --format.", parse_js());
+    program.option("-o, --output <file>", "Output file (default STDOUT).");
+    program.option("--comments [filter]", "Preserve copyright comments in the output.");
+    program.option("--config-file <file>", "Read minify() options from JSON file.");
+    program.option("-d, --define <expr>[=value]", "Global definitions.", parse_js("define"));
+    program.option("--ecma <version>", "Specify ECMAScript release: 5, 2015, 2016 or 2017...");
+    program.option("-e, --enclose [arg[,...][:value[,...]]]", "Embed output in a big function with configurable arguments and values.");
+    program.option("--ie8", "Support non-standard Internet Explorer 8.");
+    program.option("--keep-classnames", "Do not mangle/drop class names.");
+    program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.");
+    program.option("--module", "Input is an ES6 module");
+    program.option("--name-cache <file>", "File to hold mangled name mappings.");
+    program.option("--rename", "Force symbol expansion.");
+    program.option("--no-rename", "Disable symbol expansion.");
+    program.option("--safari10", "Support non-standard Safari 10.");
+    program.option("--source-map [options]", "Enable source map/specify source map options.", parse_js());
+    program.option("--timings", "Display operations run time on STDERR.");
+    program.option("--toplevel", "Compress and/or mangle variables in toplevel scope.");
+    program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally.");
+    program.arguments("[files...]").parseArgv(process.argv);
+    if (program.configFile) {
+        options = JSON.parse(read_file(program.configFile));
+    }
+    if (!program.output && program.sourceMap && program.sourceMap.url != "inline") {
+        fatal("ERROR: cannot write source map to STDOUT");
+    }
+
+    [
+        "compress",
+        "enclose",
+        "ie8",
+        "mangle",
+        "module",
+        "safari10",
+        "sourceMap",
+        "toplevel",
+        "wrap"
+    ].forEach(function(name) {
+        if (name in program) {
+            options[name] = program[name];
+        }
+    });
+
+    if ("ecma" in program) {
+        if (program.ecma != (program.ecma | 0)) fatal("ERROR: ecma must be an integer");
+        const ecma = program.ecma | 0;
+        if (ecma > 5 && ecma < 2015)
+            options.ecma = ecma + 2009;
+        else
+            options.ecma = ecma;
+    }
+    if (program.format || program.beautify) {
+        const chosenOption = program.format || program.beautify;
+        options.format = typeof chosenOption === "object" ? chosenOption : {};
+    }
+    if (program.comments) {
+        if (typeof options.format != "object") options.format = {};
+        options.format.comments = typeof program.comments == "string" ? (program.comments == "false" ? false : program.comments) : "some";
+    }
+    if (program.define) {
+        if (typeof options.compress != "object") options.compress = {};
+        if (typeof options.compress.global_defs != "object") options.compress.global_defs = {};
+        for (var expr in program.define) {
+            options.compress.global_defs[expr] = program.define[expr];
+        }
+    }
+    if (program.keepClassnames) {
+        options.keep_classnames = true;
+    }
+    if (program.keepFnames) {
+        options.keep_fnames = true;
+    }
+    if (program.mangleProps) {
+        if (program.mangleProps.domprops) {
+            delete program.mangleProps.domprops;
+        } else {
+            if (typeof program.mangleProps != "object") program.mangleProps = {};
+            if (!Array.isArray(program.mangleProps.reserved)) program.mangleProps.reserved = [];
+        }
+        if (typeof options.mangle != "object") options.mangle = {};
+        options.mangle.properties = program.mangleProps;
+    }
+    if (program.nameCache) {
+        options.nameCache = JSON.parse(read_file(program.nameCache, "{}"));
+    }
+    if (program.output == "ast") {
+        options.format = {
+            ast: true,
+            code: false
+        };
+    }
+    if (program.parse) {
+        if (!program.parse.acorn && !program.parse.spidermonkey) {
+            options.parse = program.parse;
+        } else if (program.sourceMap && program.sourceMap.content == "inline") {
+            fatal("ERROR: inline source map only works with built-in parser");
+        }
+    }
+    if (~program.rawArgs.indexOf("--rename")) {
+        options.rename = true;
+    } else if (!program.rename) {
+        options.rename = false;
+    }
+
+    let convert_path = name => name;
+    if (typeof program.sourceMap == "object" && "base" in program.sourceMap) {
+        convert_path = function() {
+            var base = program.sourceMap.base;
+            delete options.sourceMap.base;
+            return function(name) {
+                return path.relative(base, name);
+            };
+        }();
+    }
+
+    let filesList;
+    if (options.files && options.files.length) {
+        filesList = options.files;
+
+        delete options.files;
+    } else if (program.args.length) {
+        filesList = program.args;
+    }
+
+    if (filesList) {
+        simple_glob(filesList).forEach(function(name) {
+            files[convert_path(name)] = read_file(name);
+        });
+    } else {
+        await new Promise((resolve) => {
+            var chunks = [];
+            process.stdin.setEncoding("utf8");
+            process.stdin.on("data", function(chunk) {
+                chunks.push(chunk);
+            }).on("end", function() {
+                files = [ chunks.join("") ];
+                resolve();
+            });
+            process.stdin.resume();
+        });
+    }
+
+    await run_cli();
+
+    function convert_ast(fn) {
+        return AST_Node.from_mozilla_ast(Object.keys(files).reduce(fn, null));
+    }
+
+    async function run_cli() {
+        var content = program.sourceMap && program.sourceMap.content;
+        if (content && content !== "inline") {
+            options.sourceMap.content = read_file(content, content);
+        }
+        if (program.timings) options.timings = true;
+
+        try {
+            if (program.parse) {
+                if (program.parse.acorn) {
+                    files = convert_ast(function(toplevel, name) {
+                        return require("acorn").parse(files[name], {
+                            ecmaVersion: 2024,
+                            locations: true,
+                            program: toplevel,
+                            sourceFile: name,
+                            sourceType: options.module || program.parse.module ? "module" : "script"
+                        });
+                    });
+                } else if (program.parse.spidermonkey) {
+                    files = convert_ast(function(toplevel, name) {
+                        var obj = JSON.parse(files[name]);
+                        if (!toplevel) return obj;
+                        toplevel.body = toplevel.body.concat(obj.body);
+                        return toplevel;
+                    });
+                }
+            }
+        } catch (ex) {
+            fatal(ex);
+        }
+
+        let result;
+        try {
+            result = await minify(files, options, fs);
+        } catch (ex) {
+            if (ex.name == "SyntaxError") {
+                print_error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col);
+                var col = ex.col;
+                var lines = files[ex.filename].split(/\r?\n/);
+                var line = lines[ex.line - 1];
+                if (!line && !col) {
+                    line = lines[ex.line - 2];
+                    col = line.length;
+                }
+                if (line) {
+                    var limit = 70;
+                    if (col > limit) {
+                        line = line.slice(col - limit);
+                        col = limit;
+                    }
+                    print_error(line.slice(0, 80));
+                    print_error(line.slice(0, col).replace(/\S/g, " ") + "^");
+                }
+            }
+            if (ex.defs) {
+                print_error("Supported options:");
+                print_error(format_object(ex.defs));
+            }
+            fatal(ex);
+            return;
+        }
+
+        if (program.output == "ast") {
+            if (!options.compress && !options.mangle) {
+                result.ast.figure_out_scope({});
+            }
+            console.log(JSON.stringify(result.ast, function(key, value) {
+                if (value) switch (key) {
+                  case "thedef":
+                    return symdef(value);
+                  case "enclosed":
+                    return value.length ? value.map(symdef) : undefined;
+                  case "variables":
+                  case "globals":
+                    return value.size ? collect_from_map(value, symdef) : undefined;
+                }
+                if (skip_keys.has(key)) return;
+                if (value instanceof AST_Token) return;
+                if (value instanceof Map) return;
+                if (value instanceof AST_Node) {
+                    var result = {
+                        _class: "AST_" + value.TYPE
+                    };
+                    if (value.block_scope) {
+                        result.variables = value.block_scope.variables;
+                        result.enclosed = value.block_scope.enclosed;
+                    }
+                    value.CTOR.PROPS.forEach(function(prop) {
+                        if (prop !== "block_scope") {
+                            result[prop] = value[prop];
+                        }
+                    });
+                    return result;
+                }
+                return value;
+            }, 2));
+        } else if (program.output == "spidermonkey") {
+            try {
+                const minified = await minify(
+                    result.code,
+                    {
+                        compress: false,
+                        mangle: false,
+                        format: {
+                            ast: true,
+                            code: false
+                        }
+                    },
+                    fs
+                );
+                console.log(JSON.stringify(minified.ast.to_mozilla_ast(), null, 2));
+            } catch (ex) {
+                fatal(ex);
+                return;
+            }
+        } else if (program.output) {
+            fs.mkdirSync(path.dirname(program.output), { recursive: true });
+            fs.writeFileSync(program.output, result.code);
+            if (options.sourceMap && options.sourceMap.url !== "inline" && result.map) {
+                fs.writeFileSync(program.output + ".map", result.map);
+            }
+        } else {
+            console.log(result.code);
+        }
+        if (program.nameCache) {
+            fs.writeFileSync(program.nameCache, JSON.stringify(options.nameCache));
+        }
+        if (result.timings) for (var phase in result.timings) {
+            print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s");
+        }
+    }
+
+    function fatal(message) {
+        if (message instanceof Error) message = message.stack.replace(/^\S*?Error:/, "ERROR:");
+        print_error(message);
+        process.exit(1);
+    }
+
+    // A file glob function that only supports "*" and "?" wildcards in the basename.
+    // Example: "foo/bar/*baz??.*.js"
+    // Argument `glob` may be a string or an array of strings.
+    // Returns an array of strings. Garbage in, garbage out.
+    function simple_glob(glob) {
+        if (Array.isArray(glob)) {
+            return [].concat.apply([], glob.map(simple_glob));
+        }
+        if (glob && glob.match(/[*?]/)) {
+            var dir = path.dirname(glob);
+            try {
+                var entries = fs.readdirSync(dir);
+            } catch (ex) {}
+            if (entries) {
+                var pattern = "^" + path.basename(glob)
+                    .replace(/[.+^$[\]\\(){}]/g, "\\$&")
+                    .replace(/\*/g, "[^/\\\\]*")
+                    .replace(/\?/g, "[^/\\\\]") + "$";
+                var mod = process.platform === "win32" ? "i" : "";
+                var rx = new RegExp(pattern, mod);
+                var results = entries.filter(function(name) {
+                    return rx.test(name);
+                }).map(function(name) {
+                    return path.join(dir, name);
+                });
+                if (results.length) return results;
+            }
+        }
+        return [ glob ];
+    }
+
+    function read_file(path, default_value) {
+        try {
+            return fs.readFileSync(path, "utf8");
+        } catch (ex) {
+            if ((ex.code == "ENOENT" || ex.code == "ENAMETOOLONG") && default_value != null) return default_value;
+            fatal(ex);
+        }
+    }
+
+    function parse_js(flag) {
+        return function(value, options) {
+            options = options || {};
+            try {
+                walk(parse(value, { expression: true }), node => {
+                    if (node instanceof AST_Assign) {
+                        var name = node.left.print_to_string();
+                        var value = node.right;
+                        if (flag) {
+                            options[name] = value;
+                        } else if (value instanceof AST_Array) {
+                            options[name] = value.elements.map(to_string);
+                        } else if (value instanceof AST_RegExp) {
+                            value = value.value;
+                            options[name] = new RegExp(value.source, value.flags);
+                        } else {
+                            options[name] = to_string(value);
+                        }
+                        return true;
+                    }
+                    if (node instanceof AST_Symbol || node instanceof AST_PropAccess) {
+                        var name = node.print_to_string();
+                        options[name] = true;
+                        return true;
+                    }
+                    if (!(node instanceof AST_Sequence)) throw node;
+
+                    function to_string(value) {
+                        return value instanceof AST_Constant ? value.getValue() : value.print_to_string({
+                            quote_keys: true
+                        });
+                    }
+                });
+            } catch(ex) {
+                if (flag) {
+                    fatal("Error parsing arguments for '" + flag + "': " + value);
+                } else {
+                    options[value] = null;
+                }
+            }
+            return options;
+        };
+    }
+
+    function symdef(def) {
+        var ret = (1e6 + def.id) + " " + def.name;
+        if (def.mangled_name) ret += " " + def.mangled_name;
+        return ret;
+    }
+
+    function collect_from_map(map, callback) {
+        var result = [];
+        map.forEach(function (def) {
+            result.push(callback(def));
+        });
+        return result;
+    }
+
+    function format_object(obj) {
+        var lines = [];
+        var padding = "";
+        Object.keys(obj).map(function(name) {
+            if (padding.length < name.length) padding = Array(name.length + 1).join(" ");
+            return [ name, JSON.stringify(obj[name]) ];
+        }).forEach(function(tokens) {
+            lines.push("  " + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]);
+        });
+        return lines.join("\n");
+    }
+
+    function print_error(msg) {
+        process.stderr.write(msg);
+        process.stderr.write("\n");
+    }
+
+    function describe_ast() {
+        var out = OutputStream({ beautify: true });
+        function doitem(ctor) {
+            out.print("AST_" + ctor.TYPE);
+            const props = ctor.SELF_PROPS.filter(prop => !/^\$/.test(prop));
+
+            if (props.length > 0) {
+                out.space();
+                out.with_parens(function() {
+                    props.forEach(function(prop, i) {
+                        if (i) out.space();
+                        out.print(prop);
+                    });
+                });
+            }
+
+            if (ctor.documentation) {
+                out.space();
+                out.print_string(ctor.documentation);
+            }
+
+            if (ctor.SUBCLASSES.length > 0) {
+                out.space();
+                out.with_block(function() {
+                    ctor.SUBCLASSES.forEach(function(ctor) {
+                        out.indent();
+                        doitem(ctor);
+                        out.newline();
+                    });
+                });
+            }
+        }
+        doitem(AST_Node);
+        return out + "\n";
+    }
+}
Index: frontend/node_modules/terser/lib/compress/common.js
===================================================================
--- frontend/node_modules/terser/lib/compress/common.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/common.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,375 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    AST_Array,
+    AST_Arrow,
+    AST_BigInt,
+    AST_BlockStatement,
+    AST_Call,
+    AST_Chain,
+    AST_Class,
+    AST_Const,
+    AST_Constant,
+    AST_DefClass,
+    AST_Defun,
+    AST_EmptyStatement,
+    AST_Export,
+    AST_False,
+    AST_Function,
+    AST_Import,
+    AST_Infinity,
+    AST_LabeledStatement,
+    AST_Lambda,
+    AST_Let,
+    AST_LoopControl,
+    AST_NaN,
+    AST_Node,
+    AST_Null,
+    AST_Number,
+    AST_Object,
+    AST_ObjectKeyVal,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Scope,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Statement,
+    AST_String,
+    AST_SymbolRef,
+    AST_True,
+    AST_UnaryPrefix,
+    AST_Undefined,
+    AST_Using,
+
+    TreeWalker,
+    walk,
+    walk_abort,
+    walk_parent,
+} from "../ast.js";
+import { make_node, make_void_0, regexp_source_fix, string_template, makePredicate } from "../utils/index.js";
+import { first_in_statement } from "../utils/first_in_statement.js";
+import { has_flag, TOP } from "./compressor-flags.js";
+
+export function merge_sequence(array, node) {
+    if (node instanceof AST_Sequence) {
+        array.push(...node.expressions);
+    } else {
+        array.push(node);
+    }
+    return array;
+}
+
+export function make_sequence(orig, expressions) {
+    if (expressions.length == 1) return expressions[0];
+    if (expressions.length == 0) throw new Error("trying to create a sequence with length zero!");
+    return make_node(AST_Sequence, orig, {
+        expressions: expressions.reduce(merge_sequence, [])
+    });
+}
+
+export function make_empty_function(self) {
+    return make_node(AST_Function, self, {
+        uses_arguments: false,
+        argnames: [],
+        body: [],
+        is_generator: false,
+        async: false,
+        variables: new Map(),
+        uses_with: false,
+        uses_eval: false,
+        parent_scope: null,
+        enclosed: [],
+        cname: 0,
+        block_scope: undefined,
+    });
+}
+
+export function make_node_from_constant(val, orig) {
+    switch (typeof val) {
+      case "string":
+        return make_node(AST_String, orig, {
+            value: val
+        });
+      case "number":
+        if (isNaN(val)) return make_node(AST_NaN, orig);
+        if (isFinite(val)) {
+            return 1 / val < 0 ? make_node(AST_UnaryPrefix, orig, {
+                operator: "-",
+                expression: make_node(AST_Number, orig, { value: -val })
+            }) : make_node(AST_Number, orig, { value: val });
+        }
+        return val < 0 ? make_node(AST_UnaryPrefix, orig, {
+            operator: "-",
+            expression: make_node(AST_Infinity, orig)
+        }) : make_node(AST_Infinity, orig);
+      case "bigint":
+        return make_node(AST_BigInt, orig, { value: val.toString() });
+      case "boolean":
+        return make_node(val ? AST_True : AST_False, orig);
+      case "undefined":
+        return make_void_0(orig);
+      default:
+        if (val === null) {
+            return make_node(AST_Null, orig, { value: null });
+        }
+        if (val instanceof RegExp) {
+            return make_node(AST_RegExp, orig, {
+                value: {
+                    source: regexp_source_fix(val.source),
+                    flags: val.flags
+                }
+            });
+        }
+        throw new Error(string_template("Can't handle constant of type: {type}", {
+            type: typeof val
+        }));
+    }
+}
+
+export function best_of_expression(ast1, ast2) {
+    return ast1.size() > ast2.size() ? ast2 : ast1;
+}
+
+export function best_of_statement(ast1, ast2) {
+    return best_of_expression(
+        make_node(AST_SimpleStatement, ast1, {
+            body: ast1
+        }),
+        make_node(AST_SimpleStatement, ast2, {
+            body: ast2
+        })
+    ).body;
+}
+
+/** Find which node is smaller, and return that */
+export function best_of(compressor, ast1, ast2) {
+    if (first_in_statement(compressor)) {
+        return best_of_statement(ast1, ast2);
+    } else {
+        return best_of_expression(ast1, ast2);
+    }
+}
+
+/** Simplify an object property's key, if possible */
+export function get_simple_key(key) {
+    if (key instanceof AST_Constant) {
+        return key.getValue();
+    }
+    if (key instanceof AST_UnaryPrefix
+        && key.operator == "void"
+        && key.expression instanceof AST_Constant) {
+        return undefined;
+    }
+    return key;
+}
+
+export function read_property(obj, key) {
+    key = get_simple_key(key);
+    if (key instanceof AST_Node) return;
+
+    var value;
+    if (obj instanceof AST_Array) {
+        var elements = obj.elements;
+        if (key == "length") return make_node_from_constant(elements.length, obj);
+        if (typeof key == "number" && key in elements) value = elements[key];
+    } else if (obj instanceof AST_Object) {
+        key = "" + key;
+        var props = obj.properties;
+        for (var i = props.length; --i >= 0;) {
+            var prop = props[i];
+            if (!(prop instanceof AST_ObjectKeyVal)) return;
+            if (!value && props[i].key === key) value = props[i].value;
+        }
+    }
+
+    return value instanceof AST_SymbolRef && value.fixed_value() || value;
+}
+
+export function has_break_or_continue(loop, parent) {
+    var found = false;
+    var tw = new TreeWalker(function(node) {
+        if (found || node instanceof AST_Scope) return true;
+        if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === loop) {
+            return found = true;
+        }
+    });
+    if (parent instanceof AST_LabeledStatement) tw.push(parent);
+    tw.push(loop);
+    loop.body.walk(tw);
+    return found;
+}
+
+// we shouldn't compress (1,func)(something) to
+// func(something) because that changes the meaning of
+// the func (becomes lexical instead of global).
+export function maintain_this_binding(parent, orig, val) {
+    if (requires_sequence_to_maintain_binding(parent, orig, val)) {
+        const zero = make_node(AST_Number, orig, { value: 0 });
+        return make_sequence(orig, [ zero, val ]);
+    } else {
+        return val;
+    }
+}
+
+/** Detect (1, x.noThis)(), (0, eval)(), which need sequences */
+export function requires_sequence_to_maintain_binding(parent, orig, val) {
+    return (
+        parent instanceof AST_UnaryPrefix && parent.operator == "delete"
+        || parent instanceof AST_Call && parent.expression === orig
+            && (
+                val instanceof AST_Chain
+                || val instanceof AST_PropAccess
+                || val instanceof AST_SymbolRef && val.name == "eval"
+            )
+    );
+}
+
+export function is_func_expr(node) {
+    return node instanceof AST_Arrow || node instanceof AST_Function;
+}
+
+/**
+ * Used to determine whether the node can benefit from negation.
+ * Not the case with arrow functions (you need an extra set of parens). */
+export function is_iife_call(node) {
+    if (node.TYPE != "Call") return false;
+    return node.expression instanceof AST_Function || is_iife_call(node.expression);
+}
+
+export function is_empty(thing) {
+    if (thing === null) return true;
+    if (thing instanceof AST_EmptyStatement) return true;
+    if (thing instanceof AST_BlockStatement) return thing.body.length == 0;
+    return false;
+}
+
+export const identifier_atom = makePredicate("Infinity NaN undefined");
+export function is_identifier_atom(node) {
+    return node instanceof AST_Infinity
+        || node instanceof AST_NaN
+        || node instanceof AST_Undefined;
+}
+
+/** Check if this is a SymbolRef node which has one def of a certain AST type */
+export function is_ref_of(ref, type) {
+    if (!(ref instanceof AST_SymbolRef)) return false;
+    var orig = ref.definition().orig;
+    for (var i = orig.length; --i >= 0;) {
+        if (orig[i] instanceof type) return true;
+    }
+}
+
+/**Can we turn { block contents... } into just the block contents ?
+ * Not if one of these is inside.
+ **/
+export function can_be_evicted_from_block(node) {
+    return !(
+        node instanceof AST_DefClass ||
+        node instanceof AST_Defun ||
+        node instanceof AST_Let ||
+        node instanceof AST_Const ||
+        node instanceof AST_Using ||
+        node instanceof AST_Export ||
+        node instanceof AST_Import
+    );
+}
+
+export function as_statement_array(thing) {
+    if (thing === null) return [];
+    if (thing instanceof AST_BlockStatement) return thing.body;
+    if (thing instanceof AST_EmptyStatement) return [];
+    if (thing instanceof AST_Statement) return [ thing ];
+    throw new Error("Can't convert thing to statement array");
+}
+
+export function is_reachable(scope_node, defs) {
+    const find_ref = node => {
+        if (node instanceof AST_SymbolRef && defs.includes(node.definition())) {
+            return walk_abort;
+        }
+    };
+
+    return walk_parent(scope_node, (node, info) => {
+        if (node instanceof AST_Scope && node !== scope_node) {
+            var parent = info.parent();
+
+            if (
+                parent instanceof AST_Call
+                && parent.expression === node
+                // Async/Generators aren't guaranteed to sync evaluate all of
+                // their body steps, so it's possible they close over the variable.
+                && !(node.async || node.is_generator)
+            ) {
+                return;
+            }
+
+            if (walk(node, find_ref)) return walk_abort;
+
+            return true;
+        }
+    });
+}
+
+/** Check if a ref refers to the name of a function/class it's defined within */
+export function is_recursive_ref(tw, def) {
+    var node;
+    for (var i = 0; node = tw.parent(i); i++) {
+        if (node instanceof AST_Lambda || node instanceof AST_Class) {
+            var name = node.name;
+            if (name && name.definition() === def) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+// TODO this only works with AST_Defun, shouldn't it work for other ways of defining functions?
+export function retain_top_func(fn, compressor) {
+    return compressor.top_retain
+        && fn instanceof AST_Defun
+        && has_flag(fn, TOP)
+        && fn.name
+        && compressor.top_retain(fn.name.definition());
+}
Index: frontend/node_modules/terser/lib/compress/compressor-flags.js
===================================================================
--- frontend/node_modules/terser/lib/compress/compressor-flags.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/compressor-flags.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,62 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+// bitfield flags to be stored in node.flags.
+// These are set and unset during compression, and store information in the node without requiring multiple fields.
+export const UNUSED = 0b00000001;
+export const TRUTHY = 0b00000010;
+export const FALSY = 0b00000100;
+export const UNDEFINED = 0b00001000;
+export const INLINED = 0b00010000;
+// Nodes to which values are ever written. Used when keep_assign is part of the unused option string.
+export const WRITE_ONLY = 0b00100000;
+
+// information specific to a single compression pass
+export const SQUEEZED = 0b0000000100000000;
+export const OPTIMIZED = 0b0000001000000000;
+export const TOP = 0b0000010000000000;
+export const CLEAR_BETWEEN_PASSES = SQUEEZED | OPTIMIZED | TOP;
+
+export const has_flag = (node, flag) => node.flags & flag;
+export const set_flag = (node, flag) => { node.flags |= flag; };
+export const clear_flag = (node, flag) => { node.flags &= ~flag; };
Index: frontend/node_modules/terser/lib/compress/drop-side-effect-free.js
===================================================================
--- frontend/node_modules/terser/lib/compress/drop-side-effect-free.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/drop-side-effect-free.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,394 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    AST_Accessor,
+    AST_Array,
+    AST_Arrow,
+    AST_Assign,
+    AST_Binary,
+    AST_Call,
+    AST_Chain,
+    AST_Class,
+    AST_ClassPrivateProperty,
+    AST_ClassProperty,
+    AST_ClassStaticBlock,
+    AST_ConciseMethod,
+    AST_Conditional,
+    AST_Constant,
+    AST_DefClass,
+    AST_Dot,
+    AST_DynamicImport,
+    AST_Expansion,
+    AST_Function,
+    AST_Node,
+    AST_Number,
+    AST_Object,
+    AST_ObjectGetter,
+    AST_ObjectKeyVal,
+    AST_ObjectSetter,
+    AST_PrivateGetter,
+    AST_PrivateSetter,
+    AST_PrivateMethod,
+    AST_PropAccess,
+    AST_Scope,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Sub,
+    AST_SymbolRef,
+    AST_TemplateSegment,
+    AST_TemplateString,
+    AST_This,
+    AST_Unary,
+} from "../ast.js";
+import { make_node, return_null, return_this } from "../utils/index.js";
+import { first_in_statement } from "../utils/first_in_statement.js";
+
+import { pure_prop_access_globals } from "./native-objects.js";
+import { lazy_op, unary_side_effects, is_nullish_shortcircuited } from "./inference.js";
+import { WRITE_ONLY, set_flag, clear_flag } from "./compressor-flags.js";
+import { make_sequence, is_func_expr, is_iife_call } from "./common.js";
+
+// AST_Node#drop_side_effect_free() gets called when we don't care about the value,
+// only about side effects. We'll be defining this method for each node type in this module
+//
+// Examples:
+// foo++ -> foo++
+// 1 + func() -> func()
+// 10 -> (nothing)
+// knownPureFunc(foo++) -> foo++
+
+function def_drop_side_effect_free(node_or_nodes, func) {
+    for (const node of [].concat(node_or_nodes)) {
+        node.DEFMETHOD("drop_side_effect_free", func);
+    }
+}
+
+// Drop side-effect-free elements from an array of expressions.
+// Returns an array of expressions with side-effects or null
+// if all elements were dropped. Note: original array may be
+// returned if nothing changed.
+function trim(nodes, compressor, first_in_statement) {
+    var len = nodes.length;
+    if (!len)  return null;
+
+    var ret = [], changed = false;
+    for (var i = 0; i < len; i++) {
+        var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
+        changed |= node !== nodes[i];
+        if (node) {
+            ret.push(node);
+            first_in_statement = false;
+        }
+    }
+    return changed ? ret.length ? ret : null : nodes;
+}
+
+def_drop_side_effect_free(AST_Node, return_this);
+def_drop_side_effect_free(AST_Constant, return_null);
+def_drop_side_effect_free(AST_This, return_null);
+
+def_drop_side_effect_free(AST_Call, function (compressor, first_in_statement) {
+    if (is_nullish_shortcircuited(this, compressor)) {
+        return this.expression.drop_side_effect_free(compressor, first_in_statement);
+    }
+
+    if (!this.is_callee_pure(compressor)) {
+        if (this.expression.is_call_pure(compressor)) {
+            var exprs = this.args.slice();
+            exprs.unshift(this.expression.expression);
+            exprs = trim(exprs, compressor, first_in_statement);
+            return exprs && make_sequence(this, exprs);
+        }
+        if (is_func_expr(this.expression)
+            && (!this.expression.name || !this.expression.name.definition().references.length)) {
+            var node = this.clone();
+            node.expression.process_expression(false, compressor);
+            return node;
+        }
+        return this;
+    }
+
+    var args = trim(this.args, compressor, first_in_statement);
+    return args && make_sequence(this, args);
+});
+
+def_drop_side_effect_free(AST_DynamicImport, function (compressor, first_in_statement) {
+    if (this.phase !== "source") return this;
+    var args = trim(this.args, compressor, first_in_statement);
+    return args && make_sequence(this, args);
+});
+
+def_drop_side_effect_free(AST_Accessor, return_null);
+
+def_drop_side_effect_free(AST_Function, return_null);
+
+def_drop_side_effect_free(AST_Arrow, return_null);
+
+def_drop_side_effect_free(AST_Class, function (compressor) {
+    const with_effects = [];
+
+    if (this.is_self_referential() && this.has_side_effects(compressor)) {
+        return this;
+    }
+
+    const trimmed_extends = this.extends && this.extends.drop_side_effect_free(compressor);
+    if (trimmed_extends) with_effects.push(trimmed_extends);
+
+    for (const prop of this.properties) {
+        if (prop instanceof AST_ClassStaticBlock) {
+            if (prop.has_side_effects(compressor)) {
+                return this; // Be cautious about these
+            }
+        } else {
+            const trimmed_prop = prop.drop_side_effect_free(compressor);
+            if (trimmed_prop) with_effects.push(trimmed_prop);
+        }
+    }
+
+    if (!with_effects.length)
+        return null;
+
+    const exprs = make_sequence(this, with_effects);
+    if (this instanceof AST_DefClass) {
+        // We want a statement
+        return make_node(AST_SimpleStatement, this, { body: exprs });
+    } else {
+        return exprs;
+    }
+});
+
+def_drop_side_effect_free([
+    AST_ClassProperty,
+    AST_ClassPrivateProperty,
+], function (compressor) {
+    const key = this.computed_key() && this.key.drop_side_effect_free(compressor);
+
+    const value = this.static && this.value
+        && this.value.drop_side_effect_free(compressor);
+
+    if (key && value)
+        return make_sequence(this, [key, value]);
+    return key || value || null;
+});
+
+def_drop_side_effect_free(AST_Binary, function (compressor, first_in_statement) {
+    var right = this.right.drop_side_effect_free(compressor);
+    if (!right)
+        return this.left.drop_side_effect_free(compressor, first_in_statement);
+    if (lazy_op.has(this.operator)) {
+        if (right === this.right)
+            return this;
+        var node = this.clone();
+        node.right = right;
+        return node;
+    } else {
+        var left = this.left.drop_side_effect_free(compressor, first_in_statement);
+        if (!left)
+            return this.right.drop_side_effect_free(compressor, first_in_statement);
+        return make_sequence(this, [left, right]);
+    }
+});
+
+def_drop_side_effect_free(AST_Assign, function (compressor) {
+    if (this.logical)
+        return this;
+
+    var left = this.left;
+    if (left.has_side_effects(compressor)
+        || compressor.has_directive("use strict")
+        && left instanceof AST_PropAccess
+        && left.expression.is_constant()) {
+        return this;
+    }
+    set_flag(this, WRITE_ONLY);
+    while (left instanceof AST_PropAccess) {
+        left = left.expression;
+    }
+    if (left.is_constant_expression(compressor.find_parent(AST_Scope))) {
+        return this.right.drop_side_effect_free(compressor);
+    }
+    return this;
+});
+
+def_drop_side_effect_free(AST_Conditional, function (compressor) {
+    var consequent = this.consequent.drop_side_effect_free(compressor);
+    var alternative = this.alternative.drop_side_effect_free(compressor);
+    if (consequent === this.consequent && alternative === this.alternative)
+        return this;
+    if (!consequent)
+        return alternative ? make_node(AST_Binary, this, {
+            operator: "||",
+            left: this.condition,
+            right: alternative
+        }) : this.condition.drop_side_effect_free(compressor);
+    if (!alternative)
+        return make_node(AST_Binary, this, {
+            operator: "&&",
+            left: this.condition,
+            right: consequent
+        });
+    var node = this.clone();
+    node.consequent = consequent;
+    node.alternative = alternative;
+    return node;
+});
+
+def_drop_side_effect_free(AST_Unary, function (compressor, first_in_statement) {
+    if (unary_side_effects.has(this.operator)) {
+        if (!this.expression.has_side_effects(compressor)) {
+            set_flag(this, WRITE_ONLY);
+        } else {
+            clear_flag(this, WRITE_ONLY);
+        }
+        return this;
+    }
+    if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef)
+        return null;
+    var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
+    if (first_in_statement && expression && is_iife_call(expression)) {
+        if (expression === this.expression && this.operator == "!")
+            return this;
+        return expression.negate(compressor, first_in_statement);
+    }
+    return expression;
+});
+
+def_drop_side_effect_free(AST_SymbolRef, function (compressor) {
+    const safe_access = this.is_declared(compressor)
+        || pure_prop_access_globals.has(this.name);
+    return safe_access ? null : this;
+});
+
+def_drop_side_effect_free(AST_Object, function (compressor, first_in_statement) {
+    var values = trim(this.properties, compressor, first_in_statement);
+    return values && make_sequence(this, values);
+});
+
+def_drop_side_effect_free(AST_ObjectKeyVal, function (compressor, first_in_statement) {
+    const computed_key = this.key instanceof AST_Node;
+    const key = computed_key && this.key.drop_side_effect_free(compressor, first_in_statement);
+    const value = this.value.drop_side_effect_free(compressor, first_in_statement);
+    if (key && value) {
+        return make_sequence(this, [key, value]);
+    }
+    return key || value;
+});
+
+def_drop_side_effect_free([
+    AST_ConciseMethod,
+    AST_ObjectGetter,
+    AST_ObjectSetter,
+], function (compressor, first_in_statement) {
+    return this.computed_key() ? this.key.drop_side_effect_free(compressor, first_in_statement) : null;
+});
+
+def_drop_side_effect_free([
+    AST_PrivateMethod,
+    AST_PrivateGetter,
+    AST_PrivateSetter,
+], function () {
+    return null;
+});
+
+def_drop_side_effect_free(AST_Array, function (compressor, first_in_statement) {
+    var values = trim(this.elements, compressor, first_in_statement);
+    return values && make_sequence(this, values);
+});
+
+def_drop_side_effect_free(AST_Dot, function (compressor, first_in_statement) {
+    if (is_nullish_shortcircuited(this, compressor)) {
+        return this.expression.drop_side_effect_free(compressor, first_in_statement);
+    }
+    if (!this.optional && this.expression.may_throw_on_access(compressor)) {
+        return this;
+    }
+
+    return this.expression.drop_side_effect_free(compressor, first_in_statement);
+});
+
+def_drop_side_effect_free(AST_Sub, function (compressor, first_in_statement) {
+    if (is_nullish_shortcircuited(this, compressor)) {
+        return this.expression.drop_side_effect_free(compressor, first_in_statement);
+    }
+    if (!this.optional && this.expression.may_throw_on_access(compressor)) {
+        return this;
+    }
+
+    var property = this.property.drop_side_effect_free(compressor);
+    if (property && this.optional) return this;
+
+    var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
+
+    if (expression && property) return make_sequence(this, [expression, property]);
+    return expression || property;
+});
+
+def_drop_side_effect_free(AST_Chain, function (compressor, first_in_statement) {
+    return this.expression.drop_side_effect_free(compressor, first_in_statement);
+});
+
+def_drop_side_effect_free(AST_Sequence, function (compressor) {
+    var last = this.tail_node();
+    var expr = last.drop_side_effect_free(compressor);
+    if (expr === last)
+        return this;
+    var expressions = this.expressions.slice(0, -1);
+    if (expr)
+        expressions.push(expr);
+    if (!expressions.length) {
+        return make_node(AST_Number, this, { value: 0 });
+    }
+    return make_sequence(this, expressions);
+});
+
+def_drop_side_effect_free(AST_Expansion, function (compressor, first_in_statement) {
+    return this.expression.drop_side_effect_free(compressor, first_in_statement);
+});
+
+def_drop_side_effect_free(AST_TemplateSegment, return_null);
+
+def_drop_side_effect_free(AST_TemplateString, function (compressor) {
+    var values = trim(this.segments, compressor, first_in_statement);
+    return values && make_sequence(this, values);
+});
Index: frontend/node_modules/terser/lib/compress/drop-unused.js
===================================================================
--- frontend/node_modules/terser/lib/compress/drop-unused.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/drop-unused.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,507 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    AST_Accessor,
+    AST_Assign,
+    AST_BlockStatement,
+    AST_Call,
+    AST_Class,
+    AST_ClassExpression,
+    AST_ClassStaticBlock,
+    AST_DefaultAssign,
+    AST_DefClass,
+    AST_Definitions,
+    AST_Defun,
+    AST_Destructuring,
+    AST_EmptyStatement,
+    AST_Expansion,
+    AST_Export,
+    AST_For,
+    AST_ForIn,
+    AST_Function,
+    AST_LabeledStatement,
+    AST_Lambda,
+    AST_Number,
+    AST_Scope,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_SymbolBlockDeclaration,
+    AST_SymbolCatch,
+    AST_SymbolDeclaration,
+    AST_SymbolFunarg,
+    AST_SymbolRef,
+    AST_SymbolVar,
+    AST_Toplevel,
+    AST_Unary,
+    AST_Var,
+
+    TreeTransformer,
+    TreeWalker,
+    walk,
+} from "../ast.js";
+import {
+    keep_name,
+    make_node,
+    map_add,
+    MAP,
+    remove,
+    return_false,
+} from "../utils/index.js";
+import { SymbolDef } from "../scope.js";
+
+import {
+    WRITE_ONLY,
+    UNUSED,
+
+    has_flag,
+    set_flag,
+} from "./compressor-flags.js";
+import {
+    make_sequence,
+    maintain_this_binding,
+    is_empty,
+    is_ref_of,
+    can_be_evicted_from_block,
+} from "./common.js";
+import { is_used_in_expression } from "./inference.js";
+
+const r_keep_assign = /keep_assign/;
+
+/** Drop unused variables from this scope */
+AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
+    if (!compressor.option("unused")) return;
+    if (compressor.has_directive("use asm")) return;
+    if (!this.variables) return; // not really a scope (eg: AST_Class)
+
+    var self = this;
+    if (self.pinned()) return;
+    var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
+    var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
+    const assign_as_unused = r_keep_assign.test(compressor.option("unused")) ? return_false : function(node) {
+        if (node instanceof AST_Assign
+            && !node.logical
+            && (has_flag(node, WRITE_ONLY) || node.operator == "=")
+        ) {
+            return node.left;
+        }
+        if (node instanceof AST_Unary && has_flag(node, WRITE_ONLY)) {
+            return node.expression;
+        }
+    };
+    var in_use_ids = new Map();
+    var fixed_ids = new Map();
+    if (self instanceof AST_Toplevel && compressor.top_retain) {
+        self.variables.forEach(function(def) {
+            if (compressor.top_retain(def)) {
+                in_use_ids.set(def.id, def);
+            }
+        });
+    }
+    var var_defs_by_id = new Map();
+    var initializations = new Map();
+
+    // pass 1: find out which symbols are directly used in
+    // this scope (not in nested scopes).
+    var scope = this;
+    var tw = new TreeWalker(function(node, descend) {
+        if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) {
+            node.argnames.forEach(function(argname) {
+                if (!(argname instanceof AST_SymbolDeclaration)) return;
+                var def = argname.definition();
+                in_use_ids.set(def.id, def);
+            });
+        }
+        if (node === self) return;
+        if (node instanceof AST_Class && node.has_side_effects(compressor)) {
+            if (node.is_self_referential()) {
+                descend();
+            } else {
+                node.visit_nondeferred_class_parts(tw);
+            }
+        }
+        if (node instanceof AST_Defun || node instanceof AST_DefClass) {
+            var node_def = node.name.definition();
+            const in_export = tw.parent() instanceof AST_Export;
+            if (in_export || !drop_funcs && scope === self) {
+                if (node_def.global) {
+                    in_use_ids.set(node_def.id, node_def);
+                }
+            }
+
+            map_add(initializations, node_def.id, node);
+            return true; // don't go in nested scopes
+        }
+        // In the root scope, we drop things. In inner scopes, we just check for uses.
+        const in_root_scope = scope === self;
+        if (node instanceof AST_SymbolFunarg && in_root_scope) {
+            map_add(var_defs_by_id, node.definition().id, node);
+        }
+        if (node instanceof AST_Definitions && in_root_scope) {
+            const in_export = tw.parent() instanceof AST_Export;
+            node.definitions.forEach(function(def) {
+                if (def.name instanceof AST_SymbolVar) {
+                    map_add(var_defs_by_id, def.name.definition().id, def);
+                }
+                if (in_export || !drop_vars) {
+                    walk(def.name, node => {
+                        if (node instanceof AST_SymbolDeclaration) {
+                            const def = node.definition();
+                            if (def.global) {
+                                in_use_ids.set(def.id, def);
+                            }
+                        }
+                    });
+                }
+                if (def.name instanceof AST_Destructuring) {
+                    def.walk(tw);
+                }
+                if (def.name instanceof AST_SymbolDeclaration && def.value) {
+                    var node_def = def.name.definition();
+                    map_add(initializations, node_def.id, def.value);
+                    if (!node_def.chained && def.name.fixed_value() === def.value) {
+                        fixed_ids.set(node_def.id, def);
+                    }
+                    if (def.value.has_side_effects(compressor)) {
+                        def.value.walk(tw);
+                    }
+                }
+            });
+            return true;
+        }
+        return scan_ref_scoped(node, descend);
+    });
+    self.walk(tw);
+
+    // pass 2: for every used symbol we need to walk its
+    // initialization code to figure out if it uses other
+    // symbols (that may not be in_use).
+    tw = new TreeWalker(scan_ref_scoped);
+    in_use_ids.forEach(function (def) {
+        var init = initializations.get(def.id);
+        if (init) init.forEach(function(init) {
+            init.walk(tw);
+        });
+    });
+
+    // pass 3: we should drop declarations not in_use
+    var tt = new TreeTransformer(
+        function before(node, descend, in_list) {
+            var parent = tt.parent();
+            if (drop_vars) {
+                const sym = assign_as_unused(node);
+                if (sym instanceof AST_SymbolRef) {
+                    var def = sym.definition();
+                    var in_use = in_use_ids.has(def.id);
+                    if (node instanceof AST_Assign) {
+                        if (!in_use || fixed_ids.has(def.id) && fixed_ids.get(def.id) !== node) {
+                            const assignee = node.right.transform(tt);
+                            if (!in_use && !assignee.has_side_effects(compressor) && !is_used_in_expression(tt)) {
+                                return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
+                            }
+                            return maintain_this_binding(parent, node, assignee);
+                        }
+                    } else if (!in_use) {
+                        return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
+                    }
+                }
+            }
+            if (scope !== self) return;
+            var def;
+            if (node.name
+                && (node instanceof AST_ClassExpression
+                    && !keep_name(compressor.option("keep_classnames"), (def = node.name.definition()).name)
+                || node instanceof AST_Function
+                    && !keep_name(compressor.option("keep_fnames"), (def = node.name.definition()).name))) {
+                // any declarations with same name will overshadow
+                // name of this anonymous function and can therefore
+                // never be used anywhere
+                if (!in_use_ids.has(def.id) || def.orig.length > 1) node.name = null;
+            }
+            if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
+                var trim =
+                    !compressor.option("keep_fargs")
+                    // Is this an IIFE that won't refer to its name?
+                    || parent instanceof AST_Call
+                        && parent.expression === node
+                        && !node.pinned()
+                        && (!node.name || node.name.unreferenced());
+                for (var a = node.argnames, i = a.length; --i >= 0;) {
+                    var sym = a[i];
+                    if (sym instanceof AST_Expansion) {
+                        sym = sym.expression;
+                    }
+                    if (sym instanceof AST_DefaultAssign) {
+                        sym = sym.left;
+                    }
+                    // Do not drop destructuring arguments.
+                    // They constitute a type assertion of sorts
+                    if (
+                        !(sym instanceof AST_Destructuring)
+                        && !in_use_ids.has(sym.definition().id)
+                    ) {
+                        set_flag(sym, UNUSED);
+                        if (trim) {
+                            a.pop();
+                        }
+                    } else {
+                        trim = false;
+                    }
+                }
+            }
+            if (node instanceof AST_DefClass && node !== self) {
+                const def = node.name.definition();
+                descend(node, this);
+                const keep_class = def.global && !drop_funcs || in_use_ids.has(def.id);
+                if (!keep_class) {
+                    const kept = node.drop_side_effect_free(compressor);
+                    if (kept == null) {
+                        def.eliminated++;
+                        return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
+                    }
+                    return kept;
+                }
+                return node;
+            }
+            if (node instanceof AST_Defun && node !== self) {
+                const def = node.name.definition();
+                const keep = def.global && !drop_funcs || in_use_ids.has(def.id);
+                if (!keep) {
+                    def.eliminated++;
+                    return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
+                }
+            }
+            if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
+                var drop_block = !(parent instanceof AST_Toplevel) && !(node instanceof AST_Var);
+                // place uninitialized names at the start
+                var body = [], head = [], tail = [];
+                // for unused names whose initialization has
+                // side effects, we can cascade the init. code
+                // into the next one, or next statement.
+                var side_effects = [];
+                node.definitions.forEach(function(def) {
+                    if (def.value) def.value = def.value.transform(tt);
+                    var is_destructure = def.name instanceof AST_Destructuring;
+                    var sym = is_destructure
+                        ? new SymbolDef(null, { name: "<destructure>" }) /* fake SymbolDef */
+                        : def.name.definition();
+                    if (drop_block && sym.global) return tail.push(def);
+                    if (!(drop_vars || drop_block)
+                        || is_destructure
+                            && (def.name.names.length
+                                || def.name.is_array
+                                || compressor.option("pure_getters") != true)
+                        || in_use_ids.has(sym.id)
+                    ) {
+                        if (def.value && fixed_ids.has(sym.id) && fixed_ids.get(sym.id) !== def) {
+                            def.value = def.value.drop_side_effect_free(compressor);
+                        }
+                        if (def.name instanceof AST_SymbolVar) {
+                            var var_defs = var_defs_by_id.get(sym.id);
+                            if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
+                                if (def.value) {
+                                    var ref = make_node(AST_SymbolRef, def.name, def.name);
+                                    sym.references.push(ref);
+                                    var assign = make_node(AST_Assign, def, {
+                                        operator: "=",
+                                        logical: false,
+                                        left: ref,
+                                        right: def.value
+                                    });
+                                    if (fixed_ids.get(sym.id) === def) {
+                                        fixed_ids.set(sym.id, assign);
+                                    }
+                                    side_effects.push(assign.transform(tt));
+                                }
+                                remove(var_defs, def);
+                                sym.eliminated++;
+                                return;
+                            }
+                        }
+                        if (def.value) {
+                            if (side_effects.length > 0) {
+                                if (tail.length > 0) {
+                                    side_effects.push(def.value);
+                                    def.value = make_sequence(def.value, side_effects);
+                                } else {
+                                    body.push(make_node(AST_SimpleStatement, node, {
+                                        body: make_sequence(node, side_effects)
+                                    }));
+                                }
+                                side_effects = [];
+                            }
+                            tail.push(def);
+                        } else {
+                            head.push(def);
+                        }
+                    } else if (sym.orig[0] instanceof AST_SymbolCatch) {
+                        var value = def.value && def.value.drop_side_effect_free(compressor);
+                        if (value) side_effects.push(value);
+                        def.value = null;
+                        head.push(def);
+                    } else {
+                        var value = def.value && def.value.drop_side_effect_free(compressor);
+                        if (value) {
+                            side_effects.push(value);
+                        }
+                        sym.eliminated++;
+                    }
+                });
+                if (head.length > 0 || tail.length > 0) {
+                    node.definitions = head.concat(tail);
+                    body.push(node);
+                }
+                if (side_effects.length > 0) {
+                    body.push(make_node(AST_SimpleStatement, node, {
+                        body: make_sequence(node, side_effects)
+                    }));
+                }
+                switch (body.length) {
+                  case 0:
+                    return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
+                  case 1:
+                    return body[0];
+                  default:
+                    return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { body });
+                }
+            }
+            // certain combination of unused name + side effect leads to:
+            //    https://github.com/mishoo/UglifyJS2/issues/44
+            //    https://github.com/mishoo/UglifyJS2/issues/1830
+            //    https://github.com/mishoo/UglifyJS2/issues/1838
+            // that's an invalid AST.
+            // We fix it at this stage by moving the `var` outside the `for`.
+            if (node instanceof AST_For) {
+                descend(node, this);
+                var block;
+                if (node.init instanceof AST_BlockStatement) {
+                    block = node.init;
+                    node.init = block.body.pop();
+                    block.body.push(node);
+                }
+                if (node.init instanceof AST_SimpleStatement) {
+                    node.init = node.init.body;
+                } else if (is_empty(node.init)) {
+                    node.init = null;
+                }
+                return !block ? node : in_list ? MAP.splice(block.body) : block;
+            }
+            if (node instanceof AST_LabeledStatement
+                && node.body instanceof AST_For
+            ) {
+                descend(node, this);
+                if (node.body instanceof AST_BlockStatement) {
+                    var block = node.body;
+                    node.body = block.body.pop();
+                    block.body.push(node);
+                    return in_list ? MAP.splice(block.body) : block;
+                }
+                return node;
+            }
+            if (node instanceof AST_BlockStatement) {
+                descend(node, this);
+                if (in_list && node.body.every(can_be_evicted_from_block)) {
+                    return MAP.splice(node.body);
+                }
+                return node;
+            }
+            if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
+                const save_scope = scope;
+                scope = node;
+                descend(node, this);
+                scope = save_scope;
+                return node;
+            }
+        },
+        function after(node, in_list) {
+            if (node instanceof AST_Sequence) {
+                switch (node.expressions.length) {
+                    case 0: return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
+                    case 1: return node.expressions[0];
+                }
+            }
+        }
+    );
+
+    self.transform(tt);
+
+    function scan_ref_scoped(node, descend) {
+        var node_def;
+        const sym = assign_as_unused(node);
+        if (sym instanceof AST_SymbolRef
+            && !is_ref_of(node.left, AST_SymbolBlockDeclaration)
+            && self.variables.get(sym.name) === (node_def = sym.definition())
+        ) {
+            if (node instanceof AST_Assign) {
+                node.right.walk(tw);
+                if (!node_def.chained && node.left.fixed_value() === node.right) {
+                    fixed_ids.set(node_def.id, node);
+                }
+            }
+            return true;
+        }
+        if (node instanceof AST_SymbolRef) {
+            node_def = node.definition();
+            if (!in_use_ids.has(node_def.id)) {
+                in_use_ids.set(node_def.id, node_def);
+                if (node_def.orig[0] instanceof AST_SymbolCatch) {
+                    const redef = node_def.scope.is_block_scope()
+                        && node_def.scope.get_defun_scope().variables.get(node_def.name);
+                    if (redef) in_use_ids.set(redef.id, redef);
+                }
+            }
+            return true;
+        }
+        if (node instanceof AST_Class) {
+            descend();
+            return true;
+        }
+        if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
+            var save_scope = scope;
+            scope = node;
+            descend();
+            scope = save_scope;
+            return true;
+        }
+    }
+});
Index: frontend/node_modules/terser/lib/compress/evaluate.js
===================================================================
--- frontend/node_modules/terser/lib/compress/evaluate.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/evaluate.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,527 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    HOP,
+    makePredicate,
+    return_this,
+    string_template,
+    regexp_source_fix,
+    regexp_is_safe,
+} from "../utils/index.js";
+import {
+    AST_Array,
+    AST_BigInt,
+    AST_Binary,
+    AST_Call,
+    AST_Chain,
+    AST_Class,
+    AST_Conditional,
+    AST_Constant,
+    AST_Dot,
+    AST_Expansion,
+    AST_Function,
+    AST_Lambda,
+    AST_New,
+    AST_Node,
+    AST_Object,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Statement,
+    AST_Symbol,
+    AST_SymbolRef,
+    AST_TemplateString,
+    AST_UnaryPrefix,
+    AST_With,
+} from "../ast.js";
+import { is_undeclared_ref} from "./inference.js";
+
+// methods to evaluate a constant expression
+
+function def_eval(node, func) {
+    node.DEFMETHOD("_eval", func);
+}
+
+// Used to propagate a nullish short-circuit signal upwards through the chain.
+export const nullish = Symbol("This AST_Chain is nullish");
+
+// If the node has been successfully reduced to a constant,
+// then its value is returned; otherwise the element itself
+// is returned.
+// They can be distinguished as constant value is never a
+// descendant of AST_Node.
+AST_Node.DEFMETHOD("evaluate", function (compressor) {
+    if (!compressor.option("evaluate"))
+        return this;
+    var val = this._eval(compressor, 1);
+    if (!val || val instanceof RegExp)
+        return val;
+    if (typeof val == "function" || typeof val == "object" || val == nullish)
+        return this;
+
+    // Evaluated strings can be larger than the original expression
+    if (typeof val === "string") {
+        const unevaluated_size = this.size(compressor);
+        if (val.length + 2 > unevaluated_size) return this;
+    }
+
+    return val;
+});
+
+var unaryPrefix = makePredicate("! ~ - + void");
+AST_Node.DEFMETHOD("is_constant", function () {
+    // Accomodate when compress option evaluate=false
+    // as well as the common constant expressions !0 and -1
+    if (this instanceof AST_Constant) {
+        return !(this instanceof AST_RegExp);
+    } else {
+        return this instanceof AST_UnaryPrefix
+            && unaryPrefix.has(this.operator)
+            && (
+                // `this.expression` may be an `AST_RegExp`,
+                // so not only `.is_constant()`.
+                this.expression instanceof AST_Constant
+                || this.expression.is_constant()
+            );
+    }
+});
+
+def_eval(AST_Statement, function () {
+    throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
+});
+
+def_eval(AST_Lambda, return_this);
+def_eval(AST_Class, return_this);
+def_eval(AST_Node, return_this);
+def_eval(AST_Constant, function () {
+    return this.getValue();
+});
+
+const supports_bigint = typeof BigInt === "function";
+def_eval(AST_BigInt, function () {
+    if (supports_bigint) {
+        return BigInt(this.value);
+    } else {
+        return this;
+    }
+});
+
+def_eval(AST_RegExp, function (compressor) {
+    let evaluated = compressor.evaluated_regexps.get(this.value);
+    if (evaluated === undefined && regexp_is_safe(this.value.source)) {
+        try {
+            const { source, flags } = this.value;
+            evaluated = new RegExp(source, flags);
+        } catch (e) {
+            evaluated = null;
+        }
+        compressor.evaluated_regexps.set(this.value, evaluated);
+    }
+    return evaluated || this;
+});
+
+def_eval(AST_TemplateString, function () {
+    if (this.segments.length !== 1) return this;
+    return this.segments[0].value;
+});
+
+def_eval(AST_Function, function (compressor) {
+    if (compressor.option("unsafe")) {
+        var fn = function () { };
+        fn.node = this;
+        fn.toString = () => this.print_to_string();
+        return fn;
+    }
+    return this;
+});
+
+def_eval(AST_Array, function (compressor, depth) {
+    if (compressor.option("unsafe")) {
+        var elements = [];
+        for (var i = 0, len = this.elements.length; i < len; i++) {
+            var element = this.elements[i];
+            var value = element._eval(compressor, depth);
+            if (element === value)
+                return this;
+            elements.push(value);
+        }
+        return elements;
+    }
+    return this;
+});
+
+def_eval(AST_Object, function (compressor, depth) {
+    if (compressor.option("unsafe")) {
+        var val = {};
+        for (var i = 0, len = this.properties.length; i < len; i++) {
+            var prop = this.properties[i];
+            if (prop instanceof AST_Expansion)
+                return this;
+            var key = prop.key;
+            if (key instanceof AST_Symbol) {
+                key = key.name;
+            } else if (key instanceof AST_Node) {
+                key = key._eval(compressor, depth);
+                if (key === prop.key)
+                    return this;
+            }
+            if (typeof Object.prototype[key] === "function") {
+                return this;
+            }
+            if (prop.value instanceof AST_Function)
+                continue;
+            val[key] = prop.value._eval(compressor, depth);
+            if (val[key] === prop.value)
+                return this;
+        }
+        return val;
+    }
+    return this;
+});
+
+var non_converting_unary = makePredicate("! typeof void");
+def_eval(AST_UnaryPrefix, function (compressor, depth) {
+    var e = this.expression;
+    if (compressor.option("typeofs")
+        && this.operator == "typeof") {
+        // Function would be evaluated to an array and so typeof would
+        // incorrectly return 'object'. Hence making is a special case.
+        if (e instanceof AST_Lambda
+            || e instanceof AST_SymbolRef
+            && e.fixed_value() instanceof AST_Lambda) {
+            return typeof function () { };
+        }
+        if (
+            (e instanceof AST_Object
+                || e instanceof AST_Array
+                || (e instanceof AST_SymbolRef
+                    && (e.fixed_value() instanceof AST_Object
+                        || e.fixed_value() instanceof AST_Array)))
+            && !e.has_side_effects(compressor)
+        ) {
+            return typeof {};
+        }
+    }
+    if (!non_converting_unary.has(this.operator))
+        depth++;
+    e = e._eval(compressor, depth);
+    if (e === this.expression)
+        return this;
+    switch (this.operator) {
+        case "!": return !e;
+        case "typeof":
+            // typeof <RegExp> returns "object" or "function" on different platforms
+            // so cannot evaluate reliably
+            if (e instanceof RegExp)
+                return this;
+            return typeof e;
+        case "void": return void e;
+        case "~": return ~e;
+        case "-": return -e;
+        case "+": return +e;
+    }
+    return this;
+});
+
+var non_converting_binary = makePredicate("&& || ?? === !==");
+const identity_comparison = makePredicate("== != === !==");
+const has_identity = value => typeof value === "object"
+    || typeof value === "function"
+    || typeof value === "symbol";
+
+def_eval(AST_Binary, function (compressor, depth) {
+    if (!non_converting_binary.has(this.operator))
+        depth++;
+
+    var left = this.left._eval(compressor, depth);
+    if (left === this.left)
+        return this;
+    var right = this.right._eval(compressor, depth);
+    if (right === this.right)
+        return this;
+
+    if (left != null
+        && right != null
+        && identity_comparison.has(this.operator)
+        && has_identity(left)
+        && has_identity(right)
+        && typeof left === typeof right) {
+        // Do not compare by reference
+        return this;
+    }
+
+    // Do not mix BigInt and Number; Don't use `>>>` on BigInt or `/ 0n`
+    if (
+        (typeof left === "bigint") !== (typeof right === "bigint")
+        || typeof left === "bigint"
+            && (this.operator === ">>>"
+                || this.operator === "/" && Number(right) === 0)
+    ) {
+        return this;
+    }
+
+    var result;
+    switch (this.operator) {
+        case "&&": result = left && right; break;
+        case "||": result = left || right; break;
+        case "??": result = left != null ? left : right; break;
+        case "|": result = left | right; break;
+        case "&": result = left & right; break;
+        case "^": result = left ^ right; break;
+        case "+": result = left + right; break;
+        case "*": result = left * right; break;
+        case "**": result = left ** right; break;
+        case "/": result = left / right; break;
+        case "%": result = left % right; break;
+        case "-": result = left - right; break;
+        case "<<": result = left << right; break;
+        case ">>": result = left >> right; break;
+        case ">>>": result = left >>> right; break;
+        case "==": result = left == right; break;
+        case "===": result = left === right; break;
+        case "!=": result = left != right; break;
+        case "!==": result = left !== right; break;
+        case "<": result = left < right; break;
+        case "<=": result = left <= right; break;
+        case ">": result = left > right; break;
+        case ">=": result = left >= right; break;
+        default:
+            return this;
+    }
+    if (typeof result === "number" && isNaN(result) && compressor.find_parent(AST_With)) {
+        // leave original expression as is
+        return this;
+    }
+    return result;
+});
+
+def_eval(AST_Conditional, function (compressor, depth) {
+    var condition = this.condition._eval(compressor, depth);
+    if (condition === this.condition)
+        return this;
+    var node = condition ? this.consequent : this.alternative;
+    var value = node._eval(compressor, depth);
+    return value === node ? this : value;
+});
+
+// Set of AST_SymbolRef which are currently being evaluated.
+// Avoids infinite recursion of ._eval()
+const reentrant_ref_eval = new Set();
+def_eval(AST_SymbolRef, function (compressor, depth) {
+    if (reentrant_ref_eval.has(this))
+        return this;
+
+    var fixed = this.fixed_value();
+    if (!fixed)
+        return this;
+
+    reentrant_ref_eval.add(this);
+    const value = fixed._eval(compressor, depth);
+    reentrant_ref_eval.delete(this);
+
+    if (value === fixed)
+        return this;
+
+    if (value && typeof value == "object") {
+        var escaped = this.definition().escaped;
+        if (escaped && depth > escaped)
+            return this;
+    }
+    return value;
+});
+
+def_eval(AST_Chain, function (compressor, depth) {
+    const evaluated = this.expression._eval(compressor, depth, /*ast_chain=*/true);
+    return evaluated === nullish
+        ? undefined
+        : evaluated === this.expression
+          ? this
+          : evaluated;
+});
+
+const global_objs = { Array, Math, Number, Object, String };
+
+const regexp_flags = new Set([
+    "dotAll",
+    "global",
+    "ignoreCase",
+    "multiline",
+    "sticky",
+    "unicode",
+]);
+
+def_eval(AST_PropAccess, function (compressor, depth, ast_chain) {
+    let obj = (ast_chain || this.property === "length" || compressor.option("unsafe"))
+        && this.expression._eval(compressor, depth + 1, ast_chain);
+
+    if (ast_chain) {
+        if (obj === nullish || (this.optional && obj == null)) return nullish;
+    }
+
+    // `.length` of strings and arrays is always safe
+    if (this.property === "length") {
+        if (typeof obj === "string") {
+            return obj.length;
+        }
+
+        const is_spreadless_array =
+            obj instanceof AST_Array
+            && obj.elements.every(el => !(el instanceof AST_Expansion));
+
+        if (
+            is_spreadless_array
+            && obj.elements.every(el => !el.has_side_effects(compressor))
+        ) {
+            return obj.elements.length;
+        }
+    }
+
+    if (compressor.option("unsafe")) {
+        var key = this.property;
+        if (key instanceof AST_Node) {
+            key = key._eval(compressor, depth);
+            if (key === this.property)
+                return this;
+        }
+
+        var exp = this.expression;
+        if (is_undeclared_ref(exp)) {
+            var aa;
+            var first_arg = exp.name === "hasOwnProperty"
+                && key === "call"
+                && (aa = compressor.parent() && compressor.parent().args)
+                && (aa && aa[0]
+                    && aa[0].evaluate(compressor));
+
+            first_arg = first_arg instanceof AST_Dot ? first_arg.expression : first_arg;
+
+            if (first_arg == null || first_arg.thedef && first_arg.thedef.undeclared) {
+                return this.clone();
+            }
+            if (!compressor.is_pure_native_static_property(exp.name, key))
+                return this;
+            obj = global_objs[exp.name];
+        } else {
+            if (obj instanceof RegExp) {
+                if (key == "source") {
+                    return regexp_source_fix(obj.source);
+                } else if (key == "flags" || regexp_flags.has(key)) {
+                    return obj[key];
+                }
+            }
+            if (!obj || obj === exp || !HOP(obj, key))
+                return this;
+
+            if (typeof obj == "function")
+                switch (key) {
+                    case "name":
+                        return obj.node.name ? obj.node.name.name : "";
+                    case "length":
+                        return obj.node.length_property();
+                    default:
+                        return this;
+                }
+        }
+        return obj[key];
+    }
+    return this;
+});
+
+def_eval(AST_Call, function (compressor, depth, ast_chain) {
+    var exp = this.expression;
+
+    if (ast_chain) {
+        const callee = exp._eval(compressor, depth, ast_chain);
+        if (callee === nullish || (this.optional && callee == null)) return nullish;
+    }
+
+    if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
+        var key = exp.property;
+        if (key instanceof AST_Node) {
+            key = key._eval(compressor, depth);
+            if (typeof key !== "string" && typeof key !== "number")
+                return this;
+        }
+        var val;
+        var e = exp.expression;
+        if (is_undeclared_ref(e)) {
+            var first_arg = e.name === "hasOwnProperty" &&
+                key === "call" &&
+                (this.args[0] && this.args[0].evaluate(compressor));
+
+            first_arg = first_arg instanceof AST_Dot ? first_arg.expression : first_arg;
+
+            if ((first_arg == null || first_arg.thedef && first_arg.thedef.undeclared)) {
+                return this.clone();
+            }
+            if (!compressor.is_pure_native_static_fn(e.name, key)) return this;
+            val = global_objs[e.name];
+        } else {
+            val = e._eval(compressor, depth + 1, /* don't pass ast_chain (exponential work) */);
+
+            if (val === e || !val)
+                return this;
+            if (!compressor.is_pure_native_method(val.constructor.name, key))
+                return this;
+        }
+        var args = [];
+        for (var i = 0, len = this.args.length; i < len; i++) {
+            var arg = this.args[i];
+            var value = arg._eval(compressor, depth);
+            if (arg === value)
+                return this;
+            if (arg instanceof AST_Lambda)
+                return this;
+            args.push(value);
+        }
+        try {
+            return val[key].apply(val, args);
+        } catch (ex) {
+            // We don't really care
+        }
+    }
+    return this;
+});
+
+// Also a subclass of AST_Call
+def_eval(AST_New, return_this);
Index: frontend/node_modules/terser/lib/compress/global-defs.js
===================================================================
--- frontend/node_modules/terser/lib/compress/global-defs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/global-defs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,92 @@
+import {
+    AST_Array,
+    AST_Chain,
+    AST_Constant,
+    AST_Dot,
+    AST_ImportMeta,
+    AST_Node,
+    AST_Object,
+    AST_ObjectKeyVal,
+    AST_PropAccess,
+    AST_SymbolDeclaration,
+    AST_SymbolRef,
+    AST_Toplevel,
+    TreeTransformer,
+} from "../ast.js";
+import { make_node, noop, HOP } from "../utils/index.js";
+import { make_node_from_constant } from "./common.js";
+import { is_lhs } from "./inference.js";
+
+(function(def_find_defs) {
+    function to_node(value, orig) {
+        if (value instanceof AST_Node) {
+            if (!(value instanceof AST_Constant)) {
+                // Value may be a function, an array including functions and even a complex assign / block expression,
+                // so it should never be shared in different places.
+                // Otherwise wrong information may be used in the compression phase
+                value = value.clone(true);
+            }
+            return make_node(value.CTOR, orig, value);
+        }
+        if (Array.isArray(value)) return make_node(AST_Array, orig, {
+            elements: value.map(function(value) {
+                return to_node(value, orig);
+            })
+        });
+        if (value && typeof value == "object") {
+            var props = [];
+            for (var key in value) if (HOP(value, key)) {
+                props.push(make_node(AST_ObjectKeyVal, orig, {
+                    key: key,
+                    value: to_node(value[key], orig)
+                }));
+            }
+            return make_node(AST_Object, orig, {
+                properties: props
+            });
+        }
+        return make_node_from_constant(value, orig);
+    }
+
+    AST_Toplevel.DEFMETHOD("resolve_defines", function(compressor) {
+        if (!compressor.option("global_defs")) return this;
+        this.figure_out_scope({ ie8: compressor.option("ie8") });
+        return this.transform(new TreeTransformer(function(node) {
+            var def = node._find_defs(compressor, "");
+            if (!def) return;
+            var level = 0, child = node, parent;
+            while (parent = this.parent(level++)) {
+                if (!(parent instanceof AST_PropAccess)) break;
+                if (parent.expression !== child) break;
+                child = parent;
+            }
+            if (is_lhs(child, parent)) {
+                return;
+            }
+            return def;
+        }));
+    });
+    def_find_defs(AST_Node, noop);
+    def_find_defs(AST_Chain, function(compressor, suffix) {
+        return this.expression._find_defs(compressor, suffix);
+    });
+    def_find_defs(AST_Dot, function(compressor, suffix) {
+        return this.expression._find_defs(compressor, "." + this.property + suffix);
+    });
+    def_find_defs(AST_SymbolDeclaration, function() {
+        if (!this.global()) return;
+    });
+    def_find_defs(AST_SymbolRef, function(compressor, suffix) {
+        if (!this.global()) return;
+        var defines = compressor.option("global_defs");
+        var name = this.name + suffix;
+        if (HOP(defines, name)) return to_node(defines[name], this);
+    });
+    def_find_defs(AST_ImportMeta, function(compressor, suffix) {
+        var defines = compressor.option("global_defs");
+        var name = "import.meta" + suffix;
+        if (HOP(defines, name)) return to_node(defines[name], this);
+    });
+})(function(node, func) {
+    node.DEFMETHOD("_find_defs", func);
+});
Index: frontend/node_modules/terser/lib/compress/index.js
===================================================================
--- frontend/node_modules/terser/lib/compress/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,4136 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    AST_Accessor,
+    AST_Array,
+    AST_Arrow,
+    AST_Assign,
+    AST_BigInt,
+    AST_Binary,
+    AST_Block,
+    AST_BlockStatement,
+    AST_Boolean,
+    AST_Break,
+    AST_Call,
+    AST_Catch,
+    AST_Chain,
+    AST_Class,
+    AST_ClassProperty,
+    AST_ClassStaticBlock,
+    AST_ConciseMethod,
+    AST_Conditional,
+    AST_Const,
+    AST_Constant,
+    AST_Debugger,
+    AST_Default,
+    AST_DefaultAssign,
+    AST_Definitions,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Directive,
+    AST_Do,
+    AST_Dot,
+    AST_DotHash,
+    AST_DWLoop,
+    AST_EmptyStatement,
+    AST_Exit,
+    AST_Expansion,
+    AST_Export,
+    AST_False,
+    AST_For,
+    AST_ForIn,
+    AST_Function,
+    AST_Hole,
+    AST_If,
+    AST_Import,
+    AST_Infinity,
+    AST_LabeledStatement,
+    AST_Lambda,
+    AST_Let,
+    AST_NaN,
+    AST_New,
+    AST_Node,
+    AST_Null,
+    AST_Number,
+    AST_Object,
+    AST_ObjectKeyVal,
+    AST_ObjectProperty,
+    AST_PrefixedTemplateString,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Return,
+    AST_Scope,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Statement,
+    AST_String,
+    AST_Sub,
+    AST_Switch,
+    AST_SwitchBranch,
+    AST_Symbol,
+    AST_SymbolClassProperty,
+    AST_SymbolDeclaration,
+    AST_SymbolDefun,
+    AST_SymbolExport,
+    AST_SymbolFunarg,
+    AST_SymbolLambda,
+    AST_SymbolLet,
+    AST_SymbolMethod,
+    AST_SymbolRef,
+    AST_SymbolUsing,
+    AST_TemplateString,
+    AST_This,
+    AST_Toplevel,
+    AST_True,
+    AST_Try,
+    AST_Unary,
+    AST_UnaryPostfix,
+    AST_UnaryPrefix,
+    AST_Undefined,
+    AST_Using,
+    AST_Var,
+    AST_VarDef,
+    AST_While,
+    AST_With,
+    AST_Yield,
+
+    TreeTransformer,
+    TreeWalker,
+    walk,
+    walk_abort,
+
+    _NOINLINE,
+} from "../ast.js";
+import {
+    defaults,
+    HOP,
+    make_node,
+    make_void_0,
+    makePredicate,
+    MAP,
+    remove,
+    return_false,
+    return_true,
+    regexp_source_fix,
+    has_annotation,
+    regexp_is_safe,
+} from "../utils/index.js";
+import { first_in_statement } from "../utils/first_in_statement.js";
+import { equivalent_to } from "../equivalent-to.js";
+import {
+    is_basic_identifier_string,
+    JS_Parse_Error,
+    parse,
+    PRECEDENCE,
+} from "../parse.js";
+import { OutputStream } from "../output.js";
+import { base54, format_mangler_options } from "../scope.js";
+import "../size.js";
+
+import "./evaluate.js";
+import "./drop-side-effect-free.js";
+import "./drop-unused.js";
+import "./reduce-vars.js";
+import {
+    is_undeclared_ref,
+    bitwise_binop,
+    lazy_op,
+    is_nullish,
+    is_undefined,
+    is_lhs,
+    aborts,
+    is_used_in_expression,
+} from "./inference.js";
+import {
+    SQUEEZED,
+    OPTIMIZED,
+    CLEAR_BETWEEN_PASSES,
+    TOP,
+    UNDEFINED,
+    UNUSED,
+    TRUTHY,
+    FALSY,
+    has_flag,
+    set_flag,
+    clear_flag,
+} from "./compressor-flags.js";
+import {
+    make_sequence,
+    best_of,
+    best_of_expression,
+    make_empty_function,
+    make_node_from_constant,
+    merge_sequence,
+    get_simple_key,
+    has_break_or_continue,
+    maintain_this_binding,
+    is_empty,
+    is_identifier_atom,
+    is_reachable,
+    can_be_evicted_from_block,
+    as_statement_array,
+    is_func_expr,
+} from "./common.js";
+import { tighten_body, extract_from_unreachable_code } from "./tighten-body.js";
+import { inline_into_symbolref, inline_into_call } from "./inline.js";
+import "./global-defs.js";
+import { is_pure_native_fn, is_pure_native_method, is_pure_native_static_fn, is_pure_native_static_property, pure_access_globals } from "./native-objects.js";
+
+class Compressor extends TreeWalker {
+    constructor(options, { false_by_default = false, mangle_options = false }) {
+        super();
+        if (options.defaults !== undefined && !options.defaults) false_by_default = true;
+        this.options = defaults(options, {
+            arguments     : false,
+            arrows        : !false_by_default,
+            booleans      : !false_by_default,
+            booleans_as_integers : false,
+            collapse_vars : !false_by_default,
+            comparisons   : !false_by_default,
+            computed_props: !false_by_default,
+            conditionals  : !false_by_default,
+            dead_code     : !false_by_default,
+            defaults      : true,
+            directives    : !false_by_default,
+            drop_console  : false,
+            drop_debugger : !false_by_default,
+            ecma          : 5,
+            builtins_ecma : 5,
+            builtins_pure : false,
+            evaluate      : !false_by_default,
+            expression    : false,
+            global_defs   : false,
+            hoist_funs    : false,
+            hoist_props   : !false_by_default,
+            hoist_vars    : false,
+            ie8           : false,
+            if_return     : !false_by_default,
+            inline        : !false_by_default,
+            join_vars     : !false_by_default,
+            keep_classnames: false,
+            keep_fargs    : true,
+            keep_fnames   : false,
+            keep_infinity : false,
+            lhs_constants : !false_by_default,
+            loops         : !false_by_default,
+            module        : false,
+            negate_iife   : !false_by_default,
+            passes        : 1,
+            properties    : !false_by_default,
+            pure_getters  : !false_by_default && "strict",
+            pure_funcs    : null,
+            pure_new      : false,
+            reduce_funcs  : !false_by_default,
+            reduce_vars   : !false_by_default,
+            sequences     : !false_by_default,
+            side_effects  : !false_by_default,
+            switches      : !false_by_default,
+            top_retain    : null,
+            toplevel      : !!(options && options["top_retain"]),
+            typeofs       : !false_by_default,
+            unsafe        : false,
+            unsafe_arrows : false,
+            unsafe_comps  : false,
+            unsafe_Function: false,
+            unsafe_math   : false,
+            unsafe_symbols: false,
+            unsafe_methods: false,
+            unsafe_proto  : false,
+            unsafe_regexp : false,
+            unsafe_undefined: false,
+            unused        : !false_by_default,
+            warnings      : false  // legacy
+        }, true);
+        var global_defs = this.options["global_defs"];
+        if (typeof global_defs == "object") for (var key in global_defs) {
+            if (key[0] === "@" && HOP(global_defs, key)) {
+                global_defs[key.slice(1)] = parse(global_defs[key], {
+                    expression: true
+                });
+            }
+        }
+        if (this.options["inline"] === true) this.options["inline"] = 3;
+        var pure_funcs = this.options["pure_funcs"];
+        if (typeof pure_funcs == "function") {
+            this.pure_funcs = pure_funcs;
+        } else {
+            this.pure_funcs = pure_funcs ? function(node) {
+                return !pure_funcs.includes(node.expression.print_to_string());
+            } : return_true;
+        }
+        var top_retain = this.options["top_retain"];
+        if (top_retain instanceof RegExp) {
+            this.top_retain = function(def) {
+                return top_retain.test(def.name);
+            };
+        } else if (typeof top_retain == "function") {
+            this.top_retain = top_retain;
+        } else if (top_retain) {
+            if (typeof top_retain == "string") {
+                top_retain = top_retain.split(/,/);
+            }
+            this.top_retain = function(def) {
+                return top_retain.includes(def.name);
+            };
+        }
+        if (this.options["module"]) {
+            this.directives["use strict"] = true;
+            this.options["toplevel"] = true;
+        }
+        var toplevel = this.options["toplevel"];
+        this.toplevel = typeof toplevel == "string" ? {
+            funcs: /funcs/.test(toplevel),
+            vars: /vars/.test(toplevel)
+        } : {
+            funcs: toplevel,
+            vars: toplevel
+        };
+        var sequences = this.options["sequences"];
+        this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
+        this.evaluated_regexps = new Map();
+        this._toplevel = undefined;
+        this._mangle_options = mangle_options
+            ? format_mangler_options(mangle_options)
+            : mangle_options;
+
+        this.pure_access_globals = pure_access_globals(this);
+        this.is_pure_native_fn = is_pure_native_fn(this);
+        this.is_pure_native_method = is_pure_native_method(this);
+        this.is_pure_native_static_fn = is_pure_native_static_fn(this);
+        this.is_pure_native_static_property = is_pure_native_static_property(this);
+    }
+
+    mangle_options() {
+        var nth_identifier = this._mangle_options && this._mangle_options.nth_identifier || base54;
+        var module = this._mangle_options && this._mangle_options.module || this.option("module");
+        return { ie8: this.option("ie8"), nth_identifier, module };
+    }
+
+    option(key) {
+        return this.options[key];
+    }
+
+    exposed(def) {
+        if (def.export) return true;
+        if (def.global) for (var i = 0, len = def.orig.length; i < len; i++)
+            if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
+                return true;
+        return false;
+    }
+
+    in_boolean_context() {
+        if (!this.option("booleans")) return false;
+        var self = this.self();
+        for (var i = 0, p; p = this.parent(i); i++) {
+            if (p instanceof AST_SimpleStatement
+                || p instanceof AST_Conditional && p.condition === self
+                || p instanceof AST_DWLoop && p.condition === self
+                || p instanceof AST_For && p.condition === self
+                || p instanceof AST_If && p.condition === self
+                || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
+                return true;
+            }
+            if (
+                p instanceof AST_Binary
+                    && (
+                        p.operator == "&&"
+                        || p.operator == "||"
+                        || p.operator == "??"
+                    )
+                || p instanceof AST_Conditional
+                || p.tail_node() === self
+            ) {
+                self = p;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /** True if compressor.self()'s result will be turned into a 32-bit integer.
+     * ex:
+     * ~{expr}
+     * (1, 2, {expr}) | 0
+     **/
+    in_32_bit_context(other_operand_must_be_number) {
+        if (!this.option("evaluate")) return false;
+        var self = this.self();
+        for (var i = 0, p; p = this.parent(i); i++) {
+            if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) {
+                if (other_operand_must_be_number) {
+                    return (self === p.left ? p.right : p.left).is_number(this);
+                } else {
+                    return true;
+                }
+            }
+            if (p instanceof AST_UnaryPrefix) {
+                return p.operator === "~";
+            }
+            if (
+                p instanceof AST_Binary
+                    && (
+                        // Don't talk about p.left. Can change branch taken
+                        p.operator == "&&" && p.right === self
+                        || p.operator == "||" && p.right === self
+                        || p.operator == "??" && p.right === self
+                    )
+                || p instanceof AST_Conditional && p.condition !== self
+                || p.tail_node() === self
+            ) {
+                self = p;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    in_computed_key() {
+        if (!this.option("evaluate")) return false;
+        var self = this.self();
+        for (var i = 0, p; p = this.parent(i); i++) {
+            if (p instanceof AST_ObjectProperty && p.key === self) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    get_toplevel() {
+        return this._toplevel;
+    }
+
+    compress(toplevel) {
+        toplevel = toplevel.resolve_defines(this);
+        this._toplevel = toplevel;
+        if (this.option("expression")) {
+            this._toplevel.process_expression(true);
+        }
+        var passes = +this.options.passes || 1;
+        var min_count = 1 / 0;
+        var stopping = false;
+        var mangle = this.mangle_options();
+        for (var pass = 0; pass < passes; pass++) {
+            this._toplevel.figure_out_scope(mangle);
+            if (pass === 0 && this.option("drop_console")) {
+                // must be run before reduce_vars and compress pass
+                this._toplevel = this._toplevel.drop_console(this.option("drop_console"));
+            }
+            if (pass > 0 || this.option("reduce_vars")) {
+                this._toplevel.reset_opt_flags(this);
+            }
+            this._toplevel = this._toplevel.transform(this);
+            if (passes > 1) {
+                let count = 0;
+                walk(this._toplevel, () => { count++; });
+                if (count < min_count) {
+                    min_count = count;
+                    stopping = false;
+                } else if (stopping) {
+                    break;
+                } else {
+                    stopping = true;
+                }
+            }
+        }
+        if (this.option("expression")) {
+            this._toplevel.process_expression(false);
+        }
+        toplevel = this._toplevel;
+        this._toplevel = undefined;
+        return toplevel;
+    }
+
+    before(node, descend) {
+        if (has_flag(node, SQUEEZED)) return node;
+        var was_scope = false;
+        if (node instanceof AST_Scope) {
+            node = node.hoist_properties(this);
+            node = node.hoist_declarations(this);
+            was_scope = true;
+        }
+        // Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize()
+        // would call AST_Node.transform() if a different instance of AST_Node is
+        // produced after def_optimize().
+        // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
+        // Migrate and defer all children's AST_Node.transform() to below, which
+        // will now happen after this parent AST_Node has been properly substituted
+        // thus gives a consistent AST snapshot.
+        descend(node, this);
+        // Existing code relies on how AST_Node.optimize() worked, and omitting the
+        // following replacement call would result in degraded efficiency of both
+        // output and performance.
+        descend(node, this);
+        var opt = node.optimize(this);
+        if (was_scope && opt instanceof AST_Scope) {
+            opt.drop_unused(this);
+            descend(opt, this);
+        }
+        if (opt === node) set_flag(opt, SQUEEZED);
+        return opt;
+    }
+
+    /** Alternative to plain is_lhs() which doesn't work within .optimize() */
+    is_lhs() {
+        const self = this.stack[this.stack.length - 1];
+        const parent = this.stack[this.stack.length - 2];
+        return is_lhs(self, parent);
+    }
+}
+
+
+function def_optimize(node, optimizer) {
+    node.DEFMETHOD("optimize", function(compressor) {
+        var self = this;
+        if (has_flag(self, OPTIMIZED)) return self;
+        if (compressor.has_directive("use asm")) return self;
+        var opt = optimizer(self, compressor);
+        set_flag(opt, OPTIMIZED);
+        return opt;
+    });
+}
+
+def_optimize(AST_Node, function(self) {
+    return self;
+});
+
+AST_Toplevel.DEFMETHOD("drop_console", function(options) {
+    const isArray = Array.isArray(options);
+    const tt = new TreeTransformer(function(self) {
+        if (self.TYPE !== "Call") {
+            return;
+        }
+
+        var exp = self.expression;
+
+        if (!(exp instanceof AST_PropAccess)) {
+            return;
+        }
+
+        var name = exp.expression;
+        var property = exp.property;
+        var depth = 2;
+        while (name.expression) {
+            property = name.property;
+            name = name.expression;
+            depth++;
+        }
+
+        if (isArray && !options.includes(property)) {
+            return;
+        }
+
+        if (is_undeclared_ref(name) && name.name == "console") {
+            if (
+                depth === 3
+                && !["call", "apply"].includes(exp.property)
+                && is_used_in_expression(tt)
+            ) {
+                // a (used) call to Function.prototype methods (eg: console.log.bind(console))
+                // but not .call and .apply which would also return undefined.
+                exp.expression = make_empty_function(self);
+                set_flag(exp.expression, SQUEEZED);
+                self.args = [];
+            } else {
+                return make_void_0(self);
+            }
+        }
+    });
+
+    return this.transform(tt);
+});
+
+AST_Node.DEFMETHOD("equivalent_to", function(node) {
+    return equivalent_to(this, node);
+});
+
+AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
+    var self = this;
+    var tt = new TreeTransformer(function(node) {
+        if (insert && node instanceof AST_SimpleStatement) {
+            return make_node(AST_Return, node, {
+                value: node.body
+            });
+        }
+        if (!insert && node instanceof AST_Return) {
+            if (compressor) {
+                var value = node.value && node.value.drop_side_effect_free(compressor, true);
+                return value
+                    ? make_node(AST_SimpleStatement, node, { body: value })
+                    : make_node(AST_EmptyStatement, node);
+            }
+            return make_node(AST_SimpleStatement, node, {
+                body: node.value || make_void_0(node)
+            });
+        }
+        if (node instanceof AST_Class || node instanceof AST_Lambda && node !== self) {
+            return node;
+        }
+        if (node instanceof AST_Block) {
+            var index = node.body.length - 1;
+            if (index >= 0) {
+                node.body[index] = node.body[index].transform(tt);
+            }
+        } else if (node instanceof AST_If) {
+            node.body = node.body.transform(tt);
+            if (node.alternative) {
+                node.alternative = node.alternative.transform(tt);
+            }
+        } else if (node instanceof AST_With) {
+            node.body = node.body.transform(tt);
+        }
+        return node;
+    });
+    self.transform(tt);
+});
+
+AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) {
+    const self = this;
+    const reduce_vars = compressor.option("reduce_vars");
+
+    const preparation = new TreeWalker(function(node, descend) {
+        clear_flag(node, CLEAR_BETWEEN_PASSES);
+        if (reduce_vars) {
+            if (compressor.top_retain
+                && node instanceof AST_Defun  // Only functions are retained
+                && preparation.parent() === self
+            ) {
+                set_flag(node, TOP);
+            }
+            return node.reduce_vars(preparation, descend, compressor);
+        }
+    });
+    // Stack of look-up tables to keep track of whether a `SymbolDef` has been
+    // properly assigned before use:
+    // - `push()` & `pop()` when visiting conditional branches
+    preparation.safe_ids = Object.create(null);
+    preparation.in_loop = null;
+    preparation.loop_ids = new Map();
+    preparation.defs_to_safe_ids = new Map();
+    self.walk(preparation);
+});
+
+AST_Symbol.DEFMETHOD("fixed_value", function() {
+    var fixed = this.thedef.fixed;
+    if (!fixed || fixed instanceof AST_Node) return fixed;
+    return fixed();
+});
+
+AST_SymbolRef.DEFMETHOD("is_immutable", function() {
+    var orig = this.definition().orig;
+    return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
+});
+
+function find_variable(compressor, name) {
+    var scope, i = 0;
+    while (scope = compressor.parent(i++)) {
+        if (scope instanceof AST_Scope) break;
+        if (scope instanceof AST_Catch && scope.argname) {
+            scope = scope.argname.definition().scope;
+            break;
+        }
+    }
+    return scope.find_variable(name);
+}
+
+AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
+    return !this.definition().undeclared
+        || (compressor.option("unsafe") || compressor.option("builtins_pure")) && compressor.pure_access_globals(this.name);
+});
+
+/* -----[ optimizers ]----- */
+
+var directives = new Set(["use asm", "use strict"]);
+def_optimize(AST_Directive, function(self, compressor) {
+    if (compressor.option("directives")
+        && (!directives.has(self.value) || compressor.has_directive(self.value) !== self)) {
+        return make_node(AST_EmptyStatement, self);
+    }
+    return self;
+});
+
+def_optimize(AST_Debugger, function(self, compressor) {
+    if (compressor.option("drop_debugger"))
+        return make_node(AST_EmptyStatement, self);
+    return self;
+});
+
+def_optimize(AST_LabeledStatement, function(self, compressor) {
+    if (self.body instanceof AST_Break
+        && compressor.loopcontrol_target(self.body) === self.body) {
+        return make_node(AST_EmptyStatement, self);
+    }
+    return self.label.references.length == 0 ? self.body : self;
+});
+
+def_optimize(AST_Block, function(self, compressor) {
+    tighten_body(self.body, compressor);
+    return self;
+});
+
+function can_be_extracted_from_if_block(node) {
+    return !(
+        node instanceof AST_Const
+        || node instanceof AST_Let
+        || node instanceof AST_Using
+        || node instanceof AST_Class
+    );
+}
+
+def_optimize(AST_BlockStatement, function(self, compressor) {
+    tighten_body(self.body, compressor);
+    switch (self.body.length) {
+      case 1:
+        if (!compressor.has_directive("use strict")
+            && compressor.parent() instanceof AST_If
+            && can_be_extracted_from_if_block(self.body[0])
+            || can_be_evicted_from_block(self.body[0])) {
+            return self.body[0];
+        }
+        break;
+      case 0: return make_node(AST_EmptyStatement, self);
+    }
+    return self;
+});
+
+function opt_AST_Lambda(self, compressor) {
+    tighten_body(self.body, compressor);
+    if (compressor.option("side_effects")
+        && self.body.length == 1
+        && self.body[0] === compressor.has_directive("use strict")) {
+        self.body.length = 0;
+    }
+    return self;
+}
+def_optimize(AST_Lambda, opt_AST_Lambda);
+
+AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) {
+    var self = this;
+    if (compressor.has_directive("use asm")) return self;
+
+    var hoist_funs = compressor.option("hoist_funs");
+    var hoist_vars = compressor.option("hoist_vars");
+
+    if (hoist_funs || hoist_vars) {
+        var dirs = [];
+        var hoisted = [];
+        var vars = new Map(), vars_found = 0, var_decl = 0;
+        // let's count var_decl first, we seem to waste a lot of
+        // space if we hoist `var` when there's only one.
+        walk(self, node => {
+            if (node instanceof AST_Scope && node !== self)
+                return true;
+            if (node instanceof AST_Var) {
+                ++var_decl;
+                return true;
+            }
+        });
+        hoist_vars = hoist_vars && var_decl > 1;
+        var tt = new TreeTransformer(
+            function before(node) {
+                if (node !== self) {
+                    if (node instanceof AST_Directive) {
+                        dirs.push(node);
+                        return make_node(AST_EmptyStatement, node);
+                    }
+                    if (hoist_funs && node instanceof AST_Defun
+                        && !(tt.parent() instanceof AST_Export)
+                        && tt.parent() === self) {
+                        hoisted.push(node);
+                        return make_node(AST_EmptyStatement, node);
+                    }
+                    if (
+                        hoist_vars
+                        && node instanceof AST_Var
+                        && !node.definitions.some(def => def.name instanceof AST_Destructuring)
+                    ) {
+                        node.definitions.forEach(function(def) {
+                            vars.set(def.name.name, def);
+                            ++vars_found;
+                        });
+                        var seq = node.to_assignments(compressor);
+                        var p = tt.parent();
+                        if (p instanceof AST_ForIn && p.init === node) {
+                            if (seq == null) {
+                                var def = node.definitions[0].name;
+                                return make_node(AST_SymbolRef, def, def);
+                            }
+                            return seq;
+                        }
+                        if (p instanceof AST_For && p.init === node) {
+                            return seq;
+                        }
+                        if (!seq) return make_node(AST_EmptyStatement, node);
+                        return make_node(AST_SimpleStatement, node, {
+                            body: seq
+                        });
+                    }
+                    if (node instanceof AST_Scope)
+                        return node; // to avoid descending in nested scopes
+                }
+            }
+        );
+        self = self.transform(tt);
+        if (vars_found > 0) {
+            // collect only vars which don't show up in self's arguments list
+            var defs = [];
+            const is_lambda = self instanceof AST_Lambda;
+            const args_as_names = is_lambda ? self.args_as_names() : null;
+            vars.forEach((def, name) => {
+                if (is_lambda && args_as_names.some((x) => x.name === def.name.name)) {
+                    vars.delete(name);
+                } else {
+                    def = def.clone();
+                    def.value = null;
+                    defs.push(def);
+                    vars.set(name, def);
+                }
+            });
+            if (defs.length > 0) {
+                // try to merge in assignments
+                for (var i = 0; i < self.body.length;) {
+                    if (self.body[i] instanceof AST_SimpleStatement) {
+                        var expr = self.body[i].body, sym, assign;
+                        if (expr instanceof AST_Assign
+                            && expr.operator == "="
+                            && (sym = expr.left) instanceof AST_Symbol
+                            && vars.has(sym.name)
+                        ) {
+                            var def = vars.get(sym.name);
+                            if (def.value) break;
+                            def.value = expr.right;
+                            remove(defs, def);
+                            defs.push(def);
+                            self.body.splice(i, 1);
+                            continue;
+                        }
+                        if (expr instanceof AST_Sequence
+                            && (assign = expr.expressions[0]) instanceof AST_Assign
+                            && assign.operator == "="
+                            && (sym = assign.left) instanceof AST_Symbol
+                            && vars.has(sym.name)
+                        ) {
+                            var def = vars.get(sym.name);
+                            if (def.value) break;
+                            def.value = assign.right;
+                            remove(defs, def);
+                            defs.push(def);
+                            self.body[i].body = make_sequence(expr, expr.expressions.slice(1));
+                            continue;
+                        }
+                    }
+                    if (self.body[i] instanceof AST_EmptyStatement) {
+                        self.body.splice(i, 1);
+                        continue;
+                    }
+                    if (self.body[i] instanceof AST_BlockStatement) {
+                        self.body.splice(i, 1, ...self.body[i].body);
+                        continue;
+                    }
+                    break;
+                }
+                defs = make_node(AST_Var, self, {
+                    definitions: defs
+                });
+                hoisted.push(defs);
+            }
+        }
+        self.body = dirs.concat(hoisted, self.body);
+    }
+    return self;
+});
+
+AST_Scope.DEFMETHOD("hoist_properties", function(compressor) {
+    var self = this;
+    if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
+    var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false;
+    var defs_by_id = new Map();
+    var hoister = new TreeTransformer(function(node, descend) {
+        if (node instanceof AST_VarDef) {
+            const sym = node.name;
+            let def;
+            let value;
+            if (sym.scope === self
+                && !(sym instanceof AST_SymbolUsing)
+                && (def = sym.definition()).escaped != 1
+                && !def.assignments
+                && !def.direct_access
+                && !def.single_use
+                && !compressor.exposed(def)
+                && !top_retain(def)
+                && (value = sym.fixed_value()) === node.value
+                && value instanceof AST_Object
+                && !value.properties.some(prop =>
+                    prop instanceof AST_Expansion || prop.computed_key()
+                )
+            ) {
+                descend(node, this);
+                const defs = new Map();
+                const assignments = [];
+                value.properties.forEach(({ key, value }) => {
+                    const scope = hoister.find_scope();
+                    const symbol = self.create_symbol(sym.CTOR, {
+                        source: sym,
+                        scope,
+                        conflict_scopes: new Set([
+                            scope,
+                            ...sym.definition().references.map(ref => ref.scope)
+                        ]),
+                        tentative_name: sym.name + "_" + key
+                    });
+
+                    defs.set(String(key), symbol.definition());
+
+                    assignments.push(make_node(AST_VarDef, node, {
+                        name: symbol,
+                        value
+                    }));
+                });
+                defs_by_id.set(def.id, defs);
+                return MAP.splice(assignments);
+            }
+        } else if (node instanceof AST_PropAccess
+            && node.expression instanceof AST_SymbolRef
+        ) {
+            const defs = defs_by_id.get(node.expression.definition().id);
+            if (defs) {
+                const def = defs.get(String(get_simple_key(node.property)));
+                const sym = make_node(AST_SymbolRef, node, {
+                    name: def.name,
+                    scope: node.expression.scope,
+                    thedef: def
+                });
+                sym.reference({});
+                return sym;
+            }
+        }
+    });
+    return self.transform(hoister);
+});
+
+def_optimize(AST_SimpleStatement, function(self, compressor) {
+    if (compressor.option("side_effects")) {
+        var body = self.body;
+        var node = body.drop_side_effect_free(compressor, true);
+        if (!node) {
+            return make_node(AST_EmptyStatement, self);
+        }
+        if (node !== body) {
+            return make_node(AST_SimpleStatement, self, { body: node });
+        }
+    }
+    return self;
+});
+
+def_optimize(AST_While, function(self, compressor) {
+    return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self;
+});
+
+def_optimize(AST_Do, function(self, compressor) {
+    if (!compressor.option("loops")) return self;
+    var cond = self.condition.tail_node().evaluate(compressor);
+    if (!(cond instanceof AST_Node)) {
+        if (cond) return make_node(AST_For, self, {
+            body: make_node(AST_BlockStatement, self.body, {
+                body: [
+                    self.body,
+                    make_node(AST_SimpleStatement, self.condition, {
+                        body: self.condition
+                    })
+                ]
+            })
+        }).optimize(compressor);
+        if (!has_break_or_continue(self, compressor.parent())) {
+            return make_node(AST_BlockStatement, self.body, {
+                body: [
+                    self.body,
+                    make_node(AST_SimpleStatement, self.condition, {
+                        body: self.condition
+                    })
+                ]
+            }).optimize(compressor);
+        }
+    }
+    return self;
+});
+
+function if_break_in_loop(self, compressor) {
+    var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
+    if (compressor.option("dead_code") && is_break(first)) {
+        var body = [];
+        if (self.init instanceof AST_Statement) {
+            body.push(self.init);
+        } else if (self.init) {
+            body.push(make_node(AST_SimpleStatement, self.init, {
+                body: self.init
+            }));
+        }
+        if (self.condition) {
+            body.push(make_node(AST_SimpleStatement, self.condition, {
+                body: self.condition
+            }));
+        }
+        extract_from_unreachable_code(compressor, self.body, body);
+        return make_node(AST_BlockStatement, self, {
+            body: body
+        });
+    }
+    if (first instanceof AST_If) {
+        if (is_break(first.body)) {
+            if (self.condition) {
+                self.condition = make_node(AST_Binary, self.condition, {
+                    left: self.condition,
+                    operator: "&&",
+                    right: first.condition.negate(compressor),
+                });
+            } else {
+                self.condition = first.condition.negate(compressor);
+            }
+            drop_it(first.alternative);
+        } else if (is_break(first.alternative)) {
+            if (self.condition) {
+                self.condition = make_node(AST_Binary, self.condition, {
+                    left: self.condition,
+                    operator: "&&",
+                    right: first.condition,
+                });
+            } else {
+                self.condition = first.condition;
+            }
+            drop_it(first.body);
+        }
+    }
+    return self;
+
+    function is_break(node) {
+        return node instanceof AST_Break
+            && compressor.loopcontrol_target(node) === compressor.self();
+    }
+
+    function drop_it(rest) {
+        rest = as_statement_array(rest);
+        if (self.body instanceof AST_BlockStatement) {
+            self.body = self.body.clone();
+            self.body.body = rest.concat(self.body.body.slice(1));
+            self.body = self.body.transform(compressor);
+        } else {
+            self.body = make_node(AST_BlockStatement, self.body, {
+                body: rest
+            }).transform(compressor);
+        }
+        self = if_break_in_loop(self, compressor);
+    }
+}
+
+def_optimize(AST_For, function(self, compressor) {
+    if (!compressor.option("loops")) return self;
+    if (compressor.option("side_effects") && self.init) {
+        self.init = self.init.drop_side_effect_free(compressor);
+    }
+    if (self.condition) {
+        var cond = self.condition.evaluate(compressor);
+        if (!(cond instanceof AST_Node)) {
+            if (cond) self.condition = null;
+            else if (!compressor.option("dead_code")) {
+                var orig = self.condition;
+                self.condition = make_node_from_constant(cond, self.condition);
+                self.condition = best_of_expression(self.condition.transform(compressor), orig);
+            }
+        }
+        if (compressor.option("dead_code")) {
+            if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
+            if (!cond) {
+                var body = [];
+                extract_from_unreachable_code(compressor, self.body, body);
+                if (self.init instanceof AST_Statement) {
+                    body.push(self.init);
+                } else if (self.init) {
+                    body.push(make_node(AST_SimpleStatement, self.init, {
+                        body: self.init
+                    }));
+                }
+                body.push(make_node(AST_SimpleStatement, self.condition, {
+                    body: self.condition
+                }));
+                return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
+            }
+        }
+    }
+    return if_break_in_loop(self, compressor);
+});
+
+def_optimize(AST_If, function(self, compressor) {
+    if (is_empty(self.alternative)) self.alternative = null;
+
+    if (!compressor.option("conditionals")) return self;
+    // if condition can be statically determined, drop
+    // one of the blocks.  note, statically determined implies
+    // “has no side effects”; also it doesn't work for cases like
+    // `x && true`, though it probably should.
+    var cond = self.condition.evaluate(compressor);
+    if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) {
+        var orig = self.condition;
+        self.condition = make_node_from_constant(cond, orig);
+        self.condition = best_of_expression(self.condition.transform(compressor), orig);
+    }
+    if (compressor.option("dead_code")) {
+        if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
+        if (!cond) {
+            var body = [];
+            extract_from_unreachable_code(compressor, self.body, body);
+            body.push(make_node(AST_SimpleStatement, self.condition, {
+                body: self.condition
+            }));
+            if (self.alternative) body.push(self.alternative);
+            return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
+        } else if (!(cond instanceof AST_Node)) {
+            var body = [];
+            body.push(make_node(AST_SimpleStatement, self.condition, {
+                body: self.condition
+            }));
+            body.push(self.body);
+            if (self.alternative) {
+                extract_from_unreachable_code(compressor, self.alternative, body);
+            }
+            return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
+        }
+    }
+    var negated = self.condition.negate(compressor);
+    var self_condition_length = self.condition.size();
+    var negated_length = negated.size();
+    var negated_is_best = negated_length < self_condition_length;
+    if (self.alternative && negated_is_best) {
+        negated_is_best = false; // because we already do the switch here.
+        // no need to swap values of self_condition_length and negated_length
+        // here because they are only used in an equality comparison later on.
+        self.condition = negated;
+        var tmp = self.body;
+        self.body = self.alternative || make_node(AST_EmptyStatement, self);
+        self.alternative = tmp;
+    }
+    if (is_empty(self.body) && is_empty(self.alternative)) {
+        return make_node(AST_SimpleStatement, self.condition, {
+            body: self.condition.clone()
+        }).optimize(compressor);
+    }
+    if (self.body instanceof AST_SimpleStatement
+        && self.alternative instanceof AST_SimpleStatement) {
+        return make_node(AST_SimpleStatement, self, {
+            body: make_node(AST_Conditional, self, {
+                condition   : self.condition,
+                consequent  : self.body.body,
+                alternative : self.alternative.body
+            })
+        }).optimize(compressor);
+    }
+    if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
+        if (self_condition_length === negated_length && !negated_is_best
+            && self.condition instanceof AST_Binary && self.condition.operator == "||") {
+            // although the code length of self.condition and negated are the same,
+            // negated does not require additional surrounding parentheses.
+            // see https://github.com/mishoo/UglifyJS2/issues/979
+            negated_is_best = true;
+        }
+        if (negated_is_best) return make_node(AST_SimpleStatement, self, {
+            body: make_node(AST_Binary, self, {
+                operator : "||",
+                left     : negated,
+                right    : self.body.body
+            })
+        }).optimize(compressor);
+        return make_node(AST_SimpleStatement, self, {
+            body: make_node(AST_Binary, self, {
+                operator : "&&",
+                left     : self.condition,
+                right    : self.body.body
+            })
+        }).optimize(compressor);
+    }
+    if (self.body instanceof AST_EmptyStatement
+        && self.alternative instanceof AST_SimpleStatement) {
+        return make_node(AST_SimpleStatement, self, {
+            body: make_node(AST_Binary, self, {
+                operator : "||",
+                left     : self.condition,
+                right    : self.alternative.body
+            })
+        }).optimize(compressor);
+    }
+    if (self.body instanceof AST_Exit
+        && self.alternative instanceof AST_Exit
+        && self.body.TYPE == self.alternative.TYPE) {
+        return make_node(self.body.CTOR, self, {
+            value: make_node(AST_Conditional, self, {
+                condition   : self.condition,
+                consequent  : self.body.value || make_void_0(self.body),
+                alternative : self.alternative.value || make_void_0(self.alternative),
+            }).transform(compressor)
+        }).optimize(compressor);
+    }
+    if (self.body instanceof AST_If
+        && !self.body.alternative
+        && !self.alternative) {
+        self = make_node(AST_If, self, {
+            condition: make_node(AST_Binary, self.condition, {
+                operator: "&&",
+                left: self.condition,
+                right: self.body.condition
+            }),
+            body: self.body.body,
+            alternative: null
+        });
+    }
+    if (aborts(self.body)) {
+        if (self.alternative) {
+            var alt = self.alternative;
+            self.alternative = null;
+            return make_node(AST_BlockStatement, self, {
+                body: [ self, alt ]
+            }).optimize(compressor);
+        }
+    }
+    if (aborts(self.alternative)) {
+        var body = self.body;
+        self.body = self.alternative;
+        self.condition = negated_is_best ? negated : self.condition.negate(compressor);
+        self.alternative = null;
+        return make_node(AST_BlockStatement, self, {
+            body: [ self, body ]
+        }).optimize(compressor);
+    }
+    return self;
+});
+
+def_optimize(AST_Switch, function(self, compressor) {
+    if (!compressor.option("switches")) return self;
+    var branch;
+    var value = self.expression.evaluate(compressor);
+    if (!(value instanceof AST_Node)) {
+        var orig = self.expression;
+        self.expression = make_node_from_constant(value, orig);
+        self.expression = best_of_expression(self.expression.transform(compressor), orig);
+    }
+    if (!compressor.option("dead_code")) return self;
+    if (value instanceof AST_Node) {
+        value = self.expression.tail_node().evaluate(compressor);
+    }
+    var decl = [];
+    var body = [];
+    var default_branch;
+    var exact_match;
+    // - compress self.body into `body`
+    // - find and deduplicate default branch
+    // - find the exact match (`case 1234` inside `switch(1234)`)
+    for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
+        branch = self.body[i];
+        if (branch instanceof AST_Default) {
+            if (!default_branch) {
+                default_branch = branch;
+            } else {
+                eliminate_branch(branch, body[body.length - 1]);
+            }
+        } else if (!(value instanceof AST_Node)) {
+            var exp = branch.expression.evaluate(compressor);
+            if (!(exp instanceof AST_Node) && exp !== value) {
+                eliminate_branch(branch, body[body.length - 1]);
+                continue;
+            }
+            if (exp instanceof AST_Node && !exp.has_side_effects(compressor)) {
+                exp = branch.expression.tail_node().evaluate(compressor);
+            }
+            if (exp === value) {
+                exact_match = branch;
+                if (default_branch) {
+                    var default_index = body.indexOf(default_branch);
+                    body.splice(default_index, 1);
+                    eliminate_branch(default_branch, body[default_index - 1]);
+                    default_branch = null;
+                }
+            }
+        }
+        body.push(branch);
+    }
+    // i < len if we found an exact_match. eliminate the rest
+    while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
+    self.body = body;
+
+    let default_or_exact = default_branch || exact_match;
+    default_branch = null;
+    exact_match = null;
+
+    // group equivalent branches so they will be located next to each other,
+    // that way the next micro-optimization will merge them.
+    // ** bail micro-optimization if not a simple switch case with breaks
+    if (body.every((branch, i) =>
+        (branch === default_or_exact || branch.expression instanceof AST_Constant)
+        && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
+    ) {
+        for (let i = 0; i < body.length; i++) {
+            const branch = body[i];
+            for (let j = i + 1; j < body.length; j++) {
+                const next = body[j];
+                if (next.body.length === 0) continue;
+                const last_branch = j === (body.length - 1);
+                const equivalentBranch = branches_equivalent(next, branch, false);
+                if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
+                    if (!equivalentBranch && last_branch) {
+                        next.body.push(make_node(AST_Break));
+                    }
+
+                    // let's find previous siblings with inert fallthrough...
+                    let x = j - 1;
+                    let fallthroughDepth = 0;
+                    while (x > i) {
+                        if (is_inert_body(body[x--])) {
+                            fallthroughDepth++;
+                        } else {
+                            break;
+                        }
+                    }
+
+                    const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
+                    body.splice(i + 1, 0, ...plucked);
+                    i += plucked.length;
+                }
+            }
+        }
+    }
+
+    // merge equivalent branches in a row
+    for (let i = 0; i < body.length; i++) {
+        let branch = body[i];
+        if (branch.body.length === 0) continue;
+        if (!aborts(branch)) continue;
+
+        for (let j = i + 1; j < body.length; i++, j++) {
+            let next = body[j];
+            if (next.body.length === 0) continue;
+            if (
+                branches_equivalent(next, branch, false)
+                || (j === body.length - 1 && branches_equivalent(next, branch, true))
+            ) {
+                branch.body = [];
+                branch = next;
+                continue;
+            }
+            break;
+        }
+    }
+
+    // Prune any empty branches at the end of the switch statement.
+    {
+        let i = body.length - 1;
+        for (; i >= 0; i--) {
+            let bbody = body[i].body;
+            while (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
+            if (!is_inert_body(body[i])) break;
+        }
+        // i now points to the index of a branch that contains a body. By incrementing, it's
+        // pointing to the first branch that's empty.
+        i++;
+        if (!default_or_exact || body.indexOf(default_or_exact) >= i) {
+            // The default behavior is to do nothing. We can take advantage of that to
+            // remove all case expressions that are side-effect free that also do
+            // nothing, since they'll default to doing nothing. But we can't remove any
+            // case expressions before one that would side-effect, since they may cause
+            // the side-effect to be skipped.
+            for (let j = body.length - 1; j >= i; j--) {
+                let branch = body[j];
+                if (branch === default_or_exact) {
+                    default_or_exact = null;
+                    eliminate_branch(body.pop());
+                } else if (!branch.expression.has_side_effects(compressor)) {
+                    eliminate_branch(body.pop());
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+
+
+    // Prune side-effect free branches that fall into default.
+    DEFAULT: if (default_or_exact) {
+        let default_index = body.indexOf(default_or_exact);
+        let default_body_index = default_index;
+        for (; default_body_index < body.length - 1; default_body_index++) {
+            if (!is_inert_body(body[default_body_index])) break;
+        }
+        if (default_body_index < body.length - 1) {
+            break DEFAULT;
+        }
+
+        let side_effect_index = body.length - 1;
+        for (; side_effect_index >= 0; side_effect_index--) {
+            let branch = body[side_effect_index];
+            if (branch === default_or_exact) continue;
+            if (branch.expression.has_side_effects(compressor)) break;
+        }
+        // If the default behavior comes after any side-effect case expressions,
+        // then we can fold all side-effect free cases into the default branch.
+        // If the side-effect case is after the default, then any side-effect
+        // free cases could prevent the side-effect from occurring.
+        if (default_body_index > side_effect_index) {
+            let prev_body_index = default_index - 1;
+            for (; prev_body_index >= 0; prev_body_index--) {
+                if (!is_inert_body(body[prev_body_index])) break;
+            }
+            let before = Math.max(side_effect_index, prev_body_index) + 1;
+            let after = default_index;
+            if (side_effect_index > default_index) {
+                // If the default falls into the same body as a side-effect
+                // case, then we need preserve that case and only prune the
+                // cases after it.
+                after = side_effect_index;
+                body[side_effect_index].body = body[default_body_index].body;
+            } else {
+                // The default will be the last branch.
+                default_or_exact.body = body[default_body_index].body;
+            }
+
+            // Prune everything after the default (or last side-effect case)
+            // until the next case with a body.
+            body.splice(after + 1, default_body_index - after);
+            // Prune everything before the default that falls into it.
+            body.splice(before, default_index - before);
+        }
+    }
+
+    // See if we can remove the switch entirely if all cases (the default) fall into the same case body.
+    DEFAULT: if (default_or_exact) {
+        let i = body.findIndex(branch => !is_inert_body(branch));
+        let caseBody;
+        // `i` is equal to one of the following:
+        // - `-1`, there is no body in the switch statement.
+        // - `body.length - 1`, all cases fall into the same body.
+        // - anything else, there are multiple bodies in the switch.
+        if (i === body.length - 1) {
+            // All cases fall into the case body.
+            let branch = body[i];
+            if (has_nested_break(self)) break DEFAULT;
+
+            // This is the last case body, and we've already pruned any breaks, so it's
+            // safe to hoist.
+            caseBody = make_node(AST_BlockStatement, branch, {
+                body: branch.body
+            });
+            branch.body = [];
+        } else if (i !== -1) {
+            // If there are multiple bodies, then we cannot optimize anything.
+            break DEFAULT;
+        }
+
+        let sideEffect = body.find(
+            branch => branch !== default_or_exact && branch.expression.has_side_effects(compressor)
+        );
+        // If no cases cause a side-effect, we can eliminate the switch entirely.
+        if (!sideEffect) {
+            return make_node(AST_BlockStatement, self, {
+                body: decl.concat(
+                    statement(self.expression),
+                    default_or_exact.expression ? statement(default_or_exact.expression) : [],
+                    caseBody || []
+                )
+            }).optimize(compressor);
+        }
+
+        // If we're this far, either there was no body or all cases fell into the same body.
+        // If there was no body, then we don't need a default branch (because the default is
+        // do nothing). If there was a body, we'll extract it to after the switch, so the
+        // switch's new default is to do nothing and we can still prune it.
+        const default_index = body.indexOf(default_or_exact);
+        body.splice(default_index, 1);
+        default_or_exact = null;
+
+        if (caseBody) {
+            // Recurse into switch statement one more time so that we can append the case body
+            // outside of the switch. This recursion will only happen once since we've pruned
+            // the default case.
+            return make_node(AST_BlockStatement, self, {
+                body: decl.concat(self, caseBody)
+            }).optimize(compressor);
+        }
+        // If we fall here, there is a default branch somewhere, there are no case bodies,
+        // and there's a side-effect somewhere. Just let the below paths take care of it.
+    }
+
+    // Reintegrate `decl` (var statements)
+    if (body.length > 0) {
+        body[0].body = decl.concat(body[0].body);
+    }
+    if (body.length == 0) {
+        return make_node(AST_BlockStatement, self, {
+            body: decl.concat(statement(self.expression))
+        }).optimize(compressor);
+    }
+
+    if (body.length == 1 && !has_nested_break(self)) {
+        // This is the last case body, and we've already pruned any breaks, so it's
+        // safe to hoist.
+        let branch = body[0];
+        return make_node(AST_If, self, {
+            condition: make_node(AST_Binary, self, {
+                operator: "===",
+                left: self.expression,
+                right: branch.expression,
+            }),
+            body: make_node(AST_BlockStatement, branch, {
+                body: branch.body
+            }),
+            alternative: null
+        }).optimize(compressor);
+    }
+    if (body.length === 2 && default_or_exact && !has_nested_break(self)) {
+        let branch = body[0] === default_or_exact ? body[1] : body[0];
+        let exact_exp = default_or_exact.expression && statement(default_or_exact.expression);
+        if (aborts(body[0])) {
+            // Only the first branch body could have a break (at the last statement)
+            let first = body[0];
+            if (is_break(first.body[first.body.length - 1], compressor)) {
+                first.body.pop();
+            }
+            return make_node(AST_If, self, {
+                condition: make_node(AST_Binary, self, {
+                    operator: "===",
+                    left: self.expression,
+                    right: branch.expression,
+                }),
+                body: make_node(AST_BlockStatement, branch, {
+                    body: branch.body
+                }),
+                alternative: make_node(AST_BlockStatement, default_or_exact, {
+                    body: [].concat(
+                        exact_exp || [],
+                        default_or_exact.body
+                    )
+                })
+            }).optimize(compressor);
+        }
+        let operator = "===";
+        let consequent = make_node(AST_BlockStatement, branch, {
+            body: branch.body,
+        });
+        let always = make_node(AST_BlockStatement, default_or_exact, {
+            body: [].concat(
+                exact_exp || [],
+                default_or_exact.body
+            )
+        });
+        if (body[0] === default_or_exact) {
+            operator = "!==";
+            let tmp = always;
+            always = consequent;
+            consequent = tmp;
+        }
+        return make_node(AST_BlockStatement, self, {
+            body: [
+                make_node(AST_If, self, {
+                    condition: make_node(AST_Binary, self, {
+                        operator: operator,
+                        left: self.expression,
+                        right: branch.expression,
+                    }),
+                    body: consequent,
+                    alternative: null,
+                }),
+                always,
+            ],
+        }).optimize(compressor);
+    }
+    return self;
+
+    function eliminate_branch(branch, prev) {
+        if (prev && !aborts(prev)) {
+            prev.body = prev.body.concat(branch.body);
+        } else {
+            extract_from_unreachable_code(compressor, branch, decl);
+        }
+    }
+    function branches_equivalent(branch, prev, insertBreak) {
+        let bbody = branch.body;
+        let pbody = prev.body;
+        if (insertBreak) {
+            bbody = bbody.concat(make_node(AST_Break));
+        }
+        if (bbody.length !== pbody.length) return false;
+        let bblock = make_node(AST_BlockStatement, branch, { body: bbody });
+        let pblock = make_node(AST_BlockStatement, prev, { body: pbody });
+        return bblock.equivalent_to(pblock);
+    }
+    function statement(body) {
+        return make_node(AST_SimpleStatement, body, { body });
+    }
+    function has_nested_break(root) {
+        let has_break = false;
+
+        let tw = new TreeWalker(node => {
+            if (has_break) return true;
+            if (node instanceof AST_Lambda) return true;
+            if (node instanceof AST_SimpleStatement) return true;
+            if (!is_break(node, tw)) return;
+            let parent = tw.parent();
+            if (
+                parent instanceof AST_SwitchBranch
+                && parent.body[parent.body.length - 1] === node
+            ) {
+                return;
+            }
+            has_break = true;
+        });
+        root.walk(tw);
+        return has_break;
+    }
+    function is_break(node, stack) {
+        return node instanceof AST_Break
+            && stack.loopcontrol_target(node) === self;
+    }
+    function is_inert_body(branch) {
+        return !aborts(branch) && !make_node(AST_BlockStatement, branch, {
+            body: branch.body
+        }).has_side_effects(compressor);
+    }
+});
+
+def_optimize(AST_Try, function(self, compressor) {
+    if (self.bcatch && self.bfinally && self.bfinally.body.every(is_empty)) self.bfinally = null;
+
+    if (compressor.option("dead_code") && self.body.body.every(is_empty)) {
+        var body = [];
+        if (self.bcatch) {
+            extract_from_unreachable_code(compressor, self.bcatch, body);
+        }
+        if (self.bfinally) body.push(...self.bfinally.body);
+        return make_node(AST_BlockStatement, self, {
+            body: body
+        }).optimize(compressor);
+    }
+    return self;
+});
+
+AST_Definitions.DEFMETHOD("to_assignments", function(compressor) {
+    var reduce_vars = compressor.option("reduce_vars");
+    var assignments = [];
+
+    for (const def of this.definitions) {
+        if (def.value) {
+            var name = make_node(AST_SymbolRef, def.name, def.name);
+            assignments.push(make_node(AST_Assign, def, {
+                operator : "=",
+                logical: false,
+                left     : name,
+                right    : def.value
+            }));
+            if (reduce_vars) name.definition().fixed = false;
+        }
+        const thedef = def.name.definition();
+        thedef.eliminated++;
+        thedef.replaced--;
+    }
+
+    if (assignments.length == 0) return null;
+    return make_sequence(this, assignments);
+});
+
+def_optimize(AST_Definitions, function(self) {
+    if (self.definitions.length == 0) {
+        return make_node(AST_EmptyStatement, self);
+    }
+    return self;
+});
+
+def_optimize(AST_VarDef, function(self, compressor) {
+    if (
+        self.name instanceof AST_SymbolLet
+        && self.value != null
+        && is_undefined(self.value, compressor)
+    ) {
+        self.value = null;
+    }
+    return self;
+});
+
+def_optimize(AST_Import, function(self) {
+    return self;
+});
+
+def_optimize(AST_Call, function(self, compressor) {
+    var exp = self.expression;
+    var fn = exp;
+    inline_array_like_spread(self.args);
+    var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion));
+
+    if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
+        fn = fn.fixed_value();
+    }
+
+    var is_func = fn instanceof AST_Lambda;
+
+    if (is_func && fn.pinned()) return self;
+
+    if (compressor.option("unused")
+        && simple_args
+        && is_func
+        && !fn.uses_arguments) {
+        var pos = 0, last = 0;
+        for (var i = 0, len = self.args.length; i < len; i++) {
+            if (fn.argnames[i] instanceof AST_Expansion) {
+                if (has_flag(fn.argnames[i].expression, UNUSED)) while (i < len) {
+                    var node = self.args[i++].drop_side_effect_free(compressor);
+                    if (node) {
+                        self.args[pos++] = node;
+                    }
+                } else while (i < len) {
+                    self.args[pos++] = self.args[i++];
+                }
+                last = pos;
+                break;
+            }
+            var trim = i >= fn.argnames.length;
+            if (trim || has_flag(fn.argnames[i], UNUSED)) {
+                var node = self.args[i].drop_side_effect_free(compressor);
+                if (node) {
+                    self.args[pos++] = node;
+                } else if (!trim) {
+                    self.args[pos++] = make_node(AST_Number, self.args[i], {
+                        value: 0
+                    });
+                    continue;
+                }
+            } else {
+                self.args[pos++] = self.args[i];
+            }
+            last = pos;
+        }
+        self.args.length = last;
+    }
+
+    if (
+        exp instanceof AST_Dot
+        && exp.expression instanceof AST_SymbolRef
+        && exp.expression.name === "console"
+        && exp.expression.definition().undeclared
+        && exp.property === "assert"
+    ) {
+        const condition = self.args[0];
+        if (condition) {
+            const value = condition.evaluate(compressor);
+    
+            if (value === 1 || value === true) {
+                return make_void_0(self).optimize(compressor);
+            }
+        }
+    }    
+
+    if (compressor.option("unsafe") && !exp.contains_optional()) {
+        if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) {
+            const [argument] = self.args;
+            if (argument instanceof AST_Array) {
+                return make_node(AST_Array, argument, {
+                    elements: argument.elements
+                }).optimize(compressor);
+            }
+        }
+        if (is_undeclared_ref(exp)) switch (exp.name) {
+          case "Array":
+            if (self.args.length != 1) {
+                return make_node(AST_Array, self, {
+                    elements: self.args
+                }).optimize(compressor);
+            } else if (self.args[0] instanceof AST_Number && self.args[0].value <= 11) {
+                const elements = [];
+                for (let i = 0; i < self.args[0].value; i++) elements.push(new AST_Hole);
+                return new AST_Array({ elements });
+            }
+            break;
+          case "Object":
+            if (self.args.length == 0) {
+                return make_node(AST_Object, self, {
+                    properties: []
+                });
+            }
+            break;
+          case "String":
+            if (self.args.length == 0) return make_node(AST_String, self, {
+                value: ""
+            });
+            if (self.args.length <= 1) return make_node(AST_Binary, self, {
+                left: self.args[0],
+                operator: "+",
+                right: make_node(AST_String, self, { value: "" })
+            }).optimize(compressor);
+            break;
+          case "Number":
+            if (self.args.length == 0) return make_node(AST_Number, self, {
+                value: 0
+            });
+            if (self.args.length == 1 && compressor.option("unsafe_math")) {
+                return make_node(AST_UnaryPrefix, self, {
+                    expression: self.args[0],
+                    operator: "+"
+                }).optimize(compressor);
+            }
+            break;
+          case "Symbol":
+            if (self.args.length == 1 && self.args[0] instanceof AST_String && compressor.option("unsafe_symbols"))
+                self.args.length = 0;
+                break;
+          case "Boolean":
+            if (self.args.length == 0) return make_node(AST_False, self);
+            if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
+                expression: make_node(AST_UnaryPrefix, self, {
+                    expression: self.args[0],
+                    operator: "!"
+                }),
+                operator: "!"
+            }).optimize(compressor);
+            break;
+          case "RegExp":
+            var params = [];
+            if (self.args.length >= 1
+                && self.args.length <= 2
+                && self.args.every((arg) => {
+                    var value = arg.evaluate(compressor);
+                    params.push(value);
+                    return arg !== value;
+                })
+                && regexp_is_safe(params[0])
+            ) {
+                let [ source, flags ] = params;
+                source = regexp_source_fix(new RegExp(source).source);
+                const rx = make_node(AST_RegExp, self, {
+                    value: { source, flags }
+                });
+                if (rx._eval(compressor) !== rx) {
+                    return rx;
+                }
+            }
+            break;
+        } else if (exp instanceof AST_Dot) switch(exp.property) {
+          case "toString":
+            if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) {
+                return make_node(AST_Binary, self, {
+                    left: make_node(AST_String, self, { value: "" }),
+                    operator: "+",
+                    right: exp.expression
+                }).optimize(compressor);
+            }
+            break;
+          case "join":
+            if (exp.expression instanceof AST_Array) EXIT: {
+                var separator;
+                if (self.args.length > 0) {
+                    separator = self.args[0].evaluate(compressor);
+                    if (separator === self.args[0]) break EXIT; // not a constant
+                }
+                var elements = [];
+                var consts = [];
+                for (var i = 0, len = exp.expression.elements.length; i < len; i++) {
+                    var el = exp.expression.elements[i];
+                    if (el instanceof AST_Expansion) break EXIT;
+                    var value = el.evaluate(compressor);
+                    if (value !== el) {
+                        consts.push(value);
+                    } else {
+                        if (consts.length > 0) {
+                            elements.push(make_node(AST_String, self, {
+                                value: consts.join(separator)
+                            }));
+                            consts.length = 0;
+                        }
+                        elements.push(el);
+                    }
+                }
+                if (consts.length > 0) {
+                    elements.push(make_node(AST_String, self, {
+                        value: consts.join(separator)
+                    }));
+                }
+                if (elements.length == 0) return make_node(AST_String, self, { value: "" });
+                if (elements.length == 1) {
+                    if (elements[0].is_string(compressor)) {
+                        return elements[0];
+                    }
+                    return make_node(AST_Binary, elements[0], {
+                        operator : "+",
+                        left     : make_node(AST_String, self, { value: "" }),
+                        right    : elements[0]
+                    });
+                }
+                if (separator == "") {
+                    var first;
+                    if (elements[0].is_string(compressor)
+                        || elements[1].is_string(compressor)) {
+                        first = elements.shift();
+                    } else {
+                        first = make_node(AST_String, self, { value: "" });
+                    }
+                    return elements.reduce(function(prev, el) {
+                        return make_node(AST_Binary, el, {
+                            operator : "+",
+                            left     : prev,
+                            right    : el
+                        });
+                    }, first).optimize(compressor);
+                }
+                // need this awkward cloning to not affect original element
+                // best_of will decide which one to get through.
+                var node = self.clone();
+                node.expression = node.expression.clone();
+                node.expression.expression = node.expression.expression.clone();
+                node.expression.expression.elements = elements;
+                return best_of(compressor, self, node);
+            }
+            break;
+          case "charAt":
+            if (exp.expression.is_string(compressor)) {
+                var arg = self.args[0];
+                var index = arg ? arg.evaluate(compressor) : 0;
+                if (index !== arg) {
+                    return make_node(AST_Sub, exp, {
+                        expression: exp.expression,
+                        property: make_node_from_constant(index | 0, arg || exp)
+                    }).optimize(compressor);
+                }
+            }
+            break;
+          case "apply":
+            if (self.args.length == 2 && self.args[1] instanceof AST_Array) {
+                var args = self.args[1].elements.slice();
+                args.unshift(self.args[0]);
+                return make_node(AST_Call, self, {
+                    expression: make_node(AST_Dot, exp, {
+                        expression: exp.expression,
+                        optional: false,
+                        property: "call"
+                    }),
+                    args: args
+                }).optimize(compressor);
+            }
+            break;
+          case "call":
+            var func = exp.expression;
+            if (func instanceof AST_SymbolRef) {
+                func = func.fixed_value();
+            }
+            if (func instanceof AST_Lambda && !func.contains_this()) {
+                return (self.args.length ? make_sequence(this, [
+                    self.args[0],
+                    make_node(AST_Call, self, {
+                        expression: exp.expression,
+                        args: self.args.slice(1)
+                    })
+                ]) : make_node(AST_Call, self, {
+                    expression: exp.expression,
+                    args: []
+                })).optimize(compressor);
+            }
+            break;
+        }
+    }
+
+    if (compressor.option("unsafe_Function")
+        && is_undeclared_ref(exp)
+        && exp.name == "Function") {
+        // new Function() => function(){}
+        if (self.args.length == 0) return make_empty_function(self).optimize(compressor);
+        if (self.args.every((x) => x instanceof AST_String)) {
+            // quite a corner-case, but we can handle it:
+            //   https://github.com/mishoo/UglifyJS2/issues/203
+            // if the code argument is a constant, then we can minify it.
+            try {
+                var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
+                    return arg.value;
+                }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
+                var ast = parse(code);
+                var mangle = compressor.mangle_options();
+                ast.figure_out_scope(mangle);
+                var comp = new Compressor(compressor.options, {
+                    mangle_options: compressor._mangle_options
+                });
+                ast = ast.transform(comp);
+                ast.figure_out_scope(mangle);
+                ast.compute_char_frequency(mangle);
+                ast.mangle_names(mangle);
+                var fun;
+                walk(ast, node => {
+                    if (is_func_expr(node)) {
+                        fun = node;
+                        return walk_abort;
+                    }
+                });
+                var code = OutputStream();
+                AST_BlockStatement.prototype._codegen.call(fun, fun, code);
+                self.args = [
+                    make_node(AST_String, self, {
+                        value: fun.argnames.map(function(arg) {
+                            return arg.print_to_string();
+                        }).join(",")
+                    }),
+                    make_node(AST_String, self.args[self.args.length - 1], {
+                        value: code.get().replace(/^{|}$/g, "")
+                    })
+                ];
+                return self;
+            } catch (ex) {
+                if (!(ex instanceof JS_Parse_Error)) {
+                    throw ex;
+                }
+
+                // Otherwise, it crashes at runtime. Or maybe it's nonstandard syntax.
+            }
+        }
+    }
+
+    return inline_into_call(self, compressor);
+});
+
+/** Does this node contain optional property access or optional call? */
+AST_Node.DEFMETHOD("contains_optional", function() {
+    if (
+        this instanceof AST_PropAccess
+        || this instanceof AST_Call
+        || this instanceof AST_Chain
+    ) {
+        if (this.optional) {
+            return true;
+        } else {
+            return this.expression.contains_optional();
+        }
+    } else {
+        return false;
+    }
+});
+
+def_optimize(AST_New, function(self, compressor) {
+    if (
+        compressor.option("unsafe") &&
+        is_undeclared_ref(self.expression) &&
+        ["Object", "RegExp", "Function", "Error", "Array"].includes(self.expression.name)
+    ) return make_node(AST_Call, self, self).transform(compressor);
+    return self;
+});
+
+def_optimize(AST_Sequence, function(self, compressor) {
+    if (!compressor.option("side_effects")) return self;
+    var expressions = [];
+    filter_for_side_effects();
+    var end = expressions.length - 1;
+    trim_right_for_undefined();
+    if (end == 0) {
+        self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
+        if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
+        return self;
+    }
+    self.expressions = expressions;
+    return self;
+
+    function filter_for_side_effects() {
+        var first = first_in_statement(compressor);
+        var last = self.expressions.length - 1;
+        self.expressions.forEach(function(expr, index) {
+            if (index < last) expr = expr.drop_side_effect_free(compressor, first);
+            if (expr) {
+                merge_sequence(expressions, expr);
+                first = false;
+            }
+        });
+    }
+
+    function trim_right_for_undefined() {
+        while (end > 0 && is_undefined(expressions[end], compressor)) end--;
+        if (end < expressions.length - 1) {
+            expressions[end] = make_node(AST_UnaryPrefix, self, {
+                operator   : "void",
+                expression : expressions[end]
+            });
+            expressions.length = end + 1;
+        }
+    }
+});
+
+AST_Unary.DEFMETHOD("lift_sequences", function(compressor) {
+    if (compressor.option("sequences")) {
+        if (this.expression instanceof AST_Sequence) {
+            var x = this.expression.expressions.slice();
+            var e = this.clone();
+            e.expression = x.pop();
+            x.push(e);
+            return make_sequence(this, x).optimize(compressor);
+        }
+    }
+    return this;
+});
+
+def_optimize(AST_UnaryPostfix, function(self, compressor) {
+    return self.lift_sequences(compressor);
+});
+
+def_optimize(AST_UnaryPrefix, function(self, compressor) {
+    var e = self.expression;
+    if (
+        self.operator == "delete" &&
+        !(
+            e instanceof AST_SymbolRef ||
+            e instanceof AST_PropAccess ||
+            e instanceof AST_Chain ||
+            is_identifier_atom(e)
+        )
+    ) {
+        return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor);
+    }
+    // Short-circuit common `void 0`
+    if (self.operator === "void" && e instanceof AST_Number && e.value === 0) {
+        return unsafe_undefined_ref(self, compressor) || self;
+    }
+    var seq = self.lift_sequences(compressor);
+    if (seq !== self) {
+        return seq;
+    }
+    if (compressor.option("side_effects") && self.operator == "void") {
+        e = e.drop_side_effect_free(compressor);
+        if (e) {
+            self.expression = e;
+            return self;
+        } else {
+            return make_void_0(self).optimize(compressor);
+        }
+    }
+    if (compressor.in_boolean_context()) {
+        switch (self.operator) {
+          case "!":
+            if (e instanceof AST_UnaryPrefix && e.operator == "!") {
+                // !!foo ==> foo, if we're in boolean context
+                return e.expression;
+            }
+            if (e instanceof AST_Binary) {
+                self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
+            }
+            break;
+          case "typeof":
+            // typeof always returns a non-empty string, thus it's
+            // always true in booleans
+            // And we don't need to check if it's undeclared, because in typeof, that's OK
+            return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
+                e,
+                make_node(AST_True, self)
+            ])).optimize(compressor);
+        }
+    }
+    if (self.operator == "-" && e instanceof AST_Infinity) {
+        e = e.transform(compressor);
+    }
+    if (e instanceof AST_Binary
+        && (self.operator == "+" || self.operator == "-")
+        && (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
+        return make_node(AST_Binary, self, {
+            operator: e.operator,
+            left: make_node(AST_UnaryPrefix, e.left, {
+                operator: self.operator,
+                expression: e.left
+            }),
+            right: e.right
+        });
+    }
+
+    if (compressor.option("evaluate")) {
+        // ~~x => x (in 32-bit context)
+        // ~~{32 bit integer} => {32 bit integer}
+        if (
+            self.operator === "~"
+            && self.expression instanceof AST_UnaryPrefix
+            && self.expression.operator === "~"
+            && (compressor.in_32_bit_context(false) || self.expression.expression.is_32_bit_integer(compressor))
+        ) {
+            return self.expression.expression;
+        }
+
+        // ~(x ^ y) => x ^ ~y
+        if (
+            self.operator === "~"
+            && e instanceof AST_Binary
+            && e.operator === "^"
+        ) {
+            if (e.left instanceof AST_UnaryPrefix && e.left.operator === "~") {
+                // ~(~x ^ y) => x ^ y
+                e.left = e.left.bitwise_negate(compressor, true);
+            } else {
+                e.right = e.right.bitwise_negate(compressor, true);
+            }
+            return e;
+        }
+    }
+
+    if (
+        self.operator != "-"
+        // avoid infinite recursion of numerals
+        || !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)
+    ) {
+        var ev = self.evaluate(compressor);
+        if (ev !== self) {
+            ev = make_node_from_constant(ev, self).optimize(compressor);
+            return best_of(compressor, ev, self);
+        }
+    }
+    return self;
+});
+
+AST_Binary.DEFMETHOD("lift_sequences", function(compressor) {
+    if (compressor.option("sequences")) {
+        if (this.left instanceof AST_Sequence) {
+            var x = this.left.expressions.slice();
+            var e = this.clone();
+            e.left = x.pop();
+            x.push(e);
+            return make_sequence(this, x).optimize(compressor);
+        }
+        if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) {
+            var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
+            var x = this.right.expressions;
+            var last = x.length - 1;
+            for (var i = 0; i < last; i++) {
+                if (!assign && x[i].has_side_effects(compressor)) break;
+            }
+            if (i == last) {
+                x = x.slice();
+                var e = this.clone();
+                e.right = x.pop();
+                x.push(e);
+                return make_sequence(this, x).optimize(compressor);
+            } else if (i > 0) {
+                var e = this.clone();
+                e.right = make_sequence(this.right, x.slice(i));
+                x = x.slice(0, i);
+                x.push(e);
+                return make_sequence(this, x).optimize(compressor);
+            }
+        }
+    }
+    return this;
+});
+
+var commutativeOperators = makePredicate("== === != !== * & | ^");
+function is_object(node) {
+    return node instanceof AST_Array
+        || node instanceof AST_Lambda
+        || node instanceof AST_Object
+        || node instanceof AST_Class;
+}
+
+def_optimize(AST_Binary, function(self, compressor) {
+    function reversible() {
+        return self.left.is_constant()
+            || self.right.is_constant()
+            || !self.left.has_side_effects(compressor)
+                && !self.right.has_side_effects(compressor);
+    }
+    function reverse(op) {
+        if (reversible()) {
+            if (op) self.operator = op;
+            var tmp = self.left;
+            self.left = self.right;
+            self.right = tmp;
+        }
+    }
+    if (compressor.option("lhs_constants") && commutativeOperators.has(self.operator)) {
+        if (self.right.is_constant()
+            && !self.left.is_constant()) {
+            // if right is a constant, whatever side effects the
+            // left side might have could not influence the
+            // result.  hence, force switch.
+
+            if (!(self.left instanceof AST_Binary
+                  && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
+                reverse();
+            }
+        }
+    }
+    self = self.lift_sequences(compressor);
+    if (compressor.option("comparisons")) switch (self.operator) {
+      case "===":
+      case "!==":
+        var is_strict_comparison = true;
+        if (
+            (self.left.is_string(compressor) && self.right.is_string(compressor)) ||
+            (self.left.is_number(compressor) && self.right.is_number(compressor)) ||
+            (self.left.is_bigint(compressor) && self.right.is_bigint(compressor)) ||
+            (self.left.is_boolean() && self.right.is_boolean()) ||
+            self.left.equivalent_to(self.right)
+        ) {
+            self.operator = self.operator.substr(0, 2);
+        }
+
+        // XXX: intentionally falling down to the next case
+      case "==":
+      case "!=":
+        // void 0 == x => null == x
+        if (!is_strict_comparison && is_undefined(self.left, compressor)) {
+            self.left = make_node(AST_Null, self.left);
+        // x == void 0 => x == null
+        } else if (!is_strict_comparison && is_undefined(self.right, compressor)) {
+            self.right = make_node(AST_Null, self.right);
+        } else if (compressor.option("typeofs")
+            // "undefined" == typeof x => undefined === x
+            && self.left instanceof AST_String
+            && self.left.value == "undefined"
+            && self.right instanceof AST_UnaryPrefix
+            && self.right.operator == "typeof") {
+            var expr = self.right.expression;
+            if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
+                : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
+                self.right = expr;
+                self.left = make_void_0(self.left).optimize(compressor);
+                if (self.operator.length == 2) self.operator += "=";
+            }
+        } else if (compressor.option("typeofs")
+            // typeof x === "undefined" => x === undefined
+            && self.left instanceof AST_UnaryPrefix
+            && self.left.operator == "typeof"
+            && self.right instanceof AST_String
+            && self.right.value == "undefined") {
+            var expr = self.left.expression;
+            if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
+                : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
+                self.left = expr;
+                self.right = make_void_0(self.right).optimize(compressor);
+                if (self.operator.length == 2) self.operator += "=";
+            }
+        } else if (self.left instanceof AST_SymbolRef
+            // obj !== obj => false
+            && self.right instanceof AST_SymbolRef
+            && self.left.definition() === self.right.definition()
+            && is_object(self.left.fixed_value())) {
+            return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
+        } else if (self.left.is_32_bit_integer(compressor) && self.right.is_32_bit_integer(compressor)) {
+            const not = node => make_node(AST_UnaryPrefix, node, {
+                operator: "!",
+                expression: node
+            });
+            const booleanify = (node, truthy) => {
+                if (truthy) {
+                    return compressor.in_boolean_context()
+                        ? node
+                        : not(not(node));
+                } else {
+                    return not(node);
+                }
+            };
+
+            // The only falsy 32-bit integer is 0
+            if (self.left instanceof AST_Number && self.left.value === 0) {
+                return booleanify(self.right, self.operator[0] === "!");
+            }
+            if (self.right instanceof AST_Number && self.right.value === 0) {
+                return booleanify(self.left, self.operator[0] === "!");
+            }
+
+            // Mask all-bits check
+            // (x & 0xFF) != 0xFF => !(~x & 0xFF)
+            let and_op, x, mask;
+            if (
+                (and_op =
+                    self.left instanceof AST_Binary ? self.left
+                    : self.right instanceof AST_Binary ? self.right : null)
+                && (mask = and_op === self.left ? self.right : self.left)
+                && and_op.operator === "&"
+                && mask instanceof AST_Number
+                && mask.is_32_bit_integer(compressor)
+                && (x =
+                    and_op.left.equivalent_to(mask) ? and_op.right
+                    : and_op.right.equivalent_to(mask) ? and_op.left : null)
+            ) {
+                let optimized = booleanify(make_node(AST_Binary, self, {
+                    operator: "&",
+                    left: mask,
+                    right: make_node(AST_UnaryPrefix, self, {
+                        operator: "~",
+                        expression: x
+                    })
+                }), self.operator[0] === "!");
+
+                return best_of(compressor, optimized, self);
+            }
+        }
+        break;
+      case "&&":
+      case "||":
+        var lhs = self.left;
+        if (lhs.operator == self.operator) {
+            lhs = lhs.right;
+        }
+        if (lhs instanceof AST_Binary
+            && lhs.operator == (self.operator == "&&" ? "!==" : "===")
+            && self.right instanceof AST_Binary
+            && lhs.operator == self.right.operator
+            && (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null
+                || lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor))
+            && !lhs.right.has_side_effects(compressor)
+            && lhs.right.equivalent_to(self.right.right)) {
+            var combined = make_node(AST_Binary, self, {
+                operator: lhs.operator.slice(0, -1),
+                left: make_node(AST_Null, self),
+                right: lhs.right
+            });
+            if (lhs !== self.left) {
+                combined = make_node(AST_Binary, self, {
+                    operator: self.operator,
+                    left: self.left.left,
+                    right: combined
+                });
+            }
+            return combined;
+        }
+        break;
+    }
+    if (self.operator == "+" && compressor.in_boolean_context()) {
+        var ll = self.left.evaluate(compressor);
+        var rr = self.right.evaluate(compressor);
+        if (ll && typeof ll == "string") {
+            return make_sequence(self, [
+                self.right,
+                make_node(AST_True, self)
+            ]).optimize(compressor);
+        }
+        if (rr && typeof rr == "string") {
+            return make_sequence(self, [
+                self.left,
+                make_node(AST_True, self)
+            ]).optimize(compressor);
+        }
+    }
+    if (compressor.option("comparisons") && self.is_boolean()) {
+        if (!(compressor.parent() instanceof AST_Binary)
+            || compressor.parent() instanceof AST_Assign) {
+            var negated = make_node(AST_UnaryPrefix, self, {
+                operator: "!",
+                expression: self.negate(compressor, first_in_statement(compressor))
+            });
+            self = best_of(compressor, self, negated);
+        }
+        if (compressor.option("unsafe_comps")) {
+            switch (self.operator) {
+              case "<": reverse(">"); break;
+              case "<=": reverse(">="); break;
+            }
+        }
+    }
+    if (self.operator == "+") {
+        if (self.right instanceof AST_String
+            && self.right.getValue() == ""
+            && self.left.is_string(compressor)) {
+            return self.left;
+        }
+        if (self.left instanceof AST_String
+            && self.left.getValue() == ""
+            && self.right.is_string(compressor)) {
+            return self.right;
+        }
+        if (self.left instanceof AST_Binary
+            && self.left.operator == "+"
+            && self.left.left instanceof AST_String
+            && self.left.left.getValue() == ""
+            && self.right.is_string(compressor)) {
+            self.left = self.left.right;
+            return self;
+        }
+    }
+    if (compressor.option("evaluate")) {
+        switch (self.operator) {
+          case "&&":
+            var ll = has_flag(self.left, TRUTHY)
+                ? true
+                : has_flag(self.left, FALSY)
+                    ? false
+                    : self.left.evaluate(compressor);
+            if (!ll) {
+                return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
+            } else if (!(ll instanceof AST_Node)) {
+                return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
+            }
+            var rr = self.right.evaluate(compressor);
+            if (!rr) {
+                if (compressor.in_boolean_context()) {
+                    return make_sequence(self, [
+                        self.left,
+                        make_node(AST_False, self)
+                    ]).optimize(compressor);
+                } else {
+                    set_flag(self, FALSY);
+                }
+            } else if (!(rr instanceof AST_Node)) {
+                var parent = compressor.parent();
+                if (parent.operator == "&&" && parent.left === compressor.self() || compressor.in_boolean_context()) {
+                    return self.left.optimize(compressor);
+                }
+            }
+            // x || false && y ---> x ? y : false
+            if (self.left.operator == "||") {
+                var lr = self.left.right.evaluate(compressor);
+                if (!lr) return make_node(AST_Conditional, self, {
+                    condition: self.left.left,
+                    consequent: self.right,
+                    alternative: self.left.right
+                }).optimize(compressor);
+            }
+            break;
+          case "||":
+            var ll = has_flag(self.left, TRUTHY)
+              ? true
+              : has_flag(self.left, FALSY)
+                ? false
+                : self.left.evaluate(compressor);
+            if (!ll) {
+                return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
+            } else if (!(ll instanceof AST_Node)) {
+                return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
+            }
+            var rr = self.right.evaluate(compressor);
+            if (!rr) {
+                var parent = compressor.parent();
+                if (parent.operator == "||" && parent.left === compressor.self() || compressor.in_boolean_context()) {
+                    return self.left.optimize(compressor);
+                }
+            } else if (!(rr instanceof AST_Node)) {
+                if (compressor.in_boolean_context()) {
+                    return make_sequence(self, [
+                        self.left,
+                        make_node(AST_True, self)
+                    ]).optimize(compressor);
+                } else {
+                    set_flag(self, TRUTHY);
+                }
+            }
+            if (self.left.operator == "&&") {
+                var lr = self.left.right.evaluate(compressor);
+                if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
+                    condition: self.left.left,
+                    consequent: self.left.right,
+                    alternative: self.right
+                }).optimize(compressor);
+            }
+            break;
+          case "??":
+            if (is_nullish(self.left, compressor)) {
+                return self.right;
+            }
+
+            var ll = self.left.evaluate(compressor);
+            if (!(ll instanceof AST_Node)) {
+                // if we know the value for sure we can simply compute right away.
+                return ll == null ? self.right : self.left;
+            }
+
+            if (compressor.in_boolean_context()) {
+                const rr = self.right.evaluate(compressor);
+                if (!(rr instanceof AST_Node) && !rr) {
+                    return self.left;
+                }
+            }
+        }
+        var associative = true;
+        switch (self.operator) {
+          case "+":
+            // (x + "foo") + "bar" => x + "foobar"
+            if (self.right instanceof AST_Constant
+                && self.left instanceof AST_Binary
+                && self.left.operator == "+"
+                && self.left.is_string(compressor)) {
+                var binary = make_node(AST_Binary, self, {
+                    operator: "+",
+                    left: self.left.right,
+                    right: self.right,
+                });
+                var r = binary.optimize(compressor);
+                if (binary !== r) {
+                    self = make_node(AST_Binary, self, {
+                        operator: "+",
+                        left: self.left.left,
+                        right: r
+                    });
+                }
+            }
+            // (x + "foo") + ("bar" + y) => (x + "foobar") + y
+            if (self.left instanceof AST_Binary
+                && self.left.operator == "+"
+                && self.left.is_string(compressor)
+                && self.right instanceof AST_Binary
+                && self.right.operator == "+"
+                && self.right.is_string(compressor)) {
+                var binary = make_node(AST_Binary, self, {
+                    operator: "+",
+                    left: self.left.right,
+                    right: self.right.left,
+                });
+                var m = binary.optimize(compressor);
+                if (binary !== m) {
+                    self = make_node(AST_Binary, self, {
+                        operator: "+",
+                        left: make_node(AST_Binary, self.left, {
+                            operator: "+",
+                            left: self.left.left,
+                            right: m
+                        }),
+                        right: self.right.right
+                    });
+                }
+            }
+            // a + -b => a - b
+            if (self.right instanceof AST_UnaryPrefix
+                && self.right.operator == "-"
+                && self.left.is_number_or_bigint(compressor)) {
+                self = make_node(AST_Binary, self, {
+                    operator: "-",
+                    left: self.left,
+                    right: self.right.expression
+                });
+                break;
+            }
+            // -a + b => b - a
+            if (self.left instanceof AST_UnaryPrefix
+                && self.left.operator == "-"
+                && reversible()
+                && self.right.is_number_or_bigint(compressor)) {
+                self = make_node(AST_Binary, self, {
+                    operator: "-",
+                    left: self.right,
+                    right: self.left.expression
+                });
+                break;
+            }
+            // `foo${bar}baz` + 1 => `foo${bar}baz1`
+            if (self.left instanceof AST_TemplateString) {
+                var l = self.left;
+                var r = self.right.evaluate(compressor);
+                if (r != self.right) {
+                    l.segments[l.segments.length - 1].value += String(r);
+                    return l;
+                }
+            }
+            // 1 + `foo${bar}baz` => `1foo${bar}baz`
+            if (self.right instanceof AST_TemplateString) {
+                var r = self.right;
+                var l = self.left.evaluate(compressor);
+                if (l != self.left) {
+                    r.segments[0].value = String(l) + r.segments[0].value;
+                    return r;
+                }
+            }
+            // `1${bar}2` + `foo${bar}baz` => `1${bar}2foo${bar}baz`
+            if (self.left instanceof AST_TemplateString
+                && self.right instanceof AST_TemplateString) {
+                var l = self.left;
+                var segments = l.segments;
+                var r = self.right;
+                segments[segments.length - 1].value += r.segments[0].value;
+                for (var i = 1; i < r.segments.length; i++) {
+                    segments.push(r.segments[i]);
+                }
+                return l;
+            }
+          case "*":
+            associative = compressor.option("unsafe_math");
+          case "&":
+          case "|":
+          case "^":
+            // a + +b => +b + a
+            if (
+                self.left.is_number_or_bigint(compressor)
+                && self.right.is_number_or_bigint(compressor)
+                && reversible()
+                && !(self.left instanceof AST_Binary
+                    && self.left.operator != self.operator
+                    && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
+                var reversed = make_node(AST_Binary, self, {
+                    operator: self.operator,
+                    left: self.right,
+                    right: self.left
+                });
+                if (self.right instanceof AST_Constant
+                    && !(self.left instanceof AST_Constant)) {
+                    self = best_of(compressor, reversed, self);
+                } else {
+                    self = best_of(compressor, self, reversed);
+                }
+            }
+            if (associative && self.is_number_or_bigint(compressor)) {
+                // a + (b + c) => (a + b) + c
+                if (self.right instanceof AST_Binary
+                    && self.right.operator == self.operator) {
+                    self = make_node(AST_Binary, self, {
+                        operator: self.operator,
+                        left: make_node(AST_Binary, self.left, {
+                            operator: self.operator,
+                            left: self.left,
+                            right: self.right.left,
+                            start: self.left.start,
+                            end: self.right.left.end
+                        }),
+                        right: self.right.right
+                    });
+                }
+                // (n + 2) + 3 => 5 + n
+                // (2 * n) * 3 => 6 + n
+                if (self.right instanceof AST_Constant
+                    && self.left instanceof AST_Binary
+                    && self.left.operator == self.operator) {
+                    if (self.left.left instanceof AST_Constant) {
+                        self = make_node(AST_Binary, self, {
+                            operator: self.operator,
+                            left: make_node(AST_Binary, self.left, {
+                                operator: self.operator,
+                                left: self.left.left,
+                                right: self.right,
+                                start: self.left.left.start,
+                                end: self.right.end
+                            }),
+                            right: self.left.right
+                        });
+                    } else if (self.left.right instanceof AST_Constant) {
+                        self = make_node(AST_Binary, self, {
+                            operator: self.operator,
+                            left: make_node(AST_Binary, self.left, {
+                                operator: self.operator,
+                                left: self.left.right,
+                                right: self.right,
+                                start: self.left.right.start,
+                                end: self.right.end
+                            }),
+                            right: self.left.left
+                        });
+                    }
+                }
+                // (a | 1) | (2 | d) => (3 | a) | b
+                if (self.left instanceof AST_Binary
+                    && self.left.operator == self.operator
+                    && self.left.right instanceof AST_Constant
+                    && self.right instanceof AST_Binary
+                    && self.right.operator == self.operator
+                    && self.right.left instanceof AST_Constant) {
+                    self = make_node(AST_Binary, self, {
+                        operator: self.operator,
+                        left: make_node(AST_Binary, self.left, {
+                            operator: self.operator,
+                            left: make_node(AST_Binary, self.left.left, {
+                                operator: self.operator,
+                                left: self.left.right,
+                                right: self.right.left,
+                                start: self.left.right.start,
+                                end: self.right.left.end
+                            }),
+                            right: self.left.left
+                        }),
+                        right: self.right.right
+                    });
+                }
+            }
+        }
+
+        // bitwise ops
+        if (bitwise_binop.has(self.operator)) {
+            // Use De Morgan's laws
+            // z & (X | y)
+            // => z & X (given y & z === 0)
+            // => z & X | {y & z} (given y & z !== 0)
+            let y, z, x_node, y_node, z_node = self.left;
+            if (
+                self.operator === "&"
+                && self.right instanceof AST_Binary
+                && self.right.operator === "|"
+                && typeof (z = self.left.evaluate(compressor)) === "number"
+            ) {
+                if (typeof (y = self.right.right.evaluate(compressor)) === "number") {
+                    // z & (X | y)
+                    x_node = self.right.left;
+                    y_node = self.right.right;
+                } else if (typeof (y = self.right.left.evaluate(compressor)) === "number") {
+                    // z & (y | X)
+                    x_node = self.right.right;
+                    y_node = self.right.left;
+                }
+
+                if (x_node && y_node) {
+                    if ((y & z) === 0) {
+                        self = make_node(AST_Binary, self, {
+                            operator: self.operator,
+                            left: z_node,
+                            right: x_node
+                        });
+                    } else {
+                        const reordered_ops = make_node(AST_Binary, self, {
+                            operator: "|",
+                            left: make_node(AST_Binary, self, {
+                                operator: "&",
+                                left: x_node,
+                                right: z_node
+                            }),
+                            right: make_node_from_constant(y & z, y_node),
+                        });
+
+                        self = best_of(compressor, self, reordered_ops);
+                    }
+                }
+            }
+
+            // x | x => 0 | x
+            // x & x => 0 | x
+            if (
+                (self.operator === "|" || self.operator === "&")
+                && self.left.equivalent_to(self.right)
+                && !self.left.has_side_effects(compressor)
+                && compressor.in_32_bit_context(true)
+            ) {
+                self.left = make_node(AST_Number, self, { value: 0 });
+                self.operator = "|";
+            }
+
+            // ~x ^ ~y => x ^ y
+            if (
+                self.operator === "^"
+                && self.left instanceof AST_UnaryPrefix
+                && self.left.operator === "~"
+                && self.right instanceof AST_UnaryPrefix
+                && self.right.operator === "~"
+            ) {
+                self = make_node(AST_Binary, self, {
+                    operator: "^",
+                    left: self.left.expression,
+                    right: self.right.expression
+                });
+            }
+
+
+            // Shifts that do nothing
+            // {anything} >> 0 => {anything} | 0
+            // {anything} << 0 => {anything} | 0
+            if (
+                (self.operator === "<<" || self.operator === ">>")
+                && self.right instanceof AST_Number && self.right.value === 0
+            ) {
+                self.operator = "|";
+            }
+
+            // Find useless to-bitwise conversions
+            // {32 bit integer} | 0 => {32 bit integer}
+            // {32 bit integer} ^ 0 => {32 bit integer}
+            const zero_side = self.right instanceof AST_Number && self.right.value === 0 ? self.right
+                : self.left instanceof AST_Number && self.left.value === 0 ? self.left
+                : null;
+            const non_zero_side = zero_side && (zero_side === self.right ? self.left : self.right);
+            if (
+                zero_side
+                && (self.operator === "|" || self.operator === "^")
+                && (non_zero_side.is_32_bit_integer(compressor) || compressor.in_32_bit_context(true))
+            ) {
+                return non_zero_side;
+            }
+
+            // {anything} & 0 => 0
+            if (
+                zero_side
+                && self.operator === "&"
+                && !non_zero_side.has_side_effects(compressor)
+                && non_zero_side.is_32_bit_integer(compressor)
+            ) {
+                return zero_side;
+            }
+
+            // ~0 is all ones, as well as -1.
+            // We can ellide some operations with it.
+            const is_full_mask = (node) =>
+                node instanceof AST_Number && node.value === -1
+                ||
+                    node instanceof AST_UnaryPrefix
+                    && node.operator === "-"
+                    && node.expression instanceof AST_Number
+                    && node.expression.value === 1;
+
+            const full_mask = is_full_mask(self.right) ? self.right
+                : is_full_mask(self.left) ? self.left
+                : null;
+            const other_side = (full_mask === self.right ? self.left : self.right);
+
+            // {32 bit integer} & -1 => {32 bit integer}
+            if (
+                full_mask
+                && self.operator === "&"
+                && (
+                    other_side.is_32_bit_integer(compressor)
+                    || compressor.in_32_bit_context(true)
+                )
+            ) {
+                return other_side;
+            }
+
+            // {anything} ^ -1 => ~{anything}
+            if (
+                full_mask
+                && self.operator === "^"
+                && (
+                    other_side.is_32_bit_integer(compressor)
+                    || compressor.in_32_bit_context(true)
+                )
+            ) {
+                return other_side.bitwise_negate(compressor);
+            }
+        }
+    }
+    // x && (y && z)  ==>  x && y && z
+    // x || (y || z)  ==>  x || y || z
+    // x + ("y" + z)  ==>  x + "y" + z
+    // "x" + (y + "z")==>  "x" + y + "z"
+    if (self.right instanceof AST_Binary
+        && self.right.operator == self.operator
+        && (lazy_op.has(self.operator)
+            || (self.operator == "+"
+                && (self.right.left.is_string(compressor)
+                    || (self.left.is_string(compressor)
+                        && self.right.right.is_string(compressor)))))
+    ) {
+        self.left = make_node(AST_Binary, self.left, {
+            operator : self.operator,
+            left     : self.left.transform(compressor),
+            right    : self.right.left.transform(compressor)
+        });
+        self.right = self.right.right.transform(compressor);
+        return self.transform(compressor);
+    }
+    var ev = self.evaluate(compressor);
+    if (ev !== self) {
+        ev = make_node_from_constant(ev, self).optimize(compressor);
+        return best_of(compressor, ev, self);
+    }
+    return self;
+});
+
+def_optimize(AST_SymbolExport, function(self) {
+    return self;
+});
+
+def_optimize(AST_SymbolRef, function(self, compressor) {
+    if (
+        !compressor.option("ie8")
+        && is_undeclared_ref(self)
+        && !compressor.find_parent(AST_With)
+    ) {
+        switch (self.name) {
+          case "undefined":
+            return make_node(AST_Undefined, self).optimize(compressor);
+          case "NaN":
+            return make_node(AST_NaN, self).optimize(compressor);
+          case "Infinity":
+            return make_node(AST_Infinity, self).optimize(compressor);
+        }
+    }
+
+    if (compressor.option("reduce_vars") && !compressor.is_lhs()) {
+        return inline_into_symbolref(self, compressor);
+    } else {
+        return self;
+    }
+});
+
+function is_atomic(lhs, self) {
+    return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
+}
+
+/** Apply the `unsafe_undefined` option: find a variable called `undefined` and turn `self` into a reference to it. */
+function unsafe_undefined_ref(self, compressor) {
+    if (compressor.option("unsafe_undefined")) {
+        var undef = find_variable(compressor, "undefined");
+        if (undef) {
+            var ref = make_node(AST_SymbolRef, self, {
+                name   : "undefined",
+                scope  : undef.scope,
+                thedef : undef
+            });
+            set_flag(ref, UNDEFINED);
+            return ref;
+        }
+    }
+    return null;
+}
+
+def_optimize(AST_Undefined, function(self, compressor) {
+    var symbolref = unsafe_undefined_ref(self, compressor);
+    if (symbolref) return symbolref;
+    var lhs = compressor.is_lhs();
+    if (lhs && is_atomic(lhs, self)) return self;
+    return make_void_0(self);
+});
+
+def_optimize(AST_Infinity, function(self, compressor) {
+    var lhs = compressor.is_lhs();
+    if (lhs && is_atomic(lhs, self)) return self;
+    if (
+        compressor.option("keep_infinity")
+        && !(lhs && !is_atomic(lhs, self))
+        && !find_variable(compressor, "Infinity")
+    ) {
+        return self;
+    }
+    return make_node(AST_Binary, self, {
+        operator: "/",
+        left: make_node(AST_Number, self, {
+            value: 1
+        }),
+        right: make_node(AST_Number, self, {
+            value: 0
+        })
+    });
+});
+
+def_optimize(AST_NaN, function(self, compressor) {
+    var lhs = compressor.is_lhs();
+    if (lhs && !is_atomic(lhs, self)
+        || find_variable(compressor, "NaN")) {
+        return make_node(AST_Binary, self, {
+            operator: "/",
+            left: make_node(AST_Number, self, {
+                value: 0
+            }),
+            right: make_node(AST_Number, self, {
+                value: 0
+            })
+        });
+    }
+    return self;
+});
+
+const ASSIGN_OPS = makePredicate("+ - / * % >> << >>> | ^ &");
+const ASSIGN_OPS_COMMUTATIVE = makePredicate("* | ^ &");
+def_optimize(AST_Assign, function(self, compressor) {
+    if (self.logical) {
+        return self.lift_sequences(compressor);
+    }
+
+    var def;
+    // x = x ---> x
+    if (
+        self.operator === "="
+        && self.left instanceof AST_SymbolRef
+        && self.left.name !== "arguments"
+        && !(def = self.left.definition()).undeclared
+        && self.right.equivalent_to(self.left)
+    ) {
+        return self.right;
+    }
+
+    if (compressor.option("dead_code")
+        && self.left instanceof AST_SymbolRef
+        && (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) {
+        var level = 0, node, parent = self;
+        do {
+            node = parent;
+            parent = compressor.parent(level++);
+            if (parent instanceof AST_Exit) {
+                if (in_try(level, parent)) break;
+                if (is_reachable(def.scope, [ def ])) break;
+                if (self.operator == "=") return self.right;
+                def.fixed = false;
+                return make_node(AST_Binary, self, {
+                    operator: self.operator.slice(0, -1),
+                    left: self.left,
+                    right: self.right
+                }).optimize(compressor);
+            }
+        } while (parent instanceof AST_Binary && parent.right === node
+            || parent instanceof AST_Sequence && parent.tail_node() === node);
+    }
+    self = self.lift_sequences(compressor);
+
+    if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) {
+        // x = expr1 OP expr2
+        if (self.right.left instanceof AST_SymbolRef
+            && self.right.left.name == self.left.name
+            && ASSIGN_OPS.has(self.right.operator)) {
+            // x = x - 2  --->  x -= 2
+            self.operator = self.right.operator + "=";
+            self.right = self.right.right;
+        } else if (self.right.right instanceof AST_SymbolRef
+            && self.right.right.name == self.left.name
+            && ASSIGN_OPS_COMMUTATIVE.has(self.right.operator)
+            && !self.right.left.has_side_effects(compressor)) {
+            // x = 2 & x  --->  x &= 2
+            self.operator = self.right.operator + "=";
+            self.right = self.right.left;
+        }
+    }
+    return self;
+
+    function in_try(level, node) {
+        function may_assignment_throw() {
+            const right = self.right;
+            self.right = make_node(AST_Null, right);
+            const may_throw = node.may_throw(compressor);
+            self.right = right;
+
+            return may_throw;
+        }
+
+        var stop_at = self.left.definition().scope.get_defun_scope();
+        var parent;
+        while ((parent = compressor.parent(level++)) !== stop_at) {
+            if (parent instanceof AST_Try) {
+                if (parent.bfinally) return true;
+                if (parent.bcatch && may_assignment_throw()) return true;
+            }
+        }
+    }
+});
+
+def_optimize(AST_DefaultAssign, function(self, compressor) {
+    if (!compressor.option("evaluate")) {
+        return self;
+    }
+    var evaluateRight = self.right.evaluate(compressor);
+
+    // `[x = undefined] = foo` ---> `[x] = foo`
+    // `(arg = undefined) => ...` ---> `(arg) => ...` (unless `keep_fargs`)
+    // `((arg = undefined) => ...)()` ---> `((arg) => ...)()`
+    let lambda, iife;
+    if (evaluateRight === undefined) {
+        if (
+            (lambda = compressor.parent()) instanceof AST_Lambda
+                ? (
+                    compressor.option("keep_fargs") === false
+                    || (iife = compressor.parent(1)).TYPE === "Call"
+                        && iife.expression === lambda
+                )
+                : true
+        ) {
+            self = self.left;
+        }
+    } else if (evaluateRight !== self.right) {
+        evaluateRight = make_node_from_constant(evaluateRight, self.right);
+        self.right = best_of_expression(evaluateRight, self.right);
+    }
+
+    return self;
+});
+
+function is_nullish_check(check, check_subject, compressor) {
+    if (check_subject.may_throw(compressor)) return false;
+
+    let nullish_side;
+
+    // foo == null
+    if (
+        check instanceof AST_Binary
+        && check.operator === "=="
+        // which side is nullish?
+        && (
+            (nullish_side = is_nullish(check.left, compressor) && check.left)
+            || (nullish_side = is_nullish(check.right, compressor) && check.right)
+        )
+        // is the other side the same as the check_subject
+        && (
+            nullish_side === check.left
+                ? check.right
+                : check.left
+        ).equivalent_to(check_subject)
+    ) {
+        return true;
+    }
+
+    // foo === null || foo === undefined
+    if (check instanceof AST_Binary && check.operator === "||") {
+        let null_cmp;
+        let undefined_cmp;
+
+        const find_comparison = cmp => {
+            if (!(
+                cmp instanceof AST_Binary
+                && (cmp.operator === "===" || cmp.operator === "==")
+            )) {
+                return false;
+            }
+
+            let found = 0;
+            let defined_side;
+
+            if (cmp.left instanceof AST_Null) {
+                found++;
+                null_cmp = cmp;
+                defined_side = cmp.right;
+            }
+            if (cmp.right instanceof AST_Null) {
+                found++;
+                null_cmp = cmp;
+                defined_side = cmp.left;
+            }
+            if (is_undefined(cmp.left, compressor)) {
+                found++;
+                undefined_cmp = cmp;
+                defined_side = cmp.right;
+            }
+            if (is_undefined(cmp.right, compressor)) {
+                found++;
+                undefined_cmp = cmp;
+                defined_side = cmp.left;
+            }
+
+            if (found !== 1) {
+                return false;
+            }
+
+            if (!defined_side.equivalent_to(check_subject)) {
+                return false;
+            }
+
+            return true;
+        };
+
+        if (!find_comparison(check.left)) return false;
+        if (!find_comparison(check.right)) return false;
+
+        if (null_cmp && undefined_cmp && null_cmp !== undefined_cmp) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+def_optimize(AST_Conditional, function(self, compressor) {
+    if (!compressor.option("conditionals")) return self;
+    // This looks like lift_sequences(), should probably be under "sequences"
+    if (self.condition instanceof AST_Sequence) {
+        var expressions = self.condition.expressions.slice();
+        self.condition = expressions.pop();
+        expressions.push(self);
+        return make_sequence(self, expressions);
+    }
+    var cond = self.condition.evaluate(compressor);
+    if (cond !== self.condition) {
+        if (cond) {
+            return maintain_this_binding(compressor.parent(), compressor.self(), self.consequent);
+        } else {
+            return maintain_this_binding(compressor.parent(), compressor.self(), self.alternative);
+        }
+    }
+    var negated = cond.negate(compressor, first_in_statement(compressor));
+    if (best_of(compressor, cond, negated) === negated) {
+        self = make_node(AST_Conditional, self, {
+            condition: negated,
+            consequent: self.alternative,
+            alternative: self.consequent
+        });
+    }
+    var condition = self.condition;
+    var consequent = self.consequent;
+    var alternative = self.alternative;
+    // x?x:y --> x||y
+    if (condition instanceof AST_SymbolRef
+        && consequent instanceof AST_SymbolRef
+        && condition.definition() === consequent.definition()) {
+        return make_node(AST_Binary, self, {
+            operator: "||",
+            left: condition,
+            right: alternative
+        });
+    }
+    // if (foo) exp = something; else exp = something_else;
+    //                   |
+    //                   v
+    // exp = foo ? something : something_else;
+    if (
+        consequent instanceof AST_Assign
+        && alternative instanceof AST_Assign
+        && consequent.operator === alternative.operator
+        && consequent.logical === alternative.logical
+        && consequent.left.equivalent_to(alternative.left)
+        && (!self.condition.has_side_effects(compressor)
+            || consequent.operator == "="
+                && !consequent.left.has_side_effects(compressor))
+    ) {
+        return make_node(AST_Assign, self, {
+            operator: consequent.operator,
+            left: consequent.left,
+            logical: consequent.logical,
+            right: make_node(AST_Conditional, self, {
+                condition: self.condition,
+                consequent: consequent.right,
+                alternative: alternative.right
+            })
+        });
+    }
+    // x ? y(a) : y(b) --> y(x ? a : b)
+    var arg_index;
+    if (consequent instanceof AST_Call
+        && alternative.TYPE === consequent.TYPE
+        && consequent.args.length > 0
+        && consequent.args.length == alternative.args.length
+        && consequent.expression.equivalent_to(alternative.expression)
+        && !self.condition.has_side_effects(compressor)
+        && !consequent.expression.has_side_effects(compressor)
+        && typeof (arg_index = single_arg_diff()) == "number") {
+        var node = consequent.clone();
+        node.args[arg_index] = make_node(AST_Conditional, self, {
+            condition: self.condition,
+            consequent: consequent.args[arg_index],
+            alternative: alternative.args[arg_index]
+        });
+        return node;
+    }
+    // a ? b : c ? b : d --> (a || c) ? b : d
+    if (alternative instanceof AST_Conditional
+        && consequent.equivalent_to(alternative.consequent)) {
+        return make_node(AST_Conditional, self, {
+            condition: make_node(AST_Binary, self, {
+                operator: "||",
+                left: condition,
+                right: alternative.condition
+            }),
+            consequent: consequent,
+            alternative: alternative.alternative
+        }).optimize(compressor);
+    }
+
+    // a == null ? b : a -> a ?? b
+    if (
+        compressor.option("ecma") >= 2020 &&
+        is_nullish_check(condition, alternative, compressor)
+    ) {
+        return make_node(AST_Binary, self, {
+            operator: "??",
+            left: alternative,
+            right: consequent
+        }).optimize(compressor);
+    }
+
+    // a ? b : (c, b) --> (a || c), b
+    if (alternative instanceof AST_Sequence
+        && consequent.equivalent_to(alternative.expressions[alternative.expressions.length - 1])) {
+        return make_sequence(self, [
+            make_node(AST_Binary, self, {
+                operator: "||",
+                left: condition,
+                right: make_sequence(self, alternative.expressions.slice(0, -1))
+            }),
+            consequent
+        ]).optimize(compressor);
+    }
+    // a ? b : (c && b) --> (a || c) && b
+    if (alternative instanceof AST_Binary
+        && alternative.operator == "&&"
+        && consequent.equivalent_to(alternative.right)) {
+        return make_node(AST_Binary, self, {
+            operator: "&&",
+            left: make_node(AST_Binary, self, {
+                operator: "||",
+                left: condition,
+                right: alternative.left
+            }),
+            right: consequent
+        }).optimize(compressor);
+    }
+    // x?y?z:a:a --> x&&y?z:a
+    if (consequent instanceof AST_Conditional
+        && consequent.alternative.equivalent_to(alternative)) {
+        return make_node(AST_Conditional, self, {
+            condition: make_node(AST_Binary, self, {
+                left: self.condition,
+                operator: "&&",
+                right: consequent.condition
+            }),
+            consequent: consequent.consequent,
+            alternative: alternative
+        });
+    }
+    // x ? y : y --> x, y
+    if (consequent.equivalent_to(alternative)) {
+        return make_sequence(self, [
+            self.condition,
+            consequent
+        ]).optimize(compressor);
+    }
+    // x ? y || z : z --> x && y || z
+    if (consequent instanceof AST_Binary
+        && consequent.operator == "||"
+        && consequent.right.equivalent_to(alternative)) {
+        return make_node(AST_Binary, self, {
+            operator: "||",
+            left: make_node(AST_Binary, self, {
+                operator: "&&",
+                left: self.condition,
+                right: consequent.left
+            }),
+            right: alternative
+        }).optimize(compressor);
+    }
+
+    const in_bool = compressor.in_boolean_context();
+    if (is_true(self.consequent)) {
+        if (is_false(self.alternative)) {
+            // c ? true : false ---> !!c
+            return booleanize(self.condition);
+        }
+        // c ? true : x ---> !!c || x
+        return make_node(AST_Binary, self, {
+            operator: "||",
+            left: booleanize(self.condition),
+            right: self.alternative
+        });
+    }
+    if (is_false(self.consequent)) {
+        if (is_true(self.alternative)) {
+            // c ? false : true ---> !c
+            return booleanize(self.condition.negate(compressor));
+        }
+        // c ? false : x ---> !c && x
+        return make_node(AST_Binary, self, {
+            operator: "&&",
+            left: booleanize(self.condition.negate(compressor)),
+            right: self.alternative
+        });
+    }
+    if (is_true(self.alternative)) {
+        // c ? x : true ---> !c || x
+        return make_node(AST_Binary, self, {
+            operator: "||",
+            left: booleanize(self.condition.negate(compressor)),
+            right: self.consequent
+        });
+    }
+    if (is_false(self.alternative)) {
+        // c ? x : false ---> !!c && x
+        return make_node(AST_Binary, self, {
+            operator: "&&",
+            left: booleanize(self.condition),
+            right: self.consequent
+        });
+    }
+
+    return self;
+
+    function booleanize(node) {
+        if (node.is_boolean()) return node;
+        // !!expression
+        return make_node(AST_UnaryPrefix, node, {
+            operator: "!",
+            expression: node.negate(compressor)
+        });
+    }
+
+    // AST_True or !0
+    function is_true(node) {
+        return node instanceof AST_True
+            || in_bool
+                && node instanceof AST_Constant
+                && node.getValue()
+            || (node instanceof AST_UnaryPrefix
+                && node.operator == "!"
+                && node.expression instanceof AST_Constant
+                && !node.expression.getValue());
+    }
+    // AST_False or !1
+    function is_false(node) {
+        return node instanceof AST_False
+            || in_bool
+                && node instanceof AST_Constant
+                && !node.getValue()
+            || (node instanceof AST_UnaryPrefix
+                && node.operator == "!"
+                && node.expression instanceof AST_Constant
+                && node.expression.getValue());
+    }
+
+    function single_arg_diff() {
+        var a = consequent.args;
+        var b = alternative.args;
+        for (var i = 0, len = a.length; i < len; i++) {
+            if (a[i] instanceof AST_Expansion) return;
+            if (!a[i].equivalent_to(b[i])) {
+                if (b[i] instanceof AST_Expansion) return;
+                for (var j = i + 1; j < len; j++) {
+                    if (a[j] instanceof AST_Expansion) return;
+                    if (!a[j].equivalent_to(b[j])) return;
+                }
+                return i;
+            }
+        }
+    }
+});
+
+def_optimize(AST_Boolean, function(self, compressor) {
+    if (compressor.in_boolean_context()) return make_node(AST_Number, self, {
+        value: +self.value
+    });
+    var p = compressor.parent();
+    if (compressor.option("booleans_as_integers")) {
+        if (p instanceof AST_Binary && (p.operator == "===" || p.operator == "!==")) {
+            p.operator = p.operator.replace(/=$/, "");
+        }
+        return make_node(AST_Number, self, {
+            value: +self.value
+        });
+    }
+    if (compressor.option("booleans")) {
+        if (p instanceof AST_Binary && (p.operator == "=="
+                                        || p.operator == "!=")) {
+            return make_node(AST_Number, self, {
+                value: +self.value
+            });
+        }
+        return make_node(AST_UnaryPrefix, self, {
+            operator: "!",
+            expression: make_node(AST_Number, self, {
+                value: 1 - self.value
+            })
+        });
+    }
+    return self;
+});
+
+function safe_to_flatten(value, compressor) {
+    if (value instanceof AST_SymbolRef) {
+        value = value.fixed_value();
+    }
+    if (!value) return false;
+    if (!(value instanceof AST_Lambda || value instanceof AST_Class)) return true;
+    if (!(value instanceof AST_Lambda && value.contains_this())) return true;
+    return compressor.parent() instanceof AST_New;
+}
+
+AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) {
+    if (!compressor.option("properties")) return;
+    if (key === "__proto__") return;
+    if (this instanceof AST_DotHash) return;
+
+    var arrows = compressor.option("unsafe_arrows") && compressor.option("ecma") >= 2015;
+    var expr = this.expression;
+    if (expr instanceof AST_Object) {
+        var props = expr.properties;
+
+        for (var i = props.length; --i >= 0;) {
+            var prop = props[i];
+
+            if ("" + (prop instanceof AST_ConciseMethod ? prop.key.name : prop.key) == key) {
+                const all_props_flattenable = props.every((p) =>
+                    (p instanceof AST_ObjectKeyVal
+                        || arrows && p instanceof AST_ConciseMethod && !p.value.is_generator
+                    )
+                    && !p.computed_key()
+                );
+
+                if (!all_props_flattenable) return;
+                if (!safe_to_flatten(prop.value, compressor)) return;
+
+                return make_node(AST_Sub, this, {
+                    expression: make_node(AST_Array, expr, {
+                        elements: props.map(function(prop) {
+                            var v = prop.value;
+                            if (v instanceof AST_Accessor) {
+                                v = make_node(AST_Function, v, v);
+                            }
+
+                            var k = prop.key;
+                            if (k instanceof AST_Node && !(k instanceof AST_SymbolMethod)) {
+                                return make_sequence(prop, [ k, v ]);
+                            }
+
+                            return v;
+                        })
+                    }),
+                    property: make_node(AST_Number, this, {
+                        value: i
+                    })
+                });
+            }
+        }
+    }
+});
+
+def_optimize(AST_Sub, function(self, compressor) {
+    var expr = self.expression;
+    var prop = self.property;
+    if (compressor.option("properties")) {
+        var key = prop.evaluate(compressor);
+        if (key !== prop) {
+            if (typeof key == "string") {
+                if (key == "undefined") {
+                    key = undefined;
+                } else {
+                    var value = parseFloat(key);
+                    if (value.toString() == key) {
+                        key = value;
+                    }
+                }
+            }
+            prop = self.property = best_of_expression(
+                prop,
+                make_node_from_constant(key, prop).transform(compressor)
+            );
+            var property = "" + key;
+            if (is_basic_identifier_string(property)
+                && property.length <= prop.size() + 1) {
+                return make_node(AST_Dot, self, {
+                    expression: expr,
+                    optional: self.optional,
+                    property: property,
+                    quote: prop.quote,
+                }).optimize(compressor);
+            }
+        }
+    }
+    var fn;
+    OPT_ARGUMENTS: if (compressor.option("arguments")
+        && expr instanceof AST_SymbolRef
+        && expr.name == "arguments"
+        && expr.definition().orig.length == 1
+        && (fn = expr.scope) instanceof AST_Lambda
+        && fn.uses_arguments
+        && !(fn instanceof AST_Arrow)
+        && prop instanceof AST_Number) {
+        var index = prop.getValue();
+        var params = new Set();
+        var argnames = fn.argnames;
+        for (var n = 0; n < argnames.length; n++) {
+            if (!(argnames[n] instanceof AST_SymbolFunarg)) {
+                break OPT_ARGUMENTS; // destructuring parameter - bail
+            }
+            var param = argnames[n].name;
+            if (params.has(param)) {
+                break OPT_ARGUMENTS; // duplicate parameter - bail
+            }
+            params.add(param);
+        }
+        var argname = fn.argnames[index];
+        if (argname && compressor.has_directive("use strict")) {
+            var def = argname.definition();
+            if (!compressor.option("reduce_vars") || def.assignments || def.orig.length > 1) {
+                argname = null;
+            }
+        } else if (!argname && !compressor.option("keep_fargs") && index < fn.argnames.length + 5) {
+            while (index >= fn.argnames.length) {
+                argname = fn.create_symbol(AST_SymbolFunarg, {
+                    source: fn,
+                    scope: fn,
+                    tentative_name: "argument_" + fn.argnames.length,
+                });
+                fn.argnames.push(argname);
+            }
+        }
+        if (argname) {
+            var sym = make_node(AST_SymbolRef, self, argname);
+            sym.reference({});
+            clear_flag(argname, UNUSED);
+            return sym;
+        }
+    }
+    if (compressor.is_lhs()) return self;
+    if (key !== prop) {
+        var sub = self.flatten_object(property, compressor);
+        if (sub) {
+            expr = self.expression = sub.expression;
+            prop = self.property = sub.property;
+        }
+    }
+    if (compressor.option("properties") && compressor.option("side_effects")
+        && prop instanceof AST_Number && expr instanceof AST_Array) {
+        var index = prop.getValue();
+        var elements = expr.elements;
+        var retValue = elements[index];
+        FLATTEN: if (safe_to_flatten(retValue, compressor)) {
+            var flatten = true;
+            var values = [];
+            for (var i = elements.length; --i > index;) {
+                var value = elements[i].drop_side_effect_free(compressor);
+                if (value) {
+                    values.unshift(value);
+                    if (flatten && value.has_side_effects(compressor)) flatten = false;
+                }
+            }
+            if (retValue instanceof AST_Expansion) break FLATTEN;
+            retValue = retValue instanceof AST_Hole ? make_void_0(retValue) : retValue;
+            if (!flatten) values.unshift(retValue);
+            while (--i >= 0) {
+                var value = elements[i];
+                if (value instanceof AST_Expansion) break FLATTEN;
+                value = value.drop_side_effect_free(compressor);
+                if (value) values.unshift(value);
+                else index--;
+            }
+            if (flatten) {
+                values.push(retValue);
+                return make_sequence(self, values).optimize(compressor);
+            } else return make_node(AST_Sub, self, {
+                expression: make_node(AST_Array, expr, {
+                    elements: values
+                }),
+                property: make_node(AST_Number, prop, {
+                    value: index
+                })
+            });
+        }
+    }
+    var ev = self.evaluate(compressor);
+    if (ev !== self) {
+        ev = make_node_from_constant(ev, self).optimize(compressor);
+        return best_of(compressor, ev, self);
+    }
+    return self;
+});
+
+def_optimize(AST_Chain, function (self, compressor) {
+    if (is_nullish(self.expression, compressor)) {
+        let parent = compressor.parent();
+        // It's valid to delete a nullish optional chain, but if we optimized
+        // this to `delete undefined` then it would appear to be a syntax error
+        // when we try to optimize the delete. Thankfully, `delete 0` is fine.
+        if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") {
+            return make_node_from_constant(0, self);
+        }
+        return make_void_0(self).optimize(compressor);
+    }
+    if (
+        self.expression instanceof AST_PropAccess
+        || self.expression instanceof AST_Call
+    ) {
+        return self;
+    } else {
+        // Keep the AST valid, in case the child swapped itself
+        return self.expression;
+    }
+});
+
+def_optimize(AST_Dot, function(self, compressor) {
+    const parent = compressor.parent();
+    if (compressor.is_lhs()) return self;
+    if (compressor.option("unsafe_proto")
+        && self.expression instanceof AST_Dot
+        && self.expression.property == "prototype") {
+        var exp = self.expression.expression;
+        if (is_undeclared_ref(exp)) switch (exp.name) {
+          case "Array":
+            self.expression = make_node(AST_Array, self.expression, {
+                elements: []
+            });
+            break;
+          case "Function":
+            self.expression = make_empty_function(self.expression);
+            break;
+          case "Number":
+            self.expression = make_node(AST_Number, self.expression, {
+                value: 0
+            });
+            break;
+          case "Object":
+            self.expression = make_node(AST_Object, self.expression, {
+                properties: []
+            });
+            break;
+          case "RegExp":
+            self.expression = make_node(AST_RegExp, self.expression, {
+                value: { source: "t", flags: "" }
+            });
+            break;
+          case "String":
+            self.expression = make_node(AST_String, self.expression, {
+                value: ""
+            });
+            break;
+        }
+    }
+    if (!(parent instanceof AST_Call) || !has_annotation(parent, _NOINLINE)) {
+        const sub = self.flatten_object(self.property, compressor);
+        if (sub) return sub.optimize(compressor);
+    }
+
+    if (self.expression instanceof AST_PropAccess
+        && parent instanceof AST_PropAccess) {
+        return self;
+    }
+
+    let ev = self.evaluate(compressor);
+    if (ev !== self) {
+        ev = make_node_from_constant(ev, self).optimize(compressor);
+        return best_of(compressor, ev, self);
+    }
+    return self;
+});
+
+function literals_in_boolean_context(self, compressor) {
+    if (compressor.in_boolean_context()) {
+        return best_of(compressor, self, make_sequence(self, [
+            self,
+            make_node(AST_True, self)
+        ]).optimize(compressor));
+    }
+    return self;
+}
+
+function inline_array_like_spread(elements) {
+    for (var i = 0; i < elements.length; i++) {
+        var el = elements[i];
+        if (el instanceof AST_Expansion) {
+            var expr = el.expression;
+            if (
+                expr instanceof AST_Array
+                && !expr.elements.some(elm => elm instanceof AST_Hole)
+            ) {
+                elements.splice(i, 1, ...expr.elements);
+                // Step back one, as the element at i is now new.
+                i--;
+            }
+            // In array-like spread, spreading a non-iterable value is TypeError.
+            // We therefore can’t optimize anything else, unlike with object spread.
+        }
+    }
+}
+
+def_optimize(AST_Array, function(self, compressor) {
+    var optimized = literals_in_boolean_context(self, compressor);
+    if (optimized !== self) {
+        return optimized;
+    }
+    inline_array_like_spread(self.elements);
+    return self;
+});
+
+function inline_object_prop_spread(props) {
+    for (var i = 0; i < props.length; i++) {
+        var prop = props[i];
+        if (prop instanceof AST_Expansion) {
+            const expr = prop.expression;
+            if (
+                expr instanceof AST_Object
+                && expr.properties.every(prop => prop instanceof AST_ObjectKeyVal)
+            ) {
+                props.splice(i, 1, ...expr.properties);
+                // Step back one, as the property at i is now new.
+                i--;
+            } else if ((
+                    // `expr.is_constant()` returns `false` for `AST_RegExp`, so need both.
+                    expr instanceof AST_Constant
+                    || expr.is_constant()
+                ) && !(expr instanceof AST_String)) {
+                // Unlike array-like spread, in object spread, spreading a
+                // non-iterable value silently does nothing; it is thus safe
+                // to remove. AST_String is the only iterable constant.
+                props.splice(i, 1);
+                i--;
+            }
+        }
+    }
+}
+
+def_optimize(AST_Object, function(self, compressor) {
+    var optimized = literals_in_boolean_context(self, compressor);
+    if (optimized !== self) {
+        return optimized;
+    }
+    inline_object_prop_spread(self.properties);
+    return self;
+});
+
+def_optimize(AST_RegExp, literals_in_boolean_context);
+
+def_optimize(AST_Return, function(self, compressor) {
+    if (self.value && is_undefined(self.value, compressor)) {
+        self.value = null;
+    }
+    return self;
+});
+
+def_optimize(AST_Arrow, opt_AST_Lambda);
+
+def_optimize(AST_Function, function(self, compressor) {
+    self = opt_AST_Lambda(self, compressor);
+    if (compressor.option("unsafe_arrows")
+        && compressor.option("ecma") >= 2015
+        && !self.name
+        && !self.is_generator
+        && !self.uses_arguments
+        && !self.pinned()) {
+        const uses_this = walk(self, node => {
+            if (node instanceof AST_This) return walk_abort;
+        });
+        if (!uses_this) return make_node(AST_Arrow, self, self).optimize(compressor);
+    }
+    return self;
+});
+
+def_optimize(AST_Class, function(self) {
+    for (let i = 0; i < self.properties.length; i++) {
+        const prop = self.properties[i];
+        if (prop instanceof AST_ClassStaticBlock && prop.body.length == 0) {
+            self.properties.splice(i, 1);
+            i--;
+        }
+    }
+
+    return self;
+});
+
+def_optimize(AST_ClassStaticBlock, function(self, compressor) {
+    tighten_body(self.body, compressor);
+    return self;
+});
+
+def_optimize(AST_Yield, function(self, compressor) {
+    if (self.expression && !self.is_star && is_undefined(self.expression, compressor)) {
+        self.expression = null;
+    }
+    return self;
+});
+
+def_optimize(AST_TemplateString, function(self, compressor) {
+    if (
+        !compressor.option("evaluate")
+        || compressor.parent() instanceof AST_PrefixedTemplateString
+    ) {
+        return self;
+    }
+
+    var segments = [];
+    for (var i = 0; i < self.segments.length; i++) {
+        var segment = self.segments[i];
+        if (segment instanceof AST_Node) {
+            var result = segment.evaluate(compressor);
+            // Evaluate to constant value
+            // Constant value shorter than ${segment}
+            if (result !== segment && (result + "").length <= segment.size() + "${}".length) {
+                // There should always be a previous and next segment if segment is a node
+                segments[segments.length - 1].value = segments[segments.length - 1].value + result + self.segments[++i].value;
+                continue;
+            }
+            // `before ${`innerBefore ${any} innerAfter`} after` => `before innerBefore ${any} innerAfter after`
+            // TODO:
+            // `before ${'test' + foo} after` => `before innerBefore ${any} innerAfter after`
+            // `before ${foo + 'test} after` => `before innerBefore ${any} innerAfter after`
+            if (segment instanceof AST_TemplateString) {
+                var inners = segment.segments;
+                segments[segments.length - 1].value += inners[0].value;
+                for (var j = 1; j < inners.length; j++) {
+                    segment = inners[j];
+                    segments.push(segment);
+                }
+                continue;
+            }
+        }
+        segments.push(segment);
+    }
+    self.segments = segments;
+
+    // `foo` => "foo"
+    if (segments.length == 1) {
+        return make_node(AST_String, self, segments[0]);
+    }
+
+    if (
+        segments.length === 3
+        && segments[1] instanceof AST_Node
+        && (
+            segments[1].is_string(compressor)
+            || segments[1].is_number_or_bigint(compressor)
+            || is_nullish(segments[1], compressor)
+            || compressor.option("unsafe")
+        )
+    ) {
+        // `foo${bar}` => "foo" + bar
+        if (segments[2].value === "") {
+            return make_node(AST_Binary, self, {
+                operator: "+",
+                left: make_node(AST_String, self, {
+                    value: segments[0].value,
+                }),
+                right: segments[1],
+            });
+        }
+        // `${bar}baz` => bar + "baz"
+        if (segments[0].value === "") {
+            return make_node(AST_Binary, self, {
+                operator: "+",
+                left: segments[1],
+                right: make_node(AST_String, self, {
+                    value: segments[2].value,
+                }),
+            });
+        }
+    }
+    return self;
+});
+
+def_optimize(AST_PrefixedTemplateString, function(self) {
+    return self;
+});
+
+// ["p"]:1 ---> p:1
+// [42]:1 ---> 42:1
+function lift_key(self, compressor) {
+    if (!compressor.option("computed_props")) return self;
+    // save a comparison in the typical case
+    if (!(self.key instanceof AST_Constant)) return self;
+    // allow certain acceptable props as not all AST_Constants are true constants
+    if (self.key instanceof AST_String || self.key instanceof AST_Number) {
+        const key = self.key.value.toString();
+
+        if (key === "__proto__") return self;
+        if (key == "constructor"
+            && compressor.parent() instanceof AST_Class) return self;
+        if (self instanceof AST_ObjectKeyVal) {
+            self.quote = self.key.quote;
+            self.key = key;
+        } else if (self instanceof AST_ClassProperty) {
+            self.quote = self.key.quote;
+            self.key = make_node(AST_SymbolClassProperty, self.key, {
+                name: key,
+            });
+        } else {
+            self.quote = self.key.quote;
+            self.key = make_node(AST_SymbolMethod, self.key, {
+                name: key,
+            });
+        }
+    }
+    return self;
+}
+
+def_optimize(AST_ObjectProperty, lift_key);
+
+def_optimize(AST_ConciseMethod, function(self, compressor) {
+    lift_key(self, compressor);
+    // p(){return x;} ---> p:()=>x
+    if (compressor.option("arrows")
+        && compressor.parent() instanceof AST_Object
+        && !self.value.is_generator
+        && !self.value.uses_arguments
+        && !self.value.pinned()
+        && self.value.body.length == 1
+        && self.value.body[0] instanceof AST_Return
+        && self.value.body[0].value
+        && !self.value.contains_this()) {
+        var arrow = make_node(AST_Arrow, self.value, self.value);
+        arrow.async = self.value.async;
+        arrow.is_generator = self.value.is_generator;
+        return make_node(AST_ObjectKeyVal, self, {
+            key: self.key instanceof AST_SymbolMethod ? self.key.name : self.key,
+            value: arrow,
+            quote: self.quote,
+        });
+    }
+    return self;
+});
+
+def_optimize(AST_ObjectKeyVal, function(self, compressor) {
+    lift_key(self, compressor);
+    // p:function(){} ---> p(){}
+    // p:function*(){} ---> *p(){}
+    // p:async function(){} ---> async p(){}
+    // p:()=>{} ---> p(){}
+    // p:async()=>{} ---> async p(){}
+    var unsafe_methods = compressor.option("unsafe_methods");
+    if (unsafe_methods
+        && compressor.option("ecma") >= 2015
+        && (!(unsafe_methods instanceof RegExp) || unsafe_methods.test(self.key + ""))) {
+        var key = self.key;
+        var value = self.value;
+        var is_arrow_with_block = value instanceof AST_Arrow
+            && Array.isArray(value.body)
+            && !value.contains_this();
+        if ((is_arrow_with_block || value instanceof AST_Function) && !value.name) {
+            return make_node(AST_ConciseMethod, self, {
+                key: key instanceof AST_Node ? key : make_node(AST_SymbolMethod, self, {
+                    name: key,
+                }),
+                value: make_node(AST_Accessor, value, value),
+                quote: self.quote,
+            });
+        }
+    }
+    return self;
+});
+
+def_optimize(AST_Destructuring, function(self, compressor) {
+    if (compressor.option("pure_getters") == true
+        && compressor.option("unused")
+        && !self.is_array
+        && Array.isArray(self.names)
+        && !is_destructuring_export_decl(compressor)
+        && !(self.names[self.names.length - 1] instanceof AST_Expansion)) {
+        var keep = [];
+        for (var i = 0; i < self.names.length; i++) {
+            var elem = self.names[i];
+            if (!(elem instanceof AST_ObjectKeyVal
+                && typeof elem.key == "string"
+                && elem.value instanceof AST_SymbolDeclaration
+                && !should_retain(compressor, elem.value.definition()))) {
+                keep.push(elem);
+            }
+        }
+        if (keep.length != self.names.length) {
+            self.names = keep;
+        }
+    }
+    return self;
+
+    function is_destructuring_export_decl(compressor) {
+        var ancestors = [/^VarDef$/, /^(Const|Let|Var)$/, /^Export$/];
+        for (var a = 0, p = 0, len = ancestors.length; a < len; p++) {
+            var parent = compressor.parent(p);
+            if (!parent) return false;
+            if (a === 0 && parent.TYPE == "Destructuring") continue;
+            if (!ancestors[a].test(parent.TYPE)) {
+                return false;
+            }
+            a++;
+        }
+        return true;
+    }
+
+    function should_retain(compressor, def) {
+        if (def.references.length) return true;
+        if (!def.global) return false;
+        if (compressor.toplevel.vars) {
+            if (compressor.top_retain) {
+                return compressor.top_retain(def);
+            }
+            return false;
+        }
+        return true;
+    }
+});
+
+export {
+    Compressor,
+};
Index: frontend/node_modules/terser/lib/compress/inference.js
===================================================================
--- frontend/node_modules/terser/lib/compress/inference.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/inference.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1132 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+  AST_Array,
+  AST_Arrow,
+  AST_Assign,
+  AST_BigInt,
+  AST_Binary,
+  AST_Block,
+  AST_BlockStatement,
+  AST_Call,
+  AST_Case,
+  AST_Chain,
+  AST_Class,
+  AST_DefClass,
+  AST_ClassStaticBlock,
+  AST_ClassPrivateProperty,
+  AST_ClassProperty,
+  AST_ConciseMethod,
+  AST_Conditional,
+  AST_Constant,
+  AST_Definitions,
+  AST_Dot,
+  AST_EmptyStatement,
+  AST_Expansion,
+  AST_False,
+  AST_ForIn,
+  AST_Function,
+  AST_If,
+  AST_Import,
+  AST_DynamicImport,
+  AST_ImportMeta,
+  AST_Jump,
+  AST_LabeledStatement,
+  AST_Lambda,
+  AST_New,
+  AST_Node,
+  AST_Null,
+  AST_Number,
+  AST_Object,
+  AST_ObjectGetter,
+  AST_ObjectKeyVal,
+  AST_ObjectProperty,
+  AST_ObjectSetter,
+  AST_PrivateGetter,
+  AST_PrivateMethod,
+  AST_PrivateSetter,
+  AST_PropAccess,
+  AST_RegExp,
+  AST_Return,
+  AST_Scope,
+  AST_Sequence,
+  AST_SimpleStatement,
+  AST_Statement,
+  AST_String,
+  AST_Sub,
+  AST_Switch,
+  AST_SwitchBranch,
+  AST_SymbolClassProperty,
+  AST_SymbolDeclaration,
+  AST_SymbolRef,
+  AST_TemplateSegment,
+  AST_TemplateString,
+  AST_This,
+  AST_True,
+  AST_Try,
+  AST_Unary,
+  AST_UnaryPostfix,
+  AST_UnaryPrefix,
+  AST_Undefined,
+  AST_VarDef,
+
+  walk,
+  walk_abort,
+
+  _PURE
+} from "../ast.js";
+import {
+    makePredicate,
+    return_true,
+    return_false,
+    return_null,
+    return_this,
+    make_node,
+    member,
+    has_annotation,
+} from "../utils/index.js";
+import { make_sequence, best_of_expression, read_property, requires_sequence_to_maintain_binding } from "./common.js";
+
+import { INLINED, UNDEFINED, has_flag } from "./compressor-flags.js";
+import { is_pure_builtin_call, pure_prop_access_globals } from "./native-objects.js";
+
+// Functions and methods to infer certain facts about expressions
+// It's not always possible to be 100% sure about something just by static analysis,
+// so `true` means yes, and `false` means maybe
+
+export const is_undeclared_ref = (node) =>
+    node instanceof AST_SymbolRef && node.definition().undeclared;
+
+export const bitwise_binop = makePredicate("<<< >> << & | ^ ~");
+export const lazy_op = makePredicate("&& || ??");
+export const unary_side_effects = makePredicate("delete ++ --");
+
+// methods to determine whether an expression has a boolean result type
+(function(def_is_boolean) {
+    const unary_bool = makePredicate("! delete");
+    const binary_bool = makePredicate("in instanceof == != === !== < <= >= >");
+    def_is_boolean(AST_Node, return_false);
+    def_is_boolean(AST_UnaryPrefix, function() {
+        return unary_bool.has(this.operator);
+    });
+    def_is_boolean(AST_Binary, function() {
+        return binary_bool.has(this.operator)
+            || lazy_op.has(this.operator)
+                && this.left.is_boolean()
+                && this.right.is_boolean();
+    });
+    def_is_boolean(AST_Conditional, function() {
+        return this.consequent.is_boolean() && this.alternative.is_boolean();
+    });
+    def_is_boolean(AST_Assign, function() {
+        return this.operator == "=" && this.right.is_boolean();
+    });
+    def_is_boolean(AST_Sequence, function() {
+        return this.tail_node().is_boolean();
+    });
+    def_is_boolean(AST_True, return_true);
+    def_is_boolean(AST_False, return_true);
+})(function(node, func) {
+    node.DEFMETHOD("is_boolean", func);
+});
+
+// methods to determine if an expression has a numeric result type
+(function(def_is_number) {
+    def_is_number(AST_Node, return_false);
+    def_is_number(AST_Number, return_true);
+    const unary = makePredicate("+ - ~ ++ --");
+    def_is_number(AST_Unary, function(compressor) {
+        return unary.has(this.operator) && this.expression.is_number(compressor);
+    });
+    const numeric_ops = makePredicate("- * / % & | ^ << >> >>>");
+    def_is_number(AST_Binary, function(compressor) {
+        if (this.operator === "+") {
+            // Both sides need to be `number`. Or one is a `number` and the other is number-ish.
+            return this.left.is_number(compressor) && this.right.is_number_or_bigint(compressor)
+                || this.right.is_number(compressor) && this.left.is_number_or_bigint(compressor);
+        } else if (numeric_ops.has(this.operator)) {
+            return this.left.is_number(compressor) || this.right.is_number(compressor);
+        } else {
+            return false;
+        }
+    });
+    def_is_number(AST_Assign, function(compressor) {
+        return (this.operator === "=" || numeric_ops.has(this.operator.slice(0, -1)))
+            && this.right.is_number(compressor);
+    });
+    def_is_number(AST_Sequence, function(compressor) {
+        return this.tail_node().is_number(compressor);
+    });
+    def_is_number(AST_Conditional, function(compressor) {
+        return this.consequent.is_number(compressor) && this.alternative.is_number(compressor);
+    });
+})(function(node, func) {
+    node.DEFMETHOD("is_number", func);
+});
+
+// methods to determine if an expression returns a BigInt
+(function(def_is_bigint) {
+    def_is_bigint(AST_Node, return_false);
+    def_is_bigint(AST_BigInt, return_true);
+    const unary = makePredicate("+ - ~ ++ --");
+    def_is_bigint(AST_Unary, function(compressor) {
+        return unary.has(this.operator) && this.expression.is_bigint(compressor);
+    });
+    const numeric_ops = makePredicate("- * / % & | ^ << >>");
+    def_is_bigint(AST_Binary, function(compressor) {
+        if (this.operator === "+") {
+            return this.left.is_bigint(compressor) && this.right.is_number_or_bigint(compressor)
+                || this.right.is_bigint(compressor) && this.left.is_number_or_bigint(compressor);
+        } else if (numeric_ops.has(this.operator)) {
+            return this.left.is_bigint(compressor) || this.right.is_bigint(compressor);
+        } else {
+            return false;
+        }
+    });
+    def_is_bigint(AST_Assign, function(compressor) {
+        return (numeric_ops.has(this.operator.slice(0, -1)) || this.operator == "=")
+            && this.right.is_bigint(compressor);
+    });
+    def_is_bigint(AST_Sequence, function(compressor) {
+        return this.tail_node().is_bigint(compressor);
+    });
+    def_is_bigint(AST_Conditional, function(compressor) {
+        return this.consequent.is_bigint(compressor) && this.alternative.is_bigint(compressor);
+    });
+})(function(node, func) {
+    node.DEFMETHOD("is_bigint", func);
+});
+
+// methods to determine if an expression is a number or a bigint
+(function(def_is_number_or_bigint) {
+    def_is_number_or_bigint(AST_Node, return_false);
+    def_is_number_or_bigint(AST_Number, return_true);
+    def_is_number_or_bigint(AST_BigInt, return_true);
+    const numeric_unary_ops = makePredicate("+ - ~ ++ --");
+    def_is_number_or_bigint(AST_Unary, function(_compressor) {
+        return numeric_unary_ops.has(this.operator);
+    });
+    const numeric_ops = makePredicate("- * / % & | ^ << >>");
+    def_is_number_or_bigint(AST_Binary, function(compressor) {
+        return this.operator === "+"
+            ? this.left.is_number_or_bigint(compressor) && this.right.is_number_or_bigint(compressor)
+            : numeric_ops.has(this.operator);
+    });
+    def_is_number_or_bigint(AST_Assign, function(compressor) {
+        return numeric_ops.has(this.operator.slice(0, -1))
+            || this.operator == "=" && this.right.is_number_or_bigint(compressor);
+    });
+    def_is_number_or_bigint(AST_Sequence, function(compressor) {
+        return this.tail_node().is_number_or_bigint(compressor);
+    });
+    def_is_number_or_bigint(AST_Conditional, function(compressor) {
+        return this.consequent.is_number_or_bigint(compressor) && this.alternative.is_number_or_bigint(compressor);
+    });
+}(function (node, func) {
+    node.DEFMETHOD("is_number_or_bigint", func);
+}));
+
+
+// methods to determine if an expression is a 32 bit integer (IE results from bitwise ops, or is an integer constant fitting in that size
+(function(def_is_32_bit_integer) {
+    def_is_32_bit_integer(AST_Node, return_false);
+    def_is_32_bit_integer(AST_Number, function(_compressor) {
+        return this.value === (this.value | 0);
+    });
+    def_is_32_bit_integer(AST_UnaryPrefix, function(compressor) {
+        return this.operator == "~" ? this.expression.is_number(compressor)
+            : this.operator === "+" ? this.expression.is_32_bit_integer(compressor)
+            : false;
+    });
+    def_is_32_bit_integer(AST_Binary, function(compressor) {
+        return bitwise_binop.has(this.operator)
+            && (this.left.is_number(compressor) || this.right.is_number(compressor));
+    });
+}(function (node, func) {
+    node.DEFMETHOD("is_32_bit_integer", func);
+}));
+
+// methods to determine if an expression has a string result type
+(function(def_is_string) {
+    def_is_string(AST_Node, return_false);
+    def_is_string(AST_String, return_true);
+    def_is_string(AST_TemplateString, return_true);
+    def_is_string(AST_UnaryPrefix, function() {
+        return this.operator == "typeof";
+    });
+    def_is_string(AST_Binary, function(compressor) {
+        return this.operator == "+" &&
+            (this.left.is_string(compressor) || this.right.is_string(compressor));
+    });
+    def_is_string(AST_Assign, function(compressor) {
+        return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor);
+    });
+    def_is_string(AST_Sequence, function(compressor) {
+        return this.tail_node().is_string(compressor);
+    });
+    def_is_string(AST_Conditional, function(compressor) {
+        return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
+    });
+})(function(node, func) {
+    node.DEFMETHOD("is_string", func);
+});
+
+export function is_undefined(node, compressor) {
+    return (
+        has_flag(node, UNDEFINED)
+        || node instanceof AST_Undefined
+        || node instanceof AST_UnaryPrefix
+            && node.operator == "void"
+            && !node.expression.has_side_effects(compressor)
+    );
+}
+
+// Is the node explicitly null or undefined.
+function is_null_or_undefined(node, compressor) {
+    let fixed;
+    return (
+        node instanceof AST_Null
+        || is_undefined(node, compressor)
+        || (
+            node instanceof AST_SymbolRef
+            && (fixed = node.definition().fixed) instanceof AST_Node
+            && is_nullish(fixed, compressor)
+        )
+    );
+}
+
+// Find out if this expression is optionally chained from a base-point that we
+// can statically analyze as null or undefined.
+export function is_nullish_shortcircuited(node, compressor) {
+    if (node instanceof AST_PropAccess || node instanceof AST_Call) {
+        return (
+            (node.optional && is_null_or_undefined(node.expression, compressor))
+            || is_nullish_shortcircuited(node.expression, compressor)
+        );
+    }
+    if (node instanceof AST_Chain) return is_nullish_shortcircuited(node.expression, compressor);
+    return false;
+}
+
+// Find out if something is == null, or can short circuit into nullish.
+// Used to optimize ?. and ??
+export function is_nullish(node, compressor) {
+    if (is_null_or_undefined(node, compressor)) return true;
+    return is_nullish_shortcircuited(node, compressor);
+}
+
+// Determine if expression might cause side effects
+// If there's a possibility that a node may change something when it's executed, this returns true
+(function(def_has_side_effects) {
+    def_has_side_effects(AST_Node, return_true);
+
+    def_has_side_effects(AST_EmptyStatement, return_false);
+    def_has_side_effects(AST_Constant, return_false);
+    def_has_side_effects(AST_This, return_false);
+
+    function any(list, compressor) {
+        for (var i = list.length; --i >= 0;)
+            if (list[i].has_side_effects(compressor))
+                return true;
+        return false;
+    }
+
+    def_has_side_effects(AST_Block, function(compressor) {
+        return any(this.body, compressor);
+    });
+    def_has_side_effects(AST_Call, function(compressor) {
+        if (
+            !this.is_callee_pure(compressor)
+            && (!this.expression.is_call_pure(compressor)
+                || this.expression.has_side_effects(compressor))
+        ) {
+            return true;
+        }
+        return any(this.args, compressor);
+    });
+    def_has_side_effects(AST_Switch, function(compressor) {
+        return this.expression.has_side_effects(compressor)
+            || any(this.body, compressor);
+    });
+    def_has_side_effects(AST_Case, function(compressor) {
+        return this.expression.has_side_effects(compressor)
+            || any(this.body, compressor);
+    });
+    def_has_side_effects(AST_Try, function(compressor) {
+        return this.body.has_side_effects(compressor)
+            || this.bcatch && this.bcatch.has_side_effects(compressor)
+            || this.bfinally && this.bfinally.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_If, function(compressor) {
+        return this.condition.has_side_effects(compressor)
+            || this.body && this.body.has_side_effects(compressor)
+            || this.alternative && this.alternative.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_ImportMeta, return_false);
+    def_has_side_effects(AST_DynamicImport, function() {
+        // `import.source(x)` only compiles the module, which is side-effect free
+        return this.phase !== "source";
+    });
+    def_has_side_effects(AST_LabeledStatement, function(compressor) {
+        return this.body.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_SimpleStatement, function(compressor) {
+        return this.body.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_Lambda, return_false);
+    def_has_side_effects(AST_Class, function (compressor) {
+        if (this.extends && this.extends.has_side_effects(compressor)) {
+            return true;
+        }
+        return any(this.properties, compressor);
+    });
+    def_has_side_effects(AST_ClassStaticBlock, function(compressor) {
+        return any(this.body, compressor);
+    });
+    def_has_side_effects(AST_Binary, function(compressor) {
+        return this.left.has_side_effects(compressor)
+            || this.right.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_Assign, return_true);
+    def_has_side_effects(AST_Conditional, function(compressor) {
+        return this.condition.has_side_effects(compressor)
+            || this.consequent.has_side_effects(compressor)
+            || this.alternative.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_Unary, function(compressor) {
+        return unary_side_effects.has(this.operator)
+            || this.expression.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_SymbolRef, function(compressor) {
+        return !this.is_declared(compressor) && !pure_prop_access_globals.has(this.name);
+    });
+    def_has_side_effects(AST_SymbolClassProperty, return_false);
+    def_has_side_effects(AST_SymbolDeclaration, return_false);
+    def_has_side_effects(AST_Object, function(compressor) {
+        return any(this.properties, compressor);
+    });
+    def_has_side_effects(AST_ObjectKeyVal, function(compressor) {
+        return (
+            this.computed_key() && this.key.has_side_effects(compressor)
+            || this.value && this.value.has_side_effects(compressor)
+        );
+    });
+    def_has_side_effects([
+        AST_ClassProperty,
+        AST_ClassPrivateProperty,
+    ], function(compressor) {
+        return (
+            this.computed_key() && this.key.has_side_effects(compressor)
+            || this.static && this.value && this.value.has_side_effects(compressor)
+        );
+    });
+    def_has_side_effects([
+        AST_PrivateMethod,
+        AST_PrivateGetter,
+        AST_PrivateSetter,
+        AST_ConciseMethod,
+        AST_ObjectGetter,
+        AST_ObjectSetter,
+    ], function(compressor) {
+        return this.computed_key() && this.key.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_Array, function(compressor) {
+        return any(this.elements, compressor);
+    });
+    def_has_side_effects(AST_Dot, function(compressor) {
+        if (is_nullish(this, compressor)) {
+            return this.expression.has_side_effects(compressor);
+        }
+        if (!this.optional && this.expression.may_throw_on_access(compressor)) {
+            return true;
+        }
+
+        return this.expression.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_Sub, function(compressor) {
+        if (is_nullish(this, compressor)) {
+            return this.expression.has_side_effects(compressor);
+        }
+        if (!this.optional && this.expression.may_throw_on_access(compressor)) {
+            return true;
+        }
+
+        var property = this.property.has_side_effects(compressor);
+        if (property && this.optional) return true; // "?." is a condition
+
+        return property || this.expression.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_Chain, function (compressor) {
+        return this.expression.has_side_effects(compressor);
+    });
+    def_has_side_effects(AST_Sequence, function(compressor) {
+        return any(this.expressions, compressor);
+    });
+    def_has_side_effects(AST_Definitions, function(compressor) {
+        return any(this.definitions, compressor);
+    });
+    def_has_side_effects(AST_VarDef, function() {
+        return this.value != null;
+    });
+    def_has_side_effects(AST_TemplateSegment, return_false);
+    def_has_side_effects(AST_TemplateString, function(compressor) {
+        return any(this.segments, compressor);
+    });
+})(function(node_or_nodes, func) {
+    for (const node of [].concat(node_or_nodes)) {
+        node.DEFMETHOD("has_side_effects", func);
+    }
+});
+
+// determine if expression may throw
+(function(def_may_throw) {
+    def_may_throw(AST_Node, return_true);
+
+    def_may_throw(AST_Constant, return_false);
+    def_may_throw(AST_EmptyStatement, return_false);
+    def_may_throw(AST_Lambda, return_false);
+    def_may_throw(AST_SymbolDeclaration, return_false);
+    def_may_throw(AST_This, return_false);
+    def_may_throw(AST_ImportMeta, return_false);
+
+    function any(list, compressor) {
+        for (var i = list.length; --i >= 0;)
+            if (list[i].may_throw(compressor))
+                return true;
+        return false;
+    }
+
+    def_may_throw(AST_Class, function(compressor) {
+        if (this.extends && this.extends.may_throw(compressor)) return true;
+        return any(this.properties, compressor);
+    });
+    def_may_throw(AST_ClassStaticBlock, function (compressor) {
+        return any(this.body, compressor);
+    });
+
+    def_may_throw(AST_Array, function(compressor) {
+        return any(this.elements, compressor);
+    });
+    def_may_throw(AST_Assign, function(compressor) {
+        if (this.right.may_throw(compressor)) return true;
+        if (!compressor.has_directive("use strict")
+            && this.operator == "="
+            && this.left instanceof AST_SymbolRef) {
+            return false;
+        }
+        return this.left.may_throw(compressor);
+    });
+    def_may_throw(AST_Binary, function(compressor) {
+        return this.left.may_throw(compressor)
+            || this.right.may_throw(compressor);
+    });
+    def_may_throw(AST_Block, function(compressor) {
+        return any(this.body, compressor);
+    });
+    def_may_throw(AST_Call, function(compressor) {
+        if (is_nullish(this, compressor)) return false;
+        if (any(this.args, compressor)) return true;
+        if (this.is_callee_pure(compressor)) return false;
+        if (this.expression.may_throw(compressor)) return true;
+        return !(this.expression instanceof AST_Lambda)
+            || any(this.expression.body, compressor);
+    });
+    def_may_throw(AST_Case, function(compressor) {
+        return this.expression.may_throw(compressor)
+            || any(this.body, compressor);
+    });
+    def_may_throw(AST_Conditional, function(compressor) {
+        return this.condition.may_throw(compressor)
+            || this.consequent.may_throw(compressor)
+            || this.alternative.may_throw(compressor);
+    });
+    def_may_throw(AST_Definitions, function(compressor) {
+        return any(this.definitions, compressor);
+    });
+    def_may_throw(AST_If, function(compressor) {
+        return this.condition.may_throw(compressor)
+            || this.body && this.body.may_throw(compressor)
+            || this.alternative && this.alternative.may_throw(compressor);
+    });
+    def_may_throw(AST_LabeledStatement, function(compressor) {
+        return this.body.may_throw(compressor);
+    });
+    def_may_throw(AST_Object, function(compressor) {
+        return any(this.properties, compressor);
+    });
+    def_may_throw(AST_ObjectKeyVal, function(compressor) {
+        return (
+            this.computed_key() && this.key.may_throw(compressor)
+            || this.value ? this.value.may_throw(compressor) : false
+        );
+    });
+    def_may_throw([
+        AST_ClassProperty,
+        AST_ClassPrivateProperty,
+    ], function(compressor) {
+        return (
+            this.computed_key() && this.key.may_throw(compressor)
+            || this.static && this.value && this.value.may_throw(compressor)
+        );
+    });
+    def_may_throw([
+        AST_ConciseMethod,
+        AST_ObjectGetter,
+        AST_ObjectSetter,
+    ], function(compressor) {
+        return this.computed_key() && this.key.may_throw(compressor);
+    });
+    def_may_throw([
+        AST_PrivateMethod,
+        AST_PrivateGetter,
+        AST_PrivateSetter,
+    ], return_false);
+    def_may_throw(AST_Return, function(compressor) {
+        return this.value && this.value.may_throw(compressor);
+    });
+    def_may_throw(AST_Sequence, function(compressor) {
+        return any(this.expressions, compressor);
+    });
+    def_may_throw(AST_SimpleStatement, function(compressor) {
+        return this.body.may_throw(compressor);
+    });
+    def_may_throw(AST_Dot, function(compressor) {
+        if (is_nullish(this, compressor)) return false;
+        return !this.optional && this.expression.may_throw_on_access(compressor)
+            || this.expression.may_throw(compressor);
+    });
+    def_may_throw(AST_Sub, function(compressor) {
+        if (is_nullish(this, compressor)) return false;
+        return !this.optional && this.expression.may_throw_on_access(compressor)
+            || this.expression.may_throw(compressor)
+            || this.property.may_throw(compressor);
+    });
+    def_may_throw(AST_Chain, function(compressor) {
+        return this.expression.may_throw(compressor);
+    });
+    def_may_throw(AST_Switch, function(compressor) {
+        return this.expression.may_throw(compressor)
+            || any(this.body, compressor);
+    });
+    def_may_throw(AST_SymbolRef, function(compressor) {
+        return !this.is_declared(compressor) && !pure_prop_access_globals.has(this.name);
+    });
+    def_may_throw(AST_SymbolClassProperty, return_false);
+    def_may_throw(AST_Try, function(compressor) {
+        return this.bcatch ? this.bcatch.may_throw(compressor) : this.body.may_throw(compressor)
+            || this.bfinally && this.bfinally.may_throw(compressor);
+    });
+    def_may_throw(AST_Unary, function(compressor) {
+        if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef)
+            return false;
+        return this.expression.may_throw(compressor);
+    });
+    def_may_throw(AST_VarDef, function(compressor) {
+        if (!this.value) return false;
+        return this.value.may_throw(compressor);
+    });
+})(function(node_or_nodes, func) {
+    for (const node of [].concat(node_or_nodes)) {
+        node.DEFMETHOD("may_throw", func);
+    }
+});
+
+// determine if expression is constant
+(function(def_is_constant_expression) {
+    function all_refs_local(scope) {
+        let result = true;
+        walk(this, node => {
+            if (node instanceof AST_SymbolRef) {
+                if (has_flag(this, INLINED)) {
+                    result = false;
+                    return walk_abort;
+                }
+                var def = node.definition();
+                if (
+                    member(def, this.enclosed)
+                    && !this.variables.has(def.name)
+                ) {
+                    if (scope) {
+                        var scope_def = scope.find_variable(node);
+                        if (def.undeclared ? !scope_def : scope_def === def) {
+                            result = "f";
+                            return true;
+                        }
+                    }
+                    result = false;
+                    return walk_abort;
+                }
+                return true;
+            }
+            if (node instanceof AST_This && this instanceof AST_Arrow) {
+                result = false;
+                return walk_abort;
+            }
+        });
+        return result;
+    }
+
+    def_is_constant_expression(AST_Node, return_false);
+    def_is_constant_expression(AST_Constant, return_true);
+    def_is_constant_expression(AST_Class, function(scope) {
+        if (this.extends && !this.extends.is_constant_expression(scope)) {
+            return false;
+        }
+
+        for (const prop of this.properties) {
+            if (prop.computed_key() && !prop.key.is_constant_expression(scope)) {
+                return false;
+            }
+            if (prop.static && prop.value && !prop.value.is_constant_expression(scope)) {
+                return false;
+            }
+            if (prop instanceof AST_ClassStaticBlock) {
+                return false;
+            }
+        }
+
+        return all_refs_local.call(this, scope);
+    });
+    def_is_constant_expression(AST_Lambda, all_refs_local);
+    def_is_constant_expression(AST_Unary, function() {
+        return this.expression.is_constant_expression();
+    });
+    def_is_constant_expression(AST_Binary, function() {
+        return this.left.is_constant_expression()
+            && this.right.is_constant_expression();
+    });
+    def_is_constant_expression(AST_Array, function() {
+        return this.elements.every((l) => l.is_constant_expression());
+    });
+    def_is_constant_expression(AST_Object, function() {
+        return this.properties.every((l) => l.is_constant_expression());
+    });
+    def_is_constant_expression(AST_ObjectProperty, function() {
+        return !!(!(this.key instanceof AST_Node) && this.value && this.value.is_constant_expression());
+    });
+})(function(node, func) {
+    node.DEFMETHOD("is_constant_expression", func);
+});
+
+
+// may_throw_on_access()
+// returns true if this node may be null, undefined or contain `AST_Accessor`
+(function(def_may_throw_on_access) {
+    AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
+        return !compressor.option("pure_getters")
+            || this._dot_throw(compressor);
+    });
+
+    function is_strict(compressor) {
+        return /strict/.test(compressor.option("pure_getters"));
+    }
+
+    def_may_throw_on_access(AST_Node, is_strict);
+    def_may_throw_on_access(AST_Null, return_true);
+    def_may_throw_on_access(AST_Undefined, return_true);
+    def_may_throw_on_access(AST_Constant, return_false);
+    def_may_throw_on_access(AST_Array, return_false);
+    def_may_throw_on_access(AST_Object, function(compressor) {
+        if (!is_strict(compressor)) return false;
+        for (var i = this.properties.length; --i >=0;)
+            if (this.properties[i]._dot_throw(compressor)) return true;
+        return false;
+    });
+    // Do not be as strict with classes as we are with objects.
+    // Hopefully the community is not going to abuse static getters and setters.
+    // https://github.com/terser/terser/issues/724#issuecomment-643655656
+    def_may_throw_on_access(AST_Class, return_false);
+    def_may_throw_on_access(AST_ObjectProperty, return_false);
+    def_may_throw_on_access(AST_ObjectGetter, return_true);
+    def_may_throw_on_access(AST_Expansion, function(compressor) {
+        return this.expression._dot_throw(compressor);
+    });
+    def_may_throw_on_access(AST_Function, return_false);
+    def_may_throw_on_access(AST_Arrow, return_false);
+    def_may_throw_on_access(AST_UnaryPostfix, return_false);
+    def_may_throw_on_access(AST_UnaryPrefix, function() {
+        return this.operator == "void";
+    });
+    def_may_throw_on_access(AST_Binary, function(compressor) {
+        return (this.operator == "&&" || this.operator == "||" || this.operator == "??")
+            && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor));
+    });
+    def_may_throw_on_access(AST_Assign, function(compressor) {
+        if (this.logical) return true;
+
+        return this.operator == "="
+            && this.right._dot_throw(compressor);
+    });
+    def_may_throw_on_access(AST_Conditional, function(compressor) {
+        return this.consequent._dot_throw(compressor)
+            || this.alternative._dot_throw(compressor);
+    });
+    def_may_throw_on_access(AST_Dot, function(compressor) {
+        if (!is_strict(compressor)) return false;
+
+        if (this.property == "prototype") {
+            return !(
+                this.expression instanceof AST_Function
+                || this.expression instanceof AST_Class
+            );
+        }
+        return true;
+    });
+    def_may_throw_on_access(AST_Chain, function(compressor) {
+        return this.expression._dot_throw(compressor);
+    });
+    def_may_throw_on_access(AST_Sequence, function(compressor) {
+        return this.tail_node()._dot_throw(compressor);
+    });
+    def_may_throw_on_access(AST_SymbolRef, function(compressor) {
+        if (this.name === "arguments" && this.scope instanceof AST_Lambda) return false;
+        if (has_flag(this, UNDEFINED)) return true;
+        if (!is_strict(compressor)) return false;
+        if (is_undeclared_ref(this) && this.is_declared(compressor)) return false;
+        if (this.is_immutable()) return false;
+        var fixed = this.fixed_value();
+        return !fixed || fixed._dot_throw(compressor);
+    });
+})(function(node, func) {
+    node.DEFMETHOD("_dot_throw", func);
+});
+
+export function is_lhs(node, parent) {
+    if (parent instanceof AST_Unary && unary_side_effects.has(parent.operator)) return parent.expression;
+    if (parent instanceof AST_Assign && parent.left === node) return node;
+    if (parent instanceof AST_ForIn && parent.init === node) return node;
+}
+
+// method to negate an expression
+(function(def_negate) {
+    function basic_negation(exp) {
+        return make_node(AST_UnaryPrefix, exp, {
+            operator: "!",
+            expression: exp
+        });
+    }
+    function best(orig, alt, first_in_statement) {
+        var negated = basic_negation(orig);
+        if (first_in_statement) {
+            var stat = make_node(AST_SimpleStatement, alt, {
+                body: alt
+            });
+            return best_of_expression(negated, stat) === stat ? alt : negated;
+        }
+        return best_of_expression(negated, alt);
+    }
+    def_negate(AST_Node, function() {
+        return basic_negation(this);
+    });
+    def_negate(AST_Statement, function() {
+        throw new Error("Cannot negate a statement");
+    });
+    def_negate(AST_Function, function() {
+        return basic_negation(this);
+    });
+    def_negate(AST_Class, function() {
+        return basic_negation(this);
+    });
+    def_negate(AST_Arrow, function() {
+        return basic_negation(this);
+    });
+    def_negate(AST_UnaryPrefix, function() {
+        if (this.operator == "!")
+            return this.expression;
+        return basic_negation(this);
+    });
+    def_negate(AST_Sequence, function(compressor) {
+        var expressions = this.expressions.slice();
+        expressions.push(expressions.pop().negate(compressor));
+        return make_sequence(this, expressions);
+    });
+    def_negate(AST_Conditional, function(compressor, first_in_statement) {
+        var self = this.clone();
+        self.consequent = self.consequent.negate(compressor);
+        self.alternative = self.alternative.negate(compressor);
+        return best(this, self, first_in_statement);
+    });
+    def_negate(AST_Binary, function(compressor, first_in_statement) {
+        var self = this.clone(), op = this.operator;
+        if (compressor.option("unsafe_comps")) {
+            switch (op) {
+              case "<=" : self.operator = ">"  ; return self;
+              case "<"  : self.operator = ">=" ; return self;
+              case ">=" : self.operator = "<"  ; return self;
+              case ">"  : self.operator = "<=" ; return self;
+            }
+        }
+        switch (op) {
+          case "==" : self.operator = "!="; return self;
+          case "!=" : self.operator = "=="; return self;
+          case "===": self.operator = "!=="; return self;
+          case "!==": self.operator = "==="; return self;
+          case "&&":
+            self.operator = "||";
+            self.left = self.left.negate(compressor, first_in_statement);
+            self.right = self.right.negate(compressor);
+            return best(this, self, first_in_statement);
+          case "||":
+            self.operator = "&&";
+            self.left = self.left.negate(compressor, first_in_statement);
+            self.right = self.right.negate(compressor);
+            return best(this, self, first_in_statement);
+        }
+        return basic_negation(this);
+    });
+})(function(node, func) {
+    node.DEFMETHOD("negate", function(compressor, first_in_statement) {
+        return func.call(this, compressor, first_in_statement);
+    });
+});
+
+(function (def_bitwise_negate) {
+    function basic_bitwise_negation(exp) {
+        return make_node(AST_UnaryPrefix, exp, {
+            operator: "~",
+            expression: exp
+        });
+    }
+
+    def_bitwise_negate(AST_Node, function(_compressor) {
+        return basic_bitwise_negation(this);
+    });
+
+    def_bitwise_negate(AST_Number, function(_compressor) {
+        const neg = ~this.value;
+        if (neg.toString().length > this.value.toString().length) {
+            return basic_bitwise_negation(this);
+        }
+        return make_node(AST_Number, this, { value: neg });
+    });
+
+    def_bitwise_negate(AST_UnaryPrefix, function(compressor, in_32_bit_context) {
+        if (
+            this.operator == "~"
+            && (
+                this.expression.is_32_bit_integer(compressor) ||
+                (in_32_bit_context != null ? in_32_bit_context : compressor.in_32_bit_context())
+            )
+        ) {
+            return this.expression;
+        } else {
+            return basic_bitwise_negation(this);
+        }
+    });
+})(function (node, func) {
+    node.DEFMETHOD("bitwise_negate", func);
+});
+
+// Is the callee of this function pure?
+var global_pure_fns = makePredicate("Boolean decodeURI decodeURIComponent Date encodeURI encodeURIComponent Error escape EvalError isFinite isNaN Number Object parseFloat parseInt RangeError ReferenceError String SyntaxError TypeError unescape URIError");
+AST_Call.DEFMETHOD("is_callee_pure", function(compressor) {
+    if (compressor.option("unsafe")) {
+        var expr = this.expression;
+        var first_arg;
+        if (
+            expr.expression && expr.expression.name === "hasOwnProperty" &&
+            (
+                (first_arg = (this.args && this.args[0] && this.args[0].evaluate(compressor))) == null
+                || first_arg.thedef && first_arg.thedef.undeclared
+            )
+        ) {
+            return false;
+        }
+        if (is_undeclared_ref(expr) && global_pure_fns.has(expr.name)) return true;
+        if (is_pure_builtin_call(compressor, this)) return true;
+    } else if (compressor.option("builtins_pure")) {
+        if (is_pure_builtin_call(compressor, this)) return true;
+    }
+    if ((this instanceof AST_New) && compressor.option("pure_new")) {
+        return true;
+    }
+    if (compressor.option("side_effects") && has_annotation(this, _PURE)) {
+        return true;
+    }
+    return !compressor.pure_funcs(this);
+});
+
+// If I call this, is it a pure function?
+AST_Node.DEFMETHOD("is_call_pure", return_false);
+AST_Dot.DEFMETHOD("is_call_pure", function(compressor) {
+    if (!compressor.option("unsafe")) return;
+    const expr = this.expression;
+
+    let native_obj;
+    if (expr instanceof AST_Array) {
+        native_obj = "Array";
+    } else if (expr.is_boolean()) {
+        native_obj = "Boolean";
+    } else if (expr.is_number(compressor)) {
+        native_obj = "Number";
+    } else if (expr instanceof AST_RegExp) {
+        native_obj = "RegExp";
+    } else if (expr.is_string(compressor)) {
+        native_obj = "String";
+    } else if (!this.may_throw_on_access(compressor)) {
+        native_obj = "Object";
+    }
+    return native_obj != null && compressor.is_pure_native_method(native_obj, this.property);
+});
+
+// tell me if a statement aborts
+export const aborts = (thing) => thing && thing.aborts();
+
+(function(def_aborts) {
+    def_aborts(AST_Statement, return_null);
+    def_aborts(AST_Jump, return_this);
+    function block_aborts() {
+        for (var i = 0; i < this.body.length; i++) {
+            if (aborts(this.body[i])) {
+                return this.body[i];
+            }
+        }
+        return null;
+    }
+    def_aborts(AST_Import, return_null);
+    def_aborts(AST_BlockStatement, block_aborts);
+    def_aborts(AST_SwitchBranch, block_aborts);
+    def_aborts(AST_DefClass, function () {
+        for (const prop of this.properties) {
+            if (prop instanceof AST_ClassStaticBlock) {
+                if (prop.aborts()) return prop;
+            }
+        }
+        return null;
+    });
+    def_aborts(AST_ClassStaticBlock, block_aborts);
+    def_aborts(AST_If, function() {
+        return this.alternative && aborts(this.body) && aborts(this.alternative) && this;
+    });
+})(function(node, func) {
+    node.DEFMETHOD("aborts", func);
+});
+
+AST_Node.DEFMETHOD("contains_this", function() {
+    return walk(this, node => {
+        if (node instanceof AST_This) return walk_abort;
+        if (
+            node !== this
+            && node instanceof AST_Scope
+            && !(node instanceof AST_Arrow)
+        ) {
+            return true;
+        }
+    });
+});
+
+export function is_modified(compressor, tw, node, value, level, immutable) {
+    var parent = tw.parent(level);
+    var lhs = is_lhs(node, parent);
+    if (lhs) return lhs;
+    if (!immutable
+        && parent instanceof AST_Call
+        && parent.expression === node
+        && !(value instanceof AST_Arrow)
+        && !(value instanceof AST_Class)
+        && !parent.is_callee_pure(compressor)
+        && (!(value instanceof AST_Function)
+            || !(parent instanceof AST_New) && value.contains_this())) {
+        return true;
+    }
+    if (parent instanceof AST_Array) {
+        return is_modified(compressor, tw, parent, parent, level + 1);
+    }
+    if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
+        var obj = tw.parent(level + 1);
+        return is_modified(compressor, tw, obj, obj, level + 2);
+    }
+    if (parent instanceof AST_PropAccess && parent.expression === node) {
+        var prop = read_property(value, parent.property);
+        return !immutable && is_modified(compressor, tw, parent, prop, level + 1);
+    }
+}
+
+/**
+ * Check if a node may be used by the expression it's in
+ * void (0, 1, {node}, 2) -> false
+ * console.log(0, {node}) -> true
+ */
+export function is_used_in_expression(tw) {
+    for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
+        if (parent instanceof AST_Sequence) {
+            const nth_expression = parent.expressions.indexOf(node);
+            if (nth_expression !== parent.expressions.length - 1) {
+                // Detect (0, x.noThis)() constructs
+                const grandparent = tw.parent(p + 2);
+                if (
+                    parent.expressions.length > 2
+                    || parent.expressions.length === 1
+                    || !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
+                ) {
+                    return false;
+                }
+                return true;
+            } else {
+                continue;
+            }
+        }
+        if (parent instanceof AST_Unary) {
+            const op = parent.operator;
+            if (op === "void") {
+                return false;
+            }
+            if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
+                continue;
+            }
+        }
+        if (
+            parent instanceof AST_SimpleStatement
+            || parent instanceof AST_LabeledStatement
+        ) {
+            return false;
+        }
+        if (parent instanceof AST_Scope) {
+            return false;
+        }
+        return true;
+    }
+
+    return true;
+}
Index: frontend/node_modules/terser/lib/compress/inline.js
===================================================================
--- frontend/node_modules/terser/lib/compress/inline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/inline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,683 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    AST_Array,
+    AST_Assign,
+    AST_Block,
+    AST_Call,
+    AST_Catch,
+    AST_Class,
+    AST_ClassExpression,
+    AST_DefaultAssign,
+    AST_DefClass,
+    AST_Defun,
+    AST_Destructuring,
+    AST_EmptyStatement,
+    AST_Expansion,
+    AST_Export,
+    AST_Function,
+    AST_IterationStatement,
+    AST_Lambda,
+    AST_Node,
+    AST_Number,
+    AST_Object,
+    AST_ObjectKeyVal,
+    AST_PropAccess,
+    AST_Return,
+    AST_Scope,
+    AST_SimpleStatement,
+    AST_Statement,
+    AST_SymbolDefun,
+    AST_SymbolFunarg,
+    AST_SymbolLambda,
+    AST_SymbolRef,
+    AST_SymbolVar,
+    AST_This,
+    AST_Toplevel,
+    AST_UnaryPrefix,
+    AST_Var,
+    AST_VarDef,
+
+    walk,
+
+    _INLINE,
+    _NOINLINE,
+    _PURE,
+} from "../ast.js";
+import { make_node, make_void_0, has_annotation } from "../utils/index.js";
+import "../size.js";
+
+import "./evaluate.js";
+import "./drop-side-effect-free.js";
+import "./reduce-vars.js";
+import {
+    SQUEEZED,
+    INLINED,
+    UNUSED,
+
+    has_flag,
+    set_flag,
+} from "./compressor-flags.js";
+import {
+    make_sequence,
+    best_of,
+    make_node_from_constant,
+    identifier_atom,
+    is_empty,
+    is_func_expr,
+    is_iife_call,
+    is_reachable,
+    is_recursive_ref,
+    retain_top_func,
+} from "./common.js";
+
+/**
+ * Module that contains the inlining logic.
+ *
+ * @module
+ *
+ * The stars of the show are `inline_into_symbolref` and `inline_into_call`.
+ */
+
+function within_array_or_object_literal(compressor) {
+    var node, level = 0;
+    while (node = compressor.parent(level++)) {
+        if (node instanceof AST_Statement) return false;
+        if (node instanceof AST_Array
+            || node instanceof AST_ObjectKeyVal
+            || node instanceof AST_Object) {
+            return true;
+        }
+    }
+    return false;
+}
+
+function scope_encloses_variables_in_this_scope(scope, pulled_scope) {
+    for (const enclosed of pulled_scope.enclosed) {
+        if (pulled_scope.variables.has(enclosed.name)) {
+            continue;
+        }
+        const looked_up = scope.find_variable(enclosed.name);
+        if (looked_up) {
+            if (looked_up === enclosed) continue;
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * An extra check function for `top_retain` option, compare the length of const identifier
+ * and init value length and return true if init value is longer than identifier. for example:
+ * ```
+ * // top_retain: ["example"]
+ * const example = 100
+ * ```
+ * it will return false because length of "100" is short than identifier "example".
+ */
+function is_const_symbol_short_than_init_value(def, fixed_value) {
+    if (def.orig.length === 1 && fixed_value) {
+        const init_value_length = fixed_value.size();
+        const identifer_length = def.name.length;
+        return init_value_length > identifer_length;
+    }
+    return true;
+}
+
+export function inline_into_symbolref(self, compressor) {
+    if (compressor.in_computed_key()) return self;
+
+    const parent = compressor.parent();
+    const def = self.definition();
+    const nearest_scope = compressor.find_scope();
+    let fixed = self.fixed_value();
+    if (
+        compressor.top_retain &&
+        def.global && 
+        compressor.top_retain(def) && 
+        // when identifier is in top_retain option dose not mean we can always inline it.
+        // if identifier name is longer then init value, we can replace it.
+        is_const_symbol_short_than_init_value(def, fixed)
+    ) {
+        // keep it
+        def.fixed = false;
+        def.single_use = false;
+        return self;
+    }
+
+    if (dont_inline_lambda_in_loop(compressor, fixed)) return self;
+
+    let single_use = def.single_use
+        && !(parent instanceof AST_Call
+            && (parent.is_callee_pure(compressor))
+                || has_annotation(parent, _NOINLINE))
+        && !(parent instanceof AST_Export
+            && fixed instanceof AST_Lambda
+            && fixed.name);
+
+    if (single_use && fixed instanceof AST_Node) {
+        single_use =
+            !fixed.has_side_effects(compressor)
+            && !fixed.may_throw(compressor);
+    }
+
+    if (fixed instanceof AST_Class && def.scope !== self.scope) {
+        return self;
+    }
+
+    if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) {
+        if (retain_top_func(fixed, compressor)) {
+            single_use = false;
+        } else if (def.scope !== self.scope
+            && (def.escaped == 1
+                || has_flag(fixed, INLINED)
+                || within_array_or_object_literal(compressor)
+                || !compressor.option("reduce_funcs"))) {
+            single_use = false;
+        } else if (is_recursive_ref(compressor, def)) {
+            single_use = false;
+        } else if (def.scope !== self.scope || def.orig[0] instanceof AST_SymbolFunarg) {
+            single_use = fixed.is_constant_expression(self.scope);
+            if (single_use == "f") {
+                var scope = self.scope;
+                do {
+                    if (scope instanceof AST_Defun || is_func_expr(scope)) {
+                        set_flag(scope, INLINED);
+                    }
+                } while (scope = scope.parent_scope);
+            }
+        }
+    }
+
+    if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) {
+        single_use =
+            def.scope === self.scope
+                && !scope_encloses_variables_in_this_scope(nearest_scope, fixed)
+            || parent instanceof AST_Call
+                && parent.expression === self
+                && !scope_encloses_variables_in_this_scope(nearest_scope, fixed)
+                && !(fixed.name && fixed.name.definition().recursive_refs > 0);
+    }
+
+    if (single_use && fixed) {
+        if (fixed instanceof AST_DefClass) {
+            set_flag(fixed, SQUEEZED);
+            fixed = make_node(AST_ClassExpression, fixed, fixed);
+        }
+        if (fixed instanceof AST_Defun) {
+            set_flag(fixed, SQUEEZED);
+            fixed = make_node(AST_Function, fixed, fixed);
+        }
+        if (def.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) {
+            const defun_def = fixed.name.definition();
+            let lambda_def = fixed.variables.get(fixed.name.name);
+            let name = lambda_def && lambda_def.orig[0];
+            if (!(name instanceof AST_SymbolLambda)) {
+                name = make_node(AST_SymbolLambda, fixed.name, fixed.name);
+                name.scope = fixed;
+                fixed.name = name;
+                lambda_def = fixed.def_function(name);
+            }
+            walk(fixed, node => {
+                if (node instanceof AST_SymbolRef && node.definition() === defun_def) {
+                    node.thedef = lambda_def;
+                    lambda_def.references.push(node);
+                }
+            });
+        }
+        if (
+            (fixed instanceof AST_Lambda || fixed instanceof AST_Class)
+            && fixed.parent_scope !== nearest_scope
+        ) {
+            fixed = fixed.clone(true, compressor.get_toplevel());
+
+            nearest_scope.add_child_scope(fixed);
+        }
+        return fixed.optimize(compressor);
+    }
+
+    // multiple uses
+    if (fixed) {
+        let replace;
+
+        if (fixed instanceof AST_This) {
+            if (!(def.orig[0] instanceof AST_SymbolFunarg)
+                && def.references.every((ref) =>
+                    def.scope === ref.scope
+                )) {
+                replace = fixed;
+            }
+        } else {
+            var ev = fixed.evaluate(compressor);
+            if (
+                ev !== fixed
+                && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp))
+            ) {
+                replace = make_node_from_constant(ev, fixed);
+            }
+        }
+
+        if (replace) {
+            const name_length = self.size(compressor);
+            const replace_size = replace.size(compressor);
+
+            let overhead = 0;
+            if (compressor.option("unused") && !compressor.exposed(def)) {
+                overhead =
+                    (name_length + 2 + fixed.size(compressor)) /
+                    (def.references.length - def.assignments);
+            }
+
+            if (replace_size <= name_length + overhead) {
+                return replace;
+            }
+        }
+    }
+
+    return self;
+}
+
+export function inline_into_call(self, compressor) {
+    if (compressor.in_computed_key()) return self;
+
+    var exp = self.expression;
+    var fn = exp;
+    var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion));
+
+    if (compressor.option("reduce_vars")
+        && fn instanceof AST_SymbolRef
+        && !has_annotation(self, _NOINLINE)
+    ) {
+        const fixed = fn.fixed_value();
+
+        if (
+            retain_top_func(fixed, compressor)
+            || !compressor.toplevel.funcs && exp.definition().global
+        ) {
+            return self;
+        }
+
+        fn = fixed;
+    }
+
+    if (
+        dont_inline_lambda_in_loop(compressor, fn)
+        && !has_annotation(self, _INLINE)
+    ) return self;
+
+    var is_func = fn instanceof AST_Lambda;
+
+    var stat = is_func && fn.body[0];
+    var is_regular_func = is_func && !fn.is_generator && !fn.async;
+    var can_inline = is_regular_func && compressor.option("inline") && !self.is_callee_pure(compressor);
+    if (can_inline && stat instanceof AST_Return) {
+        let returned = stat.value;
+        if (!returned || returned.is_constant_expression()) {
+            if (returned) {
+                returned = returned.clone(true);
+            } else {
+                returned = make_void_0(self);
+            }
+            const args = self.args.concat(returned);
+            return make_sequence(self, args).optimize(compressor);
+        }
+
+        // optimize identity function
+        if (
+            fn.argnames.length === 1
+            && (fn.argnames[0] instanceof AST_SymbolFunarg)
+            && self.args.length < 2
+            && !(self.args[0] instanceof AST_Expansion)
+            && returned instanceof AST_SymbolRef
+            && returned.name === fn.argnames[0].name
+        ) {
+            const replacement =
+                (self.args[0] || make_void_0()).optimize(compressor);
+
+            let parent;
+            if (
+                replacement instanceof AST_PropAccess
+                && (parent = compressor.parent()) instanceof AST_Call
+                && parent.expression === self
+            ) {
+                // identity function was being used to remove `this`, like in
+                //
+                // id(bag.no_this)(...)
+                //
+                // Replace with a larger but more effish (0, bag.no_this) wrapper.
+
+                return make_sequence(self, [
+                    make_node(AST_Number, self, { value: 0 }),
+                    replacement
+                ]);
+            }
+            // replace call with first argument or undefined if none passed
+            return replacement;
+        }
+    }
+
+    if (can_inline) {
+        var scope, in_loop, level = -1;
+        let def;
+        let returned_value;
+        let nearest_scope;
+        if (simple_args
+            && !fn.uses_arguments
+            && !(compressor.parent() instanceof AST_Class)
+            && !(fn.name && fn instanceof AST_Function)
+            && (returned_value = can_flatten_body(stat))
+            && (exp === fn
+                || has_annotation(self, _INLINE)
+                || compressor.option("unused")
+                    && (def = exp.definition()).references.length == 1
+                    && !is_recursive_ref(compressor, def)
+                    && fn.is_constant_expression(exp.scope))
+            && !has_annotation(self, _PURE | _NOINLINE)
+            && !fn.contains_this()
+            && can_inject_symbols()
+            && (nearest_scope = compressor.find_scope())
+            && !scope_encloses_variables_in_this_scope(nearest_scope, fn)
+            && !(function in_default_assign() {
+                    // Due to the fact function parameters have their own scope
+                    // which can't use `var something` in the function body within,
+                    // we simply don't inline into DefaultAssign.
+                    let i = 0;
+                    let p;
+                    while ((p = compressor.parent(i++))) {
+                        if (p instanceof AST_DefaultAssign) return true;
+                        if (p instanceof AST_Block) break;
+                    }
+                    return false;
+                })()
+            && !(scope instanceof AST_Class)
+        ) {
+            set_flag(fn, SQUEEZED);
+            nearest_scope.add_child_scope(fn);
+            return make_sequence(self, flatten_fn(returned_value)).optimize(compressor);
+        }
+    }
+
+    if (can_inline && has_annotation(self, _INLINE)) {
+        set_flag(fn, SQUEEZED);
+        fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn);
+        fn = fn.clone(true);
+        fn.figure_out_scope({}, {
+            parent_scope: compressor.find_scope(),
+            toplevel: compressor.get_toplevel()
+        });
+
+        return make_node(AST_Call, self, {
+            expression: fn,
+            args: self.args,
+        }).optimize(compressor);
+    }
+
+    const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty);
+    if (can_drop_this_call) {
+        var args = self.args.concat(make_void_0(self));
+        return make_sequence(self, args).optimize(compressor);
+    }
+
+    if (compressor.option("negate_iife")
+        && compressor.parent() instanceof AST_SimpleStatement
+        && is_iife_call(self)) {
+        return self.negate(compressor, true);
+    }
+
+    var ev = self.evaluate(compressor);
+    if (ev !== self) {
+        ev = make_node_from_constant(ev, self).optimize(compressor);
+        return best_of(compressor, ev, self);
+    }
+
+    return self;
+
+    function return_value(stat) {
+        if (!stat) return make_void_0(self);
+        if (stat instanceof AST_Return) {
+            if (!stat.value) return make_void_0(self);
+            return stat.value.clone(true);
+        }
+        if (stat instanceof AST_SimpleStatement) {
+            return make_node(AST_UnaryPrefix, stat, {
+                operator: "void",
+                expression: stat.body.clone(true)
+            });
+        }
+    }
+
+    function can_flatten_body(stat) {
+        var body = fn.body;
+        var len = body.length;
+        if (compressor.option("inline") < 3) {
+            return len == 1 && return_value(stat);
+        }
+        stat = null;
+        for (var i = 0; i < len; i++) {
+            var line = body[i];
+            if (line instanceof AST_Var) {
+                if (stat && !line.definitions.every((var_def) =>
+                    !var_def.value
+                )) {
+                    return false;
+                }
+            } else if (stat) {
+                return false;
+            } else if (!(line instanceof AST_EmptyStatement)) {
+                stat = line;
+            }
+        }
+        return return_value(stat);
+    }
+
+    function can_inject_args(block_scoped, safe_to_inject) {
+        for (var i = 0, len = fn.argnames.length; i < len; i++) {
+            var arg = fn.argnames[i];
+            if (arg instanceof AST_DefaultAssign) {
+                if (has_flag(arg.left, UNUSED)) continue;
+                return false;
+            }
+            if (arg instanceof AST_Destructuring) return false;
+            if (arg instanceof AST_Expansion) {
+                if (has_flag(arg.expression, UNUSED)) continue;
+                return false;
+            }
+            if (has_flag(arg, UNUSED)) continue;
+            if (!safe_to_inject
+                || block_scoped.has(arg.name)
+                || identifier_atom.has(arg.name)
+                || scope.conflicting_def(arg.name)) {
+                return false;
+            }
+            if (in_loop) in_loop.push(arg.definition());
+        }
+        return true;
+    }
+
+    function can_inject_vars(block_scoped, safe_to_inject) {
+        var len = fn.body.length;
+        for (var i = 0; i < len; i++) {
+            var stat = fn.body[i];
+            if (!(stat instanceof AST_Var)) continue;
+            if (!safe_to_inject) return false;
+            for (var j = stat.definitions.length; --j >= 0;) {
+                var name = stat.definitions[j].name;
+                if (name instanceof AST_Destructuring
+                    || block_scoped.has(name.name)
+                    || identifier_atom.has(name.name)
+                    || scope.conflicting_def(name.name)) {
+                    return false;
+                }
+                if (in_loop) in_loop.push(name.definition());
+            }
+        }
+        return true;
+    }
+
+    function can_inject_symbols() {
+        var block_scoped = new Set();
+        do {
+            scope = compressor.parent(++level);
+            if (scope.is_block_scope() && scope.block_scope) {
+                // TODO this is sometimes undefined during compression.
+                // But it should always have a value!
+                scope.block_scope.variables.forEach(function (variable) {
+                    block_scoped.add(variable.name);
+                });
+            }
+            if (scope instanceof AST_Catch) {
+                // TODO can we delete? AST_Catch is a block scope.
+                if (scope.argname) {
+                    block_scoped.add(scope.argname.name);
+                }
+            } else if (scope instanceof AST_IterationStatement) {
+                in_loop = [];
+            } else if (scope instanceof AST_SymbolRef) {
+                if (scope.fixed_value() instanceof AST_Scope) return false;
+            }
+        } while (!(scope instanceof AST_Scope));
+
+        var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars;
+        var inline = compressor.option("inline");
+        if (!can_inject_vars(block_scoped, inline >= 3 && safe_to_inject)) return false;
+        if (!can_inject_args(block_scoped, inline >= 2 && safe_to_inject)) return false;
+        return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop);
+    }
+
+    function append_var(decls, expressions, name, value) {
+        var def = name.definition();
+
+        // Name already exists, only when a function argument had the same name
+        const already_appended = scope.variables.has(name.name);
+        if (!already_appended) {
+            scope.variables.set(name.name, def);
+            scope.enclosed.push(def);
+            decls.push(make_node(AST_VarDef, name, {
+                name: name,
+                value: null
+            }));
+        }
+
+        var sym = make_node(AST_SymbolRef, name, name);
+        def.references.push(sym);
+        if (value) expressions.push(make_node(AST_Assign, self, {
+            operator: "=",
+            logical: false,
+            left: sym,
+            right: value.clone()
+        }));
+    }
+
+    function flatten_args(decls, expressions) {
+        var len = fn.argnames.length;
+        for (var i = self.args.length; --i >= len;) {
+            expressions.push(self.args[i]);
+        }
+        for (i = len; --i >= 0;) {
+            var name = fn.argnames[i];
+            var value = self.args[i];
+            if (has_flag(name, UNUSED) || !name.name || scope.conflicting_def(name.name)) {
+                if (value) expressions.push(value);
+            } else {
+                var symbol = make_node(AST_SymbolVar, name, name);
+                name.definition().orig.push(symbol);
+                if (!value && in_loop) value = make_void_0(self);
+                append_var(decls, expressions, symbol, value);
+            }
+        }
+        decls.reverse();
+        expressions.reverse();
+    }
+
+    function flatten_vars(decls, expressions) {
+        var pos = expressions.length;
+        for (var i = 0, lines = fn.body.length; i < lines; i++) {
+            var stat = fn.body[i];
+            if (!(stat instanceof AST_Var)) continue;
+            for (var j = 0, defs = stat.definitions.length; j < defs; j++) {
+                var var_def = stat.definitions[j];
+                var name = var_def.name;
+                append_var(decls, expressions, name, var_def.value);
+                if (in_loop && fn.argnames.every((argname) =>
+                    argname.name != name.name
+                )) {
+                    var def = fn.variables.get(name.name);
+                    var sym = make_node(AST_SymbolRef, name, name);
+                    def.references.push(sym);
+                    expressions.splice(pos++, 0, make_node(AST_Assign, var_def, {
+                        operator: "=",
+                        logical: false,
+                        left: sym,
+                        right: make_void_0(name),
+                    }));
+                }
+            }
+        }
+    }
+
+    function flatten_fn(returned_value) {
+        var decls = [];
+        var expressions = [];
+        flatten_args(decls, expressions);
+        flatten_vars(decls, expressions);
+        expressions.push(returned_value);
+
+        if (decls.length) {
+            const i = scope.body.indexOf(compressor.parent(level - 1)) + 1;
+            scope.body.splice(i, 0, make_node(AST_Var, fn, {
+                definitions: decls
+            }));
+        }
+
+        return expressions.map(exp => exp.clone(true));
+    }
+}
+
+/** prevent inlining functions into loops, for performance reasons */
+function dont_inline_lambda_in_loop(compressor, maybe_lambda) {
+    return (
+        (maybe_lambda instanceof AST_Lambda || maybe_lambda instanceof AST_Class)
+        && !!compressor.is_within_loop()
+    );
+}
Index: frontend/node_modules/terser/lib/compress/native-objects.js
===================================================================
--- frontend/node_modules/terser/lib/compress/native-objects.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/native-objects.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,538 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import { AST_Array, AST_Dot, AST_New, AST_Number, AST_SymbolRef } from "../ast.js";
+import { makePredicate } from "../utils/index.js";
+import { is_undeclared_ref } from "./inference.js";
+
+// Lists of native methods, useful for `unsafe` option which assumes they exist.
+// Note: Lots of methods and functions are missing here, in case they aren't pure
+// or not available in all JS environments.
+
+const make_nested_lookup = (feature_callback) => (compressor) => {
+    const obj = feature_callback(feature_variables(compressor));
+
+    const out = new Map();
+    for (var key of Object.keys(obj)) {
+        if (obj[key]) {
+            out.set(key, makePredicate(remove_false(obj[key])));
+        }
+    }
+
+    const does_have = (global_name, fname) => {
+        const inner_map = out.get(global_name);
+        return inner_map != null && inner_map.has(fname);
+    };
+    return does_have;
+};
+
+const make_lookup = (feature_callback) => (compressor) => {
+    const obj = feature_callback(feature_variables(compressor));
+
+    const predicate = makePredicate(remove_false(obj));
+    const does_have = (global_name) => {
+        return predicate.has(global_name);
+    };
+    return does_have;
+};
+
+function remove_false(arr) {
+    for (let i = 0; i < arr.length; i++) {
+        if (arr[i] === false) {
+            arr.splice(i, 1);
+            i--;
+        }
+    }
+    return arr;
+}
+
+/** Generate the object with arguments seen below */
+function feature_variables(compressor) {
+    return {
+        sloppy: compressor.option("unsafe"),
+        es: compressor.option("builtins_ecma"),
+    };
+}
+
+// eslint-disable-next-line no-unused-vars
+export const pure_access_globals = make_lookup(({ sloppy, es }) => [
+    "Array",
+    "Boolean",
+    "clearInterval",
+    "clearTimeout",
+    "console",
+    "Date",
+    "decodeURI",
+    "decodeURIComponent",
+    "encodeURI",
+    "encodeURIComponent",
+    "Error",
+    "escape",
+    "eval",
+    "EvalError",
+    "Function",
+    es >= 2020 && "globalThis",
+    "isFinite",
+    "isNaN",
+    "JSON",
+    "Math",
+    "Number",
+    "parseFloat",
+    "parseInt",
+    "RangeError",
+    "ReferenceError",
+    "RegExp",
+    "Object",
+    "setInterval",
+    "setTimeout",
+    "String",
+    "SyntaxError",
+    "TypeError",
+    "unescape",
+    "URIError",
+]);
+
+// Objects which are safe to access without throwing or causing a side effect.
+// Usually we'd check the `unsafe` option first but these are way too common for that
+export const pure_prop_access_globals = new Set([
+    "Number",
+    "String",
+    "Array",
+    "Object",
+    "Function",
+    "Promise",
+]);
+
+// eslint-disable-next-line no-unused-vars
+export const is_pure_native_fn = make_lookup(({ sloppy, es }) => [
+    sloppy && es >= 2021 && "AggregateError",
+    "Array",
+    "ArrayBuffer",
+    es >= 2020 && "BigInt",
+    es >= 2020 && "BigInt64Array",
+    es >= 2020 && "BigUint64Array",
+    "Boolean",
+    "Date",
+    sloppy && "decodeURI",
+    sloppy && "decodeURIComponent",
+    sloppy && "encodeURI",
+    sloppy && "encodeURIComponent",
+    "Error",
+    "escape",
+    "EvalError",
+    es >= 2021 && "FinalizationRegistry",
+    es >= 2026 && "Float16Array",
+    "Float32Array",
+    "Float64Array",
+    "Int16Array",
+    "Int32Array",
+    "Int8Array",
+    "isFinite",
+    "isNaN",
+    es >= 2026 && "Iterator",
+    es >= 2015 && "Map",
+    "Number",
+    "parseFloat",
+    "parseInt",
+    es >= 2015 && "Promise",
+    es >= 2015 && "Proxy",
+    "RangeError",
+    "ReferenceError",
+    sloppy && "RegExp",
+    es >= 2015 && "Set",
+    "String",
+    es >= 2015 && "Symbol",
+    "SyntaxError",
+    "TypeError",
+    "Uint16Array",
+    "Uint32Array",
+    "Uint8Array",
+    "Uint8ClampedArray",
+    sloppy && "unescape",
+    "URIError",
+    sloppy && es >= 2015 && "WeakMap",
+    sloppy && es >= 2021 && "WeakRef",
+    sloppy && es >= 2015 && "WeakSet",
+]);
+
+const arg1_is_iterable = new Set([
+    "Map",
+    "Set",
+    "WeakMap",
+    "WeakSet",
+]);
+const arg1_is_range_or_iterable = new Set([
+    "ArrayBuffer",
+    "Float32Array",
+    "Float64Array",
+    "Int16Array",
+    "Int32Array",
+    "Int8Array",
+    "Uint16Array",
+    "Uint32Array",
+    "Uint8Array",
+    "Uint8ClampedArray",
+]);
+const lone_arg_is_range = new Set(["Array"]);
+
+const object_methods = [
+    "constructor",
+    "toString",
+    "valueOf",
+];
+
+// eslint-disable-next-line no-unused-vars
+export const is_pure_native_method = make_nested_lookup(({ sloppy, es }) => ({
+    Array: [
+        es >= 2022 && "at",
+        es >= 2019 && "flat",
+        es >= 2016 && "includes",
+        "indexOf",
+        "join",
+        "lastIndexOf",
+        "slice",
+        ...object_methods,
+    ],
+    Boolean: object_methods,
+    Function: object_methods,
+    Number: [
+        "toExponential",
+        "toFixed",
+        "toPrecision",
+        ...object_methods,
+    ],
+    Object: object_methods,
+    RegExp: [
+        "test",
+        ...object_methods,
+    ],
+    String: [
+        es >= 2022 && "at",
+        "charAt",
+        "charCodeAt",
+        es >= 2015 && "codePointAt",
+        "concat",
+        es >= 2025 && "endsWith",
+        es >= 2015 && "includes",
+        "indexOf",
+        "italics",
+        "lastIndexOf",
+        es >= 2020 && "localeCompare",
+        "match",
+        es >= 2020 && "matchAll",
+        es >= 2015 && "normalize",
+        es >= 2017 && "padStart",
+        es >= 2017 && "padEnd",
+        es >= 2015 && sloppy && "repeat",
+        "replace",
+        es >= 2021 && "replaceAll",
+        "search",
+        "slice",
+        "split",
+        es >= 2015 && "startsWith",
+        "substr",
+        "substring",
+        es >= 2015 && "repeat",
+        "toLocaleLowerCase",
+        "toLocaleUpperCase",
+        "toLowerCase",
+        "toUpperCase",
+        "trim",
+        es >= 2019 && "trimEnd",
+        es >= 2019 && "trimStart",
+        es >= 2019 && "trimLeft",
+        es >= 2019 && "trimRight",
+        ...object_methods,
+    ],
+}));
+
+// eslint-disable-next-line no-unused-vars
+export const is_pure_native_static_fn = make_nested_lookup(({ sloppy, es }) => ({
+    Array: [
+        "isArray",
+        es >= 2015 && "of",
+    ],
+    ArrayBuffer: [
+        "isView",
+    ],
+    BigInt: es >= 2020 && [
+        sloppy && "asIntN",
+        sloppy && "asUintN",
+    ],
+    BigInt64Array: sloppy && es >= 2020 && ["of"],
+    BigUint64Array: sloppy && es >= 2020 && ["of"],
+    Date: [
+        "now",
+        "parse",
+        "UTC",
+    ],
+    Error: [
+        es >= 2026 && "isError",
+    ],
+    Float16Array: sloppy && es >= 2026 && ["of"],
+    Float32Array: sloppy && ["of"],
+    Float64Array: sloppy && ["of"],
+    Int16Array: sloppy && ["of"],
+    Int32Array: sloppy && ["of"],
+    Int8Array: sloppy && ["of"],
+    Math: [
+        "abs",
+        "acos",
+        es >= 2015 && "acosh",
+        "asin",
+        es >= 2015 && "asinh",
+        "atan",
+        "atan2",
+        es >= 2015 && "atanh",
+        es >= 2015 && "cbrt",
+        "ceil",
+        es >= 2015 && "clz32",
+        "cos",
+        es >= 2015 && "cosh",
+        "exp",
+        es >= 2015 && "expm1",
+        "floor",
+        es >= 2026 && "f16round",
+        es >= 2015 && "fround",
+        es >= 2015 && "hypot",
+        es >= 2015 && "imul",
+        "log",
+        es >= 2015 && "log10",
+        es >= 2015 && "log1p",
+        es >= 2015 && "log2",
+        "max",
+        "min",
+        "pow",
+        "round",
+        es >= 2015 && "sign",
+        "sin",
+        es >= 2015 && "sinh",
+        "sqrt",
+        "tan",
+        es >= 2015 && "tanh",
+        es >= 2015 && "trunc",
+    ],
+    Number: [
+        es >= 2015 && "isFinite",
+        es >= 2015 && "isInteger",
+        es >= 2015 && "isSafeInteger",
+        es >= 2015 && "isNaN",
+        es >= 2015 && "parseFloat",
+        es >= 2015 && "parseInt",
+    ],
+    Object: [
+        sloppy && "create",
+        sloppy && "getOwnPropertyDescriptor",
+        es >= 2017 && sloppy && "getOwnPropertyDescriptors",
+        sloppy && "getOwnPropertyNames",
+        es >= 2015 && sloppy && "getOwnPropertySymbols",
+        sloppy && "getPrototypeOf",
+        es >= 2022 && sloppy && "hasOwn",
+        es >= 2015 && "is",
+        "isExtensible",
+        "isFrozen",
+        "isSealed",
+        es >= 2015 && sloppy && "keys",
+    ],
+    Promise: es >= 2015 && [
+        es >= 2024 && "withResolvers",
+    ],
+    Proxy: es >= 2015 && [
+        sloppy && "revocable",
+    ],
+    Reflect: es >= 2015 && [
+        sloppy && "has",
+        sloppy && "isExtensible",
+        sloppy && "ownKeys",
+    ],
+    RegExp: [
+        es >= 2026 && sloppy && "escape",
+    ],
+    String: [
+        "fromCharCode",
+        sloppy && es >= 2025 && "fromCodePoint",
+    ],
+    Uint16Array: ["of"],
+    Uint32Array: ["of"],
+    Uint8Array: ["of"],
+    Uint8ClampedArray: ["of"],
+}));
+
+// Known numeric values which come with JS environments
+// eslint-disable-next-line no-unused-vars
+export const is_pure_native_static_property = make_nested_lookup(({ sloppy, es }) => ({
+    Math: [
+        "E",
+        "LN10",
+        "LN2",
+        "LOG2E",
+        "LOG10E",
+        "PI",
+        "SQRT1_2",
+        "SQRT2",
+    ],
+    Number: [
+        es >= 2015 && "EPSILON",
+        es >= 2015 && "MAX_SAFE_VALUE",
+        "MAX_VALUE",
+        es >= 2015 && "MIN_SAFE_VALUE",
+        "MIN_VALUE",
+        "NaN",
+        "NEGATIVE_INFINITY",
+        "POSITIVE_INFINITY",
+    ],
+    RegExp: [
+        "$_",
+        "$0",
+        "$1",
+        "$2",
+        "$3",
+        "$4",
+        "$5",
+        "$6",
+        "$7",
+        "$8",
+        "$9",
+        "input",
+        "lastMatch",
+        "lastParen",
+        "leftContext",
+        "rightContext",
+    ],
+}));
+
+const re_uppercase_first_letter = /^[A-Z]/;
+export function is_pure_builtin_call(compressor, call) {
+    let builtin = "";
+    let method = "";
+
+    let exp = call.expression;
+    if (is_undeclared_ref(exp)) {
+        builtin = exp.name;
+    } else if (exp instanceof AST_Dot) {
+        method = exp.property;
+
+        exp = exp.expression;
+        if (is_undeclared_ref(exp)) {
+            if (
+                // globalThis.pureFunc()
+                exp.name === "globalThis"
+                && compressor.option("builtins_ecma") >= 2020
+            ) {
+                builtin = method;
+                method = "";
+            } else {
+                // SomeBuiltin.pureFunc()
+                builtin = exp.name;
+            }
+        } else if (exp instanceof AST_Dot) {
+            if (
+                is_undeclared_ref(exp.expression)
+                && exp.expression.name === "globalThis"
+                && compressor.option("builtins_ecma") >= 2020
+            ) {
+                // globalThis.SomeBuiltin.pureFunc()
+                builtin = exp.property;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    } else {
+        return false;
+    }
+
+    if (!method) {
+        if (compressor.is_pure_native_fn(builtin)) {
+            // some require `new`, others throw if you use it
+            const is_new = call instanceof AST_New;
+            const should_be_new = re_uppercase_first_letter.test(builtin); // true of all `is_pure_native_fn`
+            if (is_new !== should_be_new) return false;
+
+            if (!is_builtin_pure_with_these_args(builtin, call.args)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        return false;
+    } else {
+        return compressor.is_pure_native_static_fn(builtin, method);
+    }
+}
+
+/** Some builtins are listed above but their purity is subject to some conditions */
+function is_builtin_pure_with_these_args(builtin, args) {
+    // all the builtins we deal with here are ok with getting 0 args
+    if (args.length === 0) return true;
+
+    let arg1 = args[0];
+    if (arg1 instanceof AST_SymbolRef) {
+        arg1 = arg1.fixed_value();
+    }
+
+    if (lone_arg_is_range.has(builtin)) { // new Array(number)
+        const arg_valid = args.length > 1
+            || arg1 instanceof AST_Number
+                && arg1.value >= 0 && arg1.value <= 0xffffffff;
+            // TODO: or, we are asked to ignore TypeError
+        if (!arg_valid) return false;
+    }
+
+    if (arg1_is_range_or_iterable.has(builtin)) { // new Float32Array(number | Array)
+        const arg_valid = args.length === 0
+            || arg1 instanceof AST_Array
+            || arg1 instanceof AST_Number
+                && arg1.value >= 0 && arg1.value <= 0xffffffff;
+        if (!arg_valid) return false;
+    }
+
+    if (arg1_is_iterable.has(builtin)) { // new Set(iterable)
+        const arg_valid = args.length === 0 || arg1 instanceof AST_Array;
+        if (!arg_valid) return false;
+    }
+
+    return true;
+}
Index: frontend/node_modules/terser/lib/compress/reduce-vars.js
===================================================================
--- frontend/node_modules/terser/lib/compress/reduce-vars.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/reduce-vars.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,864 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    AST_Accessor,
+    AST_Array,
+    AST_Assign,
+    AST_Await,
+    AST_Binary,
+    AST_Block,
+    AST_Call,
+    AST_Case,
+    AST_Chain,
+    AST_Class,
+    AST_ClassStaticBlock,
+    AST_ClassExpression,
+    AST_Conditional,
+    AST_Default,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Do,
+    AST_Exit,
+    AST_Expansion,
+    AST_For,
+    AST_ForIn,
+    AST_If,
+    AST_LabeledStatement,
+    AST_Lambda,
+    AST_New,
+    AST_Node,
+    AST_Number,
+    AST_ObjectKeyVal,
+    AST_PropAccess,
+    AST_Scope,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Symbol,
+    AST_SymbolCatch,
+    AST_SymbolConst,
+    AST_SymbolDeclaration,
+    AST_SymbolDefun,
+    AST_SymbolFunarg,
+    AST_SymbolLambda,
+    AST_SymbolRef,
+    AST_This,
+    AST_Toplevel,
+    AST_Try,
+    AST_Unary,
+    AST_UnaryPrefix,
+    AST_UsingDef,
+    AST_VarDef,
+    AST_VarDefLike,
+    AST_While,
+    AST_Yield,
+
+    walk,
+    walk_body,
+    walk_parent,
+} from "../ast.js";
+import { HOP, make_node, make_void_0, noop } from "../utils/index.js";
+
+import { lazy_op, is_modified, is_lhs } from "./inference.js";
+import { INLINED, clear_flag } from "./compressor-flags.js";
+import { read_property, has_break_or_continue, is_recursive_ref } from "./common.js";
+
+/**
+ * Define the method AST_Node#reduce_vars, which goes through the AST in
+ * execution order to perform basic flow analysis
+ */
+function def_reduce_vars(node, func) {
+    node.DEFMETHOD("reduce_vars", func);
+}
+
+def_reduce_vars(AST_Node, noop);
+
+/** Clear definition properties */
+function reset_def(compressor, def) {
+    def.assignments = 0;
+    def.chained = false;
+    def.direct_access = false;
+    def.escaped = 0;
+    def.recursive_refs = 0;
+    def.references = [];
+    def.single_use = undefined;
+    if (
+        def.scope.pinned()
+        || (def.orig[0] instanceof AST_SymbolFunarg && def.scope.uses_arguments)
+    ) {
+        def.fixed = false;
+    } else if (def.orig[0] instanceof AST_SymbolConst || !compressor.exposed(def)) {
+        def.fixed = def.init;
+    } else {
+        def.fixed = false;
+    }
+}
+
+function reset_variables(tw, compressor, node) {
+    node.variables.forEach(function(def) {
+        reset_def(compressor, def);
+        if (def.fixed === null) {
+            tw.defs_to_safe_ids.set(def.id, tw.safe_ids);
+            mark(tw, def, true);
+        } else if (def.fixed) {
+            tw.loop_ids.set(def.id, tw.in_loop);
+            mark(tw, def, true);
+        }
+    });
+}
+
+function reset_block_variables(compressor, node) {
+    if (node.block_scope) node.block_scope.variables.forEach((def) => {
+        reset_def(compressor, def);
+    });
+}
+
+function push(tw) {
+    tw.safe_ids = Object.create(tw.safe_ids);
+}
+
+function pop(tw) {
+    tw.safe_ids = Object.getPrototypeOf(tw.safe_ids);
+}
+
+function mark(tw, def, safe) {
+    tw.safe_ids[def.id] = safe;
+}
+
+function safe_to_read(tw, def) {
+    if (def.single_use == "m") return false;
+    if (tw.safe_ids[def.id]) {
+        if (def.fixed == null) {
+            var orig = def.orig[0];
+            if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
+            def.fixed = make_void_0(orig);
+        }
+        return true;
+    }
+    return def.fixed instanceof AST_Defun;
+}
+
+function safe_to_assign(tw, def, scope, value) {
+    if (def.fixed === undefined) return true;
+    let def_safe_ids;
+    if (def.fixed === null
+        && (def_safe_ids = tw.defs_to_safe_ids.get(def.id))
+    ) {
+        def_safe_ids[def.id] = false;
+        tw.defs_to_safe_ids.delete(def.id);
+        return true;
+    }
+    if (!HOP(tw.safe_ids, def.id)) return false;
+    if (!safe_to_read(tw, def)) return false;
+    if (def.fixed === false) return false;
+    if (def.fixed != null && (!value || def.references.length > def.assignments)) return false;
+    if (def.fixed instanceof AST_Defun) {
+        return value instanceof AST_Node && def.fixed.parent_scope === scope;
+    }
+    return def.orig.every((sym) => {
+        return !(sym instanceof AST_SymbolConst
+            || sym instanceof AST_SymbolDefun
+            || sym instanceof AST_SymbolLambda);
+    });
+}
+
+function ref_once(tw, compressor, def) {
+    return compressor.option("unused")
+        && !def.scope.pinned()
+        && def.references.length - def.recursive_refs == 1
+        && tw.loop_ids.get(def.id) === tw.in_loop;
+}
+
+function is_immutable(value) {
+    if (!value) return false;
+    return value.is_constant()
+        || value instanceof AST_Lambda
+        || value instanceof AST_This;
+}
+
+// A definition "escapes" when its value can leave the point of use.
+// Example: `a = b || c`
+// In this example, "b" and "c" are escaping, because they're going into "a"
+//
+// def.escaped is != 0 when it escapes.
+//
+// When greater than 1, it means that N chained properties will be read off
+// of that def before an escape occurs. This is useful for evaluating
+// property accesses, where you need to know when to stop.
+function mark_escaped(tw, d, scope, node, value, level = 0, depth = 1) {
+    var parent = tw.parent(level);
+    if (value) {
+        if (value.is_constant()) return;
+        if (value instanceof AST_ClassExpression) return;
+    }
+
+    if (
+        parent instanceof AST_Assign && (parent.operator === "=" || parent.logical) && node === parent.right
+        || parent instanceof AST_Call && (node !== parent.expression || parent instanceof AST_New)
+        || parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
+        || parent instanceof AST_VarDefLike && node === parent.value
+        || parent instanceof AST_Yield && node === parent.value && node.scope !== d.scope
+    ) {
+        if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1;
+        if (!d.escaped || d.escaped > depth) d.escaped = depth;
+        return;
+    } else if (
+        parent instanceof AST_Array
+        || parent instanceof AST_Await
+        || parent instanceof AST_Binary && lazy_op.has(parent.operator)
+        || parent instanceof AST_Conditional && node !== parent.condition
+        || parent instanceof AST_Expansion
+        || parent instanceof AST_Sequence && node === parent.tail_node()
+    ) {
+        mark_escaped(tw, d, scope, parent, parent, level + 1, depth);
+    } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
+        var obj = tw.parent(level + 1);
+
+        mark_escaped(tw, d, scope, obj, obj, level + 2, depth);
+    } else if (parent instanceof AST_PropAccess && node === parent.expression) {
+        value = read_property(value, parent.property);
+
+        mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1);
+        if (value) return;
+    }
+
+    if (level > 0) return;
+    if (parent instanceof AST_Sequence && node !== parent.tail_node()) return;
+    if (parent instanceof AST_SimpleStatement) return;
+
+    d.direct_access = true;
+}
+
+const suppress = node => walk(node, node => {
+    if (!(node instanceof AST_Symbol)) return;
+    var d = node.definition();
+    if (!d) return;
+    if (node instanceof AST_SymbolRef) d.references.push(node);
+    d.fixed = false;
+});
+
+def_reduce_vars(AST_Accessor, function(tw, descend, compressor) {
+    push(tw);
+    reset_variables(tw, compressor, this);
+    descend();
+    pop(tw);
+    return true;
+});
+
+def_reduce_vars(AST_Assign, function(tw, descend, compressor) {
+    var node = this;
+    if (node.left instanceof AST_Destructuring) {
+        suppress(node.left);
+        return;
+    }
+
+    const finish_walk = () => {
+        if (node.logical) {
+            node.left.walk(tw);
+
+            push(tw);
+            node.right.walk(tw);
+            pop(tw);
+
+            return true;
+        }
+    };
+
+    var sym = node.left;
+    if (!(sym instanceof AST_SymbolRef)) return finish_walk();
+
+    var def = sym.definition();
+    var safe = safe_to_assign(tw, def, sym.scope, node.right);
+    def.assignments++;
+    if (!safe) return finish_walk();
+
+    var fixed = def.fixed;
+    if (!fixed && node.operator != "=" && !node.logical) return finish_walk();
+
+    var eq = node.operator == "=";
+    var value = eq ? node.right : node;
+    if (is_modified(compressor, tw, node, value, 0)) return finish_walk();
+
+    def.references.push(sym);
+
+    if (!node.logical) {
+        if (!eq) def.chained = true;
+
+        def.fixed = eq ? function() {
+            return node.right;
+        } : function() {
+            return make_node(AST_Binary, node, {
+                operator: node.operator.slice(0, -1),
+                left: fixed instanceof AST_Node ? fixed : fixed(),
+                right: node.right
+            });
+        };
+    }
+
+    if (node.logical) {
+        mark(tw, def, false);
+        push(tw);
+        node.right.walk(tw);
+        pop(tw);
+        return true;
+    }
+
+    mark(tw, def, false);
+    node.right.walk(tw);
+    mark(tw, def, true);
+
+    mark_escaped(tw, def, sym.scope, node, value, 0, 1);
+
+    return true;
+});
+
+def_reduce_vars(AST_Binary, function(tw) {
+    if (!lazy_op.has(this.operator)) return;
+    this.left.walk(tw);
+    push(tw);
+    this.right.walk(tw);
+    pop(tw);
+    return true;
+});
+
+def_reduce_vars(AST_Block, function(tw, descend, compressor) {
+    reset_block_variables(compressor, this);
+});
+
+def_reduce_vars(AST_Case, function(tw) {
+    push(tw);
+    this.expression.walk(tw);
+    pop(tw);
+    push(tw);
+    walk_body(this, tw);
+    pop(tw);
+    return true;
+});
+
+def_reduce_vars(AST_Class, function(tw, descend) {
+    clear_flag(this, INLINED);
+    push(tw);
+    descend();
+    pop(tw);
+    return true;
+});
+
+def_reduce_vars(AST_ClassStaticBlock, function(tw, descend, compressor) {
+    reset_block_variables(compressor, this);
+});
+
+def_reduce_vars(AST_Conditional, function(tw) {
+    this.condition.walk(tw);
+    push(tw);
+    this.consequent.walk(tw);
+    pop(tw);
+    push(tw);
+    this.alternative.walk(tw);
+    pop(tw);
+    return true;
+});
+
+def_reduce_vars(AST_Chain, function(tw, descend) {
+    // Chains' conditions apply left-to-right, cumulatively.
+    // If we walk normally we don't go in that order because we would pop before pushing again
+    // Solution: AST_PropAccess and AST_Call push when they are optional, and never pop.
+    // Then we pop everything when they are done being walked.
+    const safe_ids = tw.safe_ids;
+
+    descend();
+
+    // Unroll back to start
+    tw.safe_ids = safe_ids;
+    return true;
+});
+
+def_reduce_vars(AST_Call, function (tw) {
+    this.expression.walk(tw);
+
+    if (this.optional) {
+        // Never pop -- it's popped at AST_Chain above
+        push(tw);
+    }
+
+    for (const arg of this.args) arg.walk(tw);
+
+    return true;
+});
+
+def_reduce_vars(AST_PropAccess, function (tw) {
+    if (!this.optional) return;
+
+    this.expression.walk(tw);
+
+    // Never pop -- it's popped at AST_Chain above
+    push(tw);
+
+    if (this.property instanceof AST_Node) this.property.walk(tw);
+
+    return true;
+});
+
+def_reduce_vars(AST_Default, function(tw, descend) {
+    push(tw);
+    descend();
+    pop(tw);
+    return true;
+});
+
+function mark_lambda(tw, descend, compressor) {
+    clear_flag(this, INLINED);
+    push(tw);
+    reset_variables(tw, compressor, this);
+
+    var iife;
+    if (!this.name
+        && !this.uses_arguments
+        && !this.pinned()
+        && (iife = tw.parent()) instanceof AST_Call
+        && iife.expression === this
+        && !iife.args.some(arg => arg instanceof AST_Expansion)
+        && this.argnames.every(arg_name => arg_name instanceof AST_Symbol)
+    ) {
+        // Virtually turn IIFE parameters into variable definitions:
+        //   (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
+        // So existing transformation rules can work on them.
+        this.argnames.forEach((arg, i) => {
+            if (!arg.definition) return;
+            var d = arg.definition();
+            // Avoid setting fixed when there's more than one origin for a variable value
+            if (d.orig.length > 1) return;
+            if (d.fixed === undefined && (!this.uses_arguments || tw.has_directive("use strict"))) {
+                d.fixed = function() {
+                    return iife.args[i] || make_void_0(iife);
+                };
+                tw.loop_ids.set(d.id, tw.in_loop);
+                mark(tw, d, true);
+            } else {
+                d.fixed = false;
+            }
+        });
+    }
+
+    descend();
+    pop(tw);
+
+    handle_defined_after_hoist(this);
+
+    return true;
+}
+
+/**
+ * It's possible for a hoisted function to use something that's not defined yet. Example:
+ *
+ * hoisted();
+ * var defined_after = true;
+ * function hoisted() {
+ *   // use defined_after
+ * }
+ *
+ * Or even indirectly:
+ *
+ * B();
+ * var defined_after = true;
+ * function A() {
+ *   // use defined_after
+ * }
+ * function B() {
+ *   A();
+ * }
+ *
+ * Access a variable before declaration will either throw a ReferenceError
+ * (if the variable is declared with `let` or `const`),
+ * or get an `undefined` (if the variable is declared with `var`).
+ *
+ * If the variable is inlined into the function, the behavior will change.
+ *
+ * This function is called on the parent to disallow inlining of such variables,
+ */
+function handle_defined_after_hoist(parent) {
+    const defuns = [];
+    walk(parent, node => {
+        if (node === parent) return;
+        if (node instanceof AST_Defun) {
+            defuns.push(node);
+            return true;
+        }
+        if (
+            node instanceof AST_Scope
+            || node instanceof AST_SimpleStatement
+        ) return true;
+    });
+
+    // `defun` id to array of `defun` it uses
+    const defun_dependencies_map = new Map();
+    // `defun` id to array of enclosing `def` that are used by the function
+    const dependencies_map = new Map();
+    // all symbol ids that will be tracked for read/write
+    const symbols_of_interest = new Set();
+    const defuns_of_interest = new Set();
+
+    for (const defun of defuns) {
+        const fname_def = defun.name.definition();
+        const enclosing_defs = [];
+
+        for (const def of defun.enclosed) {
+            if (
+                def.fixed === false
+                || def === fname_def
+                || def.scope.get_defun_scope() !== parent
+            ) {
+                continue;
+            }
+
+            symbols_of_interest.add(def.id);
+
+            // found a reference to another function
+            if (
+                def.assignments === 0
+                && def.orig.length === 1
+                && def.orig[0] instanceof AST_SymbolDefun
+            ) {
+                defuns_of_interest.add(def.id);
+                symbols_of_interest.add(def.id);
+
+                defuns_of_interest.add(fname_def.id);
+                symbols_of_interest.add(fname_def.id);
+
+                if (!defun_dependencies_map.has(fname_def.id)) {
+                    defun_dependencies_map.set(fname_def.id, []);
+                }
+                defun_dependencies_map.get(fname_def.id).push(def.id);
+
+                continue;
+            }
+
+            enclosing_defs.push(def);
+        }
+
+        if (enclosing_defs.length) {
+            dependencies_map.set(fname_def.id, enclosing_defs);
+            defuns_of_interest.add(fname_def.id);
+            symbols_of_interest.add(fname_def.id);
+        }
+    }
+
+    // No defuns use outside constants
+    if (!dependencies_map.size) {
+        return;
+    }
+
+    // Increment to count "symbols of interest" (defuns or defs) that we found.
+    // These are tracked in AST order so we can check which is after which.
+    let symbol_index = 1;
+    // Map a defun ID to its first read (a `symbol_index`)
+    const defun_first_read_map = new Map();
+    // Map a symbol ID to its last write (a `symbol_index`)
+    const symbol_last_write_map = new Map();
+
+    walk_parent(parent, (node, walk_info) => {
+        if (node instanceof AST_Symbol && node.thedef) {
+            const id = node.definition().id;
+
+            symbol_index++;
+
+            // Track last-writes to symbols
+            if (symbols_of_interest.has(id)) {
+                if (node instanceof AST_SymbolDeclaration || is_lhs(node, walk_info.parent())) {
+                    symbol_last_write_map.set(id, symbol_index);
+                }
+            }
+
+            // Track first-reads of defuns (refined later)
+            if (defuns_of_interest.has(id)) {
+                if (!defun_first_read_map.has(id) && !is_recursive_ref(walk_info, id)) {
+                    defun_first_read_map.set(id, symbol_index);
+                }
+            }
+        }
+    });
+
+    // Refine `defun_first_read_map` to be as high as possible
+    for (const [defun, defun_first_read] of defun_first_read_map) {
+        // Update all dependencies of `defun`
+        const queue = new Set(defun_dependencies_map.get(defun));
+        for (const enclosed_defun of queue) {
+            let enclosed_defun_first_read = defun_first_read_map.get(enclosed_defun);
+            if (enclosed_defun_first_read != null && enclosed_defun_first_read < defun_first_read) {
+                continue;
+            }
+
+            defun_first_read_map.set(enclosed_defun, defun_first_read);
+
+            for (const enclosed_enclosed_defun of defun_dependencies_map.get(enclosed_defun) || []) {
+                queue.add(enclosed_enclosed_defun);
+            }
+        }
+    }
+
+    // ensure write-then-read order, otherwise clear `fixed`
+    // This is safe because last-writes (found_symbol_writes) are assumed to be as late as possible, and first-reads (defun_first_read_map) are assumed to be as early as possible.
+    for (const [defun, defs] of dependencies_map) {
+        const defun_first_read = defun_first_read_map.get(defun);
+        if (defun_first_read === undefined) {
+            continue;
+        }
+
+        for (const def of defs) {
+            if (def.fixed === false) {
+                continue;
+            }
+
+            let def_last_write = symbol_last_write_map.get(def.id) || 0;
+
+            if (defun_first_read < def_last_write) {
+                def.fixed = false;
+            }
+        }
+    }
+}
+
+def_reduce_vars(AST_Lambda, mark_lambda);
+
+def_reduce_vars(AST_Do, function(tw, descend, compressor) {
+    reset_block_variables(compressor, this);
+    const saved_loop = tw.in_loop;
+    tw.in_loop = this;
+    push(tw);
+    this.body.walk(tw);
+    if (has_break_or_continue(this)) {
+        pop(tw);
+        push(tw);
+    }
+    this.condition.walk(tw);
+    pop(tw);
+    tw.in_loop = saved_loop;
+    return true;
+});
+
+def_reduce_vars(AST_For, function(tw, descend, compressor) {
+    reset_block_variables(compressor, this);
+    if (this.init) this.init.walk(tw);
+    const saved_loop = tw.in_loop;
+    tw.in_loop = this;
+    push(tw);
+    if (this.condition) this.condition.walk(tw);
+    this.body.walk(tw);
+    if (this.step) {
+        if (has_break_or_continue(this)) {
+            pop(tw);
+            push(tw);
+        }
+        this.step.walk(tw);
+    }
+    pop(tw);
+    tw.in_loop = saved_loop;
+    return true;
+});
+
+def_reduce_vars(AST_ForIn, function(tw, descend, compressor) {
+    reset_block_variables(compressor, this);
+    suppress(this.init);
+    this.object.walk(tw);
+    const saved_loop = tw.in_loop;
+    tw.in_loop = this;
+    push(tw);
+    this.body.walk(tw);
+    pop(tw);
+    tw.in_loop = saved_loop;
+    return true;
+});
+
+def_reduce_vars(AST_If, function(tw) {
+    this.condition.walk(tw);
+    push(tw);
+    this.body.walk(tw);
+    pop(tw);
+    if (this.alternative) {
+        push(tw);
+        this.alternative.walk(tw);
+        pop(tw);
+    }
+    return true;
+});
+
+def_reduce_vars(AST_LabeledStatement, function(tw) {
+    push(tw);
+    this.body.walk(tw);
+    pop(tw);
+    return true;
+});
+
+def_reduce_vars(AST_SymbolCatch, function() {
+    this.definition().fixed = false;
+});
+
+def_reduce_vars(AST_SymbolRef, function(tw, descend, compressor) {
+    var d = this.definition();
+    d.references.push(this);
+    if (d.references.length == 1
+        && !d.fixed
+        && d.orig[0] instanceof AST_SymbolDefun) {
+        tw.loop_ids.set(d.id, tw.in_loop);
+    }
+    var fixed_value;
+    if (d.fixed === undefined || !safe_to_read(tw, d)) {
+        d.fixed = false;
+    } else if (d.fixed) {
+        fixed_value = this.fixed_value();
+        if (
+            fixed_value instanceof AST_Lambda
+            && is_recursive_ref(tw, d)
+        ) {
+            d.recursive_refs++;
+        } else if (fixed_value
+            && !compressor.exposed(d)
+            && ref_once(tw, compressor, d)
+        ) {
+            d.single_use =
+                fixed_value instanceof AST_Lambda && !fixed_value.pinned()
+                || fixed_value instanceof AST_Class
+                || d.scope === this.scope && fixed_value.is_constant_expression();
+        } else {
+            d.single_use = false;
+        }
+        if (is_modified(compressor, tw, this, fixed_value, 0, is_immutable(fixed_value))) {
+            if (d.single_use) {
+                d.single_use = "m";
+            } else {
+                d.fixed = false;
+            }
+        }
+    }
+    mark_escaped(tw, d, this.scope, this, fixed_value, 0, 1);
+});
+
+def_reduce_vars(AST_Toplevel, function(tw, descend, compressor) {
+    this.globals.forEach(function(def) {
+        reset_def(compressor, def);
+    });
+    reset_variables(tw, compressor, this);
+    descend();
+    handle_defined_after_hoist(this);
+    return true;
+});
+
+def_reduce_vars(AST_Try, function(tw, descend, compressor) {
+    reset_block_variables(compressor, this);
+    push(tw);
+    this.body.walk(tw);
+    pop(tw);
+    if (this.bcatch) {
+        push(tw);
+        this.bcatch.walk(tw);
+        pop(tw);
+    }
+    if (this.bfinally) this.bfinally.walk(tw);
+    return true;
+});
+
+def_reduce_vars(AST_Unary, function(tw) {
+    var node = this;
+    if (node.operator !== "++" && node.operator !== "--") return;
+    var exp = node.expression;
+    if (!(exp instanceof AST_SymbolRef)) return;
+    var def = exp.definition();
+    var safe = safe_to_assign(tw, def, exp.scope, true);
+    def.assignments++;
+    if (!safe) return;
+    var fixed = def.fixed;
+    if (!fixed) return;
+    def.references.push(exp);
+    def.chained = true;
+    def.fixed = function() {
+        return make_node(AST_Binary, node, {
+            operator: node.operator.slice(0, -1),
+            left: make_node(AST_UnaryPrefix, node, {
+                operator: "+",
+                expression: fixed instanceof AST_Node ? fixed : fixed()
+            }),
+            right: make_node(AST_Number, node, {
+                value: 1
+            })
+        });
+    };
+    mark(tw, def, true);
+    return true;
+});
+
+def_reduce_vars(AST_VarDef, function(tw, descend) {
+    var node = this;
+    if (node.name instanceof AST_Destructuring) {
+        suppress(node.name);
+        return;
+    }
+    var d = node.name.definition();
+    if (node.value) {
+        if (safe_to_assign(tw, d, node.name.scope, node.value)) {
+            d.fixed = function() {
+                return node.value;
+            };
+            tw.loop_ids.set(d.id, tw.in_loop);
+            mark(tw, d, false);
+            descend();
+            mark(tw, d, true);
+            return true;
+        } else {
+            d.fixed = false;
+        }
+    }
+});
+
+def_reduce_vars(AST_UsingDef, function() {
+    suppress(this.name);
+});
+
+def_reduce_vars(AST_While, function(tw, descend, compressor) {
+    reset_block_variables(compressor, this);
+    const saved_loop = tw.in_loop;
+    tw.in_loop = this;
+    push(tw);
+    descend();
+    pop(tw);
+    tw.in_loop = saved_loop;
+    return true;
+});
Index: frontend/node_modules/terser/lib/compress/tighten-body.js
===================================================================
--- frontend/node_modules/terser/lib/compress/tighten-body.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/compress/tighten-body.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1531 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import {
+    AST_Array,
+    AST_Arrow,
+    AST_Assign,
+    AST_Await,
+    AST_Binary,
+    AST_Block,
+    AST_BlockStatement,
+    AST_Break,
+    AST_Call,
+    AST_Case,
+    AST_Chain,
+    AST_Class,
+    AST_Conditional,
+    AST_Constant,
+    AST_Continue,
+    AST_Debugger,
+    AST_Default,
+    AST_Definitions,
+    AST_DefinitionsLike,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Directive,
+    AST_Dot,
+    AST_DWLoop,
+    AST_EmptyStatement,
+    AST_Exit,
+    AST_Expansion,
+    AST_Export,
+    AST_For,
+    AST_ForIn,
+    AST_If,
+    AST_Import,
+    AST_IterationStatement,
+    AST_Lambda,
+    AST_LoopControl,
+    AST_Node,
+    AST_Number,
+    AST_Object,
+    AST_ObjectKeyVal,
+    AST_ObjectProperty,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Return,
+    AST_Scope,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Sub,
+    AST_Switch,
+    AST_Symbol,
+    AST_SymbolConst,
+    AST_SymbolDeclaration,
+    AST_SymbolDefun,
+    AST_SymbolFunarg,
+    AST_SymbolLambda,
+    AST_SymbolLet,
+    AST_SymbolRef,
+    AST_SymbolUsing,
+    AST_SymbolVar,
+    AST_This,
+    AST_Try,
+    AST_TryBlock,
+    AST_Unary,
+    AST_UnaryPostfix,
+    AST_UnaryPrefix,
+    AST_Using,
+    AST_Var,
+    AST_VarDef,
+    AST_With,
+    AST_Yield,
+
+    TreeTransformer,
+    TreeWalker,
+    walk,
+    walk_abort,
+
+    _NOINLINE,
+} from "../ast.js";
+import {
+    make_node,
+    make_void_0,
+    MAP,
+    member,
+    remove,
+    has_annotation
+} from "../utils/index.js";
+
+import { pure_prop_access_globals } from "./native-objects.js";
+import {
+    lazy_op,
+    unary_side_effects,
+    is_modified,
+    is_lhs,
+    aborts
+} from "./inference.js";
+import { WRITE_ONLY, clear_flag } from "./compressor-flags.js";
+import {
+    make_sequence,
+    merge_sequence,
+    maintain_this_binding,
+    is_func_expr,
+    is_identifier_atom,
+    is_ref_of,
+    can_be_evicted_from_block,
+    as_statement_array,
+} from "./common.js";
+
+function loop_body(x) {
+    if (x instanceof AST_IterationStatement) {
+        return x.body instanceof AST_BlockStatement ? x.body : x;
+    }
+    return x;
+}
+
+function is_lhs_read_only(lhs) {
+    if (lhs instanceof AST_This) return true;
+    if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda;
+    if (lhs instanceof AST_PropAccess) {
+        lhs = lhs.expression;
+        if (lhs instanceof AST_SymbolRef) {
+            if (lhs.is_immutable()) return false;
+            lhs = lhs.fixed_value();
+        }
+        if (!lhs) return true;
+        if (lhs instanceof AST_RegExp) return false;
+        if (lhs instanceof AST_Constant) return true;
+        return is_lhs_read_only(lhs);
+    }
+    return false;
+}
+
+/** var a = 1 --> var a*/
+function remove_initializers(var_statement) {
+    var decls = [];
+    var_statement.definitions.forEach(function(def) {
+        if (def.name instanceof AST_SymbolDeclaration) {
+            def.value = null;
+            decls.push(def);
+        } else {
+            def.declarations_as_names().forEach(name => {
+                decls.push(make_node(AST_VarDef, def, {
+                    name,
+                    value: null
+                }));
+            });
+        }
+    });
+    return decls.length ? make_node(AST_Var, var_statement, { definitions: decls }) : null;
+}
+
+/** Called on code which won't be executed but has an effect outside of itself: `var`, `function` statements, `export`, `import`. */
+export function extract_from_unreachable_code(compressor, stat, target) {
+    walk(stat, node => {
+        if (node instanceof AST_Var) {
+            const no_initializers = remove_initializers(node);
+            if (no_initializers) target.push(no_initializers);
+            return true;
+        }
+        if (
+            node instanceof AST_Defun
+            && (node === stat || !compressor.has_directive("use strict"))
+        ) {
+            target.push(node === stat ? node : make_node(AST_Var, node, {
+                definitions: [
+                    make_node(AST_VarDef, node, {
+                        name: make_node(AST_SymbolVar, node.name, node.name),
+                        value: null
+                    })
+                ]
+            }));
+            return true;
+        }
+        if (node instanceof AST_Export || node instanceof AST_Import) {
+            target.push(node);
+            return true;
+        }
+        if (node instanceof AST_Scope || node instanceof AST_Class) {
+            // Do not go into nested scopes
+            return true;
+        }
+    });
+}
+
+/** Tighten a bunch of statements together, and perform statement-level optimization. */
+export function tighten_body(statements, compressor) {
+    const nearest_scope = compressor.find_scope();
+    const defun_scope = nearest_scope.get_defun_scope();
+    const { in_loop, in_try } = find_loop_scope_try();
+
+    var CHANGED, max_iter = 10;
+    do {
+        CHANGED = false;
+        eliminate_spurious_blocks(statements);
+        if (compressor.option("dead_code")) {
+            eliminate_dead_code(statements, compressor);
+        }
+        if (compressor.option("if_return")) {
+            handle_if_return(statements, compressor);
+        }
+        if (compressor.sequences_limit > 0) {
+            sequencesize(statements, compressor);
+            sequencesize_2(statements, compressor);
+        }
+        if (compressor.option("join_vars")) {
+            join_consecutive_vars(statements);
+        }
+        if (compressor.option("collapse_vars")) {
+            collapse(statements, compressor);
+        }
+    } while (CHANGED && max_iter-- > 0);
+
+    function find_loop_scope_try() {
+        var node = compressor.self(), level = 0, in_loop = false, in_try = false;
+        do {
+            if (node instanceof AST_IterationStatement) {
+                in_loop = true;
+            } else if (node instanceof AST_Scope) {
+                break;
+            } else if (node instanceof AST_TryBlock) {
+                in_try = true;
+            }
+        } while (node = compressor.parent(level++));
+
+        return { in_loop, in_try };
+    }
+
+    // Search from right to left for assignment-like expressions:
+    // - `var a = x;`
+    // - `a = x;`
+    // - `++a`
+    // For each candidate, scan from left to right for first usage, then try
+    // to fold assignment into the site for compression.
+    // Will not attempt to collapse assignments into or past code blocks
+    // which are not sequentially executed, e.g. loops and conditionals.
+    function collapse(statements, compressor) {
+        if (nearest_scope.pinned() || defun_scope.pinned())
+            return statements;
+        var args;
+        var candidates = [];
+        var stat_index = statements.length;
+        var scanner = new TreeTransformer(function (node) {
+            if (abort)
+                return node;
+            // Skip nodes before `candidate` as quickly as possible
+            if (!hit) {
+                if (node !== hit_stack[hit_index])
+                    return node;
+                hit_index++;
+                if (hit_index < hit_stack.length)
+                    return handle_custom_scan_order(node);
+                hit = true;
+                stop_after = find_stop(node, 0);
+                if (stop_after === node)
+                    abort = true;
+                return node;
+            }
+            // Stop immediately if these node types are encountered
+            var parent = scanner.parent();
+            if (node instanceof AST_Assign
+                    && (node.logical || node.operator != "=" && lhs.equivalent_to(node.left))
+                || node instanceof AST_Await
+                || node instanceof AST_Using
+                || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
+                ||
+                    (node instanceof AST_Call || node instanceof AST_PropAccess)
+                    && node.optional
+                || node instanceof AST_Debugger
+                || node instanceof AST_Destructuring
+                || node instanceof AST_Expansion
+                    && node.expression instanceof AST_Symbol
+                    && (
+                        node.expression instanceof AST_This
+                        || node.expression.definition().references.length > 1
+                    )
+                || node instanceof AST_IterationStatement && !(node instanceof AST_For)
+                || node instanceof AST_LoopControl
+                || node instanceof AST_Try
+                || node instanceof AST_With
+                || node instanceof AST_Yield
+                || node instanceof AST_Export
+                || node instanceof AST_Class
+                || parent instanceof AST_For && node !== parent.init
+                || !replace_all
+                    && (
+                        node instanceof AST_SymbolRef
+                        && !node.is_declared(compressor)
+                        && !pure_prop_access_globals.has(node)
+                    )
+                || node instanceof AST_SymbolRef
+                    && parent instanceof AST_Call
+                    && has_annotation(parent, _NOINLINE)
+                || node instanceof AST_ObjectProperty && node.key instanceof AST_Node
+            ) {
+                abort = true;
+                return node;
+            }
+            // Stop only if candidate is found within conditional branches
+            if (!stop_if_hit && (!lhs_local || !replace_all)
+                && (parent instanceof AST_Binary && lazy_op.has(parent.operator) && parent.left !== node
+                    || parent instanceof AST_Conditional && parent.condition !== node
+                    || parent instanceof AST_If && parent.condition !== node)) {
+                stop_if_hit = parent;
+            }
+            // Replace variable with assignment when found
+            if (
+                can_replace
+                && !(node instanceof AST_SymbolDeclaration)
+                && lhs.equivalent_to(node)
+                && !shadows(scanner.find_scope() || nearest_scope, lvalues)
+            ) {
+                if (stop_if_hit) {
+                    abort = true;
+                    return node;
+                }
+                if (is_lhs(node, parent)) {
+                    if (value_def)
+                        replaced++;
+                    return node;
+                } else {
+                    replaced++;
+                    if (value_def && candidate instanceof AST_VarDef)
+                        return node;
+                }
+                CHANGED = abort = true;
+                if (candidate instanceof AST_UnaryPostfix) {
+                    return make_node(AST_UnaryPrefix, candidate, candidate);
+                }
+                if (candidate instanceof AST_VarDef) {
+                    var def = candidate.name.definition();
+                    var value = candidate.value;
+                    if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
+                        def.replaced++;
+                        if (funarg && is_identifier_atom(value)) {
+                            return value.transform(compressor);
+                        } else {
+                            return maintain_this_binding(parent, node, value);
+                        }
+                    }
+                    return make_node(AST_Assign, candidate, {
+                        operator: "=",
+                        logical: false,
+                        left: make_node(AST_SymbolRef, candidate.name, candidate.name),
+                        right: value
+                    });
+                }
+                clear_flag(candidate, WRITE_ONLY);
+                return candidate;
+            }
+            // These node types have child nodes that execute sequentially,
+            // but are otherwise not safe to scan into or beyond them.
+            var sym;
+            if (node instanceof AST_Call
+                || node instanceof AST_Exit
+                && (side_effects || lhs instanceof AST_PropAccess || may_modify(lhs))
+                || node instanceof AST_PropAccess
+                && (side_effects || node.expression.may_throw_on_access(compressor))
+                || node instanceof AST_SymbolRef
+                && ((lvalues.has(node.name) && lvalues.get(node.name).modified) || side_effects && may_modify(node))
+                || node instanceof AST_VarDef && node.value
+                && (lvalues.has(node.name.name) || side_effects && may_modify(node.name))
+                || node instanceof AST_Using
+                || (sym = is_lhs(node.left, node))
+                && (sym instanceof AST_PropAccess || lvalues.has(sym.name))
+                || may_throw
+                && (in_try ? node.has_side_effects(compressor) : side_effects_external(node))) {
+                stop_after = node;
+                if (node instanceof AST_Scope)
+                    abort = true;
+            }
+            return handle_custom_scan_order(node);
+        }, function (node) {
+            if (abort)
+                return;
+            if (stop_after === node)
+                abort = true;
+            if (stop_if_hit === node)
+                stop_if_hit = null;
+        });
+
+        var multi_replacer = new TreeTransformer(function (node) {
+            if (abort)
+                return node;
+            // Skip nodes before `candidate` as quickly as possible
+            if (!hit) {
+                if (node !== hit_stack[hit_index])
+                    return node;
+                hit_index++;
+                if (hit_index < hit_stack.length)
+                    return;
+                hit = true;
+                return node;
+            }
+            // Replace variable when found
+            if (node instanceof AST_SymbolRef
+                && node.name == def.name) {
+                if (!--replaced)
+                    abort = true;
+                if (is_lhs(node, multi_replacer.parent()))
+                    return node;
+                def.replaced++;
+                value_def.replaced--;
+                return candidate.value;
+            }
+            // Skip (non-executed) functions and (leading) default case in switch statements
+            if (node instanceof AST_Default || node instanceof AST_Scope)
+                return node;
+        });
+
+        while (--stat_index >= 0) {
+            // Treat parameters as collapsible in IIFE, i.e.
+            //   function(a, b){ ... }(x());
+            // would be translated into equivalent assignments:
+            //   var a = x(), b = undefined;
+            if (stat_index == 0 && compressor.option("unused"))
+                extract_args();
+            // Find collapsible assignments
+            var hit_stack = [];
+            extract_candidates(statements[stat_index]);
+            while (candidates.length > 0) {
+                hit_stack = candidates.pop();
+                var hit_index = 0;
+                var candidate = hit_stack[hit_stack.length - 1];
+                var value_def = null;
+                var stop_after = null;
+                var stop_if_hit = null;
+                var lhs = get_lhs(candidate);
+                if (!lhs || is_lhs_read_only(lhs) || lhs.has_side_effects(compressor))
+                    continue;
+                // Locate symbols which may execute code outside of scanning range
+                var lvalues = get_lvalues(candidate);
+                var lhs_local = is_lhs_local(lhs);
+                if (lhs instanceof AST_SymbolRef) {
+                    lvalues.set(lhs.name, { def: lhs.definition(), modified: false });
+                }
+                var side_effects = value_has_side_effects(candidate);
+                var replace_all = replace_all_symbols();
+                var may_throw = candidate.may_throw(compressor);
+                var funarg = candidate.name instanceof AST_SymbolFunarg;
+                var hit = funarg;
+                var abort = false, replaced = 0, can_replace = !args || !hit;
+                if (!can_replace) {
+                    for (
+                        let j = compressor.self().argnames.lastIndexOf(candidate.name) + 1;
+                        !abort && j < args.length;
+                        j++
+                    ) {
+                        args[j].transform(scanner);
+                    }
+                    can_replace = true;
+                }
+                for (var i = stat_index; !abort && i < statements.length; i++) {
+                    statements[i].transform(scanner);
+                }
+                if (value_def) {
+                    var def = candidate.name.definition();
+                    if (abort && def.references.length - def.replaced > replaced)
+                        replaced = false;
+                    else {
+                        abort = false;
+                        hit_index = 0;
+                        hit = funarg;
+                        for (var i = stat_index; !abort && i < statements.length; i++) {
+                            statements[i].transform(multi_replacer);
+                        }
+                        value_def.single_use = false;
+                    }
+                }
+                if (replaced && !remove_candidate(candidate))
+                    statements.splice(stat_index, 1);
+            }
+        }
+
+        function handle_custom_scan_order(node) {
+            // Skip (non-executed) functions
+            if (node instanceof AST_Scope)
+                return node;
+
+            // Scan case expressions first in a switch statement
+            if (node instanceof AST_Switch) {
+                node.expression = node.expression.transform(scanner);
+                for (var i = 0, len = node.body.length; !abort && i < len; i++) {
+                    var branch = node.body[i];
+                    if (branch instanceof AST_Case) {
+                        if (!hit) {
+                            if (branch !== hit_stack[hit_index])
+                                continue;
+                            hit_index++;
+                        }
+                        branch.expression = branch.expression.transform(scanner);
+                        if (!replace_all)
+                            break;
+                    }
+                }
+                abort = true;
+                return node;
+            }
+        }
+
+        function redefined_within_scope(def, scope) {
+            if (def.global)
+                return false;
+            let cur_scope = def.scope;
+            while (cur_scope && cur_scope !== scope) {
+                if (cur_scope.variables.has(def.name)) {
+                    return true;
+                }
+                cur_scope = cur_scope.parent_scope;
+            }
+            return false;
+        }
+
+        function has_overlapping_symbol(fn, arg, fn_strict) {
+            var found = false, scan_this = !(fn instanceof AST_Arrow);
+            arg.walk(new TreeWalker(function (node, descend) {
+                if (found)
+                    return true;
+                if (node instanceof AST_SymbolRef && (fn.variables.has(node.name) || redefined_within_scope(node.definition(), fn))) {
+                    var s = node.definition().scope;
+                    if (s !== defun_scope)
+                        while (s = s.parent_scope) {
+                            if (s === defun_scope)
+                                return true;
+                        }
+                    return found = true;
+                }
+                if ((fn_strict || scan_this) && node instanceof AST_This) {
+                    return found = true;
+                }
+                if (node instanceof AST_Scope && !(node instanceof AST_Arrow)) {
+                    var prev = scan_this;
+                    scan_this = false;
+                    descend();
+                    scan_this = prev;
+                    return true;
+                }
+            }));
+            return found;
+        }
+
+        function arg_is_injectable(arg) {
+            if (arg instanceof AST_Expansion) return false;
+            const contains_await = walk(arg, (node) => {
+                if (node instanceof AST_Await) return walk_abort;
+            });
+            if (contains_await) return false;
+            return true;
+        }
+        function extract_args() {
+            var iife, fn = compressor.self();
+            if (is_func_expr(fn)
+                && !fn.name
+                && !fn.uses_arguments
+                && !fn.pinned()
+                && (iife = compressor.parent()) instanceof AST_Call
+                && iife.expression === fn
+                && iife.args.every(arg_is_injectable)
+            ) {
+                var fn_strict = compressor.has_directive("use strict");
+                if (fn_strict && !member(fn_strict, fn.body))
+                    fn_strict = false;
+                var len = fn.argnames.length;
+                args = iife.args.slice(len);
+                var names = new Set();
+                for (var i = len; --i >= 0;) {
+                    var sym = fn.argnames[i];
+                    var arg = iife.args[i];
+                    // The following two line fix is a duplicate of the fix at
+                    // https://github.com/terser/terser/commit/011d3eb08cefe6922c7d1bdfa113fc4aeaca1b75
+                    // This might mean that these two pieces of code (one here in collapse_vars and another in reduce_vars
+                    // Might be doing the exact same thing.
+                    const def = sym.definition && sym.definition();
+                    const is_reassigned = def && def.orig.length > 1;
+                    if (is_reassigned)
+                        continue;
+                    args.unshift(make_node(AST_VarDef, sym, {
+                        name: sym,
+                        value: arg
+                    }));
+                    if (names.has(sym.name))
+                        continue;
+                    names.add(sym.name);
+                    if (sym instanceof AST_Expansion) {
+                        var elements = iife.args.slice(i);
+                        if (elements.every((arg) => !has_overlapping_symbol(fn, arg, fn_strict)
+                        )) {
+                            candidates.unshift([make_node(AST_VarDef, sym, {
+                                name: sym.expression,
+                                value: make_node(AST_Array, iife, {
+                                    elements: elements
+                                })
+                            })]);
+                        }
+                    } else {
+                        if (!arg) {
+                            arg = make_void_0(sym).transform(compressor);
+                        } else if (arg instanceof AST_Lambda && arg.pinned()
+                            || has_overlapping_symbol(fn, arg, fn_strict)) {
+                            arg = null;
+                        }
+                        if (arg)
+                            candidates.unshift([make_node(AST_VarDef, sym, {
+                                name: sym,
+                                value: arg
+                            })]);
+                    }
+                }
+            }
+        }
+
+        function extract_candidates(expr) {
+            hit_stack.push(expr);
+            if (expr instanceof AST_Assign) {
+                if (!expr.left.has_side_effects(compressor)
+                    && !(expr.right instanceof AST_Chain)) {
+                    candidates.push(hit_stack.slice());
+                }
+                extract_candidates(expr.right);
+            } else if (expr instanceof AST_Binary) {
+                extract_candidates(expr.left);
+                extract_candidates(expr.right);
+            } else if (expr instanceof AST_Call && !has_annotation(expr, _NOINLINE)) {
+                extract_candidates(expr.expression);
+                expr.args.forEach(extract_candidates);
+            } else if (expr instanceof AST_Case) {
+                extract_candidates(expr.expression);
+            } else if (expr instanceof AST_Conditional) {
+                extract_candidates(expr.condition);
+                extract_candidates(expr.consequent);
+                extract_candidates(expr.alternative);
+            } else if (expr instanceof AST_Definitions) {
+                var len = expr.definitions.length;
+                // limit number of trailing variable definitions for consideration
+                var i = len - 200;
+                if (i < 0)
+                    i = 0;
+                for (; i < len; i++) {
+                    extract_candidates(expr.definitions[i]);
+                }
+            } else if (expr instanceof AST_DWLoop) {
+                extract_candidates(expr.condition);
+                if (!(expr.body instanceof AST_Block)) {
+                    extract_candidates(expr.body);
+                }
+            } else if (expr instanceof AST_Exit) {
+                if (expr.value)
+                    extract_candidates(expr.value);
+            } else if (expr instanceof AST_For) {
+                if (expr.init)
+                    extract_candidates(expr.init);
+                if (expr.condition)
+                    extract_candidates(expr.condition);
+                if (expr.step)
+                    extract_candidates(expr.step);
+                if (!(expr.body instanceof AST_Block)) {
+                    extract_candidates(expr.body);
+                }
+            } else if (expr instanceof AST_ForIn) {
+                extract_candidates(expr.object);
+                if (!(expr.body instanceof AST_Block)) {
+                    extract_candidates(expr.body);
+                }
+            } else if (expr instanceof AST_If) {
+                extract_candidates(expr.condition);
+                if (!(expr.body instanceof AST_Block)) {
+                    extract_candidates(expr.body);
+                }
+                if (expr.alternative && !(expr.alternative instanceof AST_Block)) {
+                    extract_candidates(expr.alternative);
+                }
+            } else if (expr instanceof AST_Sequence) {
+                expr.expressions.forEach(extract_candidates);
+            } else if (expr instanceof AST_SimpleStatement) {
+                extract_candidates(expr.body);
+            } else if (expr instanceof AST_Switch) {
+                extract_candidates(expr.expression);
+                expr.body.forEach(extract_candidates);
+            } else if (expr instanceof AST_Unary) {
+                if (expr.operator == "++" || expr.operator == "--") {
+                    candidates.push(hit_stack.slice());
+                }
+            } else if (expr instanceof AST_VarDef) {
+                if (expr.value && !(expr.value instanceof AST_Chain)) {
+                    candidates.push(hit_stack.slice());
+                    extract_candidates(expr.value);
+                }
+            }
+            hit_stack.pop();
+        }
+
+        function find_stop(node, level, write_only) {
+            var parent = scanner.parent(level);
+            if (parent instanceof AST_Assign) {
+                if (write_only
+                    && !parent.logical
+                    && !(parent.left instanceof AST_PropAccess
+                        || lvalues.has(parent.left.name))) {
+                    return find_stop(parent, level + 1, write_only);
+                }
+                return node;
+            }
+            if (parent instanceof AST_Binary) {
+                if (write_only && (!lazy_op.has(parent.operator) || parent.left === node)) {
+                    return find_stop(parent, level + 1, write_only);
+                }
+                return node;
+            }
+            if (parent instanceof AST_Call)
+                return node;
+            if (parent instanceof AST_Case)
+                return node;
+            if (parent instanceof AST_Conditional) {
+                if (write_only && parent.condition === node) {
+                    return find_stop(parent, level + 1, write_only);
+                }
+                return node;
+            }
+            if (parent instanceof AST_Definitions) {
+                return find_stop(parent, level + 1, true);
+            }
+            if (parent instanceof AST_Exit) {
+                return write_only ? find_stop(parent, level + 1, write_only) : node;
+            }
+            if (parent instanceof AST_If) {
+                if (write_only && parent.condition === node) {
+                    return find_stop(parent, level + 1, write_only);
+                }
+                return node;
+            }
+            if (parent instanceof AST_IterationStatement)
+                return node;
+            if (parent instanceof AST_Sequence) {
+                return find_stop(parent, level + 1, parent.tail_node() !== node);
+            }
+            if (parent instanceof AST_SimpleStatement) {
+                return find_stop(parent, level + 1, true);
+            }
+            if (parent instanceof AST_Switch)
+                return node;
+            if (parent instanceof AST_VarDef)
+                return node;
+            return null;
+        }
+
+        function mangleable_var(var_def) {
+            var value = var_def.value;
+            if (!(value instanceof AST_SymbolRef))
+                return;
+            if (value.name == "arguments")
+                return;
+            var def = value.definition();
+            if (def.undeclared)
+                return;
+            return value_def = def;
+        }
+
+        function get_lhs(expr) {
+            if (expr instanceof AST_Assign && expr.logical) {
+                return false;
+            } else if (expr instanceof AST_VarDef && expr.name instanceof AST_SymbolDeclaration) {
+                var def = expr.name.definition();
+                if (!member(expr.name, def.orig))
+                    return;
+                var referenced = def.references.length - def.replaced;
+                if (!referenced)
+                    return;
+                var declared = def.orig.length - def.eliminated;
+                if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)
+                    || (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) {
+                    return make_node(AST_SymbolRef, expr.name, expr.name);
+                }
+            } else {
+                const lhs = expr instanceof AST_Assign
+                    ? expr.left
+                    : expr.expression;
+                return !is_ref_of(lhs, AST_SymbolConst)
+                    && !is_ref_of(lhs, AST_SymbolLet)
+                    && !is_ref_of(lhs, AST_SymbolUsing)
+                    && lhs;
+            }
+        }
+
+        function get_rvalue(expr) {
+            if (expr instanceof AST_Assign) {
+                return expr.right;
+            } else {
+                return expr.value;
+            }
+        }
+
+        function get_lvalues(expr) {
+            var lvalues = new Map();
+            if (expr instanceof AST_Unary)
+                return lvalues;
+            var tw = new TreeWalker(function (node) {
+                var sym = node;
+                while (sym instanceof AST_PropAccess)
+                    sym = sym.expression;
+                if (sym instanceof AST_SymbolRef) {
+                    const prev = lvalues.get(sym.name);
+                    if (!prev || !prev.modified) {
+                        lvalues.set(sym.name, {
+                            def: sym.definition(),
+                            modified: is_modified(compressor, tw, node, node, 0)
+                        });
+                    }
+                }
+            });
+            get_rvalue(expr).walk(tw);
+            return lvalues;
+        }
+
+        function remove_candidate(expr) {
+            if (expr.name instanceof AST_SymbolFunarg) {
+                var iife = compressor.parent(), argnames = compressor.self().argnames;
+                var index = argnames.indexOf(expr.name);
+                if (index < 0) {
+                    iife.args.length = Math.min(iife.args.length, argnames.length - 1);
+                } else {
+                    var args = iife.args;
+                    if (args[index])
+                        args[index] = make_node(AST_Number, args[index], {
+                            value: 0
+                        });
+                }
+                return true;
+            }
+            var found = false;
+            return statements[stat_index].transform(new TreeTransformer(function (node, descend, in_list) {
+                if (found)
+                    return node;
+                if (node === expr || node.body === expr) {
+                    found = true;
+                    if (node instanceof AST_VarDef) {
+                        node.value = node.name instanceof AST_SymbolConst
+                            ? make_void_0(node.value) // `const` always needs value.
+                            : null;
+                        return node;
+                    }
+                    return in_list ? MAP.skip : null;
+                }
+            }, function (node) {
+                if (node instanceof AST_Sequence)
+                    switch (node.expressions.length) {
+                        case 0: return null;
+                        case 1: return node.expressions[0];
+                    }
+            }));
+        }
+
+        function is_lhs_local(lhs) {
+            while (lhs instanceof AST_PropAccess)
+                lhs = lhs.expression;
+            return lhs instanceof AST_SymbolRef
+                && lhs.definition().scope.get_defun_scope() === defun_scope
+                && !(in_loop
+                    && (lvalues.has(lhs.name)
+                        || candidate instanceof AST_Unary
+                        || (candidate instanceof AST_Assign
+                            && !candidate.logical
+                            && candidate.operator != "=")));
+        }
+
+        function value_has_side_effects(expr) {
+            if (expr instanceof AST_Unary)
+                return unary_side_effects.has(expr.operator);
+            return get_rvalue(expr).has_side_effects(compressor);
+        }
+
+        function replace_all_symbols() {
+            if (side_effects)
+                return false;
+            if (value_def)
+                return true;
+            if (lhs instanceof AST_SymbolRef) {
+                var def = lhs.definition();
+                if (def.references.length - def.replaced == (candidate instanceof AST_VarDef ? 1 : 2)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        function may_modify(sym) {
+            if (!sym.definition)
+                return true; // AST_Destructuring
+            var def = sym.definition();
+            if (def.orig.length == 1 && def.orig[0] instanceof AST_SymbolDefun)
+                return false;
+            if (def.scope.get_defun_scope() !== defun_scope)
+                return true;
+            return def.references.some((ref) =>
+                ref.scope.get_defun_scope() !== defun_scope
+            );
+        }
+
+        function side_effects_external(node, lhs) {
+            if (node instanceof AST_Assign)
+                return side_effects_external(node.left, true);
+            if (node instanceof AST_Unary)
+                return side_effects_external(node.expression, true);
+            if (node instanceof AST_VarDef)
+                return node.value && side_effects_external(node.value);
+            if (lhs) {
+                if (node instanceof AST_Dot)
+                    return side_effects_external(node.expression, true);
+                if (node instanceof AST_Sub)
+                    return side_effects_external(node.expression, true);
+                if (node instanceof AST_SymbolRef)
+                    return node.definition().scope.get_defun_scope() !== defun_scope;
+            }
+            return false;
+        }
+
+        /**
+         * Will any of the pulled-in lvalues shadow a variable in newScope or parents?
+         * similar to scope_encloses_variables_in_this_scope */
+        function shadows(my_scope, lvalues) {
+            for (const { def } of lvalues.values()) {
+                const looked_up = my_scope.find_variable(def.name);
+                if (looked_up) {
+                    if (looked_up === def) continue;
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    function eliminate_spurious_blocks(statements) {
+        var seen_dirs = [];
+        for (var i = 0; i < statements.length;) {
+            var stat = statements[i];
+            if (stat instanceof AST_BlockStatement && stat.body.every(can_be_evicted_from_block)) {
+                CHANGED = true;
+                eliminate_spurious_blocks(stat.body);
+                statements.splice(i, 1, ...stat.body);
+                i += stat.body.length;
+            } else if (stat instanceof AST_EmptyStatement) {
+                CHANGED = true;
+                statements.splice(i, 1);
+            } else if (stat instanceof AST_Directive) {
+                if (seen_dirs.indexOf(stat.value) < 0) {
+                    i++;
+                    seen_dirs.push(stat.value);
+                } else {
+                    CHANGED = true;
+                    statements.splice(i, 1);
+                }
+            } else
+                i++;
+        }
+    }
+
+    function handle_if_return(statements, compressor) {
+        var self = compressor.self();
+        var multiple_if_returns = has_multiple_if_returns(statements);
+        var in_lambda = self instanceof AST_Lambda;
+        // Prevent extremely deep nesting
+        // https://github.com/terser/terser/issues/1432
+        // https://github.com/webpack/webpack/issues/17548
+        const iteration_start = Math.min(statements.length, 500);
+        for (var i = iteration_start; --i >= 0;) {
+            var stat = statements[i];
+            var j = next_index(i);
+            var next = statements[j];
+
+            if (in_lambda && !next && stat instanceof AST_Return) {
+                if (!stat.value) {
+                    CHANGED = true;
+                    statements.splice(i, 1);
+                    continue;
+                }
+                if (stat.value instanceof AST_UnaryPrefix && stat.value.operator == "void") {
+                    CHANGED = true;
+                    statements[i] = make_node(AST_SimpleStatement, stat, {
+                        body: stat.value.expression
+                    });
+                    continue;
+                }
+            }
+
+            if (stat instanceof AST_If) {
+                let ab, new_else;
+
+                ab = aborts(stat.body);
+                if (
+                    can_merge_flow(ab)
+                    && (new_else = as_statement_array_with_return(stat.body, ab))
+                ) {
+                    if (ab.label) {
+                        remove(ab.label.thedef.references, ab);
+                    }
+                    CHANGED = true;
+                    stat = stat.clone();
+                    stat.condition = stat.condition.negate(compressor);
+                    stat.body = make_node(AST_BlockStatement, stat, {
+                        body: as_statement_array(stat.alternative).concat(extract_defuns())
+                    });
+                    stat.alternative = make_node(AST_BlockStatement, stat, {
+                        body: new_else
+                    });
+                    statements[i] = stat.transform(compressor);
+                    continue;
+                }
+
+                ab = aborts(stat.alternative);
+                if (
+                    can_merge_flow(ab)
+                    && (new_else = as_statement_array_with_return(stat.alternative, ab))
+                ) {
+                    if (ab.label) {
+                        remove(ab.label.thedef.references, ab);
+                    }
+                    CHANGED = true;
+                    stat = stat.clone();
+                    stat.body = make_node(AST_BlockStatement, stat.body, {
+                        body: as_statement_array(stat.body).concat(extract_defuns())
+                    });
+                    stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
+                        body: new_else
+                    });
+                    statements[i] = stat.transform(compressor);
+                    continue;
+                }
+            }
+
+            if (stat instanceof AST_If && stat.body instanceof AST_Return) {
+                var value = stat.body.value;
+                //---
+                // pretty silly case, but:
+                // if (foo()) return; return; ==> foo(); return;
+                if (!value && !stat.alternative
+                    && (in_lambda && !next || next instanceof AST_Return && !next.value)) {
+                    CHANGED = true;
+                    statements[i] = make_node(AST_SimpleStatement, stat.condition, {
+                        body: stat.condition
+                    });
+                    continue;
+                }
+                //---
+                // if (foo()) return x; return y; ==> return foo() ? x : y;
+                if (value && !stat.alternative && next instanceof AST_Return && next.value) {
+                    CHANGED = true;
+                    stat = stat.clone();
+                    stat.alternative = next;
+                    statements[i] = stat.transform(compressor);
+                    statements.splice(j, 1);
+                    continue;
+                }
+                //---
+                // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
+                if (value && !stat.alternative
+                    && (!next && in_lambda && multiple_if_returns
+                        || next instanceof AST_Return)) {
+                    CHANGED = true;
+                    stat = stat.clone();
+                    stat.alternative = next || make_node(AST_Return, stat, {
+                        value: null
+                    });
+                    statements[i] = stat.transform(compressor);
+                    if (next)
+                        statements.splice(j, 1);
+                    continue;
+                }
+                //---
+                // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e;
+                //
+                // if sequences is not enabled, this can lead to an endless loop (issue #866).
+                // however, with sequences on this helps producing slightly better output for
+                // the example code.
+                var prev = statements[prev_index(i)];
+                if (compressor.option("sequences") && in_lambda && !stat.alternative
+                    && prev instanceof AST_If && prev.body instanceof AST_Return
+                    && next_index(j) == statements.length && next instanceof AST_SimpleStatement) {
+                    CHANGED = true;
+                    stat = stat.clone();
+                    stat.alternative = make_node(AST_BlockStatement, next, {
+                        body: [
+                            next,
+                            make_node(AST_Return, next, {
+                                value: null
+                            })
+                        ]
+                    });
+                    statements[i] = stat.transform(compressor);
+                    statements.splice(j, 1);
+                    continue;
+                }
+            }
+        }
+
+        function has_multiple_if_returns(statements) {
+            var n = 0;
+            for (var i = statements.length; --i >= 0;) {
+                var stat = statements[i];
+                if (stat instanceof AST_If && stat.body instanceof AST_Return) {
+                    if (++n > 1)
+                        return true;
+                }
+            }
+            return false;
+        }
+
+        function is_return_void(value) {
+            return !value || value instanceof AST_UnaryPrefix && value.operator == "void";
+        }
+
+        function can_merge_flow(ab) {
+            if (!ab)
+                return false;
+            for (var j = i + 1, len = statements.length; j < len; j++) {
+                var stat = statements[j];
+                if (stat instanceof AST_DefinitionsLike && !(stat instanceof AST_Var))
+                    return false;
+            }
+            var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
+            return ab instanceof AST_Return && in_lambda && is_return_void(ab.value)
+                || ab instanceof AST_Continue && self === loop_body(lct)
+                || ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct;
+        }
+
+        function extract_defuns() {
+            var tail = statements.slice(i + 1);
+            statements.length = i + 1;
+            return tail.filter(function (stat) {
+                if (stat instanceof AST_Defun) {
+                    statements.push(stat);
+                    return false;
+                }
+                return true;
+            });
+        }
+
+        function as_statement_array_with_return(node, ab) {
+            var body = as_statement_array(node);
+            if (ab !== body[body.length - 1]) {
+                return undefined;
+            }
+            body = body.slice(0, -1);
+            if (!body.every(stat => can_be_evicted_from_block(stat))) {
+                return undefined;
+            }
+            if (ab.value) {
+                body.push(make_node(AST_SimpleStatement, ab.value, {
+                    body: ab.value.expression
+                }));
+            }
+            return body;
+        }
+
+        function next_index(i) {
+            for (var j = i + 1, len = statements.length; j < len; j++) {
+                var stat = statements[j];
+                if (!(stat instanceof AST_Var && declarations_only(stat))) {
+                    break;
+                }
+            }
+            return j;
+        }
+
+        function prev_index(i) {
+            for (var j = i; --j >= 0;) {
+                var stat = statements[j];
+                if (!(stat instanceof AST_Var && declarations_only(stat))) {
+                    break;
+                }
+            }
+            return j;
+        }
+    }
+
+    function eliminate_dead_code(statements, compressor) {
+        var has_quit;
+        var self = compressor.self();
+        for (var i = 0, n = 0, len = statements.length; i < len; i++) {
+            var stat = statements[i];
+            if (stat instanceof AST_LoopControl) {
+                var lct = compressor.loopcontrol_target(stat);
+                if (stat instanceof AST_Break
+                    && !(lct instanceof AST_IterationStatement)
+                    && loop_body(lct) === self
+                    || stat instanceof AST_Continue
+                    && loop_body(lct) === self) {
+                    if (stat.label) {
+                        remove(stat.label.thedef.references, stat);
+                    }
+                } else {
+                    statements[n++] = stat;
+                }
+            } else {
+                statements[n++] = stat;
+            }
+            if (aborts(stat)) {
+                has_quit = statements.slice(i + 1);
+                break;
+            }
+        }
+        statements.length = n;
+        CHANGED = n != len;
+        if (has_quit)
+            has_quit.forEach(function (stat) {
+                extract_from_unreachable_code(compressor, stat, statements);
+            });
+    }
+
+    function declarations_only(node) {
+        return node.definitions.every((var_def) => !var_def.value);
+    }
+
+    function sequencesize(statements, compressor) {
+        if (statements.length < 2)
+            return;
+        var seq = [], n = 0;
+        function push_seq() {
+            if (!seq.length)
+                return;
+            var body = make_sequence(seq[0], seq);
+            statements[n++] = make_node(AST_SimpleStatement, body, { body: body });
+            seq = [];
+        }
+        for (var i = 0, len = statements.length; i < len; i++) {
+            var stat = statements[i];
+            if (stat instanceof AST_SimpleStatement) {
+                if (seq.length >= compressor.sequences_limit)
+                    push_seq();
+                var body = stat.body;
+                if (seq.length > 0)
+                    body = body.drop_side_effect_free(compressor);
+                if (body)
+                    merge_sequence(seq, body);
+            } else if (stat instanceof AST_Definitions && declarations_only(stat)
+                || stat instanceof AST_Defun) {
+                statements[n++] = stat;
+            } else {
+                push_seq();
+                statements[n++] = stat;
+            }
+        }
+        push_seq();
+        statements.length = n;
+        if (n != len)
+            CHANGED = true;
+    }
+
+    function to_simple_statement(block, decls) {
+        if (!(block instanceof AST_BlockStatement))
+            return block;
+        var stat = null;
+        for (var i = 0, len = block.body.length; i < len; i++) {
+            var line = block.body[i];
+            if (line instanceof AST_Var && declarations_only(line)) {
+                decls.push(line);
+            } else if (stat || line instanceof AST_DefinitionsLike && !(line instanceof AST_Var)) {
+                return false;
+            } else {
+                stat = line;
+            }
+        }
+        return stat;
+    }
+
+    function sequencesize_2(statements, compressor) {
+        function cons_seq(right) {
+            n--;
+            CHANGED = true;
+            var left = prev.body;
+            return make_sequence(left, [left, right]).transform(compressor);
+        }
+        var n = 0, prev;
+        for (var i = 0; i < statements.length; i++) {
+            var stat = statements[i];
+            if (prev) {
+                if (stat instanceof AST_Exit) {
+                    stat.value = cons_seq(stat.value || make_void_0(stat).transform(compressor));
+                } else if (stat instanceof AST_For) {
+                    if (!(stat.init instanceof AST_DefinitionsLike)) {
+                        const abort = walk(prev.body, node => {
+                            if (node instanceof AST_Scope)
+                                return true;
+                            if (node instanceof AST_Binary
+                                && node.operator === "in") {
+                                return walk_abort;
+                            }
+                        });
+                        if (!abort) {
+                            if (stat.init)
+                                stat.init = cons_seq(stat.init);
+                            else {
+                                stat.init = prev.body;
+                                n--;
+                                CHANGED = true;
+                            }
+                        }
+                    }
+                } else if (stat instanceof AST_ForIn) {
+                    if (!(stat.init instanceof AST_DefinitionsLike) || stat.init instanceof AST_Var) {
+                        stat.object = cons_seq(stat.object);
+                    }
+                } else if (stat instanceof AST_If) {
+                    stat.condition = cons_seq(stat.condition);
+                } else if (stat instanceof AST_Switch) {
+                    stat.expression = cons_seq(stat.expression);
+                } else if (stat instanceof AST_With) {
+                    stat.expression = cons_seq(stat.expression);
+                }
+            }
+            if (compressor.option("conditionals") && stat instanceof AST_If) {
+                var decls = [];
+                var body = to_simple_statement(stat.body, decls);
+                var alt = to_simple_statement(stat.alternative, decls);
+                if (body !== false && alt !== false && decls.length > 0) {
+                    var len = decls.length;
+                    decls.push(make_node(AST_If, stat, {
+                        condition: stat.condition,
+                        body: body || make_node(AST_EmptyStatement, stat.body),
+                        alternative: alt
+                    }));
+                    decls.unshift(n, 1);
+                    [].splice.apply(statements, decls);
+                    i += len;
+                    n += len + 1;
+                    prev = null;
+                    CHANGED = true;
+                    continue;
+                }
+            }
+            statements[n++] = stat;
+            prev = stat instanceof AST_SimpleStatement ? stat : null;
+        }
+        statements.length = n;
+    }
+
+    function join_object_assignments(defn, body) {
+        if (!(defn instanceof AST_Definitions))
+            return;
+        var def = defn.definitions[defn.definitions.length - 1];
+        if (!(def.value instanceof AST_Object))
+            return;
+        var exprs;
+        if (body instanceof AST_Assign && !body.logical) {
+            exprs = [body];
+        } else if (body instanceof AST_Sequence) {
+            exprs = body.expressions.slice();
+        }
+        if (!exprs)
+            return;
+        var trimmed = false;
+        do {
+            var node = exprs[0];
+            if (!(node instanceof AST_Assign))
+                break;
+            if (node.operator != "=")
+                break;
+            if (!(node.left instanceof AST_PropAccess))
+                break;
+            var sym = node.left.expression;
+            if (!(sym instanceof AST_SymbolRef))
+                break;
+            if (def.name.name != sym.name)
+                break;
+            if (!node.right.is_constant_expression(nearest_scope))
+                break;
+            var prop = node.left.property;
+            if (prop instanceof AST_Node) {
+                prop = prop.evaluate(compressor);
+            }
+            if (prop instanceof AST_Node)
+                break;
+            prop = "" + prop;
+            var diff = compressor.option("ecma") < 2015
+                && compressor.has_directive("use strict") ? function (node) {
+                    return node.key != prop && (node.key && node.key.name != prop);
+                } : function (node) {
+                    return node.key && node.key.name != prop;
+                };
+            if (!def.value.properties.every(diff))
+                break;
+            var p = def.value.properties.filter(function (p) { return p.key === prop; })[0];
+            if (!p) {
+                def.value.properties.push(make_node(AST_ObjectKeyVal, node, {
+                    key: prop,
+                    value: node.right
+                }));
+            } else {
+                p.value = new AST_Sequence({
+                    start: p.start,
+                    expressions: [p.value.clone(), node.right.clone()],
+                    end: p.end
+                });
+            }
+            exprs.shift();
+            trimmed = true;
+        } while (exprs.length);
+        return trimmed && exprs;
+    }
+
+    function join_consecutive_vars(statements) {
+        var defs;
+        for (var i = 0, j = -1, len = statements.length; i < len; i++) {
+            var stat = statements[i];
+            var prev = statements[j];
+            if (stat instanceof AST_Definitions) {
+                if (prev && prev.TYPE == stat.TYPE) {
+                    prev.definitions = prev.definitions.concat(stat.definitions);
+                    CHANGED = true;
+                } else if (defs && defs.TYPE == stat.TYPE && declarations_only(stat)) {
+                    defs.definitions = defs.definitions.concat(stat.definitions);
+                    CHANGED = true;
+                } else {
+                    statements[++j] = stat;
+                    defs = stat;
+                }
+            } else if (
+                stat instanceof AST_Using
+                && prev instanceof AST_Using
+                && prev.await === stat.await
+            ) {
+                prev.definitions = prev.definitions.concat(stat.definitions);
+            } else if (stat instanceof AST_Exit) {
+                stat.value = extract_object_assignments(stat.value);
+            } else if (stat instanceof AST_For) {
+                var exprs = join_object_assignments(prev, stat.init);
+                if (exprs) {
+                    CHANGED = true;
+                    stat.init = exprs.length ? make_sequence(stat.init, exprs) : null;
+                    statements[++j] = stat;
+                } else if (
+                    prev instanceof AST_Var
+                    && (!stat.init || stat.init.TYPE == prev.TYPE)
+                ) {
+                    if (stat.init) {
+                        prev.definitions = prev.definitions.concat(stat.init.definitions);
+                    }
+                    stat.init = prev;
+                    statements[j] = stat;
+                    CHANGED = true;
+                } else if (
+                    defs instanceof AST_Var
+                    && stat.init instanceof AST_Var
+                    && declarations_only(stat.init)
+                ) {
+                    defs.definitions = defs.definitions.concat(stat.init.definitions);
+                    stat.init = null;
+                    statements[++j] = stat;
+                    CHANGED = true;
+                } else {
+                    statements[++j] = stat;
+                }
+            } else if (stat instanceof AST_ForIn) {
+                stat.object = extract_object_assignments(stat.object);
+            } else if (stat instanceof AST_If) {
+                stat.condition = extract_object_assignments(stat.condition);
+            } else if (stat instanceof AST_SimpleStatement) {
+                var exprs = join_object_assignments(prev, stat.body);
+                if (exprs) {
+                    CHANGED = true;
+                    if (!exprs.length)
+                        continue;
+                    stat.body = make_sequence(stat.body, exprs);
+                }
+                statements[++j] = stat;
+            } else if (stat instanceof AST_Switch) {
+                stat.expression = extract_object_assignments(stat.expression);
+            } else if (stat instanceof AST_With) {
+                stat.expression = extract_object_assignments(stat.expression);
+            } else {
+                statements[++j] = stat;
+            }
+        }
+        statements.length = j + 1;
+
+        function extract_object_assignments(value) {
+            statements[++j] = stat;
+            var exprs = join_object_assignments(prev, value);
+            if (exprs) {
+                CHANGED = true;
+                if (exprs.length) {
+                    return make_sequence(value, exprs);
+                } else if (value instanceof AST_Sequence) {
+                    return value.tail_node().left;
+                } else {
+                    return value.left;
+                }
+            }
+            return value;
+        }
+    }
+}
Index: frontend/node_modules/terser/lib/equivalent-to.js
===================================================================
--- frontend/node_modules/terser/lib/equivalent-to.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/equivalent-to.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,314 @@
+import {
+    AST_Array,
+    AST_Atom,
+    AST_Await,
+    AST_BigInt,
+    AST_Binary,
+    AST_Block,
+    AST_Call,
+    AST_Catch,
+    AST_Chain,
+    AST_Class,
+    AST_ClassProperty,
+    AST_ClassPrivateProperty,
+    AST_ConciseMethod,
+    AST_Conditional,
+    AST_Debugger,
+    AST_DefinitionsLike,
+    AST_Destructuring,
+    AST_Directive,
+    AST_Do,
+    AST_Dot,
+    AST_DotHash,
+    AST_EmptyStatement,
+    AST_Expansion,
+    AST_Export,
+    AST_Finally,
+    AST_For,
+    AST_ForIn,
+    AST_ForOf,
+    AST_If,
+    AST_Import,
+    AST_DynamicImport,
+    AST_ImportMeta,
+    AST_Jump,
+    AST_LabeledStatement,
+    AST_Lambda,
+    AST_LoopControl,
+    AST_NameMapping,
+    AST_NewTarget,
+    AST_Node,
+    AST_Number,
+    AST_Object,
+    AST_ObjectGetter,
+    AST_ObjectKeyVal,
+    AST_ObjectProperty,
+    AST_ObjectSetter,
+    AST_PrefixedTemplateString,
+    AST_PrivateIn,
+    AST_PrivateMethod,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_String,
+    AST_Super,
+    AST_Switch,
+    AST_SwitchBranch,
+    AST_Symbol,
+    AST_TemplateSegment,
+    AST_TemplateString,
+    AST_This,
+    AST_Toplevel,
+    AST_Try,
+    AST_Unary,
+    AST_VarDefLike,
+    AST_While,
+    AST_With,
+    AST_Yield
+} from "./ast.js";
+
+const shallow_cmp = (node1, node2) => {
+    return (
+        node1 === null && node2 === null
+        || node1.TYPE === node2.TYPE && node1.shallow_cmp(node2)
+    );
+};
+
+export const equivalent_to = (tree1, tree2) => {
+    if (!shallow_cmp(tree1, tree2)) return false;
+    const walk_1_state = [tree1];
+    const walk_2_state = [tree2];
+
+    const walk_1_push = walk_1_state.push.bind(walk_1_state);
+    const walk_2_push = walk_2_state.push.bind(walk_2_state);
+
+    while (walk_1_state.length && walk_2_state.length) {
+        const node_1 = walk_1_state.pop();
+        const node_2 = walk_2_state.pop();
+
+        if (!shallow_cmp(node_1, node_2)) return false;
+
+        node_1._children_backwards(walk_1_push);
+        node_2._children_backwards(walk_2_push);
+
+        if (walk_1_state.length !== walk_2_state.length) {
+            // Different number of children
+            return false;
+        }
+    }
+
+    return walk_1_state.length == 0 && walk_2_state.length == 0;
+};
+
+const pass_through = () => true;
+
+AST_Node.prototype.shallow_cmp = function () {
+    throw new Error("did not find a shallow_cmp function for " + this.constructor.name);
+};
+
+AST_Debugger.prototype.shallow_cmp = pass_through;
+
+AST_Directive.prototype.shallow_cmp = function(other) {
+    return this.value === other.value;
+};
+
+AST_SimpleStatement.prototype.shallow_cmp = pass_through;
+
+AST_Block.prototype.shallow_cmp = pass_through;
+
+AST_EmptyStatement.prototype.shallow_cmp = pass_through;
+
+AST_LabeledStatement.prototype.shallow_cmp = function(other) {
+    return this.label.name === other.label.name;
+};
+
+AST_Do.prototype.shallow_cmp = pass_through;
+
+AST_While.prototype.shallow_cmp = pass_through;
+
+AST_For.prototype.shallow_cmp = function(other) {
+    return (this.init == null ? other.init == null : this.init === other.init) && (this.condition == null ? other.condition == null : this.condition === other.condition) && (this.step == null ? other.step == null : this.step === other.step);
+};
+
+AST_ForIn.prototype.shallow_cmp = pass_through;
+
+AST_ForOf.prototype.shallow_cmp = pass_through;
+
+AST_With.prototype.shallow_cmp = pass_through;
+
+AST_Toplevel.prototype.shallow_cmp = pass_through;
+
+AST_Expansion.prototype.shallow_cmp = pass_through;
+
+AST_Lambda.prototype.shallow_cmp = function(other) {
+    return this.is_generator === other.is_generator && this.async === other.async;
+};
+
+AST_Destructuring.prototype.shallow_cmp = function(other) {
+    return this.is_array === other.is_array;
+};
+
+AST_PrefixedTemplateString.prototype.shallow_cmp = pass_through;
+
+AST_TemplateString.prototype.shallow_cmp = pass_through;
+
+AST_TemplateSegment.prototype.shallow_cmp = function(other) {
+    return this.value === other.value;
+};
+
+AST_Jump.prototype.shallow_cmp = pass_through;
+
+AST_LoopControl.prototype.shallow_cmp = pass_through;
+
+AST_Await.prototype.shallow_cmp = pass_through;
+
+AST_Yield.prototype.shallow_cmp = function(other) {
+    return this.is_star === other.is_star;
+};
+
+AST_If.prototype.shallow_cmp = function(other) {
+    return this.alternative == null ? other.alternative == null : this.alternative === other.alternative;
+};
+
+AST_Switch.prototype.shallow_cmp = pass_through;
+
+AST_SwitchBranch.prototype.shallow_cmp = pass_through;
+
+AST_Try.prototype.shallow_cmp = function(other) {
+    return (this.body === other.body) && (this.bcatch == null ? other.bcatch == null : this.bcatch === other.bcatch) && (this.bfinally == null ? other.bfinally == null : this.bfinally === other.bfinally);
+};
+
+AST_Catch.prototype.shallow_cmp = function(other) {
+    return this.argname == null ? other.argname == null : this.argname === other.argname;
+};
+
+AST_Finally.prototype.shallow_cmp = pass_through;
+
+AST_DefinitionsLike.prototype.shallow_cmp = pass_through;
+
+AST_VarDefLike.prototype.shallow_cmp = function(other) {
+    return this.value == null ? other.value == null : this.value === other.value;
+};
+
+AST_NameMapping.prototype.shallow_cmp = pass_through;
+
+AST_Import.prototype.shallow_cmp = function(other) {
+    return (this.imported_name || null) === (other.imported_name || null)
+        && (this.imported_names || null) === (other.imported_names || null)
+        && (this.attributes || null) === (other.attributes || null)
+        && (this.phase || null) === (other.phase || null);
+};
+
+AST_ImportMeta.prototype.shallow_cmp = pass_through;
+
+AST_DynamicImport.prototype.shallow_cmp = function(other) {
+    return (this.phase || null) === (other.phase || null) && this.args.length === other.args.length;
+};
+
+AST_Export.prototype.shallow_cmp = function(other) {
+    return (this.exported_definition == null ? other.exported_definition == null : this.exported_definition === other.exported_definition) && (this.exported_value == null ? other.exported_value == null : this.exported_value === other.exported_value) && (this.exported_names == null ? other.exported_names == null : this.exported_names === other.exported_names) && (this.attributes == null ? other.attributes == null : this.attributes === other.attributes) && this.module_name === other.module_name && this.is_default === other.is_default;
+};
+
+AST_Call.prototype.shallow_cmp = pass_through;
+
+AST_Sequence.prototype.shallow_cmp = pass_through;
+
+AST_PropAccess.prototype.shallow_cmp = pass_through;
+
+AST_Chain.prototype.shallow_cmp = pass_through;
+
+AST_Dot.prototype.shallow_cmp = function(other) {
+    return (
+        this.property === other.property
+        && !!this.quote === !!other.quote
+    );
+};
+
+AST_DotHash.prototype.shallow_cmp = function(other) {
+    return this.property === other.property;
+};
+
+AST_Unary.prototype.shallow_cmp = function(other) {
+    return this.operator === other.operator;
+};
+
+AST_Binary.prototype.shallow_cmp = function(other) {
+    return this.operator === other.operator;
+};
+
+AST_PrivateIn.prototype.shallow_cmp = pass_through;
+
+AST_Conditional.prototype.shallow_cmp = pass_through;
+
+AST_Array.prototype.shallow_cmp = pass_through;
+
+AST_Object.prototype.shallow_cmp = pass_through;
+
+AST_ObjectProperty.prototype.shallow_cmp = pass_through;
+
+AST_ObjectKeyVal.prototype.shallow_cmp = function(other) {
+    return this.key === other.key && this.quote === other.quote;
+};
+
+AST_ObjectSetter.prototype.shallow_cmp = function(other) {
+    return this.static === other.static;
+};
+
+AST_ObjectGetter.prototype.shallow_cmp = function(other) {
+    return this.static === other.static;
+};
+
+AST_ConciseMethod.prototype.shallow_cmp = function(other) {
+    return this.static === other.static;
+};
+
+AST_PrivateMethod.prototype.shallow_cmp = function(other) {
+    return this.static === other.static;
+};
+
+AST_Class.prototype.shallow_cmp = function(other) {
+    return (this.name == null ? other.name == null : this.name === other.name) && (this.extends == null ? other.extends == null : this.extends === other.extends);
+};
+
+AST_ClassProperty.prototype.shallow_cmp = function(other) {
+    return this.static === other.static
+        && (typeof this.key === "string"
+            ? this.key === other.key
+            : true /* AST_Node handled elsewhere */);
+};
+
+AST_ClassPrivateProperty.prototype.shallow_cmp = function(other) {
+    return this.static === other.static;
+};
+
+AST_Symbol.prototype.shallow_cmp = function(other) {
+    return this.name === other.name;
+};
+
+AST_NewTarget.prototype.shallow_cmp = pass_through;
+
+AST_This.prototype.shallow_cmp = pass_through;
+
+AST_Super.prototype.shallow_cmp = pass_through;
+
+AST_String.prototype.shallow_cmp = function(other) {
+    return this.value === other.value;
+};
+
+AST_Number.prototype.shallow_cmp = function(other) {
+    return this.value === other.value;
+};
+
+AST_BigInt.prototype.shallow_cmp = function(other) {
+    return this.value === other.value;
+};
+
+AST_RegExp.prototype.shallow_cmp = function (other) {
+    return (
+        this.value.flags === other.value.flags
+        && this.value.source === other.value.source
+    );
+};
+
+AST_Atom.prototype.shallow_cmp = pass_through;
Index: frontend/node_modules/terser/lib/minify.js
===================================================================
--- frontend/node_modules/terser/lib/minify.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/minify.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,412 @@
+"use strict";
+/* eslint-env browser, es6, node */
+
+import {
+    defaults,
+    map_from_object,
+    map_to_object,
+    HOP,
+} from "./utils/index.js";
+import { AST_Toplevel, AST_Node, walk, AST_Scope } from "./ast.js";
+import { parse } from "./parse.js";
+import { OutputStream } from "./output.js";
+import { Compressor } from "./compress/index.js";
+import { base54 } from "./scope.js";
+import { SourceMap } from "./sourcemap.js";
+import {
+    mangle_properties,
+    mangle_private_properties,
+    reserve_quoted_keys,
+    find_annotated_props,
+} from "./propmangle.js";
+
+// to/from base64 functions
+// Prefer built-in Buffer, if available, then use hack
+// https://developer.mozilla.org/en-US/docs/Glossary/Base64#The_Unicode_Problem
+var to_ascii = typeof Buffer !== "undefined"
+    ? (b64) => Buffer.from(b64, "base64").toString()
+    : (b64) => decodeURIComponent(escape(atob(b64)));
+var to_base64 = typeof Buffer !== "undefined"
+    ? (str) => Buffer.from(str).toString("base64")
+    : (str) => btoa(unescape(encodeURIComponent(str)));
+
+function read_source_map(code) {
+    var match = /(?:^|[^.])\/\/# sourceMappingURL=data:application\/json(;[\w=-]*)?;base64,([+/0-9A-Za-z]*=*)\s*$/.exec(code);
+    if (!match) {
+        console.warn("inline source map not found");
+        return null;
+    }
+    return to_ascii(match[2]);
+}
+
+function set_shorthand(name, options, keys) {
+    if (options[name]) {
+        keys.forEach(function(key) {
+            if (options[key]) {
+                if (typeof options[key] != "object") options[key] = {};
+                if (!(name in options[key])) options[key][name] = options[name];
+            }
+        });
+    }
+}
+
+function init_cache(cache) {
+    if (!cache) return;
+    if (!("props" in cache)) {
+        cache.props = new Map();
+    } else if (!(cache.props instanceof Map)) {
+        cache.props = map_from_object(cache.props);
+    }
+}
+
+function cache_to_json(cache) {
+    return {
+        props: map_to_object(cache.props)
+    };
+}
+
+function log_input(files, options, fs, debug_folder) {
+    if (!(fs && fs.writeFileSync && fs.mkdirSync)) {
+        return;
+    }
+
+    try {
+        fs.mkdirSync(debug_folder);
+    } catch (e) {
+        if (e.code !== "EEXIST") throw e;
+    }
+
+    const log_path = `${debug_folder}/terser-debug-${(Math.random() * 9999999) | 0}.log`;
+
+    options = options || {};
+
+    const options_str = JSON.stringify(options, (_key, thing) => {
+        if (typeof thing === "function") return "[Function " + thing.toString() + "]";
+        if (thing instanceof RegExp) return "[RegExp " + thing.toString() + "]";
+        return thing;
+    }, 4);
+
+    const files_str = (file) => {
+        if (typeof file === "object" && options.parse && options.parse.spidermonkey) {
+            return JSON.stringify(file, null, 2);
+        } else if (typeof file === "object") {
+            return Object.keys(file)
+                .map((key) => key + ": " + files_str(file[key]))
+                .join("\n\n");
+        } else if (typeof file === "string") {
+            return "```\n" + file + "\n```";
+        } else {
+            return file; // What do?
+        }
+    };
+
+    fs.writeFileSync(log_path, "Options: \n" + options_str + "\n\nInput files:\n\n" + files_str(files) + "\n");
+}
+
+function* minify_sync_or_async(files, options, _fs_module) {
+    if (
+        _fs_module
+        && typeof process === "object"
+        && process.env
+        && typeof process.env.TERSER_DEBUG_DIR === "string"
+    ) {
+        log_input(files, options, _fs_module, process.env.TERSER_DEBUG_DIR);
+    }
+
+    options = defaults(options, {
+        compress: {},
+        ecma: undefined,
+        enclose: false,
+        ie8: false,
+        keep_classnames: undefined,
+        keep_fnames: false,
+        mangle: {},
+        module: false,
+        nameCache: null,
+        output: null,
+        format: null,
+        parse: {},
+        rename: undefined,
+        safari10: false,
+        sourceMap: false,
+        spidermonkey: false,
+        timings: false,
+        toplevel: false,
+        warnings: false,
+        wrap: false,
+    }, true);
+
+    var timings = options.timings && {
+        start: Date.now()
+    };
+    if (options.keep_classnames === undefined) {
+        options.keep_classnames = options.keep_fnames;
+    }
+    if (options.rename === undefined) {
+        options.rename = options.compress && options.mangle;
+    }
+    if (options.output && options.format) {
+        throw new Error("Please only specify either output or format option, preferrably format.");
+    }
+    options.format = options.format || options.output || {};
+    set_shorthand("ecma", options, [ "parse", "compress", "format" ]);
+    set_shorthand("ie8", options, [ "compress", "mangle", "format" ]);
+    set_shorthand("keep_classnames", options, [ "compress", "mangle" ]);
+    set_shorthand("keep_fnames", options, [ "compress", "mangle" ]);
+    set_shorthand("module", options, [ "parse", "compress", "mangle" ]);
+    set_shorthand("safari10", options, [ "mangle", "format" ]);
+    set_shorthand("toplevel", options, [ "compress", "mangle" ]);
+    set_shorthand("warnings", options, [ "compress" ]); // legacy
+    var quoted_props;
+    if (options.mangle) {
+        options.mangle = defaults(options.mangle, {
+            cache: options.nameCache && (options.nameCache.vars || {}),
+            eval: false,
+            ie8: false,
+            keep_classnames: false,
+            keep_fnames: false,
+            module: false,
+            nth_identifier: base54,
+            properties: false,
+            reserved: [],
+            safari10: false,
+            toplevel: false,
+        }, true);
+        if (options.mangle.properties) {
+            if (typeof options.mangle.properties != "object") {
+                options.mangle.properties = {};
+            }
+            if (options.mangle.properties.keep_quoted) {
+                quoted_props = options.mangle.properties.reserved;
+                if (!Array.isArray(quoted_props)) quoted_props = [];
+                options.mangle.properties.reserved = quoted_props;
+            }
+            if (options.nameCache && !("cache" in options.mangle.properties)) {
+                options.mangle.properties.cache = options.nameCache.props || {};
+            }
+        }
+        init_cache(options.mangle.cache);
+        init_cache(options.mangle.properties.cache);
+    }
+    if (options.sourceMap) {
+        options.sourceMap = defaults(options.sourceMap, {
+            asObject: false,
+            content: null,
+            filename: null,
+            includeSources: false,
+            root: null,
+            url: null,
+        }, true);
+    }
+
+    // -- Parse phase --
+    if (timings) timings.parse = Date.now();
+    var toplevel;
+    if (files instanceof AST_Toplevel) {
+        toplevel = files;
+    } else {
+        if (typeof files == "string" || (options.parse.spidermonkey && !Array.isArray(files))) {
+            files = [ files ];
+        }
+        options.parse = options.parse || {};
+        options.parse.toplevel = null;
+
+        if (options.parse.spidermonkey) {
+            options.parse.toplevel = AST_Node.from_mozilla_ast(Object.keys(files).reduce(function(toplevel, name) {
+                if (!toplevel) return files[name];
+                toplevel.body = toplevel.body.concat(files[name].body);
+                return toplevel;
+            }, null));
+        } else {
+            delete options.parse.spidermonkey;
+
+            for (var name in files) if (HOP(files, name)) {
+                options.parse.filename = name;
+                options.parse.toplevel = parse(files[name], options.parse);
+                if (options.sourceMap && options.sourceMap.content == "inline") {
+                    if (Object.keys(files).length > 1)
+                        throw new Error("inline source map only works with singular input");
+                    options.sourceMap.content = read_source_map(files[name]);
+                }
+            }
+        }
+        if (options.parse.toplevel === null) {
+            throw new Error("no source file given");
+        }
+
+        toplevel = options.parse.toplevel;
+    }
+    if (quoted_props && options.mangle.properties.keep_quoted !== "strict") {
+        reserve_quoted_keys(toplevel, quoted_props);
+    }
+    var annotated_props;
+    if (options.mangle && options.mangle.properties) {
+        annotated_props = find_annotated_props(toplevel);
+    }
+    if (options.wrap) {
+        toplevel = toplevel.wrap_commonjs(options.wrap);
+    }
+    if (options.enclose) {
+        toplevel = toplevel.wrap_enclose(options.enclose);
+    }
+    if (timings) timings.rename = Date.now();
+    // disable rename on harmony due to expand_names bug in for-of loops
+    // https://github.com/mishoo/UglifyJS2/issues/2794
+    if (0 && options.rename) {
+        toplevel.figure_out_scope(options.mangle);
+        toplevel.expand_names(options.mangle);
+    }
+
+    // -- Compress phase --
+    if (timings) timings.compress = Date.now();
+    if (options.compress) {
+        toplevel = new Compressor(options.compress, {
+            mangle_options: options.mangle
+        }).compress(toplevel);
+    }
+
+    // -- Mangle phase --
+    if (timings) timings.scope = Date.now();
+    if (options.mangle) toplevel.figure_out_scope(options.mangle);
+    if (timings) timings.mangle = Date.now();
+    if (options.mangle) {
+        toplevel.compute_char_frequency(options.mangle);
+        toplevel.mangle_names(options.mangle);
+        toplevel = mangle_private_properties(toplevel, options.mangle);
+    }
+    if (timings) timings.properties = Date.now();
+    if (options.mangle && options.mangle.properties) {
+        toplevel = mangle_properties(toplevel, options.mangle.properties, annotated_props);
+    }
+
+    // Format phase
+    if (timings) timings.format = Date.now();
+    var result = {};
+    if (options.format.ast) {
+        result.ast = toplevel;
+    }
+    if (options.format.spidermonkey) {
+        result.ast = toplevel.to_mozilla_ast();
+    }
+    let format_options;
+    if (!HOP(options.format, "code") || options.format.code) {
+        // Make a shallow copy so that we can modify without mutating the user's input.
+        format_options = {...options.format};
+        if (!format_options.ast) {
+            // Destroy stuff to save RAM. (unless the deprecated `ast` option is on)
+            format_options._destroy_ast = true;
+
+            walk(toplevel, node => {
+                if (node instanceof AST_Scope) {
+                    node.variables = undefined;
+                    node.enclosed = undefined;
+                    node.parent_scope = undefined;
+                }
+                if (node.block_scope) {
+                    node.block_scope.variables = undefined;
+                    node.block_scope.enclosed = undefined;
+                    node.block_scope.parent_scope = undefined;
+                }
+            });
+        }
+
+        if (options.sourceMap) {
+            if (options.sourceMap.includeSources && files instanceof AST_Toplevel) {
+                throw new Error("original source content unavailable");
+            }
+            format_options.source_map = yield* SourceMap({
+                file: options.sourceMap.filename,
+                orig: options.sourceMap.content,
+                root: options.sourceMap.root,
+                files: options.sourceMap.includeSources ? files : null,
+            });
+        }
+        delete format_options.ast;
+        delete format_options.code;
+        delete format_options.spidermonkey;
+        var stream = OutputStream(format_options);
+        toplevel.print(stream);
+        result.code = stream.get();
+        if (options.sourceMap) {
+            Object.defineProperty(result, "map", {
+                configurable: true,
+                enumerable: true,
+                get() {
+                    const map = format_options.source_map.getEncoded();
+                    return (result.map = options.sourceMap.asObject ? map : JSON.stringify(map));
+                },
+                set(value) {
+                    Object.defineProperty(result, "map", {
+                        value,
+                        writable: true,
+                    });
+                }
+            });
+            result.decoded_map = format_options.source_map.getDecoded();
+            if (options.sourceMap.url == "inline") {
+                var sourceMap = typeof result.map === "object" ? JSON.stringify(result.map) : result.map;
+                result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(sourceMap);
+            } else if (options.sourceMap.url) {
+                result.code += "\n//# sourceMappingURL=" + options.sourceMap.url;
+            }
+        }
+    }
+    if (options.nameCache && options.mangle) {
+        if (options.mangle.cache) options.nameCache.vars = cache_to_json(options.mangle.cache);
+        if (options.mangle.properties && options.mangle.properties.cache) {
+            options.nameCache.props = cache_to_json(options.mangle.properties.cache);
+        }
+    }
+    if (format_options && format_options.source_map) {
+        format_options.source_map.destroy();
+    }
+    if (timings) {
+        timings.end = Date.now();
+        result.timings = {
+            parse: 1e-3 * (timings.rename - timings.parse),
+            rename: 1e-3 * (timings.compress - timings.rename),
+            compress: 1e-3 * (timings.scope - timings.compress),
+            scope: 1e-3 * (timings.mangle - timings.scope),
+            mangle: 1e-3 * (timings.properties - timings.mangle),
+            properties: 1e-3 * (timings.format - timings.properties),
+            format: 1e-3 * (timings.end - timings.format),
+            total: 1e-3 * (timings.end - timings.start)
+        };
+    }
+    return result;
+}
+
+async function minify(files, options, _fs_module) {
+    const gen = minify_sync_or_async(files, options, _fs_module);
+
+    let yielded;
+    let val;
+    do {
+        val = gen.next(await yielded);
+        yielded = val.value;
+    } while (!val.done);
+
+    return val.value;
+}
+
+function minify_sync(files, options, _fs_module) {
+    const gen = minify_sync_or_async(files, options, _fs_module);
+
+    let yielded;
+    let val;
+    do {
+        if (yielded && typeof yielded.then === "function") {
+            throw new Error("minify_sync cannot be used with the legacy source-map module");
+        }
+        val = gen.next(yielded);
+        yielded = val.value;
+    } while (!val.done);
+
+    return val.value;
+}
+
+export {
+  minify,
+  minify_sync,
+  to_ascii,
+};
Index: frontend/node_modules/terser/lib/mozilla-ast.js
===================================================================
--- frontend/node_modules/terser/lib/mozilla-ast.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/mozilla-ast.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2120 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+import { make_node } from "./utils/index.js";
+import {
+    AST_Accessor,
+    AST_Array,
+    AST_Arrow,
+    AST_Assign,
+    AST_Atom,
+    AST_Await,
+    AST_BigInt,
+    AST_Binary,
+    AST_Block,
+    AST_BlockStatement,
+    AST_Boolean,
+    AST_Break,
+    AST_Call,
+    AST_Case,
+    AST_Catch,
+    AST_Chain,
+    AST_Class,
+    AST_ClassStaticBlock,
+    AST_ClassExpression,
+    AST_ClassProperty,
+    AST_ClassPrivateProperty,
+    AST_ConciseMethod,
+    AST_Conditional,
+    AST_Const,
+    AST_Constant,
+    AST_Continue,
+    AST_Debugger,
+    AST_Default,
+    AST_DefaultAssign,
+    AST_DefClass,
+    AST_DefinitionsLike,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Directive,
+    AST_Do,
+    AST_Dot,
+    AST_DotHash,
+    AST_EmptyStatement,
+    AST_Expansion,
+    AST_Export,
+    AST_False,
+    AST_Finally,
+    AST_For,
+    AST_ForIn,
+    AST_ForOf,
+    AST_Function,
+    AST_Hole,
+    AST_If,
+    AST_DynamicImport,
+    AST_Import,
+    AST_ImportMeta,
+    AST_Label,
+    AST_LabeledStatement,
+    AST_LabelRef,
+    AST_Lambda,
+    AST_Let,
+    AST_NameMapping,
+    AST_New,
+    AST_NewTarget,
+    AST_Node,
+    AST_Null,
+    AST_Number,
+    AST_Object,
+    AST_ObjectGetter,
+    AST_ObjectKeyVal,
+    AST_ObjectProperty,
+    AST_ObjectSetter,
+    AST_PrefixedTemplateString,
+    AST_PrivateGetter,
+    AST_PrivateMethod,
+    AST_PrivateSetter,
+    AST_PrivateIn,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Return,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Statement,
+    AST_String,
+    AST_Sub,
+    AST_Super,
+    AST_Switch,
+    AST_SwitchBranch,
+    AST_Symbol,
+    AST_SymbolCatch,
+    AST_SymbolClass,
+    AST_SymbolClassProperty,
+    AST_SymbolPrivateProperty,
+    AST_SymbolConst,
+    AST_SymbolDefClass,
+    AST_SymbolDefun,
+    AST_SymbolExport,
+    AST_SymbolExportForeign,
+    AST_SymbolFunarg,
+    AST_SymbolImport,
+    AST_SymbolImportForeign,
+    AST_SymbolLambda,
+    AST_SymbolLet,
+    AST_SymbolMethod,
+    AST_SymbolRef,
+    AST_SymbolVar,
+    AST_SymbolUsing,
+    AST_TemplateSegment,
+    AST_TemplateString,
+    AST_This,
+    AST_Throw,
+    AST_Token,
+    AST_Toplevel,
+    AST_True,
+    AST_Try,
+    AST_TryBlock,
+    AST_Unary,
+    AST_UnaryPostfix,
+    AST_UnaryPrefix,
+    AST_Using,
+    AST_UsingDef,
+    AST_Var,
+    AST_VarDef,
+    AST_VarDefLike,
+    AST_While,
+    AST_With,
+    AST_Yield,
+} from "./ast.js";
+import { is_basic_identifier_string } from "./parse.js";
+
+(function() {
+
+    var normalize_directives = function(body) {
+        for (var i = 0; i < body.length; i++) {
+            if (body[i] instanceof AST_Statement && body[i].body instanceof AST_String) {
+                body[i] = new AST_Directive({
+                    start: body[i].start,
+                    end: body[i].end,
+                    quote: '"',
+                    value: body[i].body.value
+                });
+            } else {
+                return body;
+            }
+        }
+
+        return body;
+    };
+
+    function import_attributes_from_moz(attributes) {
+        if (attributes && attributes.length > 0) {
+            return new AST_Object({
+                start: my_start_token(attributes),
+                end: my_end_token(attributes),
+                properties: attributes.map((attr) =>
+                    new AST_ObjectKeyVal({
+                        start: my_start_token(attr),
+                        end: my_end_token(attr),
+                        key: attr.key.name || attr.key.value,
+                        value: from_moz(attr.value)
+                    })
+                )
+            });
+        }
+        return null;
+    }
+
+    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))
+            });
+        },
+
+        ArrayPattern: function(M) {
+            return new AST_Destructuring({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                names: M.elements.map(function(elm) {
+                    if (elm === null) {
+                        return new AST_Hole();
+                    }
+                    return from_moz(elm);
+                }),
+                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),
+                is_array: false
+            });
+        },
+
+        AssignmentPattern: function(M) {
+            return new AST_DefaultAssign({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                left: from_moz(M.left),
+                operator: "=",
+                right: from_moz(M.right)
+            });
+        },
+
+        SpreadElement: function(M) {
+            return new AST_Expansion({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz(M.argument)
+            });
+        },
+
+        RestElement: function(M) {
+            return new AST_Expansion({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz(M.argument)
+            });
+        },
+
+        TemplateElement: function(M) {
+            return new AST_TemplateSegment({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                value: M.value.cooked,
+                raw: M.value.raw
+            });
+        },
+
+        TemplateLiteral: function(M) {
+            var segments = [];
+            for (var i = 0; i < M.quasis.length; i++) {
+                segments.push(from_moz(M.quasis[i]));
+                if (M.expressions[i]) {
+                    segments.push(from_moz(M.expressions[i]));
+                }
+            }
+            return new AST_TemplateString({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                segments: segments
+            });
+        },
+
+        TaggedTemplateExpression: function(M) {
+            return new AST_PrefixedTemplateString({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                template_string: from_moz(M.quasi),
+                prefix: from_moz(M.tag)
+            });
+        },
+
+        FunctionDeclaration: function(M) {
+            return new AST_Defun({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                name: M.id && from_moz_symbol(AST_SymbolDefun, M.id),
+                argnames: M.params.map(M => from_moz_pattern(M, AST_SymbolFunarg)),
+                is_generator: M.generator,
+                async: M.async,
+                body: normalize_directives(from_moz(M.body).body)
+            });
+        },
+
+        FunctionExpression: function(M) {
+            return from_moz_lambda(M, /*is_method=*/false);
+        },
+
+        ArrowFunctionExpression: function(M) {
+            const body = M.body.type === "BlockStatement"
+                ? from_moz(M.body).body
+                : [make_node(AST_Return, {}, { value: from_moz(M.body) })];
+            return new AST_Arrow({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                argnames: M.params.map(p => from_moz_pattern(p, AST_SymbolFunarg)),
+                body,
+                async: M.async,
+            });
+        },
+
+        ExpressionStatement: function(M) {
+            return new AST_SimpleStatement({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                body: from_moz(M.expression)
+            });
+        },
+
+        TryStatement: function(M) {
+            var handlers = M.handlers || [M.handler];
+            if (handlers.length > 1 || M.guardedHandlers && M.guardedHandlers.length) {
+                throw new Error("Multiple catch clauses are not supported.");
+            }
+            return new AST_Try({
+                start    : my_start_token(M),
+                end      : my_end_token(M),
+                body     : new AST_TryBlock(from_moz(M.block)),
+                bcatch   : from_moz(handlers[0]),
+                bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null
+            });
+        },
+
+        Property: function(M) {
+            if (M.kind == "init" && !M.method) {
+                var args = {
+                    start    : my_start_token(M.key || M.value),
+                    end      : my_end_token(M.value),
+                    key      : M.computed
+                                ? from_moz(M.key)
+                                : M.key.name || String(M.key.value),
+                    quote    : from_moz_quote(M.key, M.computed),
+                    static   : false, // always an object
+                    value    : from_moz(M.value)
+                };
+
+                return new AST_ObjectKeyVal(args);
+            } else {
+                var value = from_moz_lambda(M.value, /*is_method=*/true);
+                var args = {
+                    start    : my_start_token(M.key || M.value),
+                    end      : my_end_token(M.value),
+                    key      : M.computed
+                                ? from_moz(M.key)
+                                : from_moz_symbol(AST_SymbolMethod, M.key),
+                    quote    : from_moz_quote(M.key, M.computed),
+                    static   : false, // always an object
+                    value,
+                };
+
+                if (M.kind == "get") return new AST_ObjectGetter(args);
+                if (M.kind == "set") return new AST_ObjectSetter(args);
+                if (M.method) return new AST_ConciseMethod(args);
+            }
+        },
+
+        MethodDefinition: function(M) {
+            const is_private = M.key.type === "PrivateIdentifier";
+            const key = M.computed ? from_moz(M.key) : new AST_SymbolMethod({ name: M.key.name || String(M.key.value) });
+
+            var args = {
+                start    : my_start_token(M),
+                end      : my_end_token(M),
+                key,
+                quote    : from_moz_quote(M.key, M.computed),
+                value    : from_moz_lambda(M.value, /*is_method=*/true),
+                static   : M.static,
+            };
+            if (M.kind == "get") {
+                return new (is_private ? AST_PrivateGetter : AST_ObjectGetter)(args);
+            }
+            if (M.kind == "set") {
+                return new (is_private ? AST_PrivateSetter : AST_ObjectSetter)(args);
+            }
+            return new (is_private ? AST_PrivateMethod : AST_ConciseMethod)(args);
+        },
+
+        FieldDefinition: function(M) {
+            let key;
+            if (M.computed) {
+                key = from_moz(M.key);
+            } else {
+                if (M.key.type !== "Identifier") throw new Error("Non-Identifier key in FieldDefinition");
+                key = from_moz(M.key);
+            }
+            return new AST_ClassProperty({
+                start    : my_start_token(M),
+                end      : my_end_token(M),
+                quote    : from_moz_quote(M.key, M.computed),
+                key,
+                value    : from_moz(M.value),
+                static   : M.static,
+            });
+        },
+
+        PropertyDefinition: function(M) {
+            let key;
+            if (M.computed) {
+                key = from_moz(M.key);
+            } else if (M.key.type === "PrivateIdentifier") {
+                return new AST_ClassPrivateProperty({
+                    start    : my_start_token(M),
+                    end      : my_end_token(M),
+                    key      : from_moz(M.key),
+                    value    : from_moz(M.value),
+                    static   : M.static,
+                });
+            } else {
+                key = from_moz_symbol(AST_SymbolClassProperty, M.key);
+            }
+
+            return new AST_ClassProperty({
+                start    : my_start_token(M),
+                end      : my_end_token(M),
+                quote    : from_moz_quote(M.key, M.computed),
+                key,
+                value    : from_moz(M.value),
+                static   : M.static,
+            });
+        },
+
+        PrivateIdentifier: function (M) {
+            return new AST_SymbolPrivateProperty({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                name: M.name
+            });
+        },
+
+        StaticBlock: function(M) {
+            return new AST_ClassStaticBlock({
+                start : my_start_token(M),
+                end   : my_end_token(M),
+                body  : M.body.map(from_moz),
+            });
+        },
+
+        ArrayExpression: function(M) {
+            return new AST_Array({
+                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) {
+                    if (prop.type === "SpreadElement") {
+                        return from_moz(prop);
+                    }
+                    prop.type = "Property";
+                    return from_moz(prop);
+                })
+            });
+        },
+
+        SequenceExpression: function(M) {
+            return new AST_Sequence({
+                start      : my_start_token(M),
+                end        : my_end_token(M),
+                expressions: M.expressions.map(from_moz)
+            });
+        },
+
+        MemberExpression: function(M) {
+            if (M.property.type === "PrivateIdentifier") {
+                return new AST_DotHash({
+                    start      : my_start_token(M),
+                    end        : my_end_token(M),
+                    property   : M.property.name,
+                    expression : from_moz(M.object),
+                    optional   : M.optional || false
+                });
+            }
+            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),
+                optional   : M.optional || false
+            });
+        },
+
+        ChainExpression: function(M) {
+            return new AST_Chain({
+                start      : my_start_token(M),
+                end        : my_end_token(M),
+                expression : from_moz(M.expression)
+            });
+        },
+
+        SwitchCase: function(M) {
+            return new (M.test ? AST_Case : AST_Default)({
+                start      : my_start_token(M),
+                end        : my_end_token(M),
+                expression : from_moz(M.test),
+                body       : M.consequent.map(from_moz)
+            });
+        },
+
+        VariableDeclaration: function(M) {
+            let decl_type;
+            let defs_type = AST_VarDef;
+            let sym_type;
+            let await_using = false;
+            if (M.kind === "const") {
+                decl_type = AST_Const;
+                sym_type = AST_SymbolConst;
+            } else if (M.kind === "let") {
+                decl_type = AST_Let;
+                sym_type = AST_SymbolLet;
+            } else if (M.kind === "using") {
+                decl_type = AST_Using;
+                defs_type = AST_UsingDef;
+                sym_type = AST_SymbolUsing;
+            } else if (M.kind === "await using") {
+                decl_type = AST_Using;
+                defs_type = AST_UsingDef;
+                sym_type = AST_SymbolUsing;
+                await_using = true;
+            } else {
+                decl_type = AST_Var;
+                sym_type = AST_SymbolVar;
+            }
+            const definitions = M.declarations.map(M => {
+                return new defs_type({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    name: from_moz_pattern(M.id, sym_type),
+                    value: from_moz(M.init),
+                });
+            });
+            return new decl_type({
+                start       : my_start_token(M),
+                end         : my_end_token(M),
+                definitions : definitions,
+                await       : await_using,
+            });
+        },
+
+        ImportDeclaration: function(M) {
+            var imported_name = null;
+            var imported_names = null;
+            M.specifiers.forEach(function (specifier) {
+                if (specifier.type === "ImportSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
+                    if (!imported_names) { imported_names = []; }
+                    imported_names.push(from_moz(specifier));
+                } else if (specifier.type === "ImportDefaultSpecifier") {
+                    imported_name = from_moz(specifier);
+                }
+            });
+            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),
+                attributes: import_attributes_from_moz(M.attributes || M.assertions),
+                phase: M.phase || null
+            });
+        },
+
+        ImportSpecifier: function(M) {
+            return new AST_NameMapping({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                foreign_name: from_moz_symbol(AST_SymbolImportForeign, M.imported, M.imported.type === "Literal"),
+                name: from_moz_symbol(AST_SymbolImport, M.local)
+            });
+        },
+
+        ImportDefaultSpecifier: function(M) {
+            return from_moz_symbol(AST_SymbolImport, M.local);
+        },
+
+        ImportNamespaceSpecifier: function(M) {
+            return new AST_NameMapping({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                foreign_name: new AST_SymbolImportForeign({ name: "*" }),
+                name: from_moz_symbol(AST_SymbolImport, M.local)
+            });
+        },
+
+        ImportExpression: function(M) {
+            const args = [from_moz(M.source)];
+            if (M.options) {
+                args.push(from_moz(M.options));
+            }
+            if (M.phase) {
+                return new AST_DynamicImport({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    phase: M.phase,
+                    args: args
+                });
+            }
+            return new AST_Call({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz({
+                    type: "Identifier",
+                    name: "import"
+                }),
+                optional: false,
+                args
+            });
+        },
+
+        ExportAllDeclaration: function(M) {
+            var foreign_name = M.exported == null ?
+                new AST_SymbolExportForeign({ name: "*" }) :
+                from_moz_symbol(AST_SymbolExportForeign, M.exported, M.exported.type === "Literal");
+            return new AST_Export({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                exported_names: [
+                    new AST_NameMapping({
+                        start: my_start_token(M),
+                        end: my_end_token(M),
+                        name: new AST_SymbolExport({ name: "*" }),
+                        foreign_name: foreign_name
+                    })
+                ],
+                module_name: from_moz(M.source),
+                attributes: import_attributes_from_moz(M.attributes || M.assertions)
+            });
+        },
+
+        ExportNamedDeclaration: function(M) {
+            if (M.declaration) {
+                // export const, export function, ...
+                return new AST_Export({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    exported_definition: from_moz(M.declaration),
+                    exported_names: null,
+                    module_name: null,
+                    attributes: null,
+                });
+            } else {
+                return new AST_Export({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    exported_definition: null,
+                    exported_names: M.specifiers && M.specifiers.length ? M.specifiers.map(from_moz) : [],
+                    module_name: from_moz(M.source),
+                    attributes: import_attributes_from_moz(M.attributes || M.assertions),
+                });
+            }
+        },
+
+        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
+            });
+        },
+
+        ExportSpecifier: function(M) {
+            return new AST_NameMapping({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                foreign_name: from_moz_symbol(AST_SymbolExportForeign, M.exported, M.exported.type === "Literal"),
+                name: from_moz_symbol(AST_SymbolExport, M.local, M.local.type === "Literal"),
+            });
+        },
+
+        Literal: function(M) {
+            var val = M.value, args = {
+                start  : my_start_token(M),
+                end    : my_end_token(M)
+            };
+            var rx = M.regex;
+            if (rx && rx.pattern) {
+                // RegExpLiteral as per ESTree AST spec
+                args.value = {
+                    source: rx.pattern,
+                    flags: rx.flags
+                };
+                return new AST_RegExp(args);
+            } else if (rx) {
+                // support legacy RegExp
+                const rx_source = M.raw || val;
+                const match = rx_source.match(/^\/(.*)\/(\w*)$/);
+                if (!match) throw new Error("Invalid regex source " + rx_source);
+                const [_, source, flags] = match;
+                args.value = { source, flags };
+                return new AST_RegExp(args);
+            }
+            const bi = typeof M.value === "bigint" ? M.value.toString() : M.bigint;
+            if (typeof bi === "string") {
+                args.value = bi;
+                args.raw = M.raw;
+                return new AST_BigInt(args);
+            }
+            if (val === null) return new AST_Null(args);
+            switch (typeof val) {
+              case "string":
+                args.quote = "\"";
+                args.value = val;
+                return new AST_String(args);
+              case "number":
+                args.value = val;
+                args.raw = M.raw || val.toString();
+                return new AST_Number(args);
+              case "boolean":
+                return new (val ? AST_True : AST_False)(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)
+                });
+            } else if (M.meta.name === "import" && M.property.name === "meta") {
+                return new AST_ImportMeta({
+                    start: my_start_token(M),
+                    end: my_end_token(M)
+                });
+            }
+        },
+
+        Identifier: function(M) {
+            return new AST_SymbolRef({
+                start : my_start_token(M),
+                end   : my_end_token(M),
+                name  : M.name
+            });
+        },
+
+        EmptyStatement: function(M) {
+            return new AST_EmptyStatement({
+                start: my_start_token(M),
+                end: my_end_token(M)
+            });
+        },
+
+        BlockStatement: function(M) {
+            return new AST_BlockStatement({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                body: M.body.map(from_moz)
+            });
+        },
+
+        IfStatement: function(M) {
+            return new AST_If({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                condition: from_moz(M.test),
+                body: from_moz(M.consequent),
+                alternative: from_moz(M.alternate)
+            });
+        },
+
+        LabeledStatement: function(M) {
+            try {
+                const label = from_moz_symbol(AST_Label, M.label);
+                FROM_MOZ_LABELS.push(label);
+
+                const stat = new AST_LabeledStatement({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    label,
+                    body: from_moz(M.body)
+                });
+
+                return stat;
+            } finally {
+                FROM_MOZ_LABELS.pop();
+            }
+        },
+
+        BreakStatement: function(M) {
+            return new AST_Break({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                label: from_moz_label_ref(M.label),
+            });
+        },
+
+        ContinueStatement: function(M) {
+            return new AST_Continue({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                label: from_moz_label_ref(M.label),
+            });
+        },
+
+        WithStatement: function(M) {
+            return new AST_With({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz(M.object),
+                body: from_moz(M.body)
+            });
+        },
+
+        SwitchStatement: function(M) {
+            return new AST_Switch({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz(M.discriminant),
+                body: M.cases.map(from_moz)
+            });
+        },
+
+        ReturnStatement: function(M) {
+            return new AST_Return({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                value: from_moz(M.argument)
+            });
+        },
+
+        ThrowStatement: function(M) {
+            return new AST_Throw({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                value: from_moz(M.argument)
+            });
+        },
+
+        WhileStatement: function(M) {
+            return new AST_While({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                condition: from_moz(M.test),
+                body: from_moz(M.body)
+            });
+        },
+
+        DoWhileStatement: function(M) {
+            return new AST_Do({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                condition: from_moz(M.test),
+                body: from_moz(M.body)
+            });
+        },
+
+        ForStatement: function(M) {
+            return new AST_For({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                init: from_moz(M.init),
+                condition: from_moz(M.test),
+                step: from_moz(M.update),
+                body: from_moz(M.body)
+            });
+        },
+
+        ForInStatement: function(M) {
+            return new AST_ForIn({
+                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)
+            });
+        },
+
+        ForOfStatement: function(M) {
+            return new 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),
+                await: M.await
+            });
+        },
+
+        AwaitExpression: function(M) {
+            return new AST_Await({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz(M.argument)
+            });
+        },
+
+        YieldExpression: function(M) {
+            return new AST_Yield({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz(M.argument),
+                is_star: M.delegate
+            });
+        },
+
+        DebuggerStatement: function(M) {
+            return new AST_Debugger({
+                start: my_start_token(M),
+                end: my_end_token(M)
+            });
+        },
+
+        CatchClause: function(M) {
+            return new AST_Catch({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                argname: M.param ? from_moz_pattern(M.param, AST_SymbolCatch) : null,
+                body: from_moz(M.body).body
+            });
+        },
+
+        ThisExpression: function(M) {
+            return new AST_This({
+                start: my_start_token(M),
+                name: "this",
+                end: my_end_token(M)
+            });
+        },
+
+        Super: function(M) {
+            return new AST_Super({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                name: "super",
+            });
+        },
+
+        BinaryExpression: function(M) {
+            if (M.left.type === "PrivateIdentifier") {
+                return new AST_PrivateIn({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    key: new AST_SymbolPrivateProperty({
+                        start: my_start_token(M.left),
+                        end: my_end_token(M.left),
+                        name: M.left.name
+                    }),
+                    value: from_moz(M.right),
+                });
+            }
+            return new AST_Binary({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                operator: M.operator,
+                left: from_moz(M.left),
+                right: from_moz(M.right)
+            });
+        },
+
+        LogicalExpression: function(M) {
+            return new AST_Binary({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                operator: M.operator,
+                left: from_moz(M.left),
+                right: from_moz(M.right)
+            });
+        },
+
+        AssignmentExpression: function(M) {
+            return new AST_Assign({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                operator: M.operator,
+                logical: M.operator === "??=" || M.operator === "&&=" || M.operator === "||=",
+                left: from_moz(M.left),
+                right: from_moz(M.right)
+            });
+        },
+
+        ConditionalExpression: function(M) {
+            return new AST_Conditional({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                condition: from_moz(M.test),
+                consequent: from_moz(M.consequent),
+                alternative: from_moz(M.alternate)
+            });
+        },
+
+        NewExpression: function(M) {
+            return new AST_New({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz(M.callee),
+                args: M.arguments.map(from_moz)
+            });
+        },
+
+        CallExpression: function(M) {
+            return new AST_Call({
+                start: my_start_token(M),
+                end: my_end_token(M),
+                expression: from_moz(M.callee),
+                optional: M.optional,
+                args: M.arguments.map(from_moz)
+            });
+        }
+    };
+
+    MOZ_TO_ME.UpdateExpression =
+    MOZ_TO_ME.UnaryExpression = function To_Moz_Unary(M) {
+        var prefix = "prefix" in M ? M.prefix
+            : M.type == "UnaryExpression" ? true : false;
+        return new (prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({
+            start      : my_start_token(M),
+            end        : my_end_token(M),
+            operator   : M.operator,
+            expression : from_moz(M.argument)
+        });
+    };
+
+    MOZ_TO_ME.ClassDeclaration =
+    MOZ_TO_ME.ClassExpression = function From_Moz_Class(M) {
+        return new (M.type === "ClassDeclaration" ? AST_DefClass : AST_ClassExpression)({
+            start    : my_start_token(M),
+            end      : my_end_token(M),
+            name     : M.id && from_moz_symbol(M.type === "ClassDeclaration" ? AST_SymbolDefClass : AST_SymbolClass, M.id),
+            extends  : from_moz(M.superClass),
+            properties: M.body.body.map(from_moz)
+        });
+    };
+
+    def_to_moz(AST_EmptyStatement, function To_Moz_EmptyStatement() {
+        return {
+            type: "EmptyStatement"
+        };
+    });
+    def_to_moz(AST_BlockStatement, function To_Moz_BlockStatement(M) {
+        return {
+            type: "BlockStatement",
+            body: M.body.map(to_moz)
+        };
+    });
+    def_to_moz(AST_If, function To_Moz_IfStatement(M) {
+        return {
+            type: "IfStatement",
+            test: to_moz(M.condition),
+            consequent: to_moz(M.body),
+            alternate: to_moz(M.alternative)
+        };
+    });
+    def_to_moz(AST_LabeledStatement, function To_Moz_LabeledStatement(M) {
+        return {
+            type: "LabeledStatement",
+            label: to_moz(M.label),
+            body: to_moz(M.body)
+        };
+    });
+    def_to_moz(AST_Break, function To_Moz_BreakStatement(M) {
+        return {
+            type: "BreakStatement",
+            label: to_moz(M.label)
+        };
+    });
+    def_to_moz(AST_Continue, function To_Moz_ContinueStatement(M) {
+        return {
+            type: "ContinueStatement",
+            label: to_moz(M.label)
+        };
+    });
+    def_to_moz(AST_With, function To_Moz_WithStatement(M) {
+        return {
+            type: "WithStatement",
+            object: to_moz(M.expression),
+            body: to_moz(M.body)
+        };
+    });
+    def_to_moz(AST_Switch, function To_Moz_SwitchStatement(M) {
+        return {
+            type: "SwitchStatement",
+            discriminant: to_moz(M.expression),
+            cases: M.body.map(to_moz)
+        };
+    });
+    def_to_moz(AST_Return, function To_Moz_ReturnStatement(M) {
+        return {
+            type: "ReturnStatement",
+            argument: to_moz(M.value)
+        };
+    });
+    def_to_moz(AST_Throw, function To_Moz_ThrowStatement(M) {
+        return {
+            type: "ThrowStatement",
+            argument: to_moz(M.value)
+        };
+    });
+    def_to_moz(AST_While, function To_Moz_WhileStatement(M) {
+        return {
+            type: "WhileStatement",
+            test: to_moz(M.condition),
+            body: to_moz(M.body)
+        };
+    });
+    def_to_moz(AST_Do, function To_Moz_DoWhileStatement(M) {
+        return {
+            type: "DoWhileStatement",
+            test: to_moz(M.condition),
+            body: to_moz(M.body)
+        };
+    });
+    def_to_moz(AST_For, function To_Moz_ForStatement(M) {
+        return {
+            type: "ForStatement",
+            init: to_moz(M.init),
+            test: to_moz(M.condition),
+            update: to_moz(M.step),
+            body: to_moz(M.body)
+        };
+    });
+    def_to_moz(AST_ForIn, function To_Moz_ForInStatement(M) {
+        return {
+            type: "ForInStatement",
+            left: to_moz(M.init),
+            right: to_moz(M.object),
+            body: to_moz(M.body)
+        };
+    });
+    def_to_moz(AST_ForOf, function To_Moz_ForOfStatement(M) {
+        return {
+            type: "ForOfStatement",
+            left: to_moz(M.init),
+            right: to_moz(M.object),
+            body: to_moz(M.body),
+            await: M.await
+        };
+    });
+    def_to_moz(AST_Await, function To_Moz_AwaitExpression(M) {
+        return {
+            type: "AwaitExpression",
+            argument: to_moz(M.expression)
+        };
+    });
+    def_to_moz(AST_Yield, function To_Moz_YieldExpression(M) {
+        return {
+            type: "YieldExpression",
+            argument: to_moz(M.expression),
+            delegate: M.is_star
+        };
+    });
+    def_to_moz(AST_Debugger, function To_Moz_DebuggerStatement() {
+        return {
+            type: "DebuggerStatement"
+        };
+    });
+    def_to_moz(AST_VarDefLike, function To_Moz_VariableDeclarator(M) {
+        return {
+            type: "VariableDeclarator",
+            id: to_moz(M.name),
+            init: to_moz(M.value)
+        };
+    });
+
+    def_to_moz(AST_This, function To_Moz_ThisExpression() {
+        return {
+            type: "ThisExpression"
+        };
+    });
+    def_to_moz(AST_Super, function To_Moz_Super() {
+        return {
+            type: "Super"
+        };
+    });
+    def_to_moz(AST_Conditional, function To_Moz_ConditionalExpression(M) {
+        return {
+            type: "ConditionalExpression",
+            test: to_moz(M.condition),
+            consequent: to_moz(M.consequent),
+            alternate: to_moz(M.alternative)
+        };
+    });
+    def_to_moz(AST_New, function To_Moz_NewExpression(M) {
+        return {
+            type: "NewExpression",
+            callee: to_moz(M.expression),
+            arguments: M.args.map(to_moz)
+        };
+    });
+    def_to_moz(AST_Call, function To_Moz_CallExpression(M) {
+        if (M.expression instanceof AST_SymbolRef && M.expression.name === "import") {
+            const [source, options] = M.args.map(to_moz);
+            return {
+                type: "ImportExpression",
+                source,
+                options: options || null
+            };
+        }
+
+        return {
+            type: "CallExpression",
+            callee: to_moz(M.expression),
+            optional: M.optional,
+            arguments: M.args.map(to_moz)
+        };
+    });
+
+    def_to_moz(AST_DynamicImport, function To_Moz_ImportExpression(M) {
+        const [source, options] = M.args.map(to_moz);
+        return {
+            type: "ImportExpression",
+            source,
+            options: options || null,
+            phase: M.phase
+        };
+    });
+
+    def_to_moz(AST_Toplevel, function To_Moz_Program(M) {
+        return to_moz_scope("Program", M);
+    });
+
+    def_to_moz(AST_Expansion, function To_Moz_Spread(M) {
+        return {
+            type: to_moz_in_destructuring() ? "RestElement" : "SpreadElement",
+            argument: to_moz(M.expression)
+        };
+    });
+
+    def_to_moz(AST_PrefixedTemplateString, function To_Moz_TaggedTemplateExpression(M) {
+        return {
+            type: "TaggedTemplateExpression",
+            tag: to_moz(M.prefix),
+            quasi: to_moz(M.template_string)
+        };
+    });
+
+    def_to_moz(AST_TemplateString, function To_Moz_TemplateLiteral(M) {
+        var quasis = [];
+        var expressions = [];
+        for (var i = 0; i < M.segments.length; i++) {
+            if (i % 2 !== 0) {
+                expressions.push(to_moz(M.segments[i]));
+            } else {
+                quasis.push({
+                    type: "TemplateElement",
+                    value: {
+                        raw: M.segments[i].raw,
+                        cooked: M.segments[i].value
+                    },
+                    tail: i === M.segments.length - 1
+                });
+            }
+        }
+        return {
+            type: "TemplateLiteral",
+            quasis: quasis,
+            expressions: expressions
+        };
+    });
+
+    def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) {
+        return {
+            type: "FunctionDeclaration",
+            id: to_moz(M.name),
+            params: M.argnames.map(to_moz_pattern),
+            generator: M.is_generator,
+            async: M.async,
+            body: to_moz_scope("BlockStatement", M)
+        };
+    });
+
+    def_to_moz(AST_Function, function To_Moz_FunctionExpression(M) {
+        return {
+            type: "FunctionExpression",
+            id: to_moz(M.name),
+            params: M.argnames.map(to_moz_pattern),
+            generator: M.is_generator || false,
+            async: M.async || false,
+            body: to_moz_scope("BlockStatement", M)
+        };
+    });
+
+    def_to_moz(AST_Arrow, function To_Moz_ArrowFunctionExpression(M) {
+        var body = M.body.length === 1 && M.body[0] instanceof AST_Return && M.body[0].value
+            ? to_moz(M.body[0].value)
+            : {
+                type: "BlockStatement",
+                body: M.body.map(to_moz)
+            };
+        return {
+            type: "ArrowFunctionExpression",
+            params: M.argnames.map(to_moz_pattern),
+            async: M.async,
+            body: body,
+        };
+    });
+
+    def_to_moz(AST_Destructuring, function To_Moz_ObjectPattern(M) {
+        if (M.is_array) {
+            return {
+                type: "ArrayPattern",
+                elements: M.names.map(
+                    M => M instanceof AST_Hole ? null : to_moz_pattern(M)
+                ),
+            };
+        }
+        return {
+            type: "ObjectPattern",
+            properties: M.names.map(M => {
+                if (M instanceof AST_ObjectKeyVal) {
+                    var computed = M.computed_key();
+                    const [shorthand, key] = to_moz_property_key(M.key, computed, M.quote, M.value);
+
+                    return {
+                        type: "Property",
+                        computed,
+                        kind: "init",
+                        key: key,
+                        method: false,
+                        shorthand,
+                        value: to_moz_pattern(M.value)
+                    };
+                } else {
+                    return to_moz_pattern(M);
+                }
+            }),
+        };
+    });
+
+    def_to_moz(AST_DefaultAssign, function To_Moz_AssignmentExpression(M) {
+        return {
+            type: "AssignmentPattern",
+            left: to_moz_pattern(M.left),
+            right: to_moz(M.right),
+        };
+    });
+
+    def_to_moz(AST_Directive, function To_Moz_Directive(M) {
+        return {
+            type: "ExpressionStatement",
+            expression: {
+                type: "Literal",
+                value: M.value,
+                raw: M.print_to_string()
+            },
+            directive: M.value
+        };
+    });
+
+    def_to_moz(AST_SimpleStatement, function To_Moz_ExpressionStatement(M) {
+        return {
+            type: "ExpressionStatement",
+            expression: to_moz(M.body)
+        };
+    });
+
+    def_to_moz(AST_SwitchBranch, function To_Moz_SwitchCase(M) {
+        return {
+            type: "SwitchCase",
+            test: to_moz(M.expression),
+            consequent: M.body.map(to_moz)
+        };
+    });
+
+    def_to_moz(AST_Try, function To_Moz_TryStatement(M) {
+        return {
+            type: "TryStatement",
+            block: to_moz_block(M.body),
+            handler: to_moz(M.bcatch),
+            guardedHandlers: [],
+            finalizer: to_moz(M.bfinally)
+        };
+    });
+
+    def_to_moz(AST_Catch, function To_Moz_CatchClause(M) {
+        return {
+            type: "CatchClause",
+            param: M.argname != null ? to_moz_pattern(M.argname) : null,
+            body: to_moz_block(M)
+        };
+    });
+
+    def_to_moz(AST_DefinitionsLike, function To_Moz_VariableDeclaration(M) {
+        return {
+            type: "VariableDeclaration",
+            kind:
+                M instanceof AST_Const ? "const" :
+                M instanceof AST_Let ? "let" :
+                M instanceof AST_Using ? (M.await ? "await using" : "using") :
+                "var",
+            declarations: M.definitions.map(to_moz)
+        };
+    });
+
+    function import_attributes_to_moz(attribute) {
+        const import_attributes = [];
+        if (attribute) {
+            for (const { key, value } of attribute.properties) {
+                const key_moz = is_basic_identifier_string(key)
+                    ? { type: "Identifier", name: key }
+                    : { type: "Literal", value: key, raw: JSON.stringify(key) };
+                import_attributes.push({
+                    type: "ImportAttribute",
+                    key: key_moz,
+                    value: to_moz(value)
+                });
+            }
+        }
+        return import_attributes;
+    }
+
+    def_to_moz(AST_Export, function To_Moz_ExportDeclaration(M) {
+        if (M.exported_names) {
+            var first_exported = M.exported_names[0];
+            if (first_exported && first_exported.name.name === "*" && !first_exported.name.quote) {
+                var foreign_name = first_exported.foreign_name;
+                var exported = foreign_name.name === "*" && !foreign_name.quote
+                    ? null
+                    : to_moz(foreign_name);
+                return {
+                    type: "ExportAllDeclaration",
+                    source: to_moz(M.module_name),
+                    exported: exported,
+                    attributes: import_attributes_to_moz(M.attributes)
+                };
+            }
+            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),
+                attributes: import_attributes_to_moz(M.attributes)
+            };
+        }
+
+        if (M.is_default) {
+            return {
+                type: "ExportDefaultDeclaration",
+                declaration: to_moz(M.exported_value || M.exported_definition),
+            };
+        } else {
+            return {
+                type: "ExportNamedDeclaration",
+                declaration: to_moz(M.exported_value || M.exported_definition),
+                specifiers: [],
+                source: null,
+            };
+        }
+    });
+
+    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) {
+            var first_imported_foreign_name = M.imported_names[0].foreign_name;
+            if (first_imported_foreign_name.name === "*" && !first_imported_foreign_name.quote) {
+                specifiers.push({
+                    type: "ImportNamespaceSpecifier",
+                    local: to_moz(M.imported_names[0].name)
+                });
+            } else {
+                M.imported_names.forEach(function(name_mapping) {
+                    specifiers.push({
+                        type: "ImportSpecifier",
+                        local: to_moz(name_mapping.name),
+                        imported: to_moz(name_mapping.foreign_name)
+                    });
+                });
+            }
+        }
+        var moz = {
+            type: "ImportDeclaration",
+            specifiers: specifiers,
+            source: to_moz(M.module_name),
+            attributes: import_attributes_to_moz(M.attributes)
+        };
+        if (M.phase) moz.phase = M.phase;
+        return moz;
+    });
+
+    def_to_moz(AST_ImportMeta, function To_Moz_MetaProperty() {
+        return {
+            type: "MetaProperty",
+            meta: {
+                type: "Identifier",
+                name: "import"
+            },
+            property: {
+                type: "Identifier",
+                name: "meta"
+            }
+        };
+    });
+
+    def_to_moz(AST_Sequence, function To_Moz_SequenceExpression(M) {
+        return {
+            type: "SequenceExpression",
+            expressions: M.expressions.map(to_moz)
+        };
+    });
+
+    def_to_moz(AST_DotHash, function To_Moz_PrivateMemberExpression(M) {
+        return {
+            type: "MemberExpression",
+            object: to_moz(M.expression),
+            computed: false,
+            property: {
+                type: "PrivateIdentifier",
+                name: M.property
+            },
+            optional: M.optional
+        };
+    });
+
+    def_to_moz(AST_PropAccess, function To_Moz_MemberExpression(M) {
+        var isComputed = 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},
+            optional: M.optional
+        };
+    });
+
+    def_to_moz(AST_Chain, function To_Moz_ChainExpression(M) {
+        return {
+            type: "ChainExpression",
+            expression: to_moz(M.expression)
+        };
+    });
+
+    def_to_moz(AST_Unary, function To_Moz_Unary(M) {
+        return {
+            type: M.operator == "++" || M.operator == "--" ? "UpdateExpression" : "UnaryExpression",
+            operator: M.operator,
+            prefix: M instanceof AST_UnaryPrefix,
+            argument: to_moz(M.expression)
+        };
+    });
+
+    def_to_moz(AST_Binary, function To_Moz_BinaryExpression(M) {
+        if (M.operator == "=" && to_moz_in_destructuring()) {
+            return {
+                type: "AssignmentPattern",
+                left: to_moz(M.left),
+                right: to_moz(M.right)
+            };
+        }
+
+        const type = M.operator == "&&" || M.operator == "||" || M.operator === "??"
+            ? "LogicalExpression"
+            : "BinaryExpression";
+
+        return {
+            type,
+            left: to_moz(M.left),
+            operator: M.operator,
+            right: to_moz(M.right)
+        };
+    });
+
+    def_to_moz(AST_Assign, function To_Moz_AssignmentExpression(M) {
+        return {
+            type: "AssignmentExpression",
+            operator: M.operator,
+            left: to_moz(M.left),
+            right: to_moz(M.right)
+        };
+    });
+
+    def_to_moz(AST_PrivateIn, function To_Moz_BinaryExpression_PrivateIn(M) {
+        return {
+            type: "BinaryExpression",
+            left: { type: "PrivateIdentifier", name: M.key.name },
+            operator: "in",
+            right: to_moz(M.value),
+        };
+    });
+
+    def_to_moz(AST_Array, function To_Moz_ArrayExpression(M) {
+        return {
+            type: "ArrayExpression",
+            elements: M.elements.map(to_moz)
+        };
+    });
+
+    def_to_moz(AST_Object, function To_Moz_ObjectExpression(M) {
+        return {
+            type: "ObjectExpression",
+            properties: M.properties.map(to_moz)
+        };
+    });
+
+    def_to_moz(AST_ObjectProperty, function To_Moz_Property(M, parent) {
+        var computed = M.computed_key();
+        const [shorthand, key] = to_moz_property_key(M.key, computed, M.quote, M.value);
+
+        var kind;
+        if (M instanceof AST_ObjectGetter) {
+            kind = "get";
+        } else
+        if (M instanceof AST_ObjectSetter) {
+            kind = "set";
+        }
+        if (M instanceof AST_PrivateGetter || M instanceof AST_PrivateSetter) {
+            const kind = M instanceof AST_PrivateGetter ? "get" : "set";
+            return {
+                type: "MethodDefinition",
+                computed: false,
+                kind: kind,
+                static: M.static,
+                key: {
+                    type: "PrivateIdentifier",
+                    name: M.key.name
+                },
+                value: to_moz(M.value)
+            };
+        }
+        if (M instanceof AST_ClassPrivateProperty) {
+            return {
+                type: "PropertyDefinition",
+                key: {
+                    type: "PrivateIdentifier",
+                    name: M.key.name
+                },
+                value: to_moz(M.value),
+                computed: false,
+                static: M.static
+            };
+        }
+        if (M instanceof AST_ClassProperty) {
+            return {
+                type: "PropertyDefinition",
+                key,
+                value: to_moz(M.value),
+                computed,
+                static: M.static
+            };
+        }
+        if (parent instanceof AST_Class) {
+            return {
+                type: "MethodDefinition",
+                computed: computed,
+                kind: kind,
+                static: M.static,
+                key: to_moz(M.key),
+                value: to_moz(M.value)
+            };
+        }
+        return {
+            type: "Property",
+            computed: computed,
+            method: false,
+            shorthand,
+            kind: kind,
+            key: key,
+            value: to_moz(M.value)
+        };
+    });
+
+    def_to_moz(AST_ObjectKeyVal, function To_Moz_Property(M) {
+        var computed = M.computed_key();
+        const [shorthand, key] = to_moz_property_key(M.key, computed, M.quote, M.value);
+
+        return {
+            type: "Property",
+            computed: computed,
+            shorthand: shorthand,
+            method: false,
+            kind: "init",
+            key: key,
+            value: to_moz(M.value)
+        };
+    });
+
+    def_to_moz(AST_ConciseMethod, function To_Moz_MethodDefinition(M, parent) {
+        const computed = M.computed_key();
+        const [_always_false, key] = to_moz_property_key(M.key, computed, M.quote, M.value);
+
+        if (parent instanceof AST_Object) {
+            return {
+                type: "Property",
+                kind: "init",
+                computed,
+                method: true,
+                shorthand: false,
+                key,
+                value: to_moz(M.value),
+            };
+        }
+
+        return {
+            type: "MethodDefinition",
+            kind: !computed && M.key.name === "constructor" ? "constructor" : "method",
+            computed,
+            key,
+            value: to_moz(M.value),
+            static: M.static,
+        };
+    });
+
+    def_to_moz(AST_PrivateMethod, function To_Moz_MethodDefinition(M) {
+        return {
+            type: "MethodDefinition",
+            kind: "method",
+            key: { type: "PrivateIdentifier", name: M.key.name },
+            value: to_moz(M.value),
+            computed: false,
+            static: M.static,
+        };
+    });
+
+    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_ClassStaticBlock, function To_Moz_StaticBlock(M) {
+        return {
+            type: "StaticBlock",
+            body: M.body.map(to_moz),
+        };
+    });
+
+    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_Symbol, function To_Moz_Identifier(M, parent) {
+        if (
+            (M instanceof AST_SymbolMethod && parent.quote) ||
+            ((
+                M instanceof AST_SymbolImportForeign ||
+                M instanceof AST_SymbolExportForeign ||
+                M instanceof AST_SymbolExport
+                ) && M.quote)
+         ) {
+            return {
+                type: "Literal",
+                value: M.name
+            };
+        }
+        var def = M.definition();
+        return {
+            type: "Identifier",
+            name: def ? def.mangled_name || def.name : M.name
+        };
+    });
+
+    def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) {
+        const pattern = M.value.source;
+        const flags = M.value.flags;
+        return {
+            type: "Literal",
+            value: null,
+            raw: M.print_to_string(),
+            regex: { pattern, flags }
+        };
+    });
+
+    def_to_moz(AST_Constant, function To_Moz_Literal(M) {
+        var value = M.value;
+        return {
+            type: "Literal",
+            value: value,
+            raw: M.raw || M.print_to_string()
+        };
+    });
+
+    def_to_moz(AST_Atom, function To_Moz_Atom(M) {
+        return {
+            type: "Identifier",
+            name: String(M.value)
+        };
+    });
+
+    def_to_moz(AST_BigInt, M => ({
+        type: "Literal",
+        // value cannot be represented natively
+        // see: https://github.com/estree/estree/blob/master/es2020.md#bigintliteral
+        value: null,
+        // `M.value` is a string that may be a hex number representation.
+        // but "bigint" property should have only decimal digits
+        bigint: typeof BigInt === "function" ? BigInt(M.value).toString() : M.value,
+        raw: M.raw,
+    }));
+
+    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; });
+
+    AST_Block.DEFMETHOD("to_mozilla_ast", AST_BlockStatement.prototype.to_mozilla_ast);
+    AST_Lambda.DEFMETHOD("to_mozilla_ast", AST_Function.prototype.to_mozilla_ast);
+
+    /* -----[ tools ]----- */
+
+    function my_start_token(moznode) {
+        var loc = moznode.loc, start = loc && loc.start;
+        var range = moznode.range;
+        return new AST_Token(
+            "",
+            "",
+            start && start.line || 0,
+            start && start.column || 0,
+            range ? range [0] : moznode.start,
+            false,
+            [],
+            [],
+            loc && loc.source,
+        );
+    }
+
+    function my_end_token(moznode) {
+        var loc = moznode.loc, end = loc && loc.end;
+        var range = moznode.range;
+        return new AST_Token(
+            "",
+            "",
+            end && end.line || 0,
+            end && end.column || 0,
+            range ? range [0] : moznode.end,
+            false,
+            [],
+            [],
+            loc && loc.source,
+        );
+    }
+
+    var FROM_MOZ_LABELS = null;
+
+    function from_moz(node) {
+        if (node == null) return null;
+        return MOZ_TO_ME[node.type](node);
+    }
+
+    function from_moz_quote(moz_key, computed) {
+        if (!computed && moz_key.type === "Literal" && typeof moz_key.value === "string") {
+            return '"';
+        } else {
+            return "";
+        }
+    }
+
+    function from_moz_symbol(symbol_type, M, has_quote) {
+        return new symbol_type({
+            start: my_start_token(M),
+            quote: has_quote ? '"' : undefined,
+            name: M.type === "Identifier" ? M.name : String(M.value),
+            end: my_end_token(M),
+        });
+    }
+
+    function from_moz_lambda(M, is_method) {
+        return new (is_method ? AST_Accessor : AST_Function)({
+            start: my_start_token(M),
+            end: my_end_token(M),
+            name: M.id && from_moz_symbol(is_method ? AST_SymbolMethod : AST_SymbolLambda, M.id),
+            argnames: M.params.map(M => from_moz_pattern(M, AST_SymbolFunarg)),
+            is_generator: M.generator,
+            async: M.async,
+            body: normalize_directives(from_moz(M.body).body)
+        });
+    }
+
+    function from_moz_pattern(M, sym_type) {
+        switch (M.type) {
+            case "ObjectPattern":
+                return new AST_Destructuring({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    names: M.properties.map(p => from_moz_pattern(p, sym_type)),
+                    is_array: false
+                });
+
+            case "Property":
+                var key = M.key;
+                var args = {
+                    start    : my_start_token(key || M.value),
+                    end      : my_end_token(M.value),
+                    key      : key.type == "Identifier" ? key.name : String(key.value),
+                    quote    : !M.computed && key.type === "Literal" && typeof key.value === "string"
+                                ? '"'
+                                : "",
+                    value    : from_moz_pattern(M.value, sym_type)
+                };
+                if (M.computed) {
+                    args.key = from_moz(M.key);
+                }
+                return new AST_ObjectKeyVal(args);
+
+            case "ArrayPattern":
+                return new AST_Destructuring({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    names: M.elements.map(function(elm) {
+                        if (elm === null) {
+                            return new AST_Hole();
+                        }
+                        return from_moz_pattern(elm, sym_type);
+                    }),
+                    is_array: true
+                });
+
+            case "SpreadElement":
+            case "RestElement":
+                return new AST_Expansion({
+                    start: my_start_token(M),
+                    end: my_end_token(M),
+                    expression: from_moz_pattern(M.argument, sym_type),
+                });
+
+            case "AssignmentPattern":
+                return new AST_DefaultAssign({
+                    start : my_start_token(M),
+                    end   : my_end_token(M),
+                    left  : from_moz_pattern(M.left, sym_type),
+                    operator: "=",
+                    right : from_moz(M.right),
+                });
+
+            case "Identifier":
+                return new sym_type({
+                    start : my_start_token(M),
+                    end   : my_end_token(M),
+                    name  : M.name,
+                });
+
+            default:
+                throw new Error("Invalid node type for destructuring: " + M.type);
+        }
+    }
+
+    function from_moz_label_ref(m_label) {
+        if (!m_label) return null;
+
+        const label = from_moz_symbol(AST_LabelRef, m_label);
+
+        let i = FROM_MOZ_LABELS.length;
+        while (i--) {
+            const label_origin = FROM_MOZ_LABELS[i];
+
+            if (label.name === label_origin.name) {
+                label.thedef = label_origin;
+                break;
+            }
+        }
+
+        return label;
+    }
+
+    AST_Node.from_mozilla_ast = function(node) {
+        var save_labels = FROM_MOZ_LABELS;
+        FROM_MOZ_LABELS = [];
+        var ast = from_moz(node);
+        FROM_MOZ_LABELS = save_labels;
+        return ast;
+    };
+
+    function set_moz_loc(mynode, moznode) {
+        var start = mynode.start;
+        var end = mynode.end;
+        if (!(start && end)) {
+            return moznode;
+        }
+        if (start.pos != null && end.endpos != null) {
+            moznode.range = [start.pos, end.endpos];
+        }
+        if (start.line) {
+            moznode.loc = {
+                start: {line: start.line, column: start.col},
+                end: end.endline ? {line: end.endline, column: end.endcol} : null
+            };
+            if (start.file) {
+                moznode.loc.source = start.file;
+            }
+        }
+        return moznode;
+    }
+
+    function def_to_moz(mytype, handler) {
+        mytype.DEFMETHOD("to_mozilla_ast", function(parent) {
+            return set_moz_loc(this, handler(this, parent));
+        });
+    }
+
+    var TO_MOZ_STACK = null;
+
+    function to_moz(node) {
+        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;
+    }
+
+    /** Object property keys can be number literals, string literals, or raw names. Additionally they can be shorthand. We decide that here. */
+    function to_moz_property_key(key, computed = false, quote = false, value = null) {
+        if (computed) {
+            return [false, to_moz(key)];
+        }
+
+        const key_name = typeof key === "string" ? key : key.name;
+        let moz_key;
+        if (quote) {
+            moz_key = { type: "Literal", value: key_name, raw: JSON.stringify(key_name) };
+        } else if ("" + +key_name === key_name && +key_name >= 0) {
+            // representable as a number
+            moz_key = { type: "Literal", value: +key_name, raw: JSON.stringify(+key_name) };
+        } else {
+            moz_key = { type: "Identifier", name: key_name };
+        }
+
+        const shorthand =
+            moz_key.type === "Identifier"
+            && moz_key.name === key_name
+            && (value instanceof AST_Symbol && value.name === key_name
+                || value instanceof AST_DefaultAssign && value.left.name === key_name);
+        return [shorthand, moz_key];
+    }
+
+    function to_moz_pattern(node) {
+        if (node instanceof AST_Expansion) {
+            return {
+                type: "RestElement",
+                argument: to_moz_pattern(node.expression),
+            };
+        }
+
+        if ((
+            node instanceof AST_Symbol
+            || node instanceof AST_Destructuring
+            || node instanceof AST_DefaultAssign
+            || node instanceof AST_PropAccess
+        )) {
+            // Plain translation
+            return to_moz(node);
+        }
+
+        throw new Error(node.TYPE);
+    }
+
+    function to_moz_in_destructuring() {
+        var i = TO_MOZ_STACK.length;
+        while (i--) {
+            if (TO_MOZ_STACK[i] instanceof AST_Destructuring) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    function to_moz_block(node) {
+        return {
+            type: "BlockStatement",
+            body: node.body.map(to_moz)
+        };
+    }
+
+    function to_moz_scope(type, node) {
+        var body = node.body.map(to_moz);
+        if (node.body[0] instanceof AST_SimpleStatement && node.body[0].body instanceof AST_String) {
+            body.unshift(to_moz(new AST_EmptyStatement(node.body[0])));
+        }
+        return {
+            type: type,
+            body: body
+        };
+    }
+})();
Index: frontend/node_modules/terser/lib/output.js
===================================================================
--- frontend/node_modules/terser/lib/output.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/output.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2551 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+"use strict";
+
+import {
+    defaults,
+    makePredicate,
+    noop,
+    regexp_source_fix,
+    sort_regexp_flags,
+    return_false,
+    return_true,
+} from "./utils/index.js";
+import { first_in_statement, left_is_object } from "./utils/first_in_statement.js";
+import {
+    AST_Array,
+    AST_Arrow,
+    AST_Assign,
+    AST_Await,
+    AST_BigInt,
+    AST_Binary,
+    AST_BlockStatement,
+    AST_Break,
+    AST_Call,
+    AST_Case,
+    AST_Catch,
+    AST_Chain,
+    AST_Class,
+    AST_ClassExpression,
+    AST_ClassPrivateProperty,
+    AST_ClassProperty,
+    AST_ClassStaticBlock,
+    AST_ConciseMethod,
+    AST_PrivateGetter,
+    AST_PrivateMethod,
+    AST_SymbolPrivateProperty,
+    AST_PrivateSetter,
+    AST_PrivateIn,
+    AST_Conditional,
+    AST_Const,
+    AST_Constant,
+    AST_Continue,
+    AST_Debugger,
+    AST_Default,
+    AST_DefaultAssign,
+    AST_Definitions,
+    AST_DefinitionsLike,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Directive,
+    AST_Do,
+    AST_Dot,
+    AST_DotHash,
+    AST_EmptyStatement,
+    AST_Exit,
+    AST_Expansion,
+    AST_Export,
+    AST_Finally,
+    AST_For,
+    AST_ForIn,
+    AST_ForOf,
+    AST_Function,
+    AST_Hole,
+    AST_If,
+    AST_DynamicImport,
+    AST_Import,
+    AST_ImportMeta,
+    AST_Jump,
+    AST_LabeledStatement,
+    AST_Lambda,
+    AST_Let,
+    AST_LoopControl,
+    AST_NameMapping,
+    AST_New,
+    AST_NewTarget,
+    AST_Node,
+    AST_Number,
+    AST_Object,
+    AST_ObjectGetter,
+    AST_ObjectKeyVal,
+    AST_ObjectProperty,
+    AST_ObjectSetter,
+    AST_PrefixedTemplateString,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Return,
+    AST_Scope,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Statement,
+    AST_StatementWithBody,
+    AST_String,
+    AST_Sub,
+    AST_Super,
+    AST_Switch,
+    AST_SwitchBranch,
+    AST_Symbol,
+    AST_SymbolClassProperty,
+    AST_SymbolMethod,
+    AST_SymbolRef,
+    AST_TemplateSegment,
+    AST_TemplateString,
+    AST_This,
+    AST_Throw,
+    AST_Toplevel,
+    AST_Try,
+    AST_TryBlock,
+    AST_Unary,
+    AST_UnaryPostfix,
+    AST_UnaryPrefix,
+    AST_Using,
+    AST_Var,
+    AST_VarDefLike,
+    AST_While,
+    AST_With,
+    AST_Yield,
+    TreeWalker,
+    walk,
+    walk_abort
+} from "./ast.js";
+import {
+    get_full_char_code,
+    get_full_char,
+    is_identifier_char,
+    is_basic_identifier_string,
+    is_identifier_string,
+    PRECEDENCE,
+    ALL_RESERVED_WORDS,
+} from "./parse.js";
+
+const CODE_LINE_BREAK = 10;
+const CODE_SPACE = 32;
+
+const r_annotation = /[@#]__(PURE|INLINE|NOINLINE)__/;
+
+function is_some_comments(comment) {
+    // multiline comment
+    return (
+        (comment.type === "comment2" || comment.type === "comment1")
+        && /@preserve|@copyright|@lic|@cc_on|^\**!/i.test(comment.value)
+    );
+}
+
+const ROPE_COMMIT_WHEN = 8 * 1000;
+class Rope {
+    constructor() {
+        this.committed = "";
+        this.current = "";
+    }
+
+    append(str) {
+        /** When `this.current` is too long, commit it. */
+        if (this.current.length > ROPE_COMMIT_WHEN) {
+            this.committed += this.current + str;
+            this.current = "";
+        } else {
+            this.current += str;
+        }
+    }
+
+    insertAt(char, index) {
+        const { committed, current } = this;
+        if (index < committed.length) {
+            this.committed = committed.slice(0, index) + char + committed.slice(index);
+        } else if (index === committed.length) {
+            this.committed += char;
+        } else {
+            index -= committed.length;
+            this.committed += current.slice(0, index) + char;
+            this.current = current.slice(index);
+        }
+    }
+
+    charAt(index) {
+        const { committed } = this;
+        if (index < committed.length) return committed[index];
+        return this.current[index - committed.length];
+    }
+
+    charCodeAt(index) {
+        const { committed } = this;
+        if (index < committed.length) return committed.charCodeAt(index);
+        return this.current.charCodeAt(index - committed.length);
+    }
+
+    length() {
+        return this.committed.length + this.current.length;
+    }
+
+    expectDirective() {
+        // /^$|[;{][\s\n]*$/
+
+        let ch, n = this.length();
+
+        if (n <= 0) return true;
+
+        // Skip N whitespace from the end
+        while (
+            (ch = this.charCodeAt(--n))
+            && (ch == CODE_SPACE || ch == CODE_LINE_BREAK)
+        );
+
+        // either ";", or "{", or the string ended
+        return !ch || ch === 59 || ch === 123;
+    }
+
+    hasNLB() {
+        let n = this.length() - 1;
+        while (n >= 0) {
+            const code = this.charCodeAt(n--);
+
+            if (code === CODE_LINE_BREAK) return true;
+            if (code !== CODE_SPACE) return false;
+        }
+        return true;
+    }
+
+
+    toString() {
+        return this.committed + this.current;
+    }
+}
+
+function OutputStream(options) {
+
+    var readonly = !options;
+    options = defaults(options, {
+        ascii_only           : false,
+        beautify             : false,
+        braces               : false,
+        comments             : "some",
+        ecma                 : 5,
+        ie8                  : false,
+        indent_level         : 4,
+        indent_start         : 0,
+        inline_script        : true,
+        keep_numbers         : false,
+        keep_quoted_props    : false,
+        max_line_len         : false,
+        preamble             : null,
+        preserve_annotations : false,
+        quote_keys           : false,
+        quote_style          : 0,
+        safari10             : false,
+        semicolons           : true,
+        shebang              : true,
+        shorthand            : undefined,
+        source_map           : null,
+        webkit               : false,
+        width                : 80,
+        wrap_iife            : false,
+        wrap_func_args       : false,
+
+        _destroy_ast         : false
+    }, true);
+
+    if (options.shorthand === undefined)
+        options.shorthand = options.ecma > 5;
+
+    // Convert comment option to RegExp if necessary and set up comments filter
+    var comment_filter = return_false; // Default case, throw all comments away
+    if (options.comments) {
+        let comments = options.comments;
+        if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) {
+            var regex_pos = options.comments.lastIndexOf("/");
+            comments = new RegExp(
+                options.comments.substr(1, regex_pos - 1),
+                options.comments.substr(regex_pos + 1)
+            );
+        }
+        if (comments instanceof RegExp) {
+            comment_filter = function(comment) {
+                return comment.type != "comment5" && comments.test(comment.value);
+            };
+        } else if (typeof comments === "function") {
+            comment_filter = function(comment) {
+                return comment.type != "comment5" && comments(this, comment);
+            };
+        } else if (comments === "some") {
+            comment_filter = is_some_comments;
+        } else { // NOTE includes "all" option
+            comment_filter = return_true;
+        }
+    }
+
+    if (options.preserve_annotations) {
+        let prev_comment_filter = comment_filter;
+        comment_filter = function (comment) {
+            return r_annotation.test(comment.value) || prev_comment_filter.apply(this, arguments);
+        };
+    }
+
+    var indentation = 0;
+    var current_col = 0;
+    var current_line = 1;
+    var current_pos = 0;
+    var OUTPUT = new Rope();
+    let printed_comments = new Set();
+
+    var to_utf8 = options.ascii_only ? function(str, identifier = false, regexp = false) {
+        if (options.ecma >= 2015 && !options.safari10 && !regexp) {
+            str = str.replace(/[\ud800-\udbff][\udc00-\udfff]/g, function(ch) {
+                var code = get_full_char_code(ch, 0).toString(16);
+                return "\\u{" + code + "}";
+            });
+        }
+        return str.replace(/[\u0000-\u001f\u007f-\uffff]/g, function(ch) {
+            var code = ch.charCodeAt(0).toString(16);
+            if (code.length <= 2 && !identifier) {
+                while (code.length < 2) code = "0" + code;
+                return "\\x" + code;
+            } else {
+                while (code.length < 4) code = "0" + code;
+                return "\\u" + code;
+            }
+        });
+    } : function(str) {
+        return str.replace(/[\ud800-\udbff][\udc00-\udfff]|([\ud800-\udbff]|[\udc00-\udfff])/g, function(match, lone) {
+            if (lone) {
+                return "\\u" + lone.charCodeAt(0).toString(16);
+            }
+            return match;
+        });
+    };
+
+    function make_string(str, quote) {
+        var dq = 0, sq = 0;
+        str = str.replace(/[\\\b\f\n\r\v\t\x22\x27\u2028\u2029\0\ufeff]/g,
+          function(s, i) {
+            switch (s) {
+              case '"': ++dq; return '"';
+              case "'": ++sq; return "'";
+              case "\\": return "\\\\";
+              case "\n": return "\\n";
+              case "\r": return "\\r";
+              case "\t": return "\\t";
+              case "\b": return "\\b";
+              case "\f": return "\\f";
+              case "\x0B": return options.ie8 ? "\\x0B" : "\\v";
+              case "\u2028": return "\\u2028";
+              case "\u2029": return "\\u2029";
+              case "\ufeff": return "\\ufeff";
+              case "\0":
+                  return /[0-9]/.test(get_full_char(str, i+1)) ? "\\x00" : "\\0";
+            }
+            return s;
+        });
+        function quote_single() {
+            return "'" + str.replace(/\x27/g, "\\'") + "'";
+        }
+        function quote_double() {
+            return '"' + str.replace(/\x22/g, '\\"') + '"';
+        }
+        function quote_template() {
+            return "`" + str.replace(/`/g, "\\`") + "`";
+        }
+        str = to_utf8(str);
+        if (quote === "`") return quote_template();
+        switch (options.quote_style) {
+          case 1:
+            return quote_single();
+          case 2:
+            return quote_double();
+          case 3:
+            return quote == "'" ? quote_single() : quote_double();
+          default:
+            return dq > sq ? quote_single() : quote_double();
+        }
+    }
+
+    function encode_string(str, quote) {
+        var ret = make_string(str, quote);
+        if (options.inline_script) {
+            ret = ret.replace(/<\x2f(script)([>\/\t\n\f\r ])/gi, "<\\/$1$2");
+            ret = ret.replace(/\x3c!--/g, "\\x3c!--");
+            ret = ret.replace(/--\x3e/g, "--\\x3e");
+        }
+        return ret;
+    }
+
+    function make_name(name) {
+        name = name.toString();
+        name = to_utf8(name, true);
+        return name;
+    }
+
+    function make_indent(back) {
+        return " ".repeat(options.indent_start + indentation - back * options.indent_level);
+    }
+
+    /* -----[ beautification/minification ]----- */
+
+    var has_parens = false;
+    var might_need_space = false;
+    var might_need_semicolon = false;
+    var might_add_newline = 0;
+    var need_newline_indented = false;
+    var need_space = false;
+    var newline_insert = -1;
+    var last = "";
+    var mapping_token, mapping_name, mappings = options.source_map && [];
+
+    var do_add_mapping = mappings ? function() {
+        mappings.forEach(function(mapping) {
+            try {
+                let { name, token } = mapping;
+                if (name !== false) {
+                    if (token.type == "name" || token.type === "privatename") {
+                        name = token.value;
+                    } else if (name instanceof AST_Symbol) {
+                        name = token.type === "string" ? token.value : name.name;
+                    }
+                }
+                options.source_map.add(
+                    mapping.token.file,
+                    mapping.line, mapping.col,
+                    mapping.token.line, mapping.token.col,
+                    is_basic_identifier_string(name) ? name : undefined
+                );
+            } catch(ex) {
+                // Ignore bad mapping
+            }
+        });
+        mappings = [];
+    } : noop;
+
+    var ensure_line_len = options.max_line_len ? function() {
+        if (current_col > options.max_line_len) {
+            if (might_add_newline) {
+                OUTPUT.insertAt("\n", might_add_newline);
+                const len_after_newline = OUTPUT.length() - might_add_newline - 1;
+                if (mappings) {
+                    var delta = len_after_newline - current_col;
+                    mappings.forEach(function(mapping) {
+                        mapping.line++;
+                        mapping.col += delta;
+                    });
+                }
+                current_line++;
+                current_pos++;
+                current_col = len_after_newline;
+            }
+        }
+        if (might_add_newline) {
+            might_add_newline = 0;
+            do_add_mapping();
+        }
+    } : noop;
+
+    var requireSemicolonChars = makePredicate("( [ + * / - , . `");
+
+    function print(str) {
+        str = String(str);
+        var ch = get_full_char(str, 0);
+        if (need_newline_indented && ch) {
+            need_newline_indented = false;
+            if (ch !== "\n") {
+                print("\n");
+                indent();
+            }
+        }
+        if (need_space && ch) {
+            need_space = false;
+            if (!/[\s;})]/.test(ch)) {
+                space();
+            }
+        }
+        newline_insert = -1;
+        var prev = last.charAt(last.length - 1);
+        if (might_need_semicolon) {
+            might_need_semicolon = false;
+
+            if (prev === ":" && ch === "}" || (!ch || !";}".includes(ch)) && prev !== ";") {
+                if (options.semicolons || requireSemicolonChars.has(ch)) {
+                    OUTPUT.append(";");
+                    current_col++;
+                    current_pos++;
+                } else {
+                    ensure_line_len();
+                    if (current_col > 0) {
+                        OUTPUT.append("\n");
+                        current_pos++;
+                        current_line++;
+                        current_col = 0;
+                    }
+
+                    if (/^\s+$/.test(str)) {
+                        // reset the semicolon flag, since we didn't print one
+                        // now and might still have to later
+                        might_need_semicolon = true;
+                    }
+                }
+
+                if (!options.beautify)
+                    might_need_space = false;
+            }
+        }
+
+        if (might_need_space) {
+            if ((is_identifier_char(prev)
+                    && (is_identifier_char(ch) || ch == "\\"))
+                || (ch == "/" && ch == prev)
+                || ((ch == "+" || ch == "-") && ch == last)
+            ) {
+                OUTPUT.append(" ");
+                current_col++;
+                current_pos++;
+            }
+            might_need_space = false;
+        }
+
+        if (mapping_token) {
+            mappings.push({
+                token: mapping_token,
+                name: mapping_name,
+                line: current_line,
+                col: current_col
+            });
+            mapping_token = false;
+            if (!might_add_newline) do_add_mapping();
+        }
+
+        OUTPUT.append(str);
+        has_parens = str[str.length - 1] == "(";
+        current_pos += str.length;
+        var a = str.split(/\r?\n/), n = a.length - 1;
+        current_line += n;
+        current_col += a[0].length;
+        if (n > 0) {
+            ensure_line_len();
+            current_col = a[n].length;
+        }
+        last = str;
+    }
+
+    var star = function() {
+        print("*");
+    };
+
+    var space = options.beautify ? function() {
+        print(" ");
+    } : function() {
+        might_need_space = true;
+    };
+
+    var indent = options.beautify ? function(half) {
+        if (options.beautify) {
+            print(make_indent(half ? 0.5 : 0));
+        }
+    } : noop;
+
+    var with_indent = options.beautify ? function(col, cont) {
+        if (col === true) col = next_indent();
+        var save_indentation = indentation;
+        indentation = col;
+        var ret = cont();
+        indentation = save_indentation;
+        return ret;
+    } : function(col, cont) { return cont(); };
+
+    var newline = options.beautify ? function() {
+        if (newline_insert < 0) return print("\n");
+        if (OUTPUT.charAt(newline_insert) != "\n") {
+            OUTPUT.insertAt("\n", newline_insert);
+            current_pos++;
+            current_line++;
+        }
+        newline_insert++;
+    } : options.max_line_len ? function() {
+        ensure_line_len();
+        might_add_newline = OUTPUT.length();
+    } : noop;
+
+    var semicolon = options.beautify ? function() {
+        print(";");
+    } : function() {
+        might_need_semicolon = true;
+    };
+
+    function force_semicolon() {
+        might_need_semicolon = false;
+        print(";");
+    }
+
+    function next_indent() {
+        return indentation + options.indent_level;
+    }
+
+    function with_block(cont) {
+        var ret;
+        print("{");
+        newline();
+        with_indent(next_indent(), function() {
+            ret = cont();
+        });
+        indent();
+        print("}");
+        return ret;
+    }
+
+    function with_parens(cont) {
+        print("(");
+        //XXX: still nice to have that for argument lists
+        //var ret = with_indent(current_col, cont);
+        var ret = cont();
+        print(")");
+        return ret;
+    }
+
+    function with_square(cont) {
+        print("[");
+        //var ret = with_indent(current_col, cont);
+        var ret = cont();
+        print("]");
+        return ret;
+    }
+
+    function comma() {
+        print(",");
+        space();
+    }
+
+    function colon() {
+        print(":");
+        space();
+    }
+
+    var add_mapping = mappings ? function(token, name) {
+        mapping_token = token;
+        mapping_name = name;
+    } : noop;
+
+    function get() {
+        if (might_add_newline) {
+            ensure_line_len();
+        }
+        return OUTPUT.toString();
+    }
+
+    function filter_comment(comment) {
+        if (!options.preserve_annotations) {
+            comment = comment.replace(r_annotation, " ");
+        }
+        if (/^\s*$/.test(comment)) {
+            return "";
+        }
+        return comment.replace(/(<\s*\/\s*)(script)/i, "<\\/$2");
+    }
+
+    function prepend_comments(node) {
+        var self = this;
+        var start = node.start;
+        if (!start) return;
+        var printed_comments = self.printed_comments;
+
+        // There cannot be a newline between return/yield and its value.
+        const keyword_with_value = 
+            node instanceof AST_Exit && node.value
+            || (node instanceof AST_Await || node instanceof AST_Yield)
+                && node.expression;
+
+        if (
+            start.comments_before
+            && printed_comments.has(start.comments_before)
+        ) {
+            if (keyword_with_value) {
+                start.comments_before = [];
+            } else {
+                return;
+            }
+        }
+
+        var comments = start.comments_before;
+        if (!comments) {
+            comments = start.comments_before = [];
+        }
+        printed_comments.add(comments);
+
+        if (keyword_with_value) {
+            var tw = new TreeWalker(function(node) {
+                var parent = tw.parent();
+                if (parent instanceof AST_Exit
+                    || parent instanceof AST_Await
+                    || parent instanceof AST_Yield
+                    || parent instanceof AST_Binary && parent.left === node
+                    || parent.TYPE == "Call" && parent.expression === node
+                    || parent instanceof AST_Conditional && parent.condition === node
+                    || parent instanceof AST_Dot && parent.expression === node
+                    || parent instanceof AST_Sequence && parent.expressions[0] === node
+                    || parent instanceof AST_Sub && parent.expression === node
+                    || parent instanceof AST_UnaryPostfix) {
+                    if (!node.start) return;
+                    var text = node.start.comments_before;
+                    if (text && !printed_comments.has(text)) {
+                        printed_comments.add(text);
+                        comments = comments.concat(text);
+                    }
+                } else {
+                    return true;
+                }
+            });
+            tw.push(node);
+            keyword_with_value.walk(tw);
+        }
+
+        if (current_pos == 0) {
+            if (comments.length > 0 && options.shebang && comments[0].type === "comment5"
+                && !printed_comments.has(comments[0])) {
+                print("#!" + comments.shift().value + "\n");
+                indent();
+            }
+            var preamble = options.preamble;
+            if (preamble) {
+                print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n"));
+            }
+        }
+
+        comments = comments.filter(comment_filter, node).filter(c => !printed_comments.has(c));
+        if (comments.length == 0) return;
+        var last_nlb = OUTPUT.hasNLB();
+        comments.forEach(function(c, i) {
+            printed_comments.add(c);
+            if (!last_nlb) {
+                if (c.nlb) {
+                    print("\n");
+                    indent();
+                    last_nlb = true;
+                } else if (i > 0) {
+                    space();
+                }
+            }
+
+            if (/comment[134]/.test(c.type)) {
+                var value = filter_comment(c.value);
+                if (value) {
+                    print("//" + value + "\n");
+                    indent();
+                }
+                last_nlb = true;
+            } else if (c.type == "comment2") {
+                var value = filter_comment(c.value);
+                if (value) {
+                    print("/*" + value + "*/");
+                }
+                last_nlb = false;
+            }
+        });
+        if (!last_nlb) {
+            if (start.nlb) {
+                print("\n");
+                indent();
+            } else {
+                space();
+            }
+        }
+    }
+
+    function append_comments(node, tail) {
+        var self = this;
+        var token = node.end;
+        if (!token) return;
+        var printed_comments = self.printed_comments;
+        var comments = token[tail ? "comments_before" : "comments_after"];
+        if (!comments || printed_comments.has(comments)) return;
+        if (!(node instanceof AST_Statement || comments.every((c) =>
+            !/comment[134]/.test(c.type)
+        ))) return;
+        printed_comments.add(comments);
+        var insert = OUTPUT.length();
+        comments.filter(comment_filter, node).forEach(function(c, i) {
+            if (printed_comments.has(c)) return;
+            printed_comments.add(c);
+            need_space = false;
+            if (need_newline_indented) {
+                print("\n");
+                indent();
+                need_newline_indented = false;
+            } else if (c.nlb && (i > 0 || !OUTPUT.hasNLB())) {
+                print("\n");
+                indent();
+            } else if (i > 0 || !tail) {
+                space();
+            }
+            if (/comment[134]/.test(c.type)) {
+                const value = filter_comment(c.value);
+                if (value) {
+                    print("//" + value);
+                }
+                need_newline_indented = true;
+            } else if (c.type == "comment2") {
+                const value = filter_comment(c.value);
+                if (value) {
+                    print("/*" + value + "*/");
+                }
+                need_space = true;
+            }
+        });
+        if (OUTPUT.length() > insert) newline_insert = insert;
+    }
+
+    /**
+     * When output.option("_destroy_ast") is enabled, destroy the function.
+     * Call this after printing it.
+     */
+    const gc_scope =
+      options["_destroy_ast"]
+        ? function gc_scope(scope) {
+            scope.body.length = 0;
+            scope.argnames.length = 0;
+        }
+        : noop;
+
+    var stack = [];
+    return {
+        get             : get,
+        toString        : get,
+        indent          : indent,
+        in_directive    : false,
+        use_asm         : null,
+        active_scope    : null,
+        indentation     : function() { return indentation; },
+        current_width   : function() { return current_col - indentation; },
+        should_break    : function() { return options.width && this.current_width() >= options.width; },
+        has_parens      : function() { return has_parens; },
+        newline         : newline,
+        print           : print,
+        star            : star,
+        space           : space,
+        comma           : comma,
+        colon           : colon,
+        last            : function() { return last; },
+        semicolon       : semicolon,
+        force_semicolon : force_semicolon,
+        to_utf8         : to_utf8,
+        print_name      : function(name) { print(make_name(name)); },
+        print_string    : function(str, quote, escape_directive) {
+            var encoded = encode_string(str, quote);
+            if (escape_directive === true && !encoded.includes("\\")) {
+                // Insert semicolons to break directive prologue
+                if (!OUTPUT.expectDirective()) {
+                    force_semicolon();
+                }
+                force_semicolon();
+            }
+            print(encoded);
+        },
+        print_template_string_chars: function(str) {
+            var encoded = encode_string(str, "`").replace(/\${/g, "\\${");
+            return print(encoded.substr(1, encoded.length - 2));
+        },
+        encode_string   : encode_string,
+        next_indent     : next_indent,
+        with_indent     : with_indent,
+        with_block      : with_block,
+        with_parens     : with_parens,
+        with_square     : with_square,
+        add_mapping     : add_mapping,
+        option          : function(opt) { return options[opt]; },
+        gc_scope,
+        printed_comments: printed_comments,
+        prepend_comments: readonly ? noop : prepend_comments,
+        append_comments : readonly || comment_filter === return_false ? noop : append_comments,
+        line            : function() { return current_line; },
+        col             : function() { return current_col; },
+        pos             : function() { return current_pos; },
+        push_node       : function(node) { stack.push(node); },
+        pop_node        : function() { return stack.pop(); },
+        parent          : function(n) {
+            return stack[stack.length - 2 - (n || 0)];
+        }
+    };
+
+}
+
+/* -----[ code generators ]----- */
+
+(function() {
+
+    /* -----[ utils ]----- */
+
+    function DEFPRINT(nodetype, generator) {
+        nodetype.DEFMETHOD("_codegen", generator);
+    }
+
+    AST_Node.DEFMETHOD("print", function(output, force_parens) {
+        var self = this, generator = self._codegen;
+        if (self instanceof AST_Scope) {
+            output.active_scope = self;
+        } else if (!output.use_asm && self instanceof AST_Directive && self.value == "use asm") {
+            output.use_asm = output.active_scope;
+        }
+        function doit() {
+            output.prepend_comments(self);
+            self.add_source_map(output);
+            generator(self, output);
+            output.append_comments(self);
+        }
+        output.push_node(self);
+        if (force_parens || self.needs_parens(output)) {
+            output.with_parens(doit);
+        } else {
+            doit();
+        }
+        output.pop_node();
+        if (self === output.use_asm) {
+            output.use_asm = null;
+        }
+    });
+    AST_Node.DEFMETHOD("_print", AST_Node.prototype.print);
+
+    AST_Node.DEFMETHOD("print_to_string", function(options) {
+        var output = OutputStream(options);
+        this.print(output);
+        return output.get();
+    });
+
+    /* -----[ PARENTHESES ]----- */
+
+    function PARENS(nodetype, func) {
+        if (Array.isArray(nodetype)) {
+            nodetype.forEach(function(nodetype) {
+                PARENS(nodetype, func);
+            });
+        } else {
+            nodetype.DEFMETHOD("needs_parens", func);
+        }
+    }
+
+    PARENS(AST_Node, return_false);
+
+    // a function expression needs parens around it when it's provably
+    // the first token to appear in a statement.
+    PARENS(AST_Function, function(output) {
+        if (!output.has_parens() && first_in_statement(output)) {
+            return true;
+        }
+
+        if (output.option("webkit")) {
+            var p = output.parent();
+            if (p instanceof AST_PropAccess && p.expression === this) {
+                return true;
+            }
+        }
+
+        if (output.option("wrap_iife")) {
+            var p = output.parent();
+            if (p instanceof AST_Call && p.expression === this) {
+                return true;
+            }
+        }
+
+        if (output.option("wrap_func_args")) {
+            var p = output.parent();
+            if (p instanceof AST_Call && p.args.includes(this)) {
+                return true;
+            }
+        }
+
+        return false;
+    });
+
+    PARENS(AST_Arrow, function(output) {
+        var p = output.parent();
+
+        if (
+            output.option("wrap_func_args")
+            && p instanceof AST_Call
+            && p.args.includes(this)
+        ) {
+            return true;
+        }
+        return p instanceof AST_PropAccess && p.expression === this
+            || p instanceof AST_Conditional && p.condition === this;
+    });
+
+    // same goes for an object literal (as in AST_Function), because
+    // otherwise {...} would be interpreted as a block of code.
+    PARENS(AST_Object, function(output) {
+        return !output.has_parens() && first_in_statement(output);
+    });
+
+    PARENS(AST_ClassExpression, first_in_statement);
+
+    PARENS(AST_Unary, function(output) {
+        var p = output.parent();
+        return p instanceof AST_PropAccess && p.expression === this
+            || p instanceof AST_Call && p.expression === this
+            || p instanceof AST_Binary
+                && p.operator === "**"
+                && this instanceof AST_UnaryPrefix
+                && p.left === this
+                && this.operator !== "++"
+                && this.operator !== "--";
+    });
+
+    PARENS(AST_Await, function(output) {
+        var p = output.parent();
+        return p instanceof AST_PropAccess && p.expression === this
+            || p instanceof AST_Call && p.expression === this
+            || p instanceof AST_Binary && p.operator === "**" && p.left === this
+            || output.option("safari10") && p instanceof AST_UnaryPrefix;
+    });
+
+    PARENS(AST_Sequence, function(output) {
+        var p = output.parent();
+        return p instanceof AST_Call                              // (foo, bar)() or foo(1, (2, 3), 4)
+            || p instanceof AST_Unary                             // !(foo, bar, baz)
+            || p instanceof AST_Binary                            // 1 + (2, 3) + 4 ==> 8
+            || p instanceof AST_VarDefLike                        // var a = (1, 2), b = a + a; ==> b == 4
+            || p instanceof AST_PropAccess && this !== p.property // (1, {foo:2}).foo, (1, {foo:2})["foo"], not foo[1, 2]
+            || p instanceof AST_Array                             // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
+            || p instanceof AST_ObjectProperty                    // { foo: (1, 2) }.foo ==> 2
+            || p instanceof AST_Conditional                       /* (false, true) ? (a = 10, b = 20) : (c = 30)
+                                                                   * ==> 20 (side effect, set a := 10 and b := 20) */
+            || p instanceof AST_Arrow                             // x => (x, x)
+            || p instanceof AST_DefaultAssign                     // x => (x = (0, function(){}))
+            || p instanceof AST_Expansion                         // [...(a, b)]
+            || p instanceof AST_ForOf && this === p.object        // for (e of (foo, bar)) {}
+            || p instanceof AST_Yield                             // yield (foo, bar)
+            || p instanceof AST_Export                            // export default (foo, bar)
+        ;
+    });
+
+    PARENS(AST_Binary, function(output) {
+        var p = output.parent();
+        // (foo && bar)()
+        if (p instanceof AST_Call && p.expression === this)
+            return true;
+        // typeof (foo && bar)
+        if (p instanceof AST_Unary)
+            return true;
+        // (foo && bar)["prop"], (foo && bar).prop
+        if (p instanceof AST_PropAccess && p.expression === this)
+            return true;
+        // this deals with precedence: 3 * (2 + 1)
+        if (p instanceof AST_Binary) {
+            const parent_op = p.operator;
+            const op = this.operator;
+
+            // It is forbidden for ?? to be used with || or && without parens.
+            if (op === "??" && (parent_op === "||" || parent_op === "&&")) {
+                return true;
+            }
+            if (parent_op === "??" && (op === "||" || op === "&&")) {
+                return true;
+            }
+
+            const pp = PRECEDENCE[parent_op];
+            const sp = PRECEDENCE[op];
+            if (pp > sp
+                || (pp == sp
+                    && (this === p.right || parent_op == "**"))) {
+                return true;
+            }
+        }
+        if (p instanceof AST_PrivateIn) {
+            const op = this.operator;
+
+            const pp = PRECEDENCE["in"];
+            const sp = PRECEDENCE[op];
+            if (pp > sp || (pp == sp && this === p.value)) {
+                return true;
+            }
+        }
+    });
+
+    PARENS(AST_PrivateIn, function(output) {
+        var p = output.parent();
+        // (#x in this)()
+        if (p instanceof AST_Call && p.expression === this) {
+            return true;
+        }
+        // typeof (#x in this)
+        if (p instanceof AST_Unary) {
+            return true;
+        }
+        // (#x in this)["prop"], (#x in this).prop
+        if (p instanceof AST_PropAccess && p.expression === this) {
+            return true;
+        }
+        // same precedence as regular in operator
+        if (p instanceof AST_Binary) {
+            const parent_op = p.operator;
+
+            const pp = PRECEDENCE[parent_op];
+            const sp = PRECEDENCE["in"];
+            if (pp > sp
+                || (pp == sp
+                    && (this === p.right || parent_op == "**"))) {
+                return true;
+            }
+        }
+        // rules are the same as binary in, but the class differs
+        if (p instanceof AST_PrivateIn && this === p.value) {
+            return true;
+        }
+    });
+
+    PARENS(AST_Yield, function(output) {
+        var p = output.parent();
+        // (yield 1) + (yield 2)
+        // a = yield 3
+        if (p instanceof AST_Binary && p.operator !== "=")
+            return true;
+        // (yield 1)()
+        // new (yield 1)()
+        if (p instanceof AST_Call && p.expression === this)
+            return true;
+        // (yield 1) ? yield 2 : yield 3
+        if (p instanceof AST_Conditional && p.condition === this)
+            return true;
+        // -(yield 4)
+        if (p instanceof AST_Unary)
+            return true;
+        // (yield x).foo
+        // (yield x)['foo']
+        if (p instanceof AST_PropAccess && p.expression === this)
+            return true;
+    });
+
+    PARENS(AST_Chain, function(output) {
+        var p = output.parent();
+        if (!(p instanceof AST_Call || p instanceof AST_PropAccess)) return false;
+        return p.expression === this;
+    });
+
+    PARENS(AST_PropAccess, function(output) {
+        var p = output.parent();
+        if (p instanceof AST_New && p.expression === this) {
+            // i.e. new (foo.bar().baz)
+            //
+            // if there's one call into this subtree, then we need
+            // parens around it too, otherwise the call will be
+            // interpreted as passing the arguments to the upper New
+            // expression.
+            return walk(this, node => {
+                if (node instanceof AST_Scope) return true;
+                if (node instanceof AST_Call) {
+                    return walk_abort;  // makes walk() return true.
+                }
+            });
+        }
+    });
+
+    PARENS(AST_Call, function(output) {
+        var p = output.parent(), p1;
+        if (p instanceof AST_New && p.expression === this
+            || p instanceof AST_Export && p.is_default && this.expression instanceof AST_Function)
+            return true;
+
+        // workaround for Safari bug.
+        // https://bugs.webkit.org/show_bug.cgi?id=123506
+        return this.expression instanceof AST_Function
+            && p instanceof AST_PropAccess
+            && p.expression === this
+            && (p1 = output.parent(1)) instanceof AST_Assign
+            && p1.left === p;
+    });
+
+    PARENS(AST_New, function(output) {
+        var p = output.parent();
+        if (this.args.length === 0
+            && (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]()
+                || p instanceof AST_Call && p.expression === this
+                || p instanceof AST_PrefixedTemplateString && p.prefix === this)) // (new foo)(bar)
+            return true;
+    });
+
+    PARENS(AST_Number, function(output) {
+        var p = output.parent();
+        if (p instanceof AST_PropAccess && p.expression === this) {
+            var value = this.getValue();
+            if (value < 0 || /^0/.test(make_num(value))) {
+                return true;
+            }
+        }
+    });
+
+    PARENS(AST_BigInt, function(output) {
+        var p = output.parent();
+        if (p instanceof AST_PropAccess && p.expression === this) {
+            var value = this.getValue();
+            if (value.startsWith("-")) {
+                return true;
+            }
+        }
+    });
+
+    PARENS([ AST_Assign, AST_Conditional ], function(output) {
+        var p = output.parent();
+        // !(a = false) → true
+        if (p instanceof AST_Unary)
+            return true;
+        // 1 + (a = 2) + 3 → 6, side effect setting a = 2
+        if (p instanceof AST_Binary && !(p instanceof AST_Assign))
+            return true;
+        // (a = func)() —or— new (a = Object)()
+        if (p instanceof AST_Call && p.expression === this)
+            return true;
+        // (a = foo) ? bar : baz
+        if (p instanceof AST_Conditional && p.condition === this)
+            return true;
+        // (a = foo)["prop"] —or— (a = foo).prop
+        if (p instanceof AST_PropAccess && p.expression === this)
+            return true;
+        // ({a, b} = {a: 1, b: 2}), a destructuring assignment
+        if (this instanceof AST_Assign && this.left instanceof AST_Destructuring && this.left.is_array === false)
+            return true;
+    });
+
+    /* -----[ PRINTERS ]----- */
+
+    DEFPRINT(AST_Directive, function(self, output) {
+        output.print_string(self.value, self.quote);
+        output.semicolon();
+    });
+
+    DEFPRINT(AST_Expansion, function (self, output) {
+        output.print("...");
+        self.expression.print(output);
+    });
+
+    DEFPRINT(AST_Destructuring, function (self, output) {
+        output.print(self.is_array ? "[" : "{");
+        var len = self.names.length;
+        self.names.forEach(function (name, i) {
+            if (i > 0) output.comma();
+            name.print(output);
+            // If the final element is a hole, we need to make sure it
+            // doesn't look like a trailing comma, by inserting an actual
+            // trailing comma.
+            if (i == len - 1 && name instanceof AST_Hole) output.comma();
+        });
+        output.print(self.is_array ? "]" : "}");
+    });
+
+    DEFPRINT(AST_Debugger, function(self, output) {
+        output.print("debugger");
+        output.semicolon();
+    });
+
+    /* -----[ statements ]----- */
+
+    function display_body(body, is_toplevel, output, allow_directives) {
+        var last = body.length - 1;
+        output.in_directive = allow_directives;
+        body.forEach(function(stmt, i) {
+            if (output.in_directive === true && !(stmt instanceof AST_Directive ||
+                stmt instanceof AST_EmptyStatement ||
+                (stmt instanceof AST_SimpleStatement && stmt.body instanceof AST_String)
+            )) {
+                output.in_directive = false;
+            }
+            if (!(stmt instanceof AST_EmptyStatement)) {
+                output.indent();
+                stmt.print(output);
+                if (!(i == last && is_toplevel)) {
+                    output.newline();
+                    if (is_toplevel) output.newline();
+                }
+            }
+            if (output.in_directive === true &&
+                stmt instanceof AST_SimpleStatement &&
+                stmt.body instanceof AST_String
+            ) {
+                output.in_directive = false;
+            }
+        });
+        output.in_directive = false;
+    }
+
+    AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output) {
+        print_maybe_braced_body(this.body, output);
+    });
+
+    DEFPRINT(AST_Statement, function(self, output) {
+        self.body.print(output);
+        output.semicolon();
+    });
+    DEFPRINT(AST_Toplevel, function(self, output) {
+        display_body(self.body, true, output, true);
+        output.print("");
+    });
+    DEFPRINT(AST_LabeledStatement, function(self, output) {
+        self.label.print(output);
+        output.colon();
+        self.body.print(output);
+    });
+    DEFPRINT(AST_SimpleStatement, function(self, output) {
+        self.body.print(output);
+        output.semicolon();
+    });
+    function print_braced_empty(self, output) {
+        output.print("{");
+        output.with_indent(output.next_indent(), function() {
+            output.append_comments(self, true);
+        });
+        output.add_mapping(self.end);
+        output.print("}");
+    }
+    function print_braced(self, output, allow_directives) {
+        if (self.body.length > 0) {
+            output.with_block(function() {
+                display_body(self.body, false, output, allow_directives);
+                output.add_mapping(self.end);
+            });
+        } else print_braced_empty(self, output);
+    }
+    DEFPRINT(AST_BlockStatement, function(self, output) {
+        print_braced(self, output);
+    });
+    DEFPRINT(AST_EmptyStatement, function(self, output) {
+        output.semicolon();
+    });
+    DEFPRINT(AST_Do, function(self, output) {
+        output.print("do");
+        output.space();
+        make_block(self.body, output);
+        output.space();
+        output.print("while");
+        output.space();
+        output.with_parens(function() {
+            self.condition.print(output);
+        });
+        output.semicolon();
+    });
+    DEFPRINT(AST_While, function(self, output) {
+        output.print("while");
+        output.space();
+        output.with_parens(function() {
+            self.condition.print(output);
+        });
+        output.space();
+        self._do_print_body(output);
+    });
+    DEFPRINT(AST_For, function(self, output) {
+        output.print("for");
+        output.space();
+        output.with_parens(function() {
+            if (self.init) {
+                if (self.init instanceof AST_DefinitionsLike) {
+                    self.init.print(output);
+                } else {
+                    parenthesize_for_noin(self.init, output, true);
+                }
+                output.print(";");
+                output.space();
+            } else {
+                output.print(";");
+            }
+            if (self.condition) {
+                self.condition.print(output);
+                output.print(";");
+                output.space();
+            } else {
+                output.print(";");
+            }
+            if (self.step) {
+                self.step.print(output);
+            }
+        });
+        output.space();
+        self._do_print_body(output);
+    });
+    DEFPRINT(AST_ForIn, function(self, output) {
+        output.print("for");
+        if (self.await) {
+            output.space();
+            output.print("await");
+        }
+        output.space();
+        output.with_parens(function() {
+            self.init.print(output);
+            output.space();
+            output.print(self instanceof AST_ForOf ? "of" : "in");
+            output.space();
+            self.object.print(output);
+        });
+        output.space();
+        self._do_print_body(output);
+    });
+    DEFPRINT(AST_With, function(self, output) {
+        output.print("with");
+        output.space();
+        output.with_parens(function() {
+            self.expression.print(output);
+        });
+        output.space();
+        self._do_print_body(output);
+    });
+
+    /* -----[ functions ]----- */
+    AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword) {
+        var self = this;
+        if (!nokeyword) {
+            if (self.async) {
+                output.print("async");
+                output.space();
+            }
+            output.print("function");
+            if (self.is_generator) {
+                output.star();
+            }
+            if (self.name) {
+                output.space();
+            }
+        }
+        if (self.name instanceof AST_Symbol) {
+            self.name.print(output);
+        } else if (nokeyword && self.name instanceof AST_Node) {
+            output.with_square(function() {
+                self.name.print(output); // Computed method name
+            });
+        }
+        output.with_parens(function() {
+            self.argnames.forEach(function(arg, i) {
+                if (i) output.comma();
+                arg.print(output);
+            });
+        });
+        output.space();
+        print_braced(self, output, true);
+    });
+    DEFPRINT(AST_Lambda, function(self, output) {
+        self._do_print(output);
+        output.gc_scope(self);
+    });
+
+    DEFPRINT(AST_PrefixedTemplateString, function(self, output) {
+        var tag = self.prefix;
+        var parenthesize_tag = tag instanceof AST_Lambda
+            || tag instanceof AST_Binary
+            || tag instanceof AST_Conditional
+            || tag instanceof AST_Sequence
+            || tag instanceof AST_Unary
+            || tag instanceof AST_Dot && tag.expression instanceof AST_Object;
+        if (parenthesize_tag) output.print("(");
+        self.prefix.print(output);
+        if (parenthesize_tag) output.print(")");
+        self.template_string.print(output);
+    });
+    DEFPRINT(AST_TemplateString, function(self, output) {
+        var is_tagged = output.parent() instanceof AST_PrefixedTemplateString;
+
+        output.print("`");
+        for (var i = 0; i < self.segments.length; i++) {
+            if (!(self.segments[i] instanceof AST_TemplateSegment)) {
+                output.print("${");
+                self.segments[i].print(output);
+                output.print("}");
+            } else if (is_tagged) {
+                output.print(self.segments[i].raw);
+            } else {
+                output.print_template_string_chars(self.segments[i].value);
+            }
+        }
+        output.print("`");
+    });
+    DEFPRINT(AST_TemplateSegment, function(self, output) {
+        output.print_template_string_chars(self.value);
+    });
+
+    AST_Arrow.DEFMETHOD("_do_print", function(output) {
+        var self = this;
+        var parent = output.parent();
+        var needs_parens = (parent instanceof AST_Binary &&
+                !(parent instanceof AST_Assign) &&
+                !(parent instanceof AST_DefaultAssign)) ||
+            parent instanceof AST_Unary ||
+            (parent instanceof AST_Call && self === parent.expression);
+        if (needs_parens) { output.print("("); }
+        if (self.async) {
+            output.print("async");
+            output.space();
+        }
+        if (self.argnames.length === 1 && self.argnames[0] instanceof AST_Symbol) {
+            self.argnames[0].print(output);
+        } else {
+            output.with_parens(function() {
+                self.argnames.forEach(function(arg, i) {
+                    if (i) output.comma();
+                    arg.print(output);
+                });
+            });
+        }
+        output.space();
+        output.print("=>");
+        output.space();
+        const first_statement = self.body[0];
+        if (
+            self.body.length === 1
+            && first_statement instanceof AST_Return
+        ) {
+            const returned = first_statement.value;
+            if (!returned) {
+                output.print("{}");
+            } else if (left_is_object(returned)) {
+                output.print("(");
+                returned.print(output);
+                output.print(")");
+            } else {
+                returned.print(output);
+            }
+        } else {
+            print_braced(self, output);
+        }
+        if (needs_parens) { output.print(")"); }
+        output.gc_scope(self);
+    });
+
+    /* -----[ exits ]----- */
+    AST_Exit.DEFMETHOD("_do_print", function(output, kind) {
+        output.print(kind);
+        if (this.value) {
+            output.space();
+            const comments = this.value.start.comments_before;
+            if (comments && comments.length && !output.printed_comments.has(comments)) {
+                output.print("(");
+                this.value.print(output);
+                output.print(")");
+            } else {
+                this.value.print(output);
+            }
+        }
+        output.semicolon();
+    });
+    DEFPRINT(AST_Return, function(self, output) {
+        self._do_print(output, "return");
+    });
+    DEFPRINT(AST_Throw, function(self, output) {
+        self._do_print(output, "throw");
+    });
+
+    /* -----[ yield ]----- */
+
+    DEFPRINT(AST_Yield, function(self, output) {
+        var star = self.is_star ? "*" : "";
+        output.print("yield" + star);
+        if (self.expression) {
+            output.space();
+            self.expression.print(output);
+        }
+    });
+
+    DEFPRINT(AST_Await, function(self, output) {
+        output.print("await");
+        output.space();
+        var e = self.expression;
+        var parens = !(
+               e instanceof AST_Call
+            || e instanceof AST_SymbolRef
+            || e instanceof AST_PropAccess
+            || e instanceof AST_Unary
+            || e instanceof AST_Constant
+            || e instanceof AST_Await
+            || e instanceof AST_Object
+        );
+        if (parens) output.print("(");
+        self.expression.print(output);
+        if (parens) output.print(")");
+    });
+
+    /* -----[ loop control ]----- */
+    AST_LoopControl.DEFMETHOD("_do_print", function(output, kind) {
+        output.print(kind);
+        if (this.label) {
+            output.space();
+            this.label.print(output);
+        }
+        output.semicolon();
+    });
+    DEFPRINT(AST_Break, function(self, output) {
+        self._do_print(output, "break");
+    });
+    DEFPRINT(AST_Continue, function(self, output) {
+        self._do_print(output, "continue");
+    });
+
+    /* -----[ if ]----- */
+    function make_then(self, output) {
+        var b = self.body;
+        if (output.option("braces")
+            || output.option("ie8") && b instanceof AST_Do)
+            return make_block(b, output);
+        // The squeezer replaces "block"-s that contain only a single
+        // statement with the statement itself; technically, the AST
+        // is correct, but this can create problems when we output an
+        // IF having an ELSE clause where the THEN clause ends in an
+        // IF *without* an ELSE block (then the outer ELSE would refer
+        // to the inner IF).  This function checks for this case and
+        // adds the block braces if needed.
+        if (!b) return output.force_semicolon();
+        while (true) {
+            if (b instanceof AST_If) {
+                if (!b.alternative) {
+                    make_block(self.body, output);
+                    return;
+                }
+                b = b.alternative;
+            } else if (b instanceof AST_StatementWithBody) {
+                b = b.body;
+            } else break;
+        }
+        print_maybe_braced_body(self.body, output);
+    }
+    DEFPRINT(AST_If, function(self, output) {
+        output.print("if");
+        output.space();
+        output.with_parens(function() {
+            self.condition.print(output);
+        });
+        output.space();
+        if (self.alternative) {
+            make_then(self, output);
+            output.space();
+            output.print("else");
+            output.space();
+            if (self.alternative instanceof AST_If)
+                self.alternative.print(output);
+            else
+                print_maybe_braced_body(self.alternative, output);
+        } else {
+            self._do_print_body(output);
+        }
+    });
+
+    /* -----[ switch ]----- */
+    DEFPRINT(AST_Switch, function(self, output) {
+        output.print("switch");
+        output.space();
+        output.with_parens(function() {
+            self.expression.print(output);
+        });
+        output.space();
+        var last = self.body.length - 1;
+        if (last < 0) print_braced_empty(self, output);
+        else output.with_block(function() {
+            self.body.forEach(function(branch, i) {
+                output.indent(true);
+                branch.print(output);
+                if (i < last && branch.body.length > 0)
+                    output.newline();
+            });
+        });
+    });
+    AST_SwitchBranch.DEFMETHOD("_do_print_body", function(output) {
+        output.newline();
+        this.body.forEach(function(stmt) {
+            output.indent();
+            stmt.print(output);
+            output.newline();
+        });
+    });
+    DEFPRINT(AST_Default, function(self, output) {
+        output.print("default:");
+        self._do_print_body(output);
+    });
+    DEFPRINT(AST_Case, function(self, output) {
+        output.print("case");
+        output.space();
+        self.expression.print(output);
+        output.print(":");
+        self._do_print_body(output);
+    });
+
+    /* -----[ exceptions ]----- */
+    DEFPRINT(AST_Try, function(self, output) {
+        output.print("try");
+        output.space();
+        self.body.print(output);
+        if (self.bcatch) {
+            output.space();
+            self.bcatch.print(output);
+        }
+        if (self.bfinally) {
+            output.space();
+            self.bfinally.print(output);
+        }
+    });
+    DEFPRINT(AST_TryBlock, function(self, output) {
+        print_braced(self, output);
+    });
+    DEFPRINT(AST_Catch, function(self, output) {
+        output.print("catch");
+        if (self.argname) {
+            output.space();
+            output.with_parens(function() {
+                self.argname.print(output);
+            });
+        }
+        output.space();
+        print_braced(self, output);
+    });
+    DEFPRINT(AST_Finally, function(self, output) {
+        output.print("finally");
+        output.space();
+        print_braced(self, output);
+    });
+
+    /* -----[ var/const ]----- */
+    AST_DefinitionsLike.DEFMETHOD("_do_print", function(output, kind) {
+        output.print(kind);
+        output.space();
+        this.definitions.forEach(function(def, i) {
+            if (i) output.comma();
+            def.print(output);
+        });
+        var p = output.parent();
+        var in_for = p instanceof AST_For || p instanceof AST_ForIn;
+        var output_semicolon = !in_for || p && p.init !== this;
+        if (output_semicolon)
+            output.semicolon();
+    });
+    DEFPRINT(AST_Let, function(self, output) {
+        self._do_print(output, "let");
+    });
+    DEFPRINT(AST_Var, function(self, output) {
+        self._do_print(output, "var");
+    });
+    DEFPRINT(AST_Const, function(self, output) {
+        self._do_print(output, "const");
+    });
+    DEFPRINT(AST_Using, function(self, output) {
+        self._do_print(output, self.await ? "await using" : "using");
+    });
+    DEFPRINT(AST_Import, function(self, output) {
+        output.print("import");
+        output.space();
+        if (self.phase) {
+            output.print(self.phase);
+            output.space();
+        }
+        if (self.imported_name) {
+            self.imported_name.print(output);
+        }
+        if (self.imported_name && self.imported_names) {
+            output.print(",");
+            output.space();
+        }
+        if (self.imported_names) {
+            if (self.imported_names.length === 1 &&
+                self.imported_names[0].foreign_name.name === "*" &&
+                !self.imported_names[0].foreign_name.quote) {
+                self.imported_names[0].print(output);
+            } else {
+                output.print("{");
+                self.imported_names.forEach(function (name_import, i) {
+                    output.space();
+                    name_import.print(output);
+                    if (i < self.imported_names.length - 1) {
+                        output.print(",");
+                    }
+                });
+                output.space();
+                output.print("}");
+            }
+        }
+        if (self.imported_name || self.imported_names) {
+            output.space();
+            output.print("from");
+            output.space();
+        }
+        self.module_name.print(output);
+        if (self.attributes) {
+            output.print("with");
+            self.attributes.print(output);
+        }
+        output.semicolon();
+    });
+    DEFPRINT(AST_ImportMeta, function(self, output) {
+        output.print("import.meta");
+    });
+    DEFPRINT(AST_DynamicImport, function(self, output) {
+        output.print("import." + self.phase);
+        output.with_parens(function() {
+            self.args.forEach(function(arg, i) {
+                if (i) output.comma();
+                arg.print(output);
+            });
+        });
+    });
+
+    DEFPRINT(AST_NameMapping, function(self, output) {
+        var is_import = output.parent() instanceof AST_Import;
+        var definition = self.name.definition();
+        var foreign_name = self.foreign_name;
+        var names_are_different =
+            (definition && definition.mangled_name || self.name.name) !==
+            foreign_name.name;
+        if (!names_are_different &&
+            foreign_name.name === "*" &&
+            !!foreign_name.quote != !!self.name.quote) {
+                // export * as "*"
+            names_are_different = true;
+        }
+        var foreign_name_is_name = !foreign_name.quote;
+        if (names_are_different) {
+            if (is_import) {
+                if (foreign_name_is_name) {
+                    output.print(foreign_name.name);
+                } else {
+                    output.print_string(foreign_name.name, foreign_name.quote);
+                }
+            } else {
+                if (!self.name.quote) {
+                    self.name.print(output);
+                } else {
+                    output.print_string(self.name.name, self.name.quote);
+                }
+                
+            }
+            output.space();
+            output.print("as");
+            output.space();
+            if (is_import) {
+                self.name.print(output);
+            } else {
+                if (foreign_name_is_name) {
+                    output.print(foreign_name.name);
+                } else {
+                    output.print_string(foreign_name.name, foreign_name.quote);
+                }
+            }
+        } else {
+            if (!self.name.quote) {
+                self.name.print(output);
+            } else {
+                output.print_string(self.name.name, self.name.quote);
+            }
+        }
+    });
+
+    DEFPRINT(AST_Export, function(self, output) {
+        output.print("export");
+        output.space();
+        if (self.is_default) {
+            output.print("default");
+            output.space();
+        }
+        if (self.exported_names) {
+            if (self.exported_names.length === 1 &&
+                self.exported_names[0].name.name === "*" &&
+                !self.exported_names[0].name.quote) {
+                    self.exported_names[0].print(output);
+            } else {
+                output.print("{");
+                self.exported_names.forEach(function(name_export, i) {
+                    output.space();
+                    name_export.print(output);
+                    if (i < self.exported_names.length - 1) {
+                        output.print(",");
+                    }
+                });
+                output.space();
+                output.print("}");
+            }
+        } else if (self.exported_value) {
+            self.exported_value.print(output);
+        } else if (self.exported_definition) {
+            self.exported_definition.print(output);
+            if (self.exported_definition instanceof AST_Definitions) return;
+        }
+        if (self.module_name) {
+            output.space();
+            output.print("from");
+            output.space();
+            self.module_name.print(output);
+        }
+        if (self.attributes) {
+            output.print("with");
+            self.attributes.print(output);
+        }
+        if (self.exported_value
+                && !(self.exported_value instanceof AST_Defun ||
+                    self.exported_value instanceof AST_Function ||
+                    self.exported_value instanceof AST_Class)
+            || self.module_name
+            || self.exported_names
+        ) {
+            output.semicolon();
+        }
+    });
+
+    function parenthesize_for_noin(node, output, noin) {
+        var parens = false;
+        // need to take some precautions here:
+        //    https://github.com/mishoo/UglifyJS2/issues/60
+        if (noin) {
+            parens = walk(node, node => {
+                // Don't go into scopes -- except arrow functions:
+                // https://github.com/terser/terser/issues/1019#issuecomment-877642607
+                if (node instanceof AST_Scope && !(node instanceof AST_Arrow)) {
+                    return true;
+                }
+                if (
+                    node instanceof AST_Binary && node.operator == "in"
+                    || node instanceof AST_PrivateIn
+                ) {
+                    return walk_abort;  // makes walk() return true
+                }
+            });
+        }
+        node.print(output, parens);
+    }
+
+    DEFPRINT(AST_VarDefLike, function(self, output) {
+        self.name.print(output);
+        if (self.value) {
+            output.space();
+            output.print("=");
+            output.space();
+            var p = output.parent(1);
+            var noin = p instanceof AST_For || p instanceof AST_ForIn;
+            parenthesize_for_noin(self.value, output, noin);
+        }
+    });
+
+    /* -----[ other expressions ]----- */
+    DEFPRINT(AST_Call, function(self, output) {
+        self.expression.print(output);
+        if (self instanceof AST_New && self.args.length === 0)
+            return;
+        if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) {
+            output.add_mapping(self.start);
+        }
+        if (self.optional) output.print("?.");
+        output.with_parens(function() {
+            self.args.forEach(function(expr, i) {
+                if (i) output.comma();
+                expr.print(output);
+            });
+        });
+    });
+    DEFPRINT(AST_New, function(self, output) {
+        output.print("new");
+        output.space();
+        AST_Call.prototype._codegen(self, output);
+    });
+
+    AST_Sequence.DEFMETHOD("_do_print", function(output) {
+        this.expressions.forEach(function(node, index) {
+            if (index > 0) {
+                output.comma();
+                if (output.should_break()) {
+                    output.newline();
+                    output.indent();
+                }
+            }
+            node.print(output);
+        });
+    });
+    DEFPRINT(AST_Sequence, function(self, output) {
+        self._do_print(output);
+        // var p = output.parent();
+        // if (p instanceof AST_Statement) {
+        //     output.with_indent(output.next_indent(), function(){
+        //         self._do_print(output);
+        //     });
+        // } else {
+        //     self._do_print(output);
+        // }
+    });
+    DEFPRINT(AST_Dot, function(self, output) {
+        var expr = self.expression;
+        expr.print(output);
+        var prop = self.property;
+        var print_computed = ALL_RESERVED_WORDS.has(prop)
+            ? output.option("ie8")
+            : !is_identifier_string(
+                prop,
+                output.option("ecma") >= 2015 && !output.option("safari10")
+            );
+
+        if (self.optional) output.print("?.");
+
+        if (print_computed) {
+            output.print("[");
+            output.add_mapping(self.end);
+            output.print_string(prop);
+            output.print("]");
+        } else {
+            if (expr instanceof AST_Number && expr.getValue() >= 0) {
+                if (!/[xa-f.)]/i.test(output.last())) {
+                    output.print(".");
+                }
+            }
+            if (!self.optional) output.print(".");
+            // the name after dot would be mapped about here.
+            output.add_mapping(self.end);
+            output.print_name(prop);
+        }
+    });
+    DEFPRINT(AST_DotHash, function(self, output) {
+        var expr = self.expression;
+        expr.print(output);
+        var prop = self.property;
+
+        if (self.optional) output.print("?");
+        output.print(".#");
+        output.add_mapping(self.end);
+        output.print_name(prop);
+    });
+    DEFPRINT(AST_Sub, function(self, output) {
+        self.expression.print(output);
+        if (self.optional) output.print("?.");
+        output.print("[");
+        self.property.print(output);
+        output.print("]");
+    });
+    DEFPRINT(AST_Chain, function(self, output) {
+        self.expression.print(output);
+    });
+    DEFPRINT(AST_UnaryPrefix, function(self, output) {
+        var op = self.operator;
+        if (op === "--" && output.last().endsWith("!")) {
+            // avoid printing "<!--"
+            output.print(" ");
+        }
+        output.print(op);
+        if (/^[a-z]/i.test(op)
+            || (/[+-]$/.test(op)
+                && self.expression instanceof AST_UnaryPrefix
+                && /^[+-]/.test(self.expression.operator))) {
+            output.space();
+        }
+        self.expression.print(output);
+    });
+    DEFPRINT(AST_UnaryPostfix, function(self, output) {
+        self.expression.print(output);
+        output.print(self.operator);
+    });
+    DEFPRINT(AST_Binary, function(self, output) {
+        var op = self.operator;
+        self.left.print(output);
+        if (op[0] == ">" /* ">>" ">>>" ">" ">=" */
+            && output.last().endsWith("--")) {
+            // space is mandatory to avoid outputting -->
+            output.print(" ");
+        } else {
+            // the space is optional depending on "beautify"
+            output.space();
+        }
+        output.print(op);
+        output.space();
+        self.right.print(output);
+    });
+    DEFPRINT(AST_Conditional, function(self, output) {
+        self.condition.print(output);
+        output.space();
+        output.print("?");
+        output.space();
+        self.consequent.print(output);
+        output.space();
+        output.colon();
+        self.alternative.print(output);
+    });
+
+    /* -----[ literals ]----- */
+    DEFPRINT(AST_Array, function(self, output) {
+        output.with_square(function() {
+            var a = self.elements, len = a.length;
+            if (len > 0) output.space();
+            a.forEach(function(exp, i) {
+                if (i) output.comma();
+                exp.print(output);
+                // If the final element is a hole, we need to make sure it
+                // doesn't look like a trailing comma, by inserting an actual
+                // trailing comma.
+                if (i === len - 1 && exp instanceof AST_Hole)
+                  output.comma();
+            });
+            if (len > 0) output.space();
+        });
+    });
+    DEFPRINT(AST_Object, function(self, output) {
+        if (self.properties.length > 0) output.with_block(function() {
+            self.properties.forEach(function(prop, i) {
+                if (i) {
+                    output.print(",");
+                    output.newline();
+                }
+                output.indent();
+                prop.print(output);
+            });
+            output.newline();
+        });
+        else print_braced_empty(self, output);
+    });
+    DEFPRINT(AST_Class, function(self, output) {
+        output.print("class");
+        output.space();
+        if (self.name) {
+            self.name.print(output);
+            output.space();
+        }
+        if (self.extends) {
+            var parens = (
+                   !(self.extends instanceof AST_SymbolRef)
+                && !(self.extends instanceof AST_PropAccess)
+                && !(self.extends instanceof AST_ClassExpression)
+                && !(self.extends instanceof AST_Function)
+            );
+            output.print("extends");
+            if (parens) {
+                output.print("(");
+            } else {
+                output.space();
+            }
+            self.extends.print(output);
+            if (parens) {
+                output.print(")");
+            } else {
+                output.space();
+            }
+        }
+        if (self.properties.length > 0) output.with_block(function() {
+            self.properties.forEach(function(prop, i) {
+                if (i) {
+                    output.newline();
+                }
+                output.indent();
+                prop.print(output);
+            });
+            output.newline();
+        });
+        else output.print("{}");
+    });
+    DEFPRINT(AST_NewTarget, function(self, output) {
+        output.print("new.target");
+    });
+
+    /** Prints a prop name. Returns whether it can be used as a shorthand. */
+    function print_property_name(key, quote, output) {
+        if (output.option("quote_keys")) {
+            output.print_string(key);
+            return false;
+        }
+        if ("" + +key == key && key >= 0) {
+            if (output.option("keep_numbers")) {
+                output.print(key);
+                return false;
+            }
+            output.print(make_num(key));
+            return false;
+        }
+        var print_string = ALL_RESERVED_WORDS.has(key)
+            ? output.option("ie8")
+            : (
+                output.option("ecma") < 2015 || output.option("safari10")
+                    ? !is_basic_identifier_string(key)
+                    : !is_identifier_string(key, true)
+            );
+        if (print_string || (quote && output.option("keep_quoted_props"))) {
+            output.print_string(key, quote);
+            return false;
+        }
+        output.print_name(key);
+        return true;
+    }
+
+    DEFPRINT(AST_ObjectKeyVal, function(self, output) {
+        function get_name(self) {
+            var def = self.definition();
+            return def ? def.mangled_name || def.name : self.name;
+        }
+
+        const try_shorthand = output.option("shorthand") && !(self.key instanceof AST_Node);
+        if (
+            try_shorthand
+            && self.value instanceof AST_Symbol
+            && get_name(self.value) === self.key
+            && !ALL_RESERVED_WORDS.has(self.key)
+        ) {
+            const was_shorthand = print_property_name(self.key, self.quote, output);
+            if (!was_shorthand) {
+                output.colon();
+                self.value.print(output);
+            }
+        } else if (
+            try_shorthand
+            && self.value instanceof AST_DefaultAssign
+            && self.value.left instanceof AST_Symbol
+            && get_name(self.value.left) === self.key
+        ) {
+            const was_shorthand = print_property_name(self.key, self.quote, output);
+            if (!was_shorthand) {
+                output.colon();
+                self.value.left.print(output);
+            }
+            output.space();
+            output.print("=");
+            output.space();
+            self.value.right.print(output);
+        } else {
+            if (!(self.key instanceof AST_Node)) {
+                print_property_name(self.key, self.quote, output);
+            } else {
+                output.with_square(function() {
+                    self.key.print(output);
+                });
+            }
+            output.colon();
+            self.value.print(output);
+        }
+    });
+    DEFPRINT(AST_ClassPrivateProperty, (self, output) => {
+        if (self.static) {
+            output.print("static");
+            output.space();
+        }
+
+        output.print("#");
+        
+        print_property_name(self.key.name, undefined, output);
+
+        if (self.value) {
+            output.print("=");
+            self.value.print(output);
+        }
+
+        output.semicolon();
+    });
+    DEFPRINT(AST_ClassProperty, (self, output) => {
+        if (self.static) {
+            output.print("static");
+            output.space();
+        }
+
+        if (self.key instanceof AST_SymbolClassProperty) {
+            print_property_name(self.key.name, self.quote, output);
+        } else {
+            output.print("[");
+            self.key.print(output);
+            output.print("]");
+        }
+
+        if (self.value) {
+            output.print("=");
+            self.value.print(output);
+        }
+
+        output.semicolon();
+    });
+    AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, is_private, output) {
+        var self = this;
+        if (self.static) {
+            output.print("static");
+            output.space();
+        }
+        if (type) {
+            output.print(type);
+            output.space();
+        }
+        if (self.key instanceof AST_SymbolMethod) {
+            if (is_private) output.print("#");
+            print_property_name(self.key.name, self.quote, output);
+            self.key.add_source_map(output);
+        } else {
+            output.with_square(function() {
+                self.key.print(output);
+            });
+        }
+        self.value._do_print(output, true);
+    });
+    DEFPRINT(AST_ObjectSetter, function(self, output) {
+        self._print_getter_setter("set", false, output);
+    });
+    DEFPRINT(AST_ObjectGetter, function(self, output) {
+        self._print_getter_setter("get", false, output);
+    });
+    DEFPRINT(AST_PrivateSetter, function(self, output) {
+        self._print_getter_setter("set", true, output);
+    });
+    DEFPRINT(AST_PrivateGetter, function(self, output) {
+        self._print_getter_setter("get", true, output);
+    });
+    DEFPRINT(AST_ConciseMethod, function(self, output) {
+        var type;
+        if (self.value.is_generator && self.value.async) {
+            type = "async*";
+        } else if (self.value.is_generator) {
+            type = "*";
+        } else if (self.value.async) {
+            type = "async";
+        }
+        self._print_getter_setter(type, false, output);
+    });
+    DEFPRINT(AST_PrivateMethod, function(self, output) {
+        var type;
+        if (self.value.is_generator && self.value.async) {
+            type = "async*";
+        } else if (self.value.is_generator) {
+            type = "*";
+        } else if (self.value.async) {
+            type = "async";
+        }
+        self._print_getter_setter(type, true, output);
+    });
+    DEFPRINT(AST_PrivateIn, function(self, output) {
+        self.key.print(output);
+        output.space();
+        output.print("in");
+        output.space();
+        self.value.print(output);
+    });
+    DEFPRINT(AST_SymbolPrivateProperty, function(self, output) {
+        output.print("#" + self.name);
+    });
+    DEFPRINT(AST_ClassStaticBlock, function (self, output) {
+        output.print("static");
+        output.space();
+        print_braced(self, output);
+    });
+    AST_Symbol.DEFMETHOD("_do_print", function(output) {
+        var def = this.definition();
+        output.print_name(def ? def.mangled_name || def.name : this.name);
+    });
+    DEFPRINT(AST_Symbol, function (self, output) {
+        self._do_print(output);
+    });
+    DEFPRINT(AST_Hole, noop);
+    DEFPRINT(AST_This, function(self, output) {
+        output.print("this");
+    });
+    DEFPRINT(AST_Super, function(self, output) {
+        output.print("super");
+    });
+    DEFPRINT(AST_Constant, function(self, output) {
+        output.print(self.getValue());
+    });
+    DEFPRINT(AST_String, function(self, output) {
+        output.print_string(self.getValue(), self.quote, output.in_directive);
+    });
+    DEFPRINT(AST_Number, function(self, output) {
+        if ((output.option("keep_numbers") || output.use_asm) && self.raw) {
+            output.print(self.raw);
+        } else {
+            output.print(make_num(self.getValue()));
+        }
+    });
+    DEFPRINT(AST_BigInt, function(self, output) {
+        if (output.option("keep_numbers") && self.raw) {
+            output.print(self.raw);
+        } else {
+            output.print(self.getValue() + "n");
+        }
+    });
+
+    const r_slash_script = /(<\s*\/\s*script)/i;
+    const r_starts_with_script = /^\s*script/i;
+    const slash_script_replace = (_, $1) => $1.replace("/", "\\/");
+    DEFPRINT(AST_RegExp, function(self, output) {
+        let { source, flags } = self.getValue();
+        source = regexp_source_fix(source);
+        flags = flags ? sort_regexp_flags(flags) : "";
+
+        // Avoid outputting end of script tag
+        source = source.replace(r_slash_script, slash_script_replace);
+        if (r_starts_with_script.test(source) && output.last().endsWith("<")) {
+            output.print(" ");
+        }
+
+        output.print(output.to_utf8(`/${source}/${flags}`, false, true));
+
+        const parent = output.parent();
+        if (
+            parent instanceof AST_Binary
+            && /^\w/.test(parent.operator)
+            && parent.left === self
+        ) {
+            output.print(" ");
+        }
+    });
+
+    /** if, for, while, may or may not have braces surrounding its body */
+    function print_maybe_braced_body(stat, output) {
+        if (output.option("braces")) {
+            make_block(stat, output);
+        } else {
+            if (!stat || stat instanceof AST_EmptyStatement)
+                output.force_semicolon();
+            else if ((stat instanceof AST_DefinitionsLike && !(stat instanceof AST_Var)) || stat instanceof AST_Class)
+                make_block(stat, output);
+            else
+                stat.print(output);
+        }
+    }
+
+    function best_of(a) {
+        var best = a[0], len = best.length;
+        for (var i = 1; i < a.length; ++i) {
+            if (a[i].length < len) {
+                best = a[i];
+                len = best.length;
+            }
+        }
+        return best;
+    }
+
+    function make_num(num) {
+        var str = num.toString(10).replace(/^0\./, ".").replace("e+", "e");
+        var candidates = [ str ];
+        if (Math.floor(num) === num) {
+            if (num < 0) {
+                candidates.push("-0x" + (-num).toString(16).toLowerCase());
+            } else {
+                candidates.push("0x" + num.toString(16).toLowerCase());
+            }
+        }
+        var match, len, digits;
+        if (match = /^\.0+/.exec(str)) {
+            len = match[0].length;
+            digits = str.slice(len);
+            candidates.push(digits + "e-" + (digits.length + len - 1));
+        } else if (match = /0+$/.exec(str)) {
+            len = match[0].length;
+            candidates.push(str.slice(0, -len) + "e" + len);
+        } else if (match = /^(\d)\.(\d+)e(-?\d+)$/.exec(str)) {
+            candidates.push(match[1] + match[2] + "e" + (match[3] - match[2].length));
+        }
+        return best_of(candidates);
+    }
+
+    function make_block(stmt, output) {
+        if (!stmt || stmt instanceof AST_EmptyStatement)
+            output.print("{}");
+        else if (stmt instanceof AST_BlockStatement)
+            stmt.print(output);
+        else output.with_block(function() {
+            output.indent();
+            stmt.print(output);
+            output.newline();
+        });
+    }
+
+    /* -----[ source map generators ]----- */
+
+    function DEFMAP(nodetype, generator) {
+        nodetype.forEach(function(nodetype) {
+            nodetype.DEFMETHOD("add_source_map", generator);
+        });
+    }
+
+    DEFMAP([
+        // We could easily add info for ALL nodes, but it seems to me that
+        // would be quite wasteful, hence this noop in the base class.
+        AST_Node,
+        // since the label symbol will mark it
+        AST_LabeledStatement,
+        AST_Toplevel,
+    ], noop);
+
+    // XXX: I'm not exactly sure if we need it for all of these nodes,
+    // or if we should add even more.
+    DEFMAP([
+        AST_Array,
+        AST_BlockStatement,
+        AST_Catch,
+        AST_Class,
+        AST_Constant,
+        AST_Debugger,
+        AST_DefinitionsLike,
+        AST_Directive,
+        AST_Finally,
+        AST_Jump,
+        AST_Lambda,
+        AST_New,
+        AST_Object,
+        AST_StatementWithBody,
+        AST_Symbol,
+        AST_Switch,
+        AST_SwitchBranch,
+        AST_TemplateString,
+        AST_TemplateSegment,
+        AST_Try,
+    ], function(output) {
+        output.add_mapping(this.start);
+    });
+
+    DEFMAP([
+        AST_ObjectGetter,
+        AST_ObjectSetter,
+        AST_PrivateGetter,
+        AST_PrivateSetter,
+        AST_ConciseMethod,
+        AST_PrivateMethod,
+    ], function(output) {
+        output.add_mapping(this.start, false /*name handled below*/);
+    });
+
+    DEFMAP([
+        AST_SymbolMethod,
+        AST_SymbolPrivateProperty
+    ], function(output) {
+        const tok_type = this.end && this.end.type;
+        if (tok_type === "name" || tok_type === "privatename") {
+            output.add_mapping(this.end, this.name);
+        } else {
+            output.add_mapping(this.end);
+        }
+    });
+
+    DEFMAP([ AST_ObjectProperty ], function(output) {
+        output.add_mapping(this.start, this.key);
+    });
+})();
+
+export {
+    OutputStream,
+};
Index: frontend/node_modules/terser/lib/parse.js
===================================================================
--- frontend/node_modules/terser/lib/parse.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/parse.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3660 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+    Parser based on parse-js (http://marijn.haverbeke.nl/parse-js/).
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+"use strict";
+
+import {
+    characters,
+    defaults,
+    makePredicate,
+    set_annotation,
+} from "./utils/index.js";
+import {
+    AST_Accessor,
+    AST_Array,
+    AST_Arrow,
+    AST_Assign,
+    AST_Await,
+    AST_BigInt,
+    AST_Binary,
+    AST_BlockStatement,
+    AST_Break,
+    AST_Call,
+    AST_Case,
+    AST_Catch,
+    AST_Chain,
+    AST_ClassExpression,
+    AST_ClassPrivateProperty,
+    AST_ClassProperty,
+    AST_ClassStaticBlock,
+    AST_ConciseMethod,
+    AST_PrivateIn,
+    AST_PrivateGetter,
+    AST_PrivateMethod,
+    AST_PrivateSetter,
+    AST_Conditional,
+    AST_Const,
+    AST_Continue,
+    AST_Debugger,
+    AST_Default,
+    AST_DefaultAssign,
+    AST_DefClass,
+    AST_Definitions,
+    AST_DefinitionsLike,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Directive,
+    AST_Do,
+    AST_Dot,
+    AST_DotHash,
+    AST_EmptyStatement,
+    AST_Expansion,
+    AST_Export,
+    AST_False,
+    AST_Finally,
+    AST_For,
+    AST_ForIn,
+    AST_ForOf,
+    AST_Function,
+    AST_Hole,
+    AST_If,
+    AST_DynamicImport,
+    AST_Import,
+    AST_ImportMeta,
+    AST_Infinity,
+    AST_IterationStatement,
+    AST_Label,
+    AST_LabeledStatement,
+    AST_LabelRef,
+    AST_Let,
+    AST_NameMapping,
+    AST_New,
+    AST_NewTarget,
+    AST_Null,
+    AST_Number,
+    AST_Object,
+    AST_ObjectGetter,
+    AST_ObjectKeyVal,
+    AST_ObjectProperty,
+    AST_ObjectSetter,
+    AST_PrefixedTemplateString,
+    AST_PropAccess,
+    AST_RegExp,
+    AST_Return,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_String,
+    AST_Sub,
+    AST_Super,
+    AST_Switch,
+    AST_SymbolCatch,
+    AST_SymbolClass,
+    AST_SymbolClassProperty,
+    AST_SymbolConst,
+    AST_SymbolDeclaration,
+    AST_SymbolDefClass,
+    AST_SymbolDefun,
+    AST_SymbolExport,
+    AST_SymbolExportForeign,
+    AST_SymbolFunarg,
+    AST_SymbolImport,
+    AST_SymbolImportForeign,
+    AST_SymbolLambda,
+    AST_SymbolLet,
+    AST_SymbolMethod,
+    AST_SymbolRef,
+    AST_SymbolVar,
+    AST_SymbolUsing,
+    AST_TemplateSegment,
+    AST_TemplateString,
+    AST_This,
+    AST_SymbolPrivateProperty,
+    AST_Throw,
+    AST_Token,
+    AST_Toplevel,
+    AST_True,
+    AST_Try,
+    AST_TryBlock,
+    AST_UnaryPostfix,
+    AST_UnaryPrefix,
+    AST_Using,
+    AST_UsingDef,
+    AST_Var,
+    AST_VarDef,
+    AST_While,
+    AST_With,
+    AST_Yield,
+    _INLINE,
+    _NOINLINE,
+    _PURE,
+    _KEY,
+    _MANGLEPROP,
+} from "./ast.js";
+
+var LATEST_RAW = "";  // Only used for numbers and template strings
+var TEMPLATE_RAWS = new Map();  // Raw template strings
+
+var KEYWORDS = "break case catch class const continue debugger default delete do else export extends finally for function if in instanceof let new return switch throw try typeof var void while with";
+var KEYWORDS_ATOM = "false null true";
+var RESERVED_WORDS = "enum import super this " + KEYWORDS_ATOM + " " + KEYWORDS;
+var ALL_RESERVED_WORDS = "implements interface package private protected public static " + RESERVED_WORDS;
+var KEYWORDS_BEFORE_EXPRESSION = "return new delete throw else case yield await";
+
+KEYWORDS = makePredicate(KEYWORDS);
+RESERVED_WORDS = makePredicate(RESERVED_WORDS);
+KEYWORDS_BEFORE_EXPRESSION = makePredicate(KEYWORDS_BEFORE_EXPRESSION);
+KEYWORDS_ATOM = makePredicate(KEYWORDS_ATOM);
+ALL_RESERVED_WORDS = makePredicate(ALL_RESERVED_WORDS);
+
+var OPERATOR_CHARS = makePredicate(characters("+-*&%=<>!?|~^"));
+
+var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
+var RE_OCT_NUMBER = /^0[0-7]+$/;
+var RE_ES6_OCT_NUMBER = /^0o[0-7]+$/i;
+var RE_BIN_NUMBER = /^0b[01]+$/i;
+var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
+var RE_BIG_INT = /^(0[xob])?[0-9a-f]+n$/i;
+
+var RE_KEYWORD_RELATIONAL_OPERATORS = /in(?:stanceof)?/y;
+
+var OPERATORS = makePredicate([
+    "in",
+    "instanceof",
+    "typeof",
+    "new",
+    "void",
+    "delete",
+    "++",
+    "--",
+    "+",
+    "-",
+    "!",
+    "~",
+    "&",
+    "|",
+    "^",
+    "*",
+    "**",
+    "/",
+    "%",
+    ">>",
+    "<<",
+    ">>>",
+    "<",
+    ">",
+    "<=",
+    ">=",
+    "==",
+    "===",
+    "!=",
+    "!==",
+    "?",
+    "=",
+    "+=",
+    "-=",
+    "||=",
+    "&&=",
+    "??=",
+    "/=",
+    "*=",
+    "**=",
+    "%=",
+    ">>=",
+    "<<=",
+    ">>>=",
+    "|=",
+    "^=",
+    "&=",
+    "&&",
+    "??",
+    "||",
+]);
+
+var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\uFEFF"));
+
+var NEWLINE_CHARS = makePredicate(characters("\n\r\u2028\u2029"));
+
+var PUNC_AFTER_EXPRESSION = makePredicate(characters(";]),:"));
+
+var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,;:"));
+
+var PUNC_CHARS = makePredicate(characters("[]{}(),;:"));
+
+/* -----[ Tokenizer ]----- */
+
+// surrogate safe regexps adapted from https://github.com/mathiasbynens/unicode-8.0.0/tree/89b412d8a71ecca9ed593d9e9fa073ab64acfebe/Binary_Property
+var UNICODE = {
+    ID_Start: /[$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]/,
+    ID_Continue: /(?:[$0-9A-Z_a-z\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B4\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF])+/,
+};
+
+function get_full_char(str, pos) {
+    if (is_surrogate_pair_head(str.charCodeAt(pos))) {
+        if (is_surrogate_pair_tail(str.charCodeAt(pos + 1))) {
+            return str.charAt(pos) + str.charAt(pos + 1);
+        }
+    } else if (is_surrogate_pair_tail(str.charCodeAt(pos))) {
+        if (is_surrogate_pair_head(str.charCodeAt(pos - 1))) {
+            return str.charAt(pos - 1) + str.charAt(pos);
+        }
+    }
+    return str.charAt(pos);
+}
+
+function get_full_char_code(str, pos) {
+    // https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates
+    if (is_surrogate_pair_head(str.charCodeAt(pos))) {
+        return 0x10000 + (str.charCodeAt(pos) - 0xd800 << 10) + str.charCodeAt(pos + 1) - 0xdc00;
+    }
+    return str.charCodeAt(pos);
+}
+
+function get_full_char_length(str) {
+    var surrogates = 0;
+
+    for (var i = 0; i < str.length; i++) {
+        if (is_surrogate_pair_head(str.charCodeAt(i)) && is_surrogate_pair_tail(str.charCodeAt(i + 1))) {
+            surrogates++;
+            i++;
+        }
+    }
+
+    return str.length - surrogates;
+}
+
+function from_char_code(code) {
+    // Based on https://github.com/mathiasbynens/String.fromCodePoint/blob/master/fromcodepoint.js
+    if (code > 0xFFFF) {
+        code -= 0x10000;
+        return (String.fromCharCode((code >> 10) + 0xD800) +
+            String.fromCharCode((code % 0x400) + 0xDC00));
+    }
+    return String.fromCharCode(code);
+}
+
+function is_surrogate_pair_head(code) {
+    return code >= 0xd800 && code <= 0xdbff;
+}
+
+function is_surrogate_pair_tail(code) {
+    return code >= 0xdc00 && code <= 0xdfff;
+}
+
+function is_digit(code) {
+    return code >= 48 && code <= 57;
+}
+
+function is_identifier_start(ch) {
+    return UNICODE.ID_Start.test(ch);
+}
+
+function is_identifier_char(ch) {
+    return UNICODE.ID_Continue.test(ch);
+}
+
+const BASIC_IDENT = /^[a-z_$][a-z0-9_$]*$/i;
+
+function is_basic_identifier_string(str) {
+    return BASIC_IDENT.test(str);
+}
+
+function is_identifier_string(str, allow_surrogates) {
+    if (BASIC_IDENT.test(str)) {
+        return true;
+    }
+    if (!allow_surrogates && /[\ud800-\udfff]/.test(str)) {
+        return false;
+    }
+    var match = UNICODE.ID_Start.exec(str);
+    if (!match || match.index !== 0) {
+        return false;
+    }
+
+    str = str.slice(match[0].length);
+    if (!str) {
+        return true;
+    }
+
+    match = UNICODE.ID_Continue.exec(str);
+    return !!match && match[0].length === str.length;
+}
+
+function parse_js_number(num, allow_e = true) {
+    if (!allow_e && num.includes("e")) {
+        return NaN;
+    }
+    if (RE_HEX_NUMBER.test(num)) {
+        return parseInt(num.substr(2), 16);
+    } else if (RE_OCT_NUMBER.test(num)) {
+        return parseInt(num.substr(1), 8);
+    } else if (RE_ES6_OCT_NUMBER.test(num)) {
+        return parseInt(num.substr(2), 8);
+    } else if (RE_BIN_NUMBER.test(num)) {
+        return parseInt(num.substr(2), 2);
+    } else if (RE_DEC_NUMBER.test(num)) {
+        return parseFloat(num);
+    } else {
+        var val = parseFloat(num);
+        if (val == num) return val;
+    }
+}
+
+class JS_Parse_Error extends Error {
+    constructor(message, filename, line, col, pos) {
+        super();
+
+        this.name = "SyntaxError";
+        this.message = message;
+        this.filename = filename;
+        this.line = line;
+        this.col = col;
+        this.pos = pos;
+    }
+}
+
+function js_error(message, filename, line, col, pos) {
+    throw new JS_Parse_Error(message, filename, line, col, pos);
+}
+
+function is_token(token, type, val) {
+    return token.type == type && (val == null || token.value == val);
+}
+
+var EX_EOF = {};
+
+function tokenizer($TEXT, filename, html5_comments, shebang) {
+    var S = {
+        text            : $TEXT,
+        filename        : filename,
+        pos             : 0,
+        tokpos          : 0,
+        line            : 1,
+        tokline         : 0,
+        col             : 0,
+        tokcol          : 0,
+        newline_before  : false,
+        regex_allowed   : false,
+        brace_counter   : 0,
+        template_braces : [],
+        comments_before : [],
+        directives      : {},
+        directive_stack : []
+    };
+
+    function peek() { return get_full_char(S.text, S.pos); }
+
+    // Used because parsing ?. involves a lookahead for a digit
+    function is_option_chain_op() {
+        const must_be_dot = S.text.charCodeAt(S.pos + 1) === 46;
+        if (!must_be_dot) return false;
+
+        const cannot_be_digit = S.text.charCodeAt(S.pos + 2);
+        return cannot_be_digit < 48 || cannot_be_digit > 57;
+    }
+
+    function next(signal_eof, in_string) {
+        var ch = get_full_char(S.text, S.pos++);
+        if (signal_eof && !ch)
+            throw EX_EOF;
+        if (NEWLINE_CHARS.has(ch)) {
+            S.newline_before = S.newline_before || !in_string;
+            ++S.line;
+            S.col = 0;
+            if (ch == "\r" && peek() == "\n") {
+                // treat a \r\n sequence as a single \n
+                ++S.pos;
+                ch = "\n";
+            }
+        } else {
+            if (ch.length > 1) {
+                ++S.pos;
+                ++S.col;
+            }
+            ++S.col;
+        }
+        return ch;
+    }
+
+    function forward(i) {
+        while (i--) next();
+    }
+
+    function looking_at(str) {
+        return S.text.substr(S.pos, str.length) == str;
+    }
+
+    function find_eol() {
+        var text = S.text;
+        for (var i = S.pos, n = S.text.length; i < n; ++i) {
+            var ch = text[i];
+            if (NEWLINE_CHARS.has(ch))
+                return i;
+        }
+        return -1;
+    }
+
+    function find(what, signal_eof) {
+        var pos = S.text.indexOf(what, S.pos);
+        if (signal_eof && pos == -1) throw EX_EOF;
+        return pos;
+    }
+
+    function start_token() {
+        S.tokline = S.line;
+        S.tokcol = S.col;
+        S.tokpos = S.pos;
+    }
+
+    var prev_was_dot = false;
+    var previous_token = null;
+    function token(type, value, is_comment) {
+        S.regex_allowed = ((type == "operator" && !UNARY_POSTFIX.has(value)) ||
+                           (type == "keyword" && KEYWORDS_BEFORE_EXPRESSION.has(value)) ||
+                           (type == "punc" && PUNC_BEFORE_EXPRESSION.has(value))) ||
+                           (type == "arrow");
+        if (type == "punc" && (value == "." || value == "?.")) {
+            prev_was_dot = true;
+        } else if (!is_comment) {
+            prev_was_dot = false;
+        }
+        const line     = S.tokline;
+        const col      = S.tokcol;
+        const pos      = S.tokpos;
+        const nlb      = S.newline_before;
+        const file     = filename;
+        let comments_before = [];
+        let comments_after  = [];
+
+        if (!is_comment) {
+            comments_before = S.comments_before;
+            comments_after = S.comments_before = [];
+        }
+        S.newline_before = false;
+        const tok = new AST_Token(type, value, line, col, pos, nlb, comments_before, comments_after, file);
+
+        if (!is_comment) previous_token = tok;
+        return tok;
+    }
+
+    function skip_whitespace() {
+        while (WHITESPACE_CHARS.has(peek()))
+            next();
+    }
+
+    function peek_next_token_start_or_newline() {
+        var pos = S.pos;
+        for (var in_multiline_comment = false; pos < S.text.length; ) {
+            var ch = get_full_char(S.text, pos);
+            if (NEWLINE_CHARS.has(ch)) {
+                return { char: ch, pos: pos };
+            } else if (in_multiline_comment) {
+                if (ch == "*" && get_full_char(S.text, pos + 1) == "/") {
+                    pos += 2;
+                    in_multiline_comment = false;
+                } else {
+                    pos++;
+                }
+            } else if (!WHITESPACE_CHARS.has(ch)) {
+                if (ch == "/") {
+                    var next_ch = get_full_char(S.text, pos + 1);
+                    if (next_ch == "/") {
+                        pos = find_eol();
+                        return { char: get_full_char(S.text, pos), pos: pos };
+                    } else if (next_ch == "*") {
+                        in_multiline_comment = true;
+                        pos += 2;
+                        continue;
+                    }
+                }
+                return { char: ch, pos: pos };
+            } else {
+                pos++;
+            }
+        }
+        return { char: null, pos: pos };
+    }
+
+    function ch_starts_binding_identifier(ch, pos) {
+        if (ch == "\\") {
+            return true;
+        } else if (is_identifier_start(ch)) {
+            RE_KEYWORD_RELATIONAL_OPERATORS.lastIndex = pos;
+            if (RE_KEYWORD_RELATIONAL_OPERATORS.test(S.text)) {
+                var after = get_full_char(S.text, RE_KEYWORD_RELATIONAL_OPERATORS.lastIndex);
+                if (!is_identifier_char(after) && after != "\\") {
+                    // "in" or "instanceof" are keywords, not binding identifiers
+                    return false; 
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function read_while(pred) {
+        var ret = "", ch, i = 0;
+        while ((ch = peek()) && pred(ch, i++))
+            ret += next();
+        return ret;
+    }
+
+    function parse_error(err) {
+        js_error(err, filename, S.tokline, S.tokcol, S.tokpos);
+    }
+
+    function read_num(prefix) {
+        var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".", is_big_int = false, numeric_separator = false;
+        var num = read_while(function(ch, i) {
+            if (is_big_int) return false;
+
+            var code = ch.charCodeAt(0);
+            switch (code) {
+              case 95: // _
+                return (numeric_separator = true);
+              case 98: case 66: // bB
+                return (has_x = true); // Can occur in hex sequence, don't return false yet
+              case 111: case 79: // oO
+              case 120: case 88: // xX
+                return has_x ? false : (has_x = true);
+              case 101: case 69: // eE
+                return has_x ? true : has_e ? false : (has_e = after_e = true);
+              case 45: // -
+                return after_e || (i == 0 && !prefix);
+              case 43: // +
+                return after_e;
+              case (after_e = false, 46): // .
+                return (!has_dot && !has_x && !has_e) ? (has_dot = true) : false;
+              case 110: // n
+                is_big_int = true;
+                return true;
+            }
+
+            return (
+                code >= 48 && code <= 57 // 0-9
+                || code >= 97 && code <= 102 // a-f
+                || code >= 65 && code <= 70 // A-F
+            );
+        });
+        if (prefix) num = prefix + num;
+
+        LATEST_RAW = num;
+
+        if (RE_OCT_NUMBER.test(num) && next_token.has_directive("use strict")) {
+            parse_error("Legacy octal literals are not allowed in strict mode");
+        }
+        if (numeric_separator) {
+            if (num.endsWith("_")) {
+                parse_error("Numeric separators are not allowed at the end of numeric literals");
+            } else if (num.includes("__")) {
+                parse_error("Only one underscore is allowed as numeric separator");
+            }
+            num = num.replace(/_/g, "");
+        }
+        if (is_big_int) {
+            const without_n = num.slice(0, -1);
+            const allow_e = RE_HEX_NUMBER.test(without_n);
+            const valid = parse_js_number(without_n, allow_e);
+            if (!has_dot && RE_BIG_INT.test(num) && !isNaN(valid))
+                return token("big_int", without_n);
+            parse_error("Invalid or unexpected token");
+        }
+        var valid = parse_js_number(num);
+        if (!isNaN(valid)) {
+            return token("num", valid);
+        } else {
+            parse_error("Invalid syntax: " + num);
+        }
+    }
+
+    function is_octal(ch) {
+        return ch >= "0" && ch <= "7";
+    }
+
+    function read_escaped_char(in_string, strict_hex, template_string) {
+        var ch = next(true, in_string);
+        switch (ch.charCodeAt(0)) {
+          case 110 : return "\n";
+          case 114 : return "\r";
+          case 116 : return "\t";
+          case 98  : return "\b";
+          case 118 : return "\u000b"; // \v
+          case 102 : return "\f";
+          case 120 : return String.fromCharCode(hex_bytes(2, strict_hex)); // \x
+          case 117 : // \u
+            if (peek() == "{") {
+                next(true);
+                if (peek() === "}")
+                    parse_error("Expecting hex-character between {}");
+                while (peek() == "0") next(true); // No significance
+                var result, length = find("}", true) - S.pos;
+                // Avoid 32 bit integer overflow (1 << 32 === 1)
+                // We know first character isn't 0 and thus out of range anyway
+                if (length > 6 || (result = hex_bytes(length, strict_hex)) > 0x10FFFF) {
+                    parse_error("Unicode reference out of bounds");
+                }
+                next(true);
+                return from_char_code(result);
+            }
+            return String.fromCharCode(hex_bytes(4, strict_hex));
+          case 10  : return ""; // newline
+          case 13  :            // \r
+            if (peek() == "\n") { // DOS newline
+                next(true, in_string);
+                return "";
+            }
+        }
+        if (is_octal(ch)) {
+            if (template_string && strict_hex) {
+                const represents_null_character = ch === "0" && !is_octal(peek());
+                if (!represents_null_character) {
+                    parse_error("Octal escape sequences are not allowed in template strings");
+                }
+            }
+            return read_octal_escape_sequence(ch, strict_hex);
+        }
+        return ch;
+    }
+
+    function read_octal_escape_sequence(ch, strict_octal) {
+        // Read
+        var p = peek();
+        if (p >= "0" && p <= "7") {
+            ch += next(true);
+            if (ch[0] <= "3" && (p = peek()) >= "0" && p <= "7")
+                ch += next(true);
+        }
+
+        // Parse
+        if (ch === "0") return "\0";
+        if (ch.length > 0 && next_token.has_directive("use strict") && strict_octal)
+            parse_error("Legacy octal escape sequences are not allowed in strict mode");
+        return String.fromCharCode(parseInt(ch, 8));
+    }
+
+    function hex_bytes(n, strict_hex) {
+        var num = 0;
+        for (; n > 0; --n) {
+            if (!strict_hex && isNaN(parseInt(peek(), 16))) {
+                return parseInt(num, 16) || "";
+            }
+            var digit = next(true);
+            if (isNaN(parseInt(digit, 16)))
+                parse_error("Invalid hex-character pattern in string");
+            num += digit;
+        }
+        return parseInt(num, 16);
+    }
+
+    var read_string = with_eof_error("Unterminated string constant", function() {
+        const start_pos = S.pos;
+        var quote = next(), ret = [];
+        for (;;) {
+            var ch = next(true, true);
+            if (ch == "\\") ch = read_escaped_char(true, true);
+            else if (ch == "\r" || ch == "\n") parse_error("Unterminated string constant");
+            else if (ch == quote) break;
+            ret.push(ch);
+        }
+        var tok = token("string", ret.join(""));
+        LATEST_RAW = S.text.slice(start_pos, S.pos);
+        tok.quote = quote;
+        return tok;
+    });
+
+    var read_template_characters = with_eof_error("Unterminated template", function(begin) {
+        if (begin) {
+            S.template_braces.push(S.brace_counter);
+        }
+        var content = "", raw = "", ch, tok;
+        next(true, true);
+        while ((ch = next(true, true)) != "`") {
+            if (ch == "\r") {
+                if (peek() == "\n") ++S.pos;
+                ch = "\n";
+            } else if (ch == "$" && peek() == "{") {
+                next(true, true);
+                S.brace_counter++;
+                tok = token(begin ? "template_head" : "template_cont", content);
+                TEMPLATE_RAWS.set(tok, raw);
+                tok.template_end = false;
+                return tok;
+            }
+
+            raw += ch;
+            if (ch == "\\") {
+                var tmp = S.pos;
+                var prev_is_tag = previous_token && (previous_token.type === "name" || previous_token.type === "punc" && (previous_token.value === ")" || previous_token.value === "]"));
+                ch = read_escaped_char(true, !prev_is_tag, true);
+                raw += S.text.substr(tmp, S.pos - tmp);
+            }
+
+            content += ch;
+        }
+        S.template_braces.pop();
+        tok = token(begin ? "template_head" : "template_cont", content);
+        TEMPLATE_RAWS.set(tok, raw);
+        tok.template_end = true;
+        return tok;
+    });
+
+    function skip_line_comment(type) {
+        var regex_allowed = S.regex_allowed;
+        var i = find_eol(), ret;
+        if (i == -1) {
+            ret = S.text.substr(S.pos);
+            S.pos = S.text.length;
+        } else {
+            ret = S.text.substring(S.pos, i);
+            S.pos = i;
+        }
+        S.col = S.tokcol + (S.pos - S.tokpos);
+        S.comments_before.push(token(type, ret, true));
+        S.regex_allowed = regex_allowed;
+        return next_token;
+    }
+
+    var skip_multiline_comment = with_eof_error("Unterminated multiline comment", function() {
+        var regex_allowed = S.regex_allowed;
+        var i = find("*/", true);
+        var text = S.text.substring(S.pos, i).replace(/\r\n|\r|\u2028|\u2029/g, "\n");
+        // update stream position
+        forward(get_full_char_length(text) /* text length doesn't count \r\n as 2 char while S.pos - i does */ + 2);
+        S.comments_before.push(token("comment2", text, true));
+        S.newline_before = S.newline_before || text.includes("\n");
+        S.regex_allowed = regex_allowed;
+        return next_token;
+    });
+
+    var read_name = function () {
+        let start = S.pos, end = start - 1, ch = "c";
+
+        while (
+            (ch = S.text.charAt(++end))
+            && (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z")
+        );
+
+        // 0x7F is very rare in actual code, so we compare it to "~" (0x7E)
+        if (end > start + 1 && ch && ch !== "\\" && !is_identifier_char(ch) && ch <= "~") {
+            S.pos += end - start;
+            S.col += end - start;
+            return S.text.slice(start, S.pos);
+        }
+
+        return read_name_hard();
+    };
+
+    var read_name_hard = with_eof_error("Unterminated identifier name", function() {
+        var name = [], ch, escaped = false;
+        var read_escaped_identifier_char = function() {
+            escaped = true;
+            next();
+            if (peek() !== "u") {
+                parse_error("Expecting UnicodeEscapeSequence -- uXXXX or u{XXXX}");
+            }
+            return read_escaped_char(false, true);
+        };
+
+        // Read first character (ID_Start)
+        if ((ch = peek()) === "\\") {
+            ch = read_escaped_identifier_char();
+            if (!is_identifier_start(ch)) {
+                parse_error("First identifier char is an invalid identifier char");
+            }
+        } else if (is_identifier_start(ch)) {
+            next();
+        } else {
+            return "";
+        }
+
+        name.push(ch);
+
+        // Read ID_Continue
+        while ((ch = peek()) != null) {
+            if ((ch = peek()) === "\\") {
+                ch = read_escaped_identifier_char();
+                if (!is_identifier_char(ch)) {
+                    parse_error("Invalid escaped identifier char");
+                }
+            } else {
+                if (!is_identifier_char(ch)) {
+                    break;
+                }
+                next();
+            }
+            name.push(ch);
+        }
+        const name_str = name.join("");
+        if (RESERVED_WORDS.has(name_str) && escaped) {
+            parse_error("Escaped characters are not allowed in keywords");
+        }
+        return name_str;
+    });
+
+    var read_regexp = with_eof_error("Unterminated regular expression", function(source) {
+        var prev_backslash = false, ch, in_class = false;
+        while ((ch = next(true))) if (NEWLINE_CHARS.has(ch)) {
+            parse_error("Unexpected line terminator");
+        } else if (prev_backslash) {
+            if (/^[\u0000-\u007F]$/.test(ch)) {
+                source += "\\" + ch;
+            } else {
+                // Remove the useless slash before the escape, but only for characters that won't be added to regexp syntax
+                source += ch;
+            }
+            prev_backslash = false;
+        } else if (ch == "[") {
+            in_class = true;
+            source += ch;
+        } else if (ch == "]" && in_class) {
+            in_class = false;
+            source += ch;
+        } else if (ch == "/" && !in_class) {
+            break;
+        } else if (ch == "\\") {
+            prev_backslash = true;
+        } else {
+            source += ch;
+        }
+        const flags = read_name();
+        return token("regexp", "/" + source + "/" + flags);
+    });
+
+    function read_operator(prefix) {
+        function grow(op) {
+            if (!peek()) return op;
+            var bigger = op + peek();
+            if (OPERATORS.has(bigger)) {
+                next();
+                return grow(bigger);
+            } else {
+                return op;
+            }
+        }
+        return token("operator", grow(prefix || next()));
+    }
+
+    function handle_slash() {
+        next();
+        switch (peek()) {
+          case "/":
+            next();
+            return skip_line_comment("comment1");
+          case "*":
+            next();
+            return skip_multiline_comment();
+        }
+        return S.regex_allowed ? read_regexp("") : read_operator("/");
+    }
+
+    function handle_eq_sign() {
+        next();
+        if (peek() === ">") {
+            next();
+            return token("arrow", "=>");
+        } else {
+            return read_operator("=");
+        }
+    }
+
+    function handle_dot() {
+        next();
+        if (is_digit(peek().charCodeAt(0))) {
+            return read_num(".");
+        }
+        if (peek() === ".") {
+            next();  // Consume second dot
+            next();  // Consume third dot
+            return token("expand", "...");
+        }
+
+        return token("punc", ".");
+    }
+
+    function read_word() {
+        var word = read_name();
+        if (prev_was_dot) return token("name", word);
+        return KEYWORDS_ATOM.has(word) ? token("atom", word)
+            : !KEYWORDS.has(word) ? token("name", word)
+            : OPERATORS.has(word) ? token("operator", word)
+            : token("keyword", word);
+    }
+
+    function read_private_word() {
+        next();
+        return token("privatename", read_name());
+    }
+
+    function with_eof_error(eof_error, cont) {
+        return function(x) {
+            try {
+                return cont(x);
+            } catch(ex) {
+                if (ex === EX_EOF) parse_error(eof_error);
+                else throw ex;
+            }
+        };
+    }
+
+    function next_token(force_regexp) {
+        if (force_regexp != null)
+            return read_regexp(force_regexp);
+        if (shebang && S.pos == 0 && looking_at("#!")) {
+            start_token();
+            forward(2);
+            skip_line_comment("comment5");
+        }
+        for (;;) {
+            skip_whitespace();
+            start_token();
+            if (html5_comments) {
+                if (looking_at("<!--")) {
+                    forward(4);
+                    skip_line_comment("comment3");
+                    continue;
+                }
+                if (looking_at("-->") && S.newline_before) {
+                    forward(3);
+                    skip_line_comment("comment4");
+                    continue;
+                }
+            }
+            var ch = peek();
+            if (!ch) return token("eof");
+            var code = ch.charCodeAt(0);
+            switch (code) {
+              case 34: case 39: return read_string();
+              case 46: return handle_dot();
+              case 47: {
+                  var tok = handle_slash();
+                  if (tok === next_token) continue;
+                  return tok;
+              }
+              case 61: return handle_eq_sign();
+              case 63: {
+                  if (!is_option_chain_op()) break;  // Handled below
+
+                  next(); // ?
+                  next(); // .
+
+                  return token("punc", "?.");
+              }
+              case 96: return read_template_characters(true);
+              case 123:
+                S.brace_counter++;
+                break;
+              case 125:
+                S.brace_counter--;
+                if (S.template_braces.length > 0
+                    && S.template_braces[S.template_braces.length - 1] === S.brace_counter)
+                    return read_template_characters(false);
+                break;
+            }
+            if (is_digit(code)) return read_num();
+            if (PUNC_CHARS.has(ch)) return token("punc", next());
+            if (OPERATOR_CHARS.has(ch)) return read_operator();
+            if (code == 92 || is_identifier_start(ch)) return read_word();
+            if (code == 35) return read_private_word();
+            break;
+        }
+        parse_error("Unexpected character '" + ch + "'");
+    }
+
+    next_token.next = next;
+    next_token.peek = peek;
+
+    next_token.context = function(nc) {
+        if (nc) S = nc;
+        return S;
+    };
+
+    next_token.add_directive = function(directive) {
+        S.directive_stack[S.directive_stack.length - 1].push(directive);
+
+        if (S.directives[directive] === undefined) {
+            S.directives[directive] = 1;
+        } else {
+            S.directives[directive]++;
+        }
+    };
+
+    next_token.push_directives_stack = function() {
+        S.directive_stack.push([]);
+    };
+
+    next_token.pop_directives_stack = function() {
+        var directives = S.directive_stack[S.directive_stack.length - 1];
+
+        for (var i = 0; i < directives.length; i++) {
+            S.directives[directives[i]]--;
+        }
+
+        S.directive_stack.pop();
+    };
+
+    next_token.has_directive = function(directive) {
+        return S.directives[directive] > 0;
+    };
+
+    next_token.peek_next_token_start_or_newline = peek_next_token_start_or_newline;
+    next_token.ch_starts_binding_identifier = ch_starts_binding_identifier;
+
+    return next_token;
+
+}
+
+/* -----[ Parser (constants) ]----- */
+
+var UNARY_PREFIX = makePredicate([
+    "typeof",
+    "void",
+    "delete",
+    "--",
+    "++",
+    "!",
+    "~",
+    "-",
+    "+"
+]);
+
+var UNARY_POSTFIX = makePredicate([ "--", "++" ]);
+
+var ASSIGNMENT = makePredicate([ "=", "+=", "-=", "??=", "&&=", "||=", "/=", "*=", "**=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&=" ]);
+
+var LOGICAL_ASSIGNMENT = makePredicate([ "??=", "&&=", "||=" ]);
+
+var PRECEDENCE = (function(a, ret) {
+    for (var i = 0; i < a.length; ++i) {
+        for (const op of a[i]) {
+            ret[op] = i + 1;
+        }
+    }
+    return ret;
+})(
+    [
+        ["||"],
+        ["??"],
+        ["&&"],
+        ["|"],
+        ["^"],
+        ["&"],
+        ["==", "===", "!=", "!=="],
+        ["<", ">", "<=", ">=", "in", "instanceof"],
+        [">>", "<<", ">>>"],
+        ["+", "-"],
+        ["*", "/", "%"],
+        ["**"]
+    ],
+    {}
+);
+
+var ATOMIC_START_TOKEN = makePredicate([ "atom", "num", "big_int", "string", "regexp", "name"]);
+
+/* -----[ Parser ]----- */
+
+function parse($TEXT, options) {
+    // maps start tokens to count of comments found outside of their parens
+    // Example: /* I count */ ( /* I don't */ foo() )
+    // Useful because comments_before property of call with parens outside
+    // contains both comments inside and outside these parens. Used to find the
+    // right #__PURE__ comments for an expression
+    const outer_comments_before_counts = new WeakMap();
+
+    options = defaults(options, {
+        bare_returns   : false,
+        ecma           : null,  // Legacy
+        expression     : false,
+        filename       : null,
+        html5_comments : true,
+        module         : false,
+        shebang        : true,
+        strict         : false,
+        toplevel       : null,
+    }, true);
+
+    var S = {
+        input         : (typeof $TEXT == "string"
+                         ? tokenizer($TEXT, options.filename,
+                                     options.html5_comments, options.shebang)
+                         : $TEXT),
+        token         : null,
+        prev          : null,
+        peeked        : null,
+        in_function   : 0,
+        in_async      : -1,
+        in_generator  : -1,
+        in_directives : true,
+        in_loop       : 0,
+        labels        : []
+    };
+
+    S.token = next();
+
+    function is(type, value) {
+        return is_token(S.token, type, value);
+    }
+
+    function peek() { return S.peeked || (S.peeked = S.input()); }
+
+    function next() {
+        S.prev = S.token;
+
+        if (!S.peeked) peek();
+        S.token = S.peeked;
+        S.peeked = null;
+        S.in_directives = S.in_directives && (
+            S.token.type == "string" || is("punc", ";")
+        );
+        return S.token;
+    }
+
+    function prev() {
+        return S.prev;
+    }
+
+    function croak(msg, line, col, pos) {
+        var ctx = S.input.context();
+        js_error(msg,
+                 ctx.filename,
+                 line != null ? line : ctx.tokline,
+                 col != null ? col : ctx.tokcol,
+                 pos != null ? pos : ctx.tokpos);
+    }
+
+    function token_error(token, msg) {
+        croak(msg, token.line, token.col);
+    }
+
+    function unexpected(token) {
+        if (token == null)
+            token = S.token;
+        token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
+    }
+
+    function expect_token(type, val) {
+        if (is(type, val)) {
+            return next();
+        }
+        token_error(S.token, "Unexpected token " + S.token.type + " «" + S.token.value + "»" + ", expected " + type + " «" + val + "»");
+    }
+
+    function expect(punc) { return expect_token("punc", punc); }
+
+    function has_newline_before(token) {
+        return token.nlb || !token.comments_before.every((comment) => !comment.nlb);
+    }
+
+    function can_insert_semicolon() {
+        return !options.strict
+            && (is("eof") || is("punc", "}") || has_newline_before(S.token));
+    }
+
+    function is_in_generator() {
+        return S.in_generator === S.in_function;
+    }
+
+    function is_in_async() {
+        return S.in_async === S.in_function;
+    }
+
+    function can_await() {
+        return (
+            S.in_async === S.in_function
+            || S.in_function === 0 && S.input.has_directive("use strict")
+        );
+    }
+
+    function semicolon(optional) {
+        if (is("punc", ";")) next();
+        else if (!optional && !can_insert_semicolon()) unexpected();
+    }
+
+    function parenthesised() {
+        expect("(");
+        var exp = expression(true);
+        expect(")");
+        return exp;
+    }
+
+    function embed_tokens(parser) {
+        return function _embed_tokens_wrapper(...args) {
+            const start = S.token;
+            const expr = parser(...args);
+            expr.start = start;
+            expr.end = prev();
+            return expr;
+        };
+    }
+
+    function handle_regexp() {
+        if (is("operator", "/") || is("operator", "/=")) {
+            S.peeked = null;
+            S.token = S.input(S.token.value.substr(1)); // force regexp
+        }
+    }
+
+    var statement = embed_tokens(function statement(is_export_default, is_for_body, is_if_body) {
+        handle_regexp();
+        switch (S.token.type) {
+          case "string":
+            if (S.in_directives) {
+                var token = peek();
+                if (!LATEST_RAW.includes("\\")
+                    && (is_token(token, "punc", ";")
+                        || is_token(token, "punc", "}")
+                        || has_newline_before(token)
+                        || is_token(token, "eof"))) {
+                    S.input.add_directive(S.token.value);
+                } else {
+                    S.in_directives = false;
+                }
+            }
+            var dir = S.in_directives, stat = simple_statement();
+            return dir && stat.body instanceof AST_String ? new AST_Directive(stat.body) : stat;
+          case "template_head":
+          case "num":
+          case "big_int":
+          case "regexp":
+          case "operator":
+          case "atom":
+            return simple_statement();
+
+          case "name":
+            if (S.token.value == "async" && is_token(peek(), "keyword", "function")) {
+                next();
+                next();
+                if (is_for_body) {
+                    croak("functions are not allowed as the body of a loop");
+                }
+                return function_(AST_Defun, false, true, is_export_default);
+            }
+            if (S.token.value == "import" && !is_token(peek(), "punc", "(") && !is_token(peek(), "punc", ".")) {
+                next();
+                var node = import_statement();
+                semicolon();
+                return node;
+            }
+            if (S.token.value == "using" && is_token(peek(), "name") && !has_newline_before(peek())) {
+                next();
+                var node = using_();
+                semicolon();
+                return node;
+            }
+            if (S.token.value == "await" && can_await() && is_token(peek(), "name", "using") && !has_newline_before(peek())) {
+                var next_next = S.input.peek_next_token_start_or_newline();
+                if (S.input.ch_starts_binding_identifier(next_next.char, next_next.pos)) {
+                    next();
+                    // The "using" token will be consumed by the await_using_ function.
+                    var node = await_using_();
+                    semicolon();
+                    return node;
+                }
+            }
+            return is_token(peek(), "punc", ":")
+                ? labeled_statement()
+                : simple_statement();
+
+          case "privatename":
+            if(!S.in_class)
+              croak("Private field must be used in an enclosing class");
+            return simple_statement();
+
+          case "punc":
+            switch (S.token.value) {
+              case "{":
+                return new AST_BlockStatement({
+                    start : S.token,
+                    body  : block_(),
+                    end   : prev()
+                });
+              case "[":
+              case "(":
+                return simple_statement();
+              case ";":
+                S.in_directives = false;
+                next();
+                return new AST_EmptyStatement();
+              default:
+                unexpected();
+            }
+
+          case "keyword":
+            switch (S.token.value) {
+              case "break":
+                next();
+                return break_cont(AST_Break);
+
+              case "continue":
+                next();
+                return break_cont(AST_Continue);
+
+              case "debugger":
+                next();
+                semicolon();
+                return new AST_Debugger();
+
+              case "do":
+                next();
+                var body = in_loop(statement);
+                expect_token("keyword", "while");
+                var condition = parenthesised();
+                semicolon(true);
+                return new AST_Do({
+                    body      : body,
+                    condition : condition
+                });
+
+              case "while":
+                next();
+                return new AST_While({
+                    condition : parenthesised(),
+                    body      : in_loop(function() { return statement(false, true); })
+                });
+
+              case "for":
+                next();
+                return for_();
+
+              case "class":
+                next();
+                if (is_for_body) {
+                    croak("classes are not allowed as the body of a loop");
+                }
+                if (is_if_body) {
+                    croak("classes are not allowed as the body of an if");
+                }
+                return class_(AST_DefClass, is_export_default);
+
+              case "function":
+                next();
+                if (is_for_body) {
+                    croak("functions are not allowed as the body of a loop");
+                }
+                return function_(AST_Defun, false, false, is_export_default);
+
+              case "if":
+                next();
+                return if_();
+
+              case "return":
+                if (S.in_function == 0 && !options.bare_returns)
+                    croak("'return' outside of function");
+                next();
+                var value = null;
+                if (is("punc", ";")) {
+                    next();
+                } else if (!can_insert_semicolon()) {
+                    value = expression(true);
+                    semicolon();
+                }
+                return new AST_Return({
+                    value: value
+                });
+
+              case "switch":
+                next();
+                return new AST_Switch({
+                    expression : parenthesised(),
+                    body       : in_loop(switch_body_)
+                });
+
+              case "throw":
+                next();
+                if (has_newline_before(S.token))
+                    croak("Illegal newline after 'throw'");
+                var value = expression(true);
+                semicolon();
+                return new AST_Throw({
+                    value: value
+                });
+
+              case "try":
+                next();
+                return try_();
+
+              case "var":
+                next();
+                var node = var_();
+                semicolon();
+                return node;
+
+              case "let":
+                next();
+                var node = let_();
+                semicolon();
+                return node;
+
+              case "const":
+                next();
+                var node = const_();
+                semicolon();
+                return node;
+
+              case "with":
+                if (S.input.has_directive("use strict")) {
+                    croak("Strict mode may not include a with statement");
+                }
+                next();
+                return new AST_With({
+                    expression : parenthesised(),
+                    body       : statement()
+                });
+
+              case "export":
+                if (!is_token(peek(), "punc", "(")) {
+                    next();
+                    var node = export_statement();
+                    if (is("punc", ";")) semicolon();
+                    return node;
+                }
+            }
+        }
+        unexpected();
+    });
+
+    function labeled_statement() {
+        var label = as_symbol(AST_Label);
+        if (label.name === "await" && is_in_async()) {
+            token_error(S.prev, "await cannot be used as label inside async function");
+        }
+        if (S.labels.some((l) => l.name === label.name)) {
+            // ECMA-262, 12.12: An ECMAScript program is considered
+            // syntactically incorrect if it contains a
+            // LabelledStatement that is enclosed by a
+            // LabelledStatement with the same Identifier as label.
+            croak("Label " + label.name + " defined twice");
+        }
+        expect(":");
+        S.labels.push(label);
+        var stat = statement();
+        S.labels.pop();
+        if (!(stat instanceof AST_IterationStatement)) {
+            // check for `continue` that refers to this label.
+            // those should be reported as syntax errors.
+            // https://github.com/mishoo/UglifyJS2/issues/287
+            label.references.forEach(function(ref) {
+                if (ref instanceof AST_Continue) {
+                    ref = ref.label.start;
+                    croak("Continue label `" + label.name + "` refers to non-IterationStatement.",
+                          ref.line, ref.col, ref.pos);
+                }
+            });
+        }
+        return new AST_LabeledStatement({ body: stat, label: label });
+    }
+
+    function simple_statement(tmp) {
+        return new AST_SimpleStatement({ body: (tmp = expression(true), semicolon(), tmp) });
+    }
+
+    function break_cont(type) {
+        var label = null, ldef;
+        if (!can_insert_semicolon()) {
+            label = as_symbol(AST_LabelRef, true);
+        }
+        if (label != null) {
+            ldef = S.labels.find((l) => l.name === label.name);
+            if (!ldef)
+                croak("Undefined label " + label.name);
+            label.thedef = ldef;
+        } else if (S.in_loop == 0)
+            croak(type.TYPE + " not inside a loop or switch");
+        semicolon();
+        var stat = new type({ label: label });
+        if (ldef) ldef.references.push(stat);
+        return stat;
+    }
+
+    function for_() {
+        var for_await_error = "`for await` invalid in this context";
+        var await_tok = S.token;
+        if (await_tok.type == "name" && await_tok.value == "await") {
+            if (!can_await()) {
+                token_error(await_tok, for_await_error);
+            }
+            next();
+        } else {
+            await_tok = false;
+        }
+        expect("(");
+        var init = null;
+        if (!is("punc", ";")) {
+            init =
+                is("keyword", "var") ? (next(), var_(true)) :
+                is("keyword", "let") ? (next(), let_(true)) :
+                is("keyword", "const") ? (next(), const_(true)) :
+                is("name", "using") && is_token(peek(), "name") && (peek().value != "of" || S.input.peek_next_token_start_or_newline().char == "=") ? (next(), using_(true)) :
+                is("name", "await") && can_await() && is_token(peek(), "name", "using") ? (next(), await_using_(true)) :
+                                       expression(true, true);
+            var is_in = is("operator", "in");
+            var is_of = is("name", "of");
+            if (await_tok && !is_of) {
+                token_error(await_tok, for_await_error);
+            }
+            if (is_in || is_of) {
+                if (init instanceof AST_DefinitionsLike) {
+                    if (init.definitions.length > 1)
+                        token_error(init.start, "Only one variable declaration allowed in for..in loop");
+                    if (is_in && init instanceof AST_Using) {
+                        token_error(init.start, "Invalid using declaration in for..in loop");
+                    }
+                } else if (!(is_assignable(init) || (init = to_destructuring(init)) instanceof AST_Destructuring)) {
+                    token_error(init.start, "Invalid left-hand side in for..in loop");
+                }
+                next();
+                if (is_in) {
+                    return for_in(init);
+                } else {
+                    return for_of(init, !!await_tok);
+                }
+            }
+        } else if (await_tok) {
+            token_error(await_tok, for_await_error);
+        }
+        return regular_for(init);
+    }
+
+    function regular_for(init) {
+        expect(";");
+        var test = is("punc", ";") ? null : expression(true);
+        expect(";");
+        var step = is("punc", ")") ? null : expression(true);
+        expect(")");
+        return new AST_For({
+            init      : init,
+            condition : test,
+            step      : step,
+            body      : in_loop(function() { return statement(false, true); })
+        });
+    }
+
+    function for_of(init, is_await) {
+        var lhs = init instanceof AST_DefinitionsLike ? init.definitions[0].name : null;
+        var obj = expression(true);
+        expect(")");
+        return new AST_ForOf({
+            await  : is_await,
+            init   : init,
+            name   : lhs,
+            object : obj,
+            body   : in_loop(function() { return statement(false, true); })
+        });
+    }
+
+    function for_in(init) {
+        var obj = expression(true);
+        expect(")");
+        return new AST_ForIn({
+            init   : init,
+            object : obj,
+            body   : in_loop(function() { return statement(false, true); })
+        });
+    }
+
+    var arrow_function = function(start, argnames, is_async) {
+        if (has_newline_before(S.token)) {
+            croak("Unexpected newline before arrow (=>)");
+        }
+
+        expect_token("arrow", "=>");
+
+        var body = _function_body(is("punc", "{"), false, is_async);
+
+        return new AST_Arrow({
+            start    : start,
+            end      : body.end,
+            async    : is_async,
+            argnames : argnames,
+            body     : body
+        });
+    };
+
+    var function_ = function(ctor, is_generator, is_async, is_export_default) {
+        var in_statement = ctor === AST_Defun;
+        if (is("operator", "*")) {
+            is_generator = true;
+            next();
+        }
+
+        var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null;
+        if (in_statement && !name) {
+            if (is_export_default) {
+                ctor = AST_Function;
+            } else {
+                unexpected();
+            }
+        }
+
+        if (name && ctor !== AST_Accessor && !(name instanceof AST_SymbolDeclaration))
+            unexpected(prev());
+
+        var args = [];
+        var body = _function_body(true, is_generator, is_async, name, args);
+        return new ctor({
+            start : args.start,
+            end   : body.end,
+            is_generator: is_generator,
+            async : is_async,
+            name  : name,
+            argnames: args,
+            body  : body
+        });
+    };
+
+    class UsedParametersTracker {
+        constructor(is_parameter, strict, duplicates_ok = false) {
+            this.is_parameter = is_parameter;
+            this.duplicates_ok = duplicates_ok;
+            this.parameters = new Set();
+            this.duplicate = null;
+            this.default_assignment = false;
+            this.spread = false;
+            this.strict_mode = !!strict;
+        }
+        add_parameter(token) {
+            if (this.parameters.has(token.value)) {
+                if (this.duplicate === null) {
+                    this.duplicate = token;
+                }
+                this.check_strict();
+            } else {
+                this.parameters.add(token.value);
+                if (this.is_parameter) {
+                    switch (token.value) {
+                      case "arguments":
+                      case "eval":
+                      case "yield":
+                        if (this.strict_mode) {
+                            token_error(token, "Unexpected " + token.value + " identifier as parameter inside strict mode");
+                        }
+                        break;
+                      default:
+                        if (RESERVED_WORDS.has(token.value)) {
+                            unexpected();
+                        }
+                    }
+                }
+            }
+        }
+        mark_default_assignment(token) {
+            if (this.default_assignment === false) {
+                this.default_assignment = token;
+            }
+        }
+        mark_spread(token) {
+            if (this.spread === false) {
+                this.spread = token;
+            }
+        }
+        mark_strict_mode() {
+            this.strict_mode = true;
+        }
+        is_strict() {
+            return this.default_assignment !== false || this.spread !== false || this.strict_mode;
+        }
+        check_strict() {
+            if (this.is_strict() && this.duplicate !== null && !this.duplicates_ok) {
+                token_error(this.duplicate, "Parameter " + this.duplicate.value + " was used already");
+            }
+        }
+    }
+
+    function parameters(params) {
+        var used_parameters = new UsedParametersTracker(true, S.input.has_directive("use strict"));
+
+        expect("(");
+
+        while (!is("punc", ")")) {
+            var param = parameter(used_parameters);
+            params.push(param);
+
+            if (!is("punc", ")")) {
+                expect(",");
+            }
+
+            if (param instanceof AST_Expansion) {
+                break;
+            }
+        }
+
+        next();
+    }
+
+    function parameter(used_parameters, symbol_type) {
+        var param;
+        var expand = false;
+        if (used_parameters === undefined) {
+            used_parameters = new UsedParametersTracker(true, S.input.has_directive("use strict"));
+        }
+        if (is("expand", "...")) {
+            expand = S.token;
+            used_parameters.mark_spread(S.token);
+            next();
+        }
+        param = binding_element(used_parameters, symbol_type);
+
+        if (is("operator", "=") && expand === false) {
+            used_parameters.mark_default_assignment(S.token);
+            next();
+            param = new AST_DefaultAssign({
+                start: param.start,
+                left: param,
+                operator: "=",
+                right: expression(false),
+                end: S.token
+            });
+        }
+
+        if (expand !== false) {
+            if (!is("punc", ")")) {
+                unexpected();
+            }
+            param = new AST_Expansion({
+                start: expand,
+                expression: param,
+                end: expand
+            });
+        }
+        used_parameters.check_strict();
+
+        return param;
+    }
+
+    function binding_element(used_parameters, symbol_type) {
+        var elements = [];
+        var first = true;
+        var is_expand = false;
+        var expand_token;
+        var first_token = S.token;
+        if (used_parameters === undefined) {
+            const strict = S.input.has_directive("use strict");
+            const duplicates_ok = symbol_type === AST_SymbolVar;
+            used_parameters = new UsedParametersTracker(false, strict, duplicates_ok);
+        }
+        symbol_type = symbol_type === undefined ? AST_SymbolFunarg : symbol_type;
+        if (is("punc", "[")) {
+            next();
+            while (!is("punc", "]")) {
+                if (first) {
+                    first = false;
+                } else {
+                    expect(",");
+                }
+
+                if (is("expand", "...")) {
+                    is_expand = true;
+                    expand_token = S.token;
+                    used_parameters.mark_spread(S.token);
+                    next();
+                }
+                if (is("punc")) {
+                    switch (S.token.value) {
+                      case ",":
+                        elements.push(new AST_Hole({
+                            start: S.token,
+                            end: S.token
+                        }));
+                        continue;
+                      case "]": // Trailing comma after last element
+                        break;
+                      case "[":
+                      case "{":
+                        elements.push(binding_element(used_parameters, symbol_type));
+                        break;
+                      default:
+                        unexpected();
+                    }
+                } else if (is("name")) {
+                    used_parameters.add_parameter(S.token);
+                    elements.push(as_symbol(symbol_type));
+                } else {
+                    croak("Invalid function parameter");
+                }
+                if (is("operator", "=") && is_expand === false) {
+                    used_parameters.mark_default_assignment(S.token);
+                    next();
+                    elements[elements.length - 1] = new AST_DefaultAssign({
+                        start: elements[elements.length - 1].start,
+                        left: elements[elements.length - 1],
+                        operator: "=",
+                        right: expression(false),
+                        end: S.token
+                    });
+                }
+                if (is_expand) {
+                    if (!is("punc", "]")) {
+                        croak("Rest element must be last element");
+                    }
+                    elements[elements.length - 1] = new AST_Expansion({
+                        start: expand_token,
+                        expression: elements[elements.length - 1],
+                        end: expand_token
+                    });
+                }
+            }
+            expect("]");
+            used_parameters.check_strict();
+            return new AST_Destructuring({
+                start: first_token,
+                names: elements,
+                is_array: true,
+                end: prev()
+            });
+        } else if (is("punc", "{")) {
+            next();
+            while (!is("punc", "}")) {
+                if (first) {
+                    first = false;
+                } else {
+                    expect(",");
+                }
+                if (is("expand", "...")) {
+                    is_expand = true;
+                    expand_token = S.token;
+                    used_parameters.mark_spread(S.token);
+                    next();
+                }
+                if (is("name") && (is_token(peek(), "punc") || is_token(peek(), "operator")) && [",", "}", "="].includes(peek().value)) {
+                    used_parameters.add_parameter(S.token);
+                    var start = prev();
+                    var value = as_symbol(symbol_type);
+                    if (is_expand) {
+                        elements.push(new AST_Expansion({
+                            start: expand_token,
+                            expression: value,
+                            end: value.end,
+                        }));
+                    } else {
+                        elements.push(new AST_ObjectKeyVal({
+                            start: start,
+                            key: value.name,
+                            value: value,
+                            end: value.end,
+                        }));
+                    }
+                } else if (is("punc", "}")) {
+                    continue; // Allow trailing hole
+                } else {
+                    var property_token = S.token;
+                    var property = as_property_name();
+                    if (property === null) {
+                        unexpected(prev());
+                    } else if (prev().type === "name" && !is("punc", ":")) {
+                        elements.push(new AST_ObjectKeyVal({
+                            start: prev(),
+                            key: property,
+                            value: new symbol_type({
+                                start: prev(),
+                                name: property,
+                                end: prev()
+                            }),
+                            end: prev()
+                        }));
+                    } else {
+                        expect(":");
+                        elements.push(new AST_ObjectKeyVal({
+                            start: property_token,
+                            quote: property_token.quote,
+                            key: property,
+                            value: binding_element(used_parameters, symbol_type),
+                            end: prev()
+                        }));
+                    }
+                }
+                if (is_expand) {
+                    if (!is("punc", "}")) {
+                        croak("Rest element must be last element");
+                    }
+                } else if (is("operator", "=")) {
+                    used_parameters.mark_default_assignment(S.token);
+                    next();
+                    elements[elements.length - 1].value = new AST_DefaultAssign({
+                        start: elements[elements.length - 1].value.start,
+                        left: elements[elements.length - 1].value,
+                        operator: "=",
+                        right: expression(false),
+                        end: S.token
+                    });
+                }
+            }
+            expect("}");
+            used_parameters.check_strict();
+            return new AST_Destructuring({
+                start: first_token,
+                names: elements,
+                is_array: false,
+                end: prev()
+            });
+        } else if (is("name")) {
+            used_parameters.add_parameter(S.token);
+            return as_symbol(symbol_type);
+        } else {
+            croak("Invalid function parameter");
+        }
+    }
+
+    function params_or_seq_(allow_arrows, maybe_sequence) {
+        var spread_token;
+        var invalid_sequence;
+        var trailing_comma;
+        var a = [];
+        expect("(");
+        while (!is("punc", ")")) {
+            if (spread_token) unexpected(spread_token);
+            if (is("expand", "...")) {
+                spread_token = S.token;
+                if (maybe_sequence) invalid_sequence = S.token;
+                next();
+                a.push(new AST_Expansion({
+                    start: prev(),
+                    expression: expression(),
+                    end: S.token,
+                }));
+            } else {
+                a.push(expression());
+            }
+            if (!is("punc", ")")) {
+                expect(",");
+                if (is("punc", ")")) {
+                    trailing_comma = prev();
+                    if (maybe_sequence) invalid_sequence = trailing_comma;
+                }
+            }
+        }
+        expect(")");
+        if (allow_arrows && is("arrow", "=>")) {
+            if (spread_token && trailing_comma) unexpected(trailing_comma);
+        } else if (invalid_sequence) {
+            unexpected(invalid_sequence);
+        }
+        return a;
+    }
+
+    function _function_body(block, generator, is_async, name, args) {
+        var loop = S.in_loop;
+        var labels = S.labels;
+        var current_generator = S.in_generator;
+        var current_async = S.in_async;
+        ++S.in_function;
+        if (generator)
+            S.in_generator = S.in_function;
+        if (is_async)
+            S.in_async = S.in_function;
+        if (args) parameters(args);
+        if (block)
+            S.in_directives = true;
+        S.in_loop = 0;
+        S.labels = [];
+        if (block) {
+            S.input.push_directives_stack();
+            var a = block_();
+            if (name) _verify_symbol(name);
+            if (args) args.forEach(_verify_symbol);
+            S.input.pop_directives_stack();
+        } else {
+            var a = [new AST_Return({
+                start: S.token,
+                value: expression(false),
+                end: S.token
+            })];
+        }
+        --S.in_function;
+        S.in_loop = loop;
+        S.labels = labels;
+        S.in_generator = current_generator;
+        S.in_async = current_async;
+        return a;
+    }
+
+    function _await_expression() {
+        // Previous token must be "await" and not be interpreted as an identifier
+        if (!can_await()) {
+            croak("Unexpected await expression outside async function",
+                S.prev.line, S.prev.col, S.prev.pos);
+        }
+        // the await expression is parsed as a unary expression in Babel
+        return new AST_Await({
+            start: prev(),
+            end: S.token,
+            expression : maybe_unary(true),
+        });
+    }
+
+    function _yield_expression() {
+        var start = S.token;
+        var star = false;
+        var has_expression = true;
+
+        // Attempt to get expression or star (and then the mandatory expression)
+        // behind yield on the same line.
+        //
+        // If nothing follows on the same line of the yieldExpression,
+        // it should default to the value `undefined` for yield to return.
+        // In that case, the `undefined` stored as `null` in ast.
+        //
+        // Note 1: It isn't allowed for yield* to close without an expression
+        // Note 2: If there is a nlb between yield and star, it is interpret as
+        //         yield <explicit undefined> <inserted automatic semicolon> *
+        if (
+            can_insert_semicolon()
+            || is("punc") && PUNC_AFTER_EXPRESSION.has(S.token.value)
+            || is("template_cont")
+        ) {
+            has_expression = false;
+        } else if (is("operator", "*")) {
+            star = true;
+            next();
+        }
+
+        return new AST_Yield({
+            start      : start,
+            is_star    : star,
+            expression : has_expression ? expression() : null,
+            end        : prev()
+        });
+    }
+
+    function if_() {
+        var cond = parenthesised(), body = statement(false, false, true), belse = null;
+        if (is("keyword", "else")) {
+            next();
+            belse = statement(false, false, true);
+        }
+        return new AST_If({
+            condition   : cond,
+            body        : body,
+            alternative : belse
+        });
+    }
+
+    function block_() {
+        expect("{");
+        var a = [];
+        while (!is("punc", "}")) {
+            if (is("eof")) unexpected();
+            a.push(statement());
+        }
+        next();
+        return a;
+    }
+
+    function switch_body_() {
+        expect("{");
+        var a = [], cur = null, branch = null, tmp;
+        while (!is("punc", "}")) {
+            if (is("eof")) unexpected();
+            if (is("keyword", "case")) {
+                if (branch) branch.end = prev();
+                cur = [];
+                branch = new AST_Case({
+                    start      : (tmp = S.token, next(), tmp),
+                    expression : expression(true),
+                    body       : cur
+                });
+                a.push(branch);
+                expect(":");
+            } else if (is("keyword", "default")) {
+                if (branch) branch.end = prev();
+                cur = [];
+                branch = new AST_Default({
+                    start : (tmp = S.token, next(), expect(":"), tmp),
+                    body  : cur
+                });
+                a.push(branch);
+            } else {
+                if (!cur) unexpected();
+                cur.push(statement());
+            }
+        }
+        if (branch) branch.end = prev();
+        next();
+        return a;
+    }
+
+    function try_() {
+        var body, bcatch = null, bfinally = null;
+        body = new AST_TryBlock({
+            start : S.token,
+            body  : block_(),
+            end   : prev(),
+        });
+        if (is("keyword", "catch")) {
+            var start = S.token;
+            next();
+            if (is("punc", "{")) {
+                var name = null;
+            } else {
+                expect("(");
+                var name = parameter(undefined, AST_SymbolCatch);
+                expect(")");
+            }
+            bcatch = new AST_Catch({
+                start   : start,
+                argname : name,
+                body    : block_(),
+                end     : prev()
+            });
+        }
+        if (is("keyword", "finally")) {
+            var start = S.token;
+            next();
+            bfinally = new AST_Finally({
+                start : start,
+                body  : block_(),
+                end   : prev()
+            });
+        }
+        if (!bcatch && !bfinally)
+            croak("Missing catch/finally blocks");
+        return new AST_Try({
+            body     : body,
+            bcatch   : bcatch,
+            bfinally : bfinally
+        });
+    }
+
+    /**
+     * var
+     *   vardef1 = 2,
+     *   vardef2 = 3;
+     */
+    function vardefs(no_in, kind) {
+        var var_defs = [];
+        var def;
+        for (;;) {
+            var sym_type =
+                kind === "var" ? AST_SymbolVar :
+                kind === "const" ? AST_SymbolConst :
+                kind === "let" ? AST_SymbolLet :
+                kind === "using" ? AST_SymbolUsing :
+                kind === "await using" ? AST_SymbolUsing : null;
+            var def_type = kind === "using" || kind === "await using" ? AST_UsingDef : AST_VarDef;
+            // var { a } = b
+            if (is("punc", "{") || is("punc", "[")) {
+                def = new def_type({
+                    start: S.token,
+                    name: binding_element(undefined, sym_type),
+                    value: is("operator", "=") ? (expect_token("operator", "="), expression(false, no_in)) : null,
+                    end: prev()
+                });
+            } else {
+                def = new def_type({
+                    start : S.token,
+                    name  : as_symbol(sym_type),
+                    value : is("operator", "=")
+                        ? (next(), expression(false, no_in))
+                        : !no_in && (kind === "const" || kind === "using" || kind === "await using")
+                            ? croak("Missing initializer in " + kind + " declaration") : null,
+                    end   : prev()
+                });
+                if (def.name.name == "import") croak("Unexpected token: import");
+            }
+            var_defs.push(def);
+            if (!is("punc", ","))
+                break;
+            next();
+        }
+        return var_defs;
+    }
+
+    var var_ = function(no_in) {
+        return new AST_Var({
+            start       : prev(),
+            definitions : vardefs(no_in, "var"),
+            end         : prev()
+        });
+    };
+
+    var let_ = function(no_in) {
+        return new AST_Let({
+            start       : prev(),
+            definitions : vardefs(no_in, "let"),
+            end         : prev()
+        });
+    };
+
+    var const_ = function(no_in) {
+        return new AST_Const({
+            start       : prev(),
+            definitions : vardefs(no_in, "const"),
+            end         : prev()
+        });
+    };
+
+    var using_ = function(no_in) {
+        return new AST_Using({
+            start       : prev(),
+            await       : false,
+            definitions : vardefs(no_in, "using"),
+            end         : prev()
+        });
+    };
+
+    var await_using_ = function(no_in) {
+        // Assumption: When await_using_ is called, only the `await` token has been consumed.
+        return new AST_Using({
+            start       : prev(),
+            await       : true,
+            definitions : (next(), vardefs(no_in, "await using")),
+            end         : prev()
+        });
+    };
+
+    var new_ = function(allow_calls) {
+        var start = S.token;
+        expect_token("operator", "new");
+        if (is("punc", ".")) {
+            next();
+            expect_token("name", "target");
+            return subscripts(new AST_NewTarget({
+                start : start,
+                end   : prev()
+            }), allow_calls);
+        }
+        var newexp = expr_atom(false), args;
+        if (is("punc", "(")) {
+            next();
+            args = expr_list(")", true);
+        } else {
+            args = [];
+        }
+        var call = new AST_New({
+            start      : start,
+            expression : newexp,
+            args       : args,
+            end        : prev()
+        });
+        annotate(call);
+        return subscripts(call, allow_calls);
+    };
+
+    function as_atom_node() {
+        var tok = S.token, ret;
+        switch (tok.type) {
+          case "name":
+            ret = _make_symbol(AST_SymbolRef);
+            break;
+          case "num":
+            if (tok.value === Infinity) {
+                // very large float values are parsed as Infinity
+                ret = new AST_Infinity({
+                    start: tok,
+                    end: tok,
+                });
+            } else {
+                ret = new AST_Number({
+                    start: tok,
+                    end: tok,
+                    value: tok.value,
+                    raw: LATEST_RAW
+                });
+            }
+            break;
+          case "big_int":
+            ret = new AST_BigInt({
+                start: tok,
+                end: tok,
+                value: tok.value,
+                raw: LATEST_RAW,
+            });
+            break;
+          case "string":
+            ret = new AST_String({
+                start : tok,
+                end   : tok,
+                value : tok.value,
+                quote : tok.quote
+            });
+            annotate(ret);
+            break;
+          case "regexp":
+            const [_, source, flags] = tok.value.match(/^\/(.*)\/(\w*)$/);
+
+            ret = new AST_RegExp({ start: tok, end: tok, value: { source, flags } });
+            break;
+          case "atom":
+            switch (tok.value) {
+              case "false":
+                ret = new AST_False({ start: tok, end: tok });
+                break;
+              case "true":
+                ret = new AST_True({ start: tok, end: tok });
+                break;
+              case "null":
+                ret = new AST_Null({ start: tok, end: tok });
+                break;
+            }
+            break;
+        }
+        next();
+        return ret;
+    }
+
+    function to_fun_args(ex, default_seen_above) {
+        var insert_default = function(ex, default_value) {
+            if (default_value) {
+                return new AST_DefaultAssign({
+                    start: ex.start,
+                    left: ex,
+                    operator: "=",
+                    right: default_value,
+                    end: default_value.end
+                });
+            }
+            return ex;
+        };
+        if (ex instanceof AST_Object) {
+            return insert_default(new AST_Destructuring({
+                start: ex.start,
+                end: ex.end,
+                is_array: false,
+                names: ex.properties.map(prop => to_fun_args(prop))
+            }), default_seen_above);
+        } else if (ex instanceof AST_ObjectKeyVal) {
+            ex.value = to_fun_args(ex.value);
+            return insert_default(ex, default_seen_above);
+        } else if (ex instanceof AST_Hole) {
+            return ex;
+        } else if (ex instanceof AST_Destructuring) {
+            ex.names = ex.names.map(name => to_fun_args(name));
+            return insert_default(ex, default_seen_above);
+        } else if (ex instanceof AST_SymbolRef) {
+            return insert_default(new AST_SymbolFunarg({
+                name: ex.name,
+                start: ex.start,
+                end: ex.end
+            }), default_seen_above);
+        } else if (ex instanceof AST_Expansion) {
+            ex.expression = to_fun_args(ex.expression);
+            return insert_default(ex, default_seen_above);
+        } else if (ex instanceof AST_Array) {
+            return insert_default(new AST_Destructuring({
+                start: ex.start,
+                end: ex.end,
+                is_array: true,
+                names: ex.elements.map(elm => to_fun_args(elm))
+            }), default_seen_above);
+        } else if (ex instanceof AST_Assign) {
+            return insert_default(to_fun_args(ex.left, ex.right), default_seen_above);
+        } else if (ex instanceof AST_DefaultAssign) {
+            ex.left = to_fun_args(ex.left);
+            return ex;
+        } else {
+            croak("Invalid function parameter", ex.start.line, ex.start.col);
+        }
+    }
+
+    var expr_atom = function(allow_calls, allow_arrows) {
+        if (is("operator", "new")) {
+            return new_(allow_calls);
+        }
+        if (is("name", "import") && is_token(peek(), "punc", ".")) {
+            return parse_import_expr(allow_calls);
+        }
+        var start = S.token;
+        var peeked;
+        var async = is("name", "async")
+            && (peeked = peek()).value != "["
+            && peeked.type != "arrow"
+            && as_atom_node();
+        if (is("punc")) {
+            switch (S.token.value) {
+              case "(":
+                if (async && !allow_calls) break;
+                var exprs = params_or_seq_(allow_arrows, !async);
+                if (allow_arrows && is("arrow", "=>")) {
+                    return arrow_function(start, exprs.map(e => to_fun_args(e)), !!async);
+                }
+                var ex = async ? new AST_Call({
+                    expression: async,
+                    args: exprs
+                }) : to_expr_or_sequence(start, exprs);
+                if (ex.start) {
+                    const outer_comments_before = start.comments_before.length;
+                    outer_comments_before_counts.set(start, outer_comments_before);
+                    ex.start.comments_before.unshift(...start.comments_before);
+                    start.comments_before = ex.start.comments_before;
+                    if (outer_comments_before == 0 && start.comments_before.length > 0) {
+                        var comment = start.comments_before[0];
+                        if (!comment.nlb) {
+                            comment.nlb = start.nlb;
+                            start.nlb = false;
+                        }
+                    }
+                    start.comments_after = ex.start.comments_after;
+                }
+                ex.start = start;
+                var end = prev();
+                if (ex.end) {
+                    end.comments_before = ex.end.comments_before;
+                    ex.end.comments_after.push(...end.comments_after);
+                    end.comments_after = ex.end.comments_after;
+                }
+                ex.end = end;
+                if (ex instanceof AST_Call) annotate(ex);
+                return subscripts(ex, allow_calls);
+              case "[":
+                return subscripts(array_(), allow_calls);
+              case "{":
+                return subscripts(object_or_destructuring_(), allow_calls);
+            }
+            if (!async) unexpected();
+        }
+        if (allow_arrows && is("name") && is_token(peek(), "arrow")) {
+            var param = new AST_SymbolFunarg({
+                name: S.token.value,
+                start: start,
+                end: start,
+            });
+            next();
+            return arrow_function(start, [param], !!async);
+        }
+        if (is("keyword", "function")) {
+            next();
+            var func = function_(AST_Function, false, !!async);
+            func.start = start;
+            func.end = prev();
+            return subscripts(func, allow_calls);
+        }
+        if (async) return subscripts(async, allow_calls);
+        if (is("keyword", "class")) {
+            next();
+            var cls = class_(AST_ClassExpression);
+            cls.start = start;
+            cls.end = prev();
+            return subscripts(cls, allow_calls);
+        }
+        if (is("template_head")) {
+            return subscripts(template_string(), allow_calls);
+        }
+        if (ATOMIC_START_TOKEN.has(S.token.type)) {
+            return subscripts(as_atom_node(), allow_calls);
+        }
+        unexpected();
+    };
+
+    function template_string() {
+        var segments = [], start = S.token;
+
+        segments.push(new AST_TemplateSegment({
+            start: S.token,
+            raw: TEMPLATE_RAWS.get(S.token),
+            value: S.token.value,
+            end: S.token
+        }));
+
+        while (!S.token.template_end) {
+            next();
+            handle_regexp();
+            segments.push(expression(true));
+
+            segments.push(new AST_TemplateSegment({
+                start: S.token,
+                raw: TEMPLATE_RAWS.get(S.token),
+                value: S.token.value,
+                end: S.token
+            }));
+        }
+        next();
+
+        return new AST_TemplateString({
+            start: start,
+            segments: segments,
+            end: S.token
+        });
+    }
+
+    function expr_list(closing, allow_trailing_comma, allow_empty) {
+        var first = true, a = [];
+        while (!is("punc", closing)) {
+            if (first) first = false; else expect(",");
+            if (allow_trailing_comma && is("punc", closing)) break;
+            if (is("punc", ",") && allow_empty) {
+                a.push(new AST_Hole({ start: S.token, end: S.token }));
+            } else if (is("expand", "...")) {
+                next();
+                a.push(new AST_Expansion({start: prev(), expression: expression(),end: S.token}));
+            } else {
+                a.push(expression(false));
+            }
+        }
+        next();
+        return a;
+    }
+
+    var array_ = embed_tokens(function() {
+        expect("[");
+        return new AST_Array({
+            elements: expr_list("]", !options.strict, true)
+        });
+    });
+
+    var create_accessor = embed_tokens((is_generator, is_async) => {
+        return function_(AST_Accessor, is_generator, is_async);
+    });
+
+    var object_or_destructuring_ = embed_tokens(function object_or_destructuring_() {
+        var start = S.token, first = true, a = [];
+        expect("{");
+        while (!is("punc", "}")) {
+            if (first) first = false; else expect(",");
+            if (!options.strict && is("punc", "}"))
+                // allow trailing comma
+                break;
+
+            start = S.token;
+            if (start.type == "expand") {
+                next();
+                a.push(new AST_Expansion({
+                    start: start,
+                    expression: expression(false),
+                    end: prev(),
+                }));
+                continue;
+            }
+            if(is("privatename")) {
+                croak("private fields are not allowed in an object");
+            }
+            var name = as_property_name();
+            var value;
+
+            // Check property and fetch value
+            if (!is("punc", ":")) {
+                var concise = object_or_class_property(name, start);
+                if (concise) {
+                    a.push(concise);
+                    continue;
+                }
+
+                value = new AST_SymbolRef({
+                    start: prev(),
+                    name: name,
+                    end: prev()
+                });
+            } else if (name === null) {
+                unexpected(prev());
+            } else {
+                next(); // `:` - see first condition
+                value = expression(false);
+            }
+
+            // Check for default value and alter value accordingly if necessary
+            if (is("operator", "=")) {
+                next();
+                value = new AST_Assign({
+                    start: start,
+                    left: value,
+                    operator: "=",
+                    right: expression(false),
+                    logical: false,
+                    end: prev()
+                });
+            }
+
+            // Create property
+            const kv = new AST_ObjectKeyVal({
+                start: start,
+                quote: start.quote,
+                key: name,
+                value: value,
+                end: prev()
+            });
+            a.push(annotate(kv));
+        }
+        next();
+        return new AST_Object({ properties: a });
+    });
+
+    function class_(KindOfClass, is_export_default) {
+        var start, method, class_name, extends_, properties = [];
+
+        S.input.push_directives_stack(); // Push directive stack, but not scope stack
+        S.input.add_directive("use strict");
+
+        if (S.token.type == "name" && S.token.value != "extends") {
+            class_name = as_symbol(KindOfClass === AST_DefClass ? AST_SymbolDefClass : AST_SymbolClass);
+        }
+
+        if (KindOfClass === AST_DefClass && !class_name) {
+            if (is_export_default) {
+                KindOfClass = AST_ClassExpression;
+            } else {
+                unexpected();
+            }
+        }
+
+        if (S.token.value == "extends") {
+            next();
+            extends_ = expression(true);
+        }
+
+        expect("{");
+        // mark in class feild,
+        const save_in_class = S.in_class;
+        S.in_class = true;
+        while (is("punc", ";")) { next(); }  // Leading semicolons are okay in class bodies.
+        while (!is("punc", "}")) {
+            start = S.token;
+            method = object_or_class_property(as_property_name(), start, true);
+            if (!method) { unexpected(); }
+            properties.push(method);
+            while (is("punc", ";")) { next(); }
+        }
+        // mark in class feild,
+        S.in_class = save_in_class;
+
+        S.input.pop_directives_stack();
+
+        next();
+
+        return new KindOfClass({
+            start: start,
+            name: class_name,
+            extends: extends_,
+            properties: properties,
+            end: prev(),
+        });
+    }
+
+    function object_or_class_property(name, start, is_class) {
+        const get_symbol_ast = (name, SymbolClass) => {
+            if (typeof name === "string") {
+                return new SymbolClass({ start, name, end: prev() });
+            } else if (name === null) {
+                unexpected();
+            }
+            return name;
+        };
+
+        var is_private = prev().type === "privatename";
+        const is_not_method_start = () =>
+            !is("punc", "(") && !is("punc", ",") && !is("punc", "}") && !is("punc", ";") && !is("operator", "=") && !is_private;
+
+        var is_async = false;
+        var is_static = false;
+        var is_generator = false;
+        var accessor_type = null;
+
+        if (is_class && name === "static" && is_not_method_start()) {
+            const static_block = class_static_block();
+            if (static_block != null) {
+                return static_block;
+            }
+            is_static = true;
+            name = as_property_name();
+        }
+        if (name === "async" && is_not_method_start()) {
+            is_async = true;
+            name = as_property_name();
+        }
+        if (prev().type === "operator" && prev().value === "*") {
+            is_generator = true;
+            name = as_property_name();
+        }
+        if ((name === "get" || name === "set") && is_not_method_start()) {
+            accessor_type = name;
+            name = as_property_name();
+        }
+        if (!is_private && prev().type === "privatename") {
+            is_private = true;
+        }
+
+        const property_token = prev();
+
+        if (accessor_type != null) {
+            if (!is_private) {
+                const AccessorClass = accessor_type === "get"
+                    ? AST_ObjectGetter
+                    : AST_ObjectSetter;
+
+                name = get_symbol_ast(name, AST_SymbolMethod);
+                return annotate(new AccessorClass({
+                    start,
+                    static: is_static,
+                    key: name,
+                    quote: name instanceof AST_SymbolMethod ? property_token.quote : undefined,
+                    value: create_accessor(),
+                    end: prev()
+                }));
+            } else {
+                const AccessorClass = accessor_type === "get"
+                    ? AST_PrivateGetter
+                    : AST_PrivateSetter;
+
+                return annotate(new AccessorClass({
+                    start,
+                    static: is_static,
+                    key: get_symbol_ast(name, AST_SymbolMethod),
+                    value: create_accessor(),
+                    end: prev(),
+                }));
+            }
+        }
+
+        if (is("punc", "(")) {
+            name = get_symbol_ast(name, AST_SymbolMethod);
+            const AST_MethodVariant = is_private
+                ? AST_PrivateMethod
+                : AST_ConciseMethod;
+            var node = new AST_MethodVariant({
+                start       : start,
+                static      : is_static,
+                key         : name,
+                quote       : name instanceof AST_SymbolMethod ?
+                              property_token.quote : undefined,
+                value       : create_accessor(is_generator, is_async),
+                end         : prev()
+            });
+            return annotate(node);
+        }
+
+        if (is_class) {
+            const AST_SymbolVariant = is_private
+                ? AST_SymbolPrivateProperty
+                : AST_SymbolClassProperty;
+            const AST_ClassPropertyVariant = is_private
+                ? AST_ClassPrivateProperty
+                : AST_ClassProperty;
+
+            const key = get_symbol_ast(name, AST_SymbolVariant);
+            const quote = key instanceof AST_SymbolClassProperty
+                ? property_token.quote
+                : undefined;
+            if (is("operator", "=")) {
+                next();
+                return annotate(
+                    new AST_ClassPropertyVariant({
+                        start,
+                        static: is_static,
+                        quote,
+                        key,
+                        value: expression(false),
+                        end: prev()
+                    })
+                );
+            } else if (
+                is("name")
+                || is("privatename")
+                || is("punc", "[")
+                || is("operator", "*")
+                || is("punc", ";")
+                || is("punc", "}")
+                || is("string")
+                || is("num")
+                || is("big_int")
+            ) {
+                return annotate(
+                    new AST_ClassPropertyVariant({
+                        start,
+                        static: is_static,
+                        quote,
+                        key,
+                        end: prev()
+                    })
+                );
+            }
+        }
+    }
+
+    function class_static_block() {
+        if (!is("punc", "{")) {
+            return null;
+        }
+
+        const start = S.token;
+        const body = [];
+
+        next();
+
+        while (!is("punc", "}")) {
+            body.push(statement());
+        }
+
+        next();
+
+        return new AST_ClassStaticBlock({ start, body, end: prev() });
+    }
+
+    function maybe_import_attributes() {
+        if (
+            (is("keyword", "with") || is("name", "assert"))
+            && !has_newline_before(S.token)
+        ) {
+            next();
+            return object_or_destructuring_();
+        }
+        return null;
+    }
+
+    function import_statement() {
+        var start = prev();
+
+        // import source x from "..."
+        // import defer * as x from "..."
+        var phase = null;
+        if (is("name", "source") || is("name", "defer")) {
+            var peeked = peek();
+            if (!is_token(peeked, "name", "from") && !is_token(peeked, "punc", ",")) {
+                phase = S.token.value;
+                next();
+            }
+        }
+
+        var imported_name;
+        var imported_names;
+        if (is("name")) {
+            imported_name = as_symbol(AST_SymbolImport);
+        }
+
+        if (is("punc", ",")) {
+            next();
+        }
+
+        imported_names = map_names(true);
+
+        if (imported_names || imported_name) {
+            expect_token("name", "from");
+        }
+        var mod_str = S.token;
+        if (mod_str.type !== "string") {
+            unexpected();
+        }
+        next();
+
+        const attributes = maybe_import_attributes();
+
+        return new AST_Import({
+            start,
+            imported_name,
+            imported_names,
+            module_name: new AST_String({
+                start: mod_str,
+                value: mod_str.value,
+                quote: mod_str.quote,
+                end: mod_str,
+            }),
+            attributes,
+            phase,
+            end: S.token,
+        });
+    }
+
+    //   import.meta
+    //   import.source("module")
+    //   import.defer("module")
+    function parse_import_expr(allow_calls) {
+        var start = S.token;
+        expect_token("name", "import");
+        expect_token("punc", ".");
+        if (is("name", "source") || is("name", "defer")) {
+            var phase = S.token.value;
+            next();
+            if (!is("punc", "(")) {
+                croak("'import." + phase + "' can only be used in a dynamic import");
+            }
+            next();
+            var args = expr_list(")");
+            return subscripts(new AST_DynamicImport({
+                start: start,
+                phase: phase,
+                args: args,
+                end: prev()
+            }), allow_calls);
+        }
+        expect_token("name", "meta");
+        return subscripts(new AST_ImportMeta({
+            start: start,
+            end: prev()
+        }), allow_calls);
+    }
+
+    function map_name(is_import) {
+        function make_symbol(type, quote) {
+            return new type({
+                name: as_property_name(),
+                quote: quote || undefined,
+                start: prev(),
+                end: prev()
+            });
+        }
+
+        var foreign_type = is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign;
+        var type = is_import ? AST_SymbolImport : AST_SymbolExport;
+        var start = S.token;
+        var foreign_name;
+        var name;
+
+        if (is_import) {
+            foreign_name = make_symbol(foreign_type, start.quote);
+        } else {
+            name = make_symbol(type, start.quote);
+        }
+        if (is("name", "as")) {
+            next();  // The "as" word
+            if (is_import) {
+                name = make_symbol(type);
+            } else {
+                foreign_name = make_symbol(foreign_type, S.token.quote);
+            }
+        } else {
+            if (is_import) {
+                name = new type(foreign_name);
+            } else {
+                foreign_name = new foreign_type(name);
+            }
+        }
+
+        return new AST_NameMapping({
+            start: start,
+            foreign_name: foreign_name,
+            name: name,
+            end: prev(),
+        });
+    }
+
+    function map_nameAsterisk(is_import, import_or_export_foreign_name) {
+        var foreign_type = is_import ? AST_SymbolImportForeign : AST_SymbolExportForeign;
+        var type = is_import ? AST_SymbolImport : AST_SymbolExport;
+        var start = S.token;
+        var name, foreign_name;
+        var end = prev();
+
+        if (is_import) {
+            name = import_or_export_foreign_name;
+        } else {
+            foreign_name = import_or_export_foreign_name;
+        }
+
+        name = name || new type({
+            start: start,
+            name: "*",
+            end: end,
+        });
+
+        foreign_name = foreign_name || new foreign_type({
+            start: start,
+            name: "*",
+            end: end,
+        });
+
+        return new AST_NameMapping({
+            start: start,
+            foreign_name: foreign_name,
+            name: name,
+            end: end,
+        });
+    }
+
+    function map_names(is_import) {
+        var names;
+        if (is("punc", "{")) {
+            next();
+            names = [];
+            while (!is("punc", "}")) {
+                names.push(map_name(is_import));
+                if (is("punc", ",")) {
+                    next();
+                }
+            }
+            next();
+        } else if (is("operator", "*")) {
+            var name;
+            next();
+            if (is("name", "as")) {
+                next();  // The "as" word
+                name = is_import ? as_symbol(AST_SymbolImport) : as_symbol_or_string(AST_SymbolExportForeign);
+            }
+            names = [map_nameAsterisk(is_import, name)];
+        }
+        return names;
+    }
+
+    function export_statement() {
+        var start = S.token;
+        var is_default;
+        var exported_names;
+
+        if (is("keyword", "default")) {
+            is_default = true;
+            next();
+        } else if (exported_names = map_names(false)) {
+            if (is("name", "from")) {
+                next();
+
+                var mod_str = S.token;
+                if (mod_str.type !== "string") {
+                    unexpected();
+                }
+                next();
+
+                const attributes = maybe_import_attributes();
+
+                return new AST_Export({
+                    start: start,
+                    is_default: is_default,
+                    exported_names: exported_names,
+                    module_name: new AST_String({
+                        start: mod_str,
+                        value: mod_str.value,
+                        quote: mod_str.quote,
+                        end: mod_str,
+                    }),
+                    end: prev(),
+                    attributes
+                });
+            } else {
+                return new AST_Export({
+                    start: start,
+                    is_default: is_default,
+                    exported_names: exported_names,
+                    end: prev(),
+                });
+            }
+        }
+
+        var node;
+        var exported_value;
+        var exported_definition;
+        if (is("punc", "{")
+            || is_default
+                && (is("keyword", "class") || is("keyword", "function"))
+                && is_token(peek(), "punc")) {
+            exported_value = expression(false);
+            semicolon();
+        } else if ((node = statement(is_default)) instanceof AST_Definitions && is_default) {
+            unexpected(node.start);
+        } else if (
+            node instanceof AST_Definitions
+            || node instanceof AST_Defun
+            || node instanceof AST_DefClass
+        ) {
+            exported_definition = node;
+        } else if (
+            node instanceof AST_ClassExpression
+            || node instanceof AST_Function
+        ) {
+            exported_value = node;
+        } else if (node instanceof AST_SimpleStatement) {
+            exported_value = node.body;
+        } else {
+            unexpected(node.start);
+        }
+
+        return new AST_Export({
+            start: start,
+            is_default: is_default,
+            exported_value: exported_value,
+            exported_definition: exported_definition,
+            end: prev(),
+            attributes: null
+        });
+    }
+
+    function as_property_name() {
+        var tmp = S.token;
+        switch (tmp.type) {
+          case "punc":
+            if (tmp.value === "[") {
+                next();
+                var ex = expression(false);
+                expect("]");
+                return ex;
+            } else unexpected(tmp);
+          case "operator":
+            if (tmp.value === "*") {
+                next();
+                return null;
+            }
+            if (!["delete", "in", "instanceof", "new", "typeof", "void"].includes(tmp.value)) {
+                unexpected(tmp);
+            }
+            /* falls through */
+          case "name":
+          case "privatename":
+          case "string":
+          case "keyword":
+          case "atom":
+            next();
+            return tmp.value;
+          case "num":
+          case "big_int":
+            next();
+            return "" + tmp.value;
+          default:
+            unexpected(tmp);
+        }
+    }
+
+    function as_name() {
+        var tmp = S.token;
+        if (tmp.type != "name" && tmp.type != "privatename") unexpected();
+        next();
+        return tmp.value;
+    }
+
+    function _make_symbol(type) {
+        var name = S.token.value;
+        return new (name == "this" ? AST_This :
+                    name == "super" ? AST_Super :
+                    type)({
+            name  : String(name),
+            start : S.token,
+            end   : S.token
+        });
+    }
+
+    function _verify_symbol(sym) {
+        var name = sym.name;
+        if (is_in_generator() && name == "yield") {
+            token_error(sym.start, "Yield cannot be used as identifier inside generators");
+        }
+        if (S.input.has_directive("use strict")) {
+            if (name == "yield") {
+                token_error(sym.start, "Unexpected yield identifier inside strict mode");
+            }
+            if (sym instanceof AST_SymbolDeclaration && (name == "arguments" || name == "eval")) {
+                token_error(sym.start, "Unexpected " + name + " in strict mode");
+            }
+        }
+    }
+
+    function as_symbol(type, noerror) {
+        if (!is("name")) {
+            if (!noerror) croak("Name expected");
+            return null;
+        }
+        var sym = _make_symbol(type);
+        _verify_symbol(sym);
+        next();
+        return sym;
+    }
+
+    function as_symbol_or_string(type) {
+        if (!is("name")) {
+            if (!is("string")) {
+                croak("Name or string expected");
+            }
+            var tok = S.token;
+            var ret = new type({
+                start : tok,
+                end   : tok,
+                name : tok.value,
+                quote : tok.quote
+            });
+            next();
+            return ret;
+        }
+        var sym = _make_symbol(type);
+        _verify_symbol(sym);
+        next();
+        return sym;
+    }
+
+    // Annotate AST_Call, AST_Lambda or AST_New with the special comments
+    function annotate(node, before_token = node.start) {
+        var comments = before_token.comments_before;
+        const comments_outside_parens = outer_comments_before_counts.get(before_token);
+        var i = comments_outside_parens != null ? comments_outside_parens : comments.length;
+        while (--i >= 0) {
+            var comment = comments[i];
+            if (/[@#]__/.test(comment.value)) {
+                if (/[@#]__PURE__/.test(comment.value)) {
+                    set_annotation(node, _PURE);
+                    break;
+                }
+                if (/[@#]__INLINE__/.test(comment.value)) {
+                    set_annotation(node, _INLINE);
+                    break;
+                }
+                if (/[@#]__NOINLINE__/.test(comment.value)) {
+                    set_annotation(node, _NOINLINE);
+                    break;
+                }
+                if (/[@#]__KEY__/.test(comment.value)) {
+                    set_annotation(node, _KEY);
+                    break;
+                }
+                if (/[@#]__MANGLE_PROP__/.test(comment.value)) {
+                    set_annotation(node, _MANGLEPROP);
+                    break;
+                }
+            }
+        }
+        return node;
+    }
+
+    var subscripts = function(expr, allow_calls, is_chain) {
+        var start = expr.start;
+        if (is("punc", ".")) {
+            next();
+            if(is("privatename") && !S.in_class) 
+                croak("Private field must be used in an enclosing class");
+            const AST_DotVariant = is("privatename") ? AST_DotHash : AST_Dot;
+            return annotate(subscripts(new AST_DotVariant({
+                start      : start,
+                expression : expr,
+                optional   : false,
+                property   : as_name(),
+                end        : prev()
+            }), allow_calls, is_chain));
+        }
+        if (is("punc", "[")) {
+            next();
+            var prop = expression(true);
+            expect("]");
+            return annotate(subscripts(new AST_Sub({
+                start      : start,
+                expression : expr,
+                optional   : false,
+                property   : prop,
+                end        : prev()
+            }), allow_calls, is_chain));
+        }
+        if (allow_calls && is("punc", "(")) {
+            next();
+            var call = new AST_Call({
+                start      : start,
+                expression : expr,
+                optional   : false,
+                args       : call_args(),
+                end        : prev()
+            });
+            annotate(call);
+            return subscripts(call, true, is_chain);
+        }
+
+        // Optional chain
+        if (is("punc", "?.")) {
+            next();
+
+            let chain_contents;
+
+            if (allow_calls && is("punc", "(")) {
+                next();
+
+                const call = new AST_Call({
+                    start,
+                    optional: true,
+                    expression: expr,
+                    args: call_args(),
+                    end: prev()
+                });
+                annotate(call);
+
+                chain_contents = subscripts(call, true, true);
+            } else if (is("name") || is("privatename")) {
+                if(is("privatename") && !S.in_class) 
+                    croak("Private field must be used in an enclosing class");
+                const AST_DotVariant = is("privatename") ? AST_DotHash : AST_Dot;
+                chain_contents = annotate(subscripts(new AST_DotVariant({
+                    start,
+                    expression: expr,
+                    optional: true,
+                    property: as_name(),
+                    end: prev()
+                }), allow_calls, true));
+            } else if (is("punc", "[")) {
+                next();
+                const property = expression(true);
+                expect("]");
+                chain_contents = annotate(subscripts(new AST_Sub({
+                    start,
+                    expression: expr,
+                    optional: true,
+                    property,
+                    end: prev()
+                }), allow_calls, true));
+            }
+
+            if (!chain_contents) unexpected();
+
+            if (chain_contents instanceof AST_Chain) return chain_contents;
+
+            return new AST_Chain({
+                start,
+                expression: chain_contents,
+                end: prev()
+            });
+        }
+
+        if (is("template_head")) {
+            if (is_chain) {
+                // a?.b`c` is a syntax error
+                unexpected();
+            }
+
+            return subscripts(new AST_PrefixedTemplateString({
+                start: start,
+                prefix: expr,
+                template_string: template_string(),
+                end: prev()
+            }), allow_calls);
+        }
+        return expr;
+    };
+
+    function call_args() {
+        var args = [];
+        while (!is("punc", ")")) {
+            if (is("expand", "...")) {
+                next();
+                args.push(new AST_Expansion({
+                    start: prev(),
+                    expression: expression(false),
+                    end: prev()
+                }));
+            } else {
+                args.push(expression(false));
+            }
+            if (!is("punc", ")")) {
+                expect(",");
+            }
+        }
+        next();
+        return args;
+    }
+
+    var maybe_unary = function(allow_calls, allow_arrows) {
+        var start = S.token;
+        if (start.type == "name" && start.value == "await" && can_await()) {
+            next();
+            return _await_expression();
+        }
+        if (is("operator") && UNARY_PREFIX.has(start.value)) {
+            next();
+            handle_regexp();
+            var ex = make_unary(AST_UnaryPrefix, start, maybe_unary(allow_calls));
+            ex.start = start;
+            ex.end = prev();
+            return ex;
+        }
+        var val = expr_atom(allow_calls, allow_arrows);
+        while (is("operator") && UNARY_POSTFIX.has(S.token.value) && !has_newline_before(S.token)) {
+            if (val instanceof AST_Arrow) unexpected();
+            val = make_unary(AST_UnaryPostfix, S.token, val);
+            val.start = start;
+            val.end = S.token;
+            next();
+        }
+        return val;
+    };
+
+    function make_unary(ctor, token, expr) {
+        var op = token.value;
+        switch (op) {
+          case "++":
+          case "--":
+            if (!is_assignable(expr))
+                croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
+            break;
+          case "delete":
+            if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict"))
+                croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos);
+            break;
+        }
+        return new ctor({ operator: op, expression: expr });
+    }
+
+    var expr_op = function(left, min_prec, no_in) {
+        var op = is("operator") ? S.token.value : null;
+        if (op == "in" && no_in) op = null;
+        if (op == "**" && left instanceof AST_UnaryPrefix
+            /* unary token in front not allowed - parenthesis required */
+            && !is_token(left.start, "punc", "(")
+            && left.operator !== "--" && left.operator !== "++")
+                unexpected(left.start);
+        var prec = op != null ? PRECEDENCE[op] : null;
+        if (prec != null && (prec > min_prec || (op === "**" && min_prec === prec))) {
+            next();
+            var right = expr_ops(no_in, prec, true);
+            return expr_op(new AST_Binary({
+                start    : left.start,
+                left     : left,
+                operator : op,
+                right    : right,
+                end      : right.end
+            }), min_prec, no_in);
+        }
+        return left;
+    };
+
+    function expr_ops(no_in, min_prec, allow_calls, allow_arrows) {
+        // maybe_unary won't return us a AST_SymbolPrivateProperty
+        if (!no_in && min_prec < PRECEDENCE["in"] && is("privatename")) {
+            if(!S.in_class) {
+                croak("Private field must be used in an enclosing class");
+            }
+
+            const start = S.token;
+            const key = new AST_SymbolPrivateProperty({
+                start,
+                name: start.value,
+                end: start
+            });
+            next();
+            expect_token("operator", "in");
+
+            const private_in = new AST_PrivateIn({
+                start,
+                key,
+                value: expr_ops(no_in, PRECEDENCE["in"], true),
+                end: prev()
+            });
+
+            return expr_op(private_in, 0, no_in);
+        } else {
+            return expr_op(maybe_unary(allow_calls, allow_arrows), min_prec, no_in);
+        }
+    }
+
+    var maybe_conditional = function(no_in) {
+        var start = S.token;
+        var expr = expr_ops(no_in, 0, true, true);
+        if (is("operator", "?")) {
+            next();
+            var yes = expression(false);
+            expect(":");
+            return new AST_Conditional({
+                start       : start,
+                condition   : expr,
+                consequent  : yes,
+                alternative : expression(false, no_in),
+                end         : prev()
+            });
+        }
+        return expr;
+    };
+
+    function is_assignable(expr) {
+        return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
+    }
+
+    function to_destructuring(node) {
+        if (node instanceof AST_Object) {
+            node = new AST_Destructuring({
+                start: node.start,
+                names: node.properties.map(to_destructuring),
+                is_array: false,
+                end: node.end
+            });
+        } else if (node instanceof AST_Array) {
+            var names = [];
+
+            for (var i = 0; i < node.elements.length; i++) {
+                // Only allow expansion as last element
+                if (node.elements[i] instanceof AST_Expansion) {
+                    if (i + 1 !== node.elements.length) {
+                        token_error(node.elements[i].start, "Spread must the be last element in destructuring array");
+                    }
+                    node.elements[i].expression = to_destructuring(node.elements[i].expression);
+                }
+
+                names.push(to_destructuring(node.elements[i]));
+            }
+
+            node = new AST_Destructuring({
+                start: node.start,
+                names: names,
+                is_array: true,
+                end: node.end
+            });
+        } else if (node instanceof AST_ObjectProperty) {
+            node.value = to_destructuring(node.value);
+        } else if (node instanceof AST_Assign) {
+            node = new AST_DefaultAssign({
+                start: node.start,
+                left: node.left,
+                operator: "=",
+                right: node.right,
+                end: node.end
+            });
+        }
+        return node;
+    }
+
+    // In ES6, AssignmentExpression can also be an ArrowFunction
+    var maybe_assign = function(no_in) {
+        handle_regexp();
+        var start = S.token;
+
+        if (start.type == "name" && start.value == "yield") {
+            if (is_in_generator()) {
+                next();
+                return _yield_expression();
+            } else if (S.input.has_directive("use strict")) {
+                token_error(S.token, "Unexpected yield identifier inside strict mode");
+            }
+        }
+
+        var left = maybe_conditional(no_in);
+        var val = S.token.value;
+
+        if (is("operator") && ASSIGNMENT.has(val)) {
+            if (is_assignable(left) || (left = to_destructuring(left)) instanceof AST_Destructuring) {
+                next();
+
+                return new AST_Assign({
+                    start    : start,
+                    left     : left,
+                    operator : val,
+                    right    : maybe_assign(no_in),
+                    logical  : LOGICAL_ASSIGNMENT.has(val),
+                    end      : prev()
+                });
+            }
+            croak("Invalid assignment");
+        }
+        return left;
+    };
+
+    var to_expr_or_sequence = function(start, exprs) {
+        if (exprs.length === 1) {
+            return exprs[0];
+        } else if (exprs.length > 1) {
+            return new AST_Sequence({ start, expressions: exprs, end: peek() });
+        } else {
+            croak("Invalid parenthesized expression");
+        }
+    };
+
+    var expression = function(commas, no_in) {
+        var start = S.token;
+        var exprs = [];
+        while (true) {
+            exprs.push(maybe_assign(no_in));
+            if (!commas || !is("punc", ",")) break;
+            next();
+            commas = true;
+        }
+        return to_expr_or_sequence(start, exprs);
+    };
+
+    function in_loop(cont) {
+        ++S.in_loop;
+        var ret = cont();
+        --S.in_loop;
+        return ret;
+    }
+
+    if (options.expression) {
+        return expression(true);
+    }
+
+    return (function parse_toplevel() {
+        var start = S.token;
+        var body = [];
+        S.input.push_directives_stack();
+        if (options.module) S.input.add_directive("use strict");
+        while (!is("eof")) {
+            body.push(statement());
+        }
+        S.input.pop_directives_stack();
+        var end = prev();
+        var toplevel = options.toplevel;
+        if (toplevel) {
+            toplevel.body = toplevel.body.concat(body);
+            toplevel.end = end;
+        } else {
+            toplevel = new AST_Toplevel({ start: start, body: body, end: end });
+        }
+        TEMPLATE_RAWS = new Map();
+        return toplevel;
+    })();
+
+}
+
+export {
+    get_full_char_code,
+    get_full_char,
+    is_identifier_char,
+    is_basic_identifier_string,
+    is_identifier_string,
+    is_surrogate_pair_head,
+    is_surrogate_pair_tail,
+    js_error,
+    JS_Parse_Error,
+    parse,
+    PRECEDENCE,
+    ALL_RESERVED_WORDS,
+    tokenizer,
+};
Index: frontend/node_modules/terser/lib/propmangle.js
===================================================================
--- frontend/node_modules/terser/lib/propmangle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/propmangle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,434 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+"use strict";
+/* global global, self */
+
+import {
+    defaults,
+    push_uniq,
+    has_annotation,
+    clear_annotation,
+} from "./utils/index.js";
+import { base54 } from "./scope.js";
+import {
+    AST_Binary,
+    AST_Call,
+    AST_ClassPrivateProperty,
+    AST_Conditional,
+    AST_Dot,
+    AST_DotHash,
+    AST_ObjectKeyVal,
+    AST_ObjectProperty,
+    AST_PrivateMethod,
+    AST_PrivateGetter,
+    AST_PrivateSetter,
+    AST_PrivateIn,
+    AST_Sequence,
+    AST_String,
+    AST_Sub,
+    TreeTransformer,
+    TreeWalker,
+    _KEY,
+    _MANGLEPROP,
+
+    walk,
+} from "./ast.js";
+import { domprops } from "../tools/domprops.js";
+
+function find_builtins(reserved) {
+    domprops.forEach(add);
+
+    // Compatibility fix for some standard defined globals not defined on every js environment
+    var new_globals = ["Symbol", "Map", "Promise", "Proxy", "Reflect", "Set", "WeakMap", "WeakSet"];
+    var objects = {};
+    var global_ref = typeof global === "object" ? global : self;
+
+    new_globals.forEach(function (new_global) {
+        objects[new_global] = global_ref[new_global] || function() {};
+    });
+
+    [
+        "null",
+        "true",
+        "false",
+        "NaN",
+        "Infinity",
+        "-Infinity",
+        "undefined",
+    ].forEach(add);
+    [ Object, Array, Function, Number,
+      String, Boolean, Error, Math,
+      Date, RegExp, objects.Symbol, ArrayBuffer,
+      DataView, decodeURI, decodeURIComponent,
+      encodeURI, encodeURIComponent, eval, EvalError,
+      Float32Array, Float64Array, Int8Array, Int16Array,
+      Int32Array, isFinite, isNaN, JSON, objects.Map, parseFloat,
+      parseInt, objects.Promise, objects.Proxy, RangeError, ReferenceError,
+      objects.Reflect, objects.Set, SyntaxError, TypeError, Uint8Array,
+      Uint8ClampedArray, Uint16Array, Uint32Array, URIError,
+      objects.WeakMap, objects.WeakSet
+    ].forEach(function(ctor) {
+        Object.getOwnPropertyNames(ctor).map(add);
+        if (ctor.prototype) {
+            Object.getOwnPropertyNames(ctor.prototype).map(add);
+        }
+    });
+    function add(name) {
+        reserved.add(name);
+    }
+}
+
+function reserve_quoted_keys(ast, reserved) {
+    function add(name) {
+        push_uniq(reserved, name);
+    }
+
+    ast.walk(new TreeWalker(function(node) {
+        if (node instanceof AST_ObjectKeyVal && node.quote) {
+            add(node.key);
+        } else if (node instanceof AST_ObjectProperty && node.quote) {
+            add(node.key.name);
+        } else if (node instanceof AST_Sub) {
+            addStrings(node.property, add);
+        }
+    }));
+}
+
+function addStrings(node, add) {
+    node.walk(new TreeWalker(function(node) {
+        if (node instanceof AST_Sequence) {
+            addStrings(node.tail_node(), add);
+        } else if (node instanceof AST_String) {
+            add(node.value);
+        } else if (node instanceof AST_Conditional) {
+            addStrings(node.consequent, add);
+            addStrings(node.alternative, add);
+        }
+        return true;
+    }));
+}
+
+function mangle_private_properties(ast, options) {
+    var cprivate = -1;
+    var private_cache = new Map();
+    var nth_identifier = options.nth_identifier || base54;
+
+    ast =  ast.transform(new TreeTransformer(function(node) {
+        if (
+            node instanceof AST_ClassPrivateProperty
+            || node instanceof AST_PrivateMethod
+            || node instanceof AST_PrivateGetter
+            || node instanceof AST_PrivateSetter
+            || node instanceof AST_PrivateIn
+        ) {
+            node.key.name = mangle_private(node.key.name);
+        } else if (node instanceof AST_DotHash) {
+            node.property = mangle_private(node.property);
+        }
+    }));
+    return ast;
+
+    function mangle_private(name) {
+        let mangled = private_cache.get(name);
+        if (!mangled) {
+            mangled = nth_identifier.get(++cprivate);
+            private_cache.set(name, mangled);
+        }
+
+        return mangled;
+    }
+}
+
+function find_annotated_props(ast) {
+    var annotated_props = new Set();
+    walk(ast, node => {
+        if (
+            node instanceof AST_ClassPrivateProperty
+            || node instanceof AST_PrivateMethod
+            || node instanceof AST_PrivateGetter
+            || node instanceof AST_PrivateSetter
+            || node instanceof AST_DotHash
+        ) {
+            // handled by mangle_private_properties
+        } else if (node instanceof AST_ObjectKeyVal) {
+            if (typeof node.key == "string" && has_annotation(node, _MANGLEPROP)) {
+                annotated_props.add(node.key);
+            }
+        } else if (node instanceof AST_ObjectProperty) {
+            // setter or getter, since KeyVal is handled above
+            if (has_annotation(node, _MANGLEPROP)) {
+                annotated_props.add(node.key.name);
+            }
+        } else if (node instanceof AST_Dot) {
+            if (has_annotation(node, _MANGLEPROP)) {
+                annotated_props.add(node.property);
+            }
+        } else if (node instanceof AST_Sub) {
+            if (node.property instanceof AST_String && has_annotation(node, _MANGLEPROP)) {
+                annotated_props.add(node.property.value);
+            }
+        }
+    });
+    return annotated_props;
+}
+
+function mangle_properties(ast, options, annotated_props = find_annotated_props(ast)) {
+    options = defaults(options, {
+        builtins: false,
+        cache: null,
+        debug: false,
+        keep_quoted: false,
+        nth_identifier: base54,
+        only_cache: false,
+        regex: null,
+        reserved: null,
+        undeclared: false,
+        only_annotated: false,
+    }, true);
+
+    var nth_identifier = options.nth_identifier;
+
+    var reserved_option = options.reserved;
+    if (!Array.isArray(reserved_option)) reserved_option = [reserved_option];
+    var reserved = new Set(reserved_option);
+    if (!options.builtins) find_builtins(reserved);
+
+    var cname = -1;
+
+    var cache;
+    if (options.cache) {
+        cache = options.cache.props;
+    } else {
+        cache = new Map();
+    }
+
+    var only_annotated = options.only_annotated;
+    var regex = options.regex && new RegExp(options.regex);
+
+    // note debug is either false (disabled), or a string of the debug suffix to use (enabled).
+    // note debug may be enabled as an empty string, which is falsey. Also treat passing 'true'
+    // the same as passing an empty string.
+    var debug = options.debug !== false;
+    var debug_name_suffix;
+    if (debug) {
+        debug_name_suffix = (options.debug === true ? "" : options.debug);
+    }
+
+    var names_to_mangle = new Set();
+    var unmangleable = new Set();
+    // Track each already-mangled name to prevent nth_identifier from generating
+    // the same name.
+    cache.forEach((mangled_name) => unmangleable.add(mangled_name));
+
+    var keep_quoted = !!options.keep_quoted;
+
+    // step 1: find candidates to mangle
+    ast.walk(new TreeWalker(function(node) {
+        if (
+            node instanceof AST_ClassPrivateProperty
+            || node instanceof AST_PrivateMethod
+            || node instanceof AST_PrivateGetter
+            || node instanceof AST_PrivateSetter
+            || node instanceof AST_DotHash
+        ) {
+            // handled by mangle_private_properties
+        } else if (node instanceof AST_ObjectKeyVal) {
+            if (typeof node.key == "string" && (!keep_quoted || !node.quote)) {
+                add(node.key);
+            }
+        } else if (node instanceof AST_ObjectProperty) {
+            // setter or getter, since KeyVal is handled above
+            if (!keep_quoted || !node.quote) {
+                add(node.key.name);
+            }
+        } else if (node instanceof AST_Dot) {
+            var declared = !!options.undeclared;
+            if (!declared) {
+                var root = node;
+                while (root.expression) {
+                    root = root.expression;
+                }
+                declared = !(root.thedef && root.thedef.undeclared);
+            }
+            if (declared &&
+                (!keep_quoted || !node.quote)) {
+                add(node.property);
+            }
+        } else if (node instanceof AST_Sub) {
+            if (!keep_quoted) {
+                addStrings(node.property, add);
+            }
+        } else if (node instanceof AST_Call
+            && node.expression.print_to_string() == "Object.defineProperty") {
+            addStrings(node.args[1], add);
+        } else if (node instanceof AST_Binary && node.operator === "in") {
+            addStrings(node.left, add);
+        } else if (node instanceof AST_String && has_annotation(node, _KEY)) {
+            add(node.value);
+        }
+    }));
+
+    // step 2: transform the tree, renaming properties
+    return ast.transform(new TreeTransformer(function(node) {
+        if (
+            node instanceof AST_ClassPrivateProperty
+            || node instanceof AST_PrivateMethod
+            || node instanceof AST_PrivateGetter
+            || node instanceof AST_PrivateSetter
+            || node instanceof AST_DotHash
+        ) {
+            // handled by mangle_private_properties
+        } else if (node instanceof AST_ObjectKeyVal) {
+            if (typeof node.key == "string" && (!keep_quoted || !node.quote)) {
+                node.key = mangle(node.key);
+            }
+        } else if (node instanceof AST_ObjectProperty) {
+            // setter, getter, method or class field
+            if (!keep_quoted || !node.quote) {
+                if (!node.computed_key()) {
+                    node.key.name = mangle(node.key.name);
+                }
+            }
+        } else if (node instanceof AST_Dot) {
+            if (!keep_quoted || !node.quote) {
+                node.property = mangle(node.property);
+            }
+        } else if (!keep_quoted && node instanceof AST_Sub) {
+            node.property = mangleStrings(node.property);
+        } else if (node instanceof AST_Call
+            && node.expression.print_to_string() == "Object.defineProperty") {
+            node.args[1] = mangleStrings(node.args[1]);
+        } else if (node instanceof AST_Binary && node.operator === "in") {
+            node.left = mangleStrings(node.left);
+        } else if (node instanceof AST_String && has_annotation(node, _KEY)) {
+            // Clear _KEY annotation to prevent double mangling
+            clear_annotation(node, _KEY);
+            node.value = mangle(node.value);
+        }
+    }));
+
+    // only function declarations after this line
+
+    function can_mangle(name) {
+        if (unmangleable.has(name)) return false;
+        if (reserved.has(name)) return false;
+        if (options.only_cache) {
+            return cache.has(name);
+        }
+        if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
+        return true;
+    }
+
+    function should_mangle(name) {
+        if (only_annotated && !annotated_props.has(name)) return false;
+        if (regex && !regex.test(name)) {
+            return annotated_props.has(name);
+        }
+        if (reserved.has(name)) return false;
+        return cache.has(name)
+            || names_to_mangle.has(name);
+    }
+
+    function add(name) {
+        if (can_mangle(name)) {
+            names_to_mangle.add(name);
+        }
+
+        if (!should_mangle(name)) {
+            unmangleable.add(name);
+        }
+    }
+
+    function mangle(name) {
+        if (!should_mangle(name)) {
+            return name;
+        }
+
+        var mangled = cache.get(name);
+        if (!mangled) {
+            if (debug) {
+                // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_.
+                var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_";
+
+                if (can_mangle(debug_mangled)) {
+                    mangled = debug_mangled;
+                }
+            }
+
+            // either debug mode is off, or it is on and we could not use the mangled name
+            if (!mangled) {
+                do {
+                    mangled = nth_identifier.get(++cname);
+                } while (!can_mangle(mangled));
+            }
+
+            cache.set(name, mangled);
+        }
+        return mangled;
+    }
+
+    function mangleStrings(node) {
+        return node.transform(new TreeTransformer(function(node) {
+            if (node instanceof AST_Sequence) {
+                var last = node.expressions.length - 1;
+                node.expressions[last] = mangleStrings(node.expressions[last]);
+            } else if (node instanceof AST_String) {
+                // Clear _KEY annotation to prevent double mangling
+                clear_annotation(node, _KEY);
+                node.value = mangle(node.value);
+            } else if (node instanceof AST_Conditional) {
+                node.consequent = mangleStrings(node.consequent);
+                node.alternative = mangleStrings(node.alternative);
+            }
+            return node;
+        }));
+    }
+}
+
+export {
+    reserve_quoted_keys,
+    mangle_properties,
+    mangle_private_properties,
+    find_annotated_props,
+};
Index: frontend/node_modules/terser/lib/scope.js
===================================================================
--- frontend/node_modules/terser/lib/scope.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/scope.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1069 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+"use strict";
+
+import {
+    defaults,
+    keep_name,
+    mergeSort,
+    push_uniq,
+    make_node,
+    return_false,
+    return_this,
+    return_true,
+    string_template,
+} from "./utils/index.js";
+import {
+    AST_Arrow,
+    AST_Block,
+    AST_Call,
+    AST_Class,
+    AST_Conditional,
+    AST_DefClass,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Dot,
+    AST_DotHash,
+    AST_Export,
+    AST_For,
+    AST_ForIn,
+    AST_ForOf,
+    AST_Function,
+    AST_Import,
+    AST_IterationStatement,
+    AST_Label,
+    AST_LabeledStatement,
+    AST_LabelRef,
+    AST_Lambda,
+    AST_LoopControl,
+    AST_NameMapping,
+    AST_Node,
+    AST_Scope,
+    AST_Sequence,
+    AST_String,
+    AST_Sub,
+    AST_Switch,
+    AST_SwitchBranch,
+    AST_Symbol,
+    AST_SymbolBlockDeclaration,
+    AST_SymbolCatch,
+    AST_SymbolClass,
+    AST_SymbolConst,
+    AST_SymbolDefClass,
+    AST_SymbolDefun,
+    AST_SymbolExport,
+    AST_SymbolFunarg,
+    AST_SymbolImport,
+    AST_SymbolLambda,
+    AST_SymbolLet,
+    AST_SymbolMethod,
+    AST_SymbolRef,
+    AST_SymbolUsing,
+    AST_SymbolVar,
+    AST_Toplevel,
+    AST_VarDef,
+    AST_With,
+    TreeWalker,
+    walk,
+    walk_abort
+} from "./ast.js";
+import {
+    ALL_RESERVED_WORDS,
+    js_error,
+} from "./parse.js";
+
+const MASK_EXPORT_DONT_MANGLE = 1 << 0;
+const MASK_EXPORT_WANT_MANGLE = 1 << 1;
+
+let function_defs = null;
+let unmangleable_names = null;
+/**
+ * When defined, there is a function declaration somewhere that's inside of a block.
+ * See https://tc39.es/ecma262/multipage/additional-ecmascript-features-for-web-browsers.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics
+*/
+let scopes_with_block_defuns = null;
+
+class SymbolDef {
+    constructor(scope, orig, init) {
+        this.name = orig.name;
+        this.orig = [ orig ];
+        this.init = init;
+        this.eliminated = 0;
+        this.assignments = 0;
+        this.scope = scope;
+        this.replaced = 0;
+        this.global = false;
+        this.export = 0;
+        this.mangled_name = null;
+        this.undeclared = false;
+        this.id = SymbolDef.next_id++;
+        this.chained = false;
+        this.direct_access = false;
+        this.escaped = 0;
+        this.recursive_refs = 0;
+        this.references = [];
+        this.should_replace = undefined;
+        this.single_use = false;
+        this.fixed = false;
+        Object.seal(this);
+    }
+    fixed_value() {
+        if (!this.fixed || this.fixed instanceof AST_Node) return this.fixed;
+        return this.fixed();
+    }
+    unmangleable(options) {
+        if (!options) options = {};
+
+        if (
+            function_defs &&
+            function_defs.has(this.id) &&
+            keep_name(options.keep_fnames, this.orig[0].name)
+        ) return true;
+
+        return this.global && !options.toplevel
+            || (this.export & MASK_EXPORT_DONT_MANGLE)
+            || this.undeclared
+            || !options.eval && this.scope.pinned()
+            || (this.orig[0] instanceof AST_SymbolLambda
+                  || this.orig[0] instanceof AST_SymbolDefun) && keep_name(options.keep_fnames, this.orig[0].name)
+            || this.orig[0] instanceof AST_SymbolMethod
+            || (this.orig[0] instanceof AST_SymbolClass
+                  || this.orig[0] instanceof AST_SymbolDefClass) && keep_name(options.keep_classnames, this.orig[0].name);
+    }
+    mangle(options) {
+        const cache = options.cache && options.cache.props;
+        if (this.global && cache && cache.has(this.name)) {
+            this.mangled_name = cache.get(this.name);
+        } else if (!this.mangled_name && !this.unmangleable(options)) {
+            var s = this.scope;
+            var sym = this.orig[0];
+            if (options.ie8 && sym instanceof AST_SymbolLambda)
+                s = s.parent_scope;
+            const redefinition = redefined_catch_def(this);
+            this.mangled_name = redefinition
+                ? redefinition.mangled_name || redefinition.name
+                : s.next_mangled(options, this);
+            if (this.global && cache) {
+                cache.set(this.name, this.mangled_name);
+            }
+        }
+    }
+}
+
+SymbolDef.next_id = 1;
+
+function redefined_catch_def(def) {
+    if (def.orig[0] instanceof AST_SymbolCatch
+        && def.scope.is_block_scope()
+    ) {
+        return def.scope.get_defun_scope().variables.get(def.name);
+    }
+}
+
+AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = undefined, toplevel = this } = {}) {
+    options = defaults(options, {
+        cache: null,
+        ie8: false,
+        safari10: false,
+        module: false,
+    });
+
+    if (!(toplevel instanceof AST_Toplevel)) {
+        throw new Error("Invalid toplevel scope");
+    }
+
+    // pass 1: setup scope chaining and handle definitions
+    var scope = this.parent_scope = parent_scope;
+    var labels = new Map();
+    var defun = null;
+    var in_destructuring = null;
+    var for_scopes = [];
+    var tw = new TreeWalker((node, descend) => {
+        if (node.is_block_scope()) {
+            const save_scope = scope;
+            node.block_scope = scope = new AST_Scope(node);
+            scope._block_scope = true;
+            scope.init_scope_vars(save_scope);
+            scope.uses_with = save_scope.uses_with;
+            scope.uses_eval = save_scope.uses_eval;
+
+            if (options.safari10) {
+                if (node instanceof AST_For || node instanceof AST_ForIn || node instanceof AST_ForOf) {
+                    for_scopes.push(scope);
+                }
+            }
+
+            if (node instanceof AST_Switch) {
+                // XXX: HACK! Ensure the switch expression gets the correct scope (the parent scope) and the body gets the contained scope
+                // AST_Switch has a scope within the body, but it itself "is a block scope"
+                // This means the switched expression has to belong to the outer scope
+                // while the body inside belongs to the switch itself.
+                // This is pretty nasty and warrants an AST change
+                const the_block_scope = scope;
+                scope = save_scope;
+                node.expression.walk(tw);
+                scope = the_block_scope;
+                for (let i = 0; i < node.body.length; i++) {
+                    node.body[i].walk(tw);
+                }
+            } else {
+                descend();
+            }
+            scope = save_scope;
+            return true;
+        }
+        if (node instanceof AST_Destructuring) {
+            const save_destructuring = in_destructuring;
+            in_destructuring = node;
+            descend();
+            in_destructuring = save_destructuring;
+            return true;
+        }
+        if (node instanceof AST_Scope) {
+            node.init_scope_vars(scope);
+            var save_scope = scope;
+            var save_defun = defun;
+            var save_labels = labels;
+            defun = scope = node;
+            labels = new Map();
+            descend();
+            scope = save_scope;
+            defun = save_defun;
+            labels = save_labels;
+            return true;        // don't descend again in TreeWalker
+        }
+        if (node instanceof AST_LabeledStatement) {
+            var l = node.label;
+            if (labels.has(l.name)) {
+                throw new Error(string_template("Label {name} defined twice", l));
+            }
+            labels.set(l.name, l);
+            descend();
+            labels.delete(l.name);
+            return true;        // no descend again
+        }
+        if (node instanceof AST_With) {
+            for (var s = scope; s; s = s.parent_scope)
+                s.uses_with = true;
+            return;
+        }
+        if (node instanceof AST_Symbol) {
+            node.scope = scope;
+        }
+        if (node instanceof AST_Label) {
+            node.thedef = node;
+            node.references = [];
+        }
+        if (node instanceof AST_SymbolLambda) {
+            defun.def_function(node, node.name == "arguments" ? undefined : defun);
+        } else if (node instanceof AST_SymbolDefun) {
+            // Careful here, the scope where this should be defined is
+            // the parent scope.  The reason is that we enter a new
+            // scope when we encounter the AST_Defun node (which is
+            // instanceof AST_Scope) but we get to the symbol a bit
+            // later.
+            const closest_scope = defun.parent_scope;
+
+            // In strict mode, function definitions are block-scoped
+            node.scope = tw.directives["use strict"]
+                ? closest_scope
+                : closest_scope.get_defun_scope();
+
+            mark_export(node.scope.def_function(node, defun), 1);
+        } else if (node instanceof AST_SymbolClass) {
+            mark_export(defun.def_variable(node, defun), 1);
+        } else if (node instanceof AST_SymbolImport) {
+            scope.def_variable(node);
+        } else if (node instanceof AST_SymbolDefClass) {
+            // This deals with the name of the class being available
+            // inside the class.
+            mark_export((node.scope = defun.parent_scope).def_function(node, defun), 1);
+        } else if (
+            node instanceof AST_SymbolVar
+            || node instanceof AST_SymbolLet
+            || node instanceof AST_SymbolConst
+            || node instanceof AST_SymbolUsing
+            || node instanceof AST_SymbolCatch
+        ) {
+            var def;
+            if (node instanceof AST_SymbolBlockDeclaration) {
+                def = scope.def_variable(node, null);
+            } else {
+                def = defun.def_variable(node, node.TYPE == "SymbolVar" ? null : undefined);
+            }
+            if (!def.orig.every((sym) => {
+                if (sym === node) return true;
+                if (node instanceof AST_SymbolBlockDeclaration) {
+                    return sym instanceof AST_SymbolLambda;
+                }
+                return !(sym instanceof AST_SymbolLet || sym instanceof AST_SymbolConst || sym instanceof AST_SymbolUsing);
+            })) {
+                js_error(
+                    `"${node.name}" is redeclared`,
+                    node.start.file,
+                    node.start.line,
+                    node.start.col,
+                    node.start.pos
+                );
+            }
+            if (!(node instanceof AST_SymbolFunarg)) mark_export(def, 2);
+            if (defun !== scope) {
+                node.mark_enclosed();
+                var def = scope.find_variable(node);
+                if (node.thedef !== def) {
+                    node.thedef = def;
+                    node.reference();
+                }
+            }
+        } else if (node instanceof AST_LabelRef) {
+            var sym = labels.get(node.name);
+            if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", {
+                name: node.name,
+                line: node.start.line,
+                col: node.start.col
+            }));
+            node.thedef = sym;
+        }
+        if (!(scope instanceof AST_Toplevel) && (node instanceof AST_Export || node instanceof AST_Import)) {
+            js_error(
+                `"${node.TYPE}" statement may only appear at the top level`,
+                node.start.file,
+                node.start.line,
+                node.start.col,
+                node.start.pos
+            );
+        }
+    });
+
+    if (options.module) {
+        tw.directives["use strict"] = true;
+    }
+
+    this.walk(tw);
+
+    function mark_export(def, level) {
+        if (in_destructuring) {
+            var i = 0;
+            do {
+                level++;
+            } while (tw.parent(i++) !== in_destructuring);
+        }
+        var node = tw.parent(level);
+        if (def.export = node instanceof AST_Export ? MASK_EXPORT_DONT_MANGLE : 0) {
+            var exported = node.exported_definition;
+            if ((exported instanceof AST_Defun || exported instanceof AST_DefClass) && node.is_default) {
+                def.export = MASK_EXPORT_WANT_MANGLE;
+            }
+        }
+    }
+
+    // pass 2: find back references and eval
+    const is_toplevel = this instanceof AST_Toplevel;
+    if (is_toplevel) {
+        this.globals = new Map();
+    }
+
+    var tw = new TreeWalker(node => {
+        if (node instanceof AST_LoopControl && node.label) {
+            node.label.thedef.references.push(node);
+            return true;
+        }
+        if (node instanceof AST_SymbolRef) {
+            var name = node.name;
+            if (name == "eval" && tw.parent() instanceof AST_Call) {
+                for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
+                    s.uses_eval = true;
+                }
+            }
+            var sym;
+            if (tw.parent() instanceof AST_NameMapping && tw.parent(1).module_name
+                || !(sym = node.scope.find_variable(name))) {
+
+                sym = toplevel.def_global(node);
+                if (node instanceof AST_SymbolExport) sym.export = MASK_EXPORT_DONT_MANGLE;
+            } else if (sym.scope instanceof AST_Lambda && name == "arguments") {
+                sym.scope.get_defun_scope().uses_arguments = true;
+            }
+            node.thedef = sym;
+            node.reference();
+            if (node.scope.is_block_scope()
+                && !(sym.orig[0] instanceof AST_SymbolBlockDeclaration)) {
+                node.scope = node.scope.get_defun_scope();
+            }
+            return true;
+        }
+        // ensure mangling works if catch reuses a scope variable
+        var def;
+        if (node instanceof AST_SymbolCatch && (def = redefined_catch_def(node.definition()))) {
+            var s = node.scope;
+            while (s) {
+                push_uniq(s.enclosed, def);
+                if (s === def.scope) break;
+                s = s.parent_scope;
+            }
+        }
+    });
+    this.walk(tw);
+
+    // pass 3: work around IE8 and Safari catch scope bugs
+    if (options.ie8 || options.safari10) {
+        walk(this, node => {
+            if (node instanceof AST_SymbolCatch) {
+                var name = node.name;
+                var refs = node.thedef.references;
+                var scope = node.scope.get_defun_scope();
+                var def = scope.find_variable(name)
+                    || toplevel.globals.get(name)
+                    || scope.def_variable(node);
+                refs.forEach(function(ref) {
+                    ref.thedef = def;
+                    ref.reference();
+                });
+                node.thedef = def;
+                node.reference();
+                return true;
+            }
+        });
+    }
+
+    // pass 4: add symbol definitions to loop scopes
+    // Safari/Webkit bug workaround - loop init let variable shadowing argument.
+    // https://github.com/mishoo/UglifyJS2/issues/1753
+    // https://bugs.webkit.org/show_bug.cgi?id=171041
+    if (options.safari10) {
+        for (const scope of for_scopes) {
+            scope.parent_scope.variables.forEach(function(def) {
+                push_uniq(scope.enclosed, def);
+            });
+        }
+    }
+});
+
+AST_Toplevel.DEFMETHOD("def_global", function(node) {
+    var globals = this.globals, name = node.name;
+    if (globals.has(name)) {
+        return globals.get(name);
+    } else {
+        var g = new SymbolDef(this, node);
+        g.undeclared = true;
+        g.global = true;
+        globals.set(name, g);
+        return g;
+    }
+});
+
+AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) {
+    this.variables = new Map();         // map name to AST_SymbolVar (variables defined in this scope; includes functions)
+    this.uses_with = false;             // will be set to true if this or some nested scope uses the `with` statement
+    this.uses_eval = false;             // will be set to true if this or nested scope uses the global `eval`
+    this.parent_scope = parent_scope;   // the parent scope
+    this.enclosed = [];                 // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
+    this.cname = -1;                    // the current index for mangling functions/variables
+});
+
+AST_Scope.DEFMETHOD("conflicting_def", function (name) {
+    return (
+        this.enclosed.find(def => def.name === name)
+        || this.variables.has(name)
+        || (this.parent_scope && this.parent_scope.conflicting_def(name))
+    );
+});
+
+AST_Scope.DEFMETHOD("conflicting_def_shallow", function (name) {
+    return (
+        this.enclosed.find(def => def.name === name)
+        || this.variables.has(name)
+    );
+});
+
+AST_Scope.DEFMETHOD("add_child_scope", function (scope) {
+    // `scope` is going to be moved into `this` right now.
+    // Update the required scopes' information
+
+    if (scope.parent_scope === this) return;
+
+    scope.parent_scope = this;
+
+    // Propagate to this.uses_arguments from arrow functions
+    if ((scope instanceof AST_Arrow) && (this instanceof AST_Lambda && !this.uses_arguments)) {
+        this.uses_arguments = walk(scope, node => {
+            if (
+                node instanceof AST_SymbolRef
+                && node.scope instanceof AST_Lambda
+                && node.name === "arguments"
+            ) {
+                return walk_abort;
+            }
+
+            if (node instanceof AST_Lambda && !(node instanceof AST_Arrow)) {
+                return true;
+            }
+        });
+    }
+
+    this.uses_with = this.uses_with || scope.uses_with;
+    this.uses_eval = this.uses_eval || scope.uses_eval;
+
+    const scope_ancestry = (() => {
+        const ancestry = [];
+        let cur = this;
+        do {
+            ancestry.push(cur);
+        } while ((cur = cur.parent_scope));
+        ancestry.reverse();
+        return ancestry;
+    })();
+
+    const new_scope_enclosed_set = new Set(scope.enclosed);
+    const to_enclose = [];
+    for (const scope_topdown of scope_ancestry) {
+        to_enclose.forEach(e => push_uniq(scope_topdown.enclosed, e));
+        for (const def of scope_topdown.variables.values()) {
+            if (new_scope_enclosed_set.has(def)) {
+                push_uniq(to_enclose, def);
+                push_uniq(scope_topdown.enclosed, def);
+            }
+        }
+    }
+});
+
+function find_scopes_visible_from(scopes) {
+    const found_scopes = new Set();
+
+    for (const scope of new Set(scopes)) {
+        (function bubble_up(scope) {
+            if (scope == null || found_scopes.has(scope)) return;
+
+            found_scopes.add(scope);
+
+            bubble_up(scope.parent_scope);
+        })(scope);
+    }
+
+    return [...found_scopes];
+}
+
+// Creates a symbol during compression
+AST_Scope.DEFMETHOD("create_symbol", function(SymClass, {
+    source,
+    tentative_name,
+    scope,
+    conflict_scopes = [scope],
+    init = null
+} = {}) {
+    let symbol_name;
+
+    conflict_scopes = find_scopes_visible_from(conflict_scopes);
+
+    if (tentative_name) {
+        // Implement hygiene (no new names are conflicting with existing names)
+        tentative_name =
+            symbol_name =
+            tentative_name.replace(/(?:^[^a-z_$]|[^a-z0-9_$])/ig, "_");
+
+        let i = 0;
+        while (conflict_scopes.find(s => s.conflicting_def_shallow(symbol_name))) {
+            symbol_name = tentative_name + "$" + i++;
+        }
+    }
+
+    if (!symbol_name) {
+        throw new Error("No symbol name could be generated in create_symbol()");
+    }
+
+    const symbol = make_node(SymClass, source, {
+        name: symbol_name,
+        scope
+    });
+
+    this.def_variable(symbol, init || null);
+
+    symbol.mark_enclosed();
+
+    return symbol;
+});
+
+
+AST_Node.DEFMETHOD("is_block_scope", return_false);
+AST_Class.DEFMETHOD("is_block_scope", return_false);
+AST_Lambda.DEFMETHOD("is_block_scope", return_false);
+AST_Toplevel.DEFMETHOD("is_block_scope", return_false);
+AST_SwitchBranch.DEFMETHOD("is_block_scope", return_false);
+AST_Block.DEFMETHOD("is_block_scope", return_true);
+AST_Scope.DEFMETHOD("is_block_scope", function () {
+    return this._block_scope || false;
+});
+AST_IterationStatement.DEFMETHOD("is_block_scope", return_true);
+
+AST_Lambda.DEFMETHOD("init_scope_vars", function() {
+    AST_Scope.prototype.init_scope_vars.apply(this, arguments);
+    this.uses_arguments = false;
+    this.def_variable(new AST_SymbolFunarg({
+        name: "arguments",
+        start: this.start,
+        end: this.end
+    }));
+});
+
+AST_Arrow.DEFMETHOD("init_scope_vars", function() {
+    AST_Scope.prototype.init_scope_vars.apply(this, arguments);
+    this.uses_arguments = false;
+});
+
+AST_Symbol.DEFMETHOD("mark_enclosed", function() {
+    var def = this.definition();
+    var s = this.scope;
+    while (s) {
+        push_uniq(s.enclosed, def);
+        if (s === def.scope) break;
+        s = s.parent_scope;
+    }
+});
+
+AST_Symbol.DEFMETHOD("reference", function() {
+    this.definition().references.push(this);
+    this.mark_enclosed();
+});
+
+AST_Scope.DEFMETHOD("find_variable", function(name) {
+    if (name instanceof AST_Symbol) name = name.name;
+    return this.variables.get(name)
+        || (this.parent_scope && this.parent_scope.find_variable(name));
+});
+
+AST_Scope.DEFMETHOD("def_function", function(symbol, init) {
+    var def = this.def_variable(symbol, init);
+    if (!def.init || def.init instanceof AST_Defun) def.init = init;
+    return def;
+});
+
+AST_Scope.DEFMETHOD("def_variable", function(symbol, init) {
+    var def = this.variables.get(symbol.name);
+    if (def) {
+        def.orig.push(symbol);
+        if (def.init && (def.scope !== symbol.scope || def.init instanceof AST_Function)) {
+            def.init = init;
+        }
+    } else {
+        def = new SymbolDef(this, symbol, init);
+        this.variables.set(symbol.name, def);
+        def.global = !this.parent_scope;
+    }
+    return symbol.thedef = def;
+});
+
+function next_mangled(scope, options) {
+    let defun_scope;
+    if (
+        scopes_with_block_defuns
+        && (defun_scope = scope.get_defun_scope())
+        && scopes_with_block_defuns.has(defun_scope)
+    ) {
+        scope = defun_scope;
+    }
+
+    var ext = scope.enclosed;
+    var nth_identifier = options.nth_identifier;
+    out: while (true) {
+        var m = nth_identifier.get(++scope.cname);
+        if (ALL_RESERVED_WORDS.has(m)) continue; // skip over "do"
+
+        // https://github.com/mishoo/UglifyJS2/issues/242 -- do not
+        // shadow a name reserved from mangling.
+        if (options.reserved.has(m)) continue;
+
+        // Functions with short names might collide with base54 output
+        // and therefore cause collisions when keep_fnames is true.
+        if (unmangleable_names && unmangleable_names.has(m)) continue out;
+
+        // we must ensure that the mangled name does not shadow a name
+        // from some parent scope that is referenced in this or in
+        // inner scopes.
+        for (let i = ext.length; --i >= 0;) {
+            const def = ext[i];
+            const name = def.mangled_name || (def.unmangleable(options) && def.name);
+            if (m == name) continue out;
+        }
+        return m;
+    }
+}
+
+AST_Scope.DEFMETHOD("next_mangled", function(options) {
+    return next_mangled(this, options);
+});
+
+AST_Toplevel.DEFMETHOD("next_mangled", function(options) {
+    let name;
+    const mangled_names = this.mangled_names;
+    do {
+        name = next_mangled(this, options);
+    } while (mangled_names.has(name));
+    return name;
+});
+
+AST_Function.DEFMETHOD("next_mangled", function(options, def) {
+    // #179, #326
+    // in Safari strict mode, something like (function x(x){...}) is a syntax error;
+    // a function expression's argument cannot shadow the function expression's name
+
+    var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition();
+
+    // the function's mangled_name is null when keep_fnames is true
+    var tricky_name = tricky_def ? tricky_def.mangled_name || tricky_def.name : null;
+
+    while (true) {
+        var name = next_mangled(this, options);
+        if (!tricky_name || tricky_name != name)
+            return name;
+    }
+});
+
+AST_Symbol.DEFMETHOD("unmangleable", function(options) {
+    var def = this.definition();
+    return !def || def.unmangleable(options);
+});
+
+// labels are always mangleable
+AST_Label.DEFMETHOD("unmangleable", return_false);
+
+AST_Symbol.DEFMETHOD("unreferenced", function() {
+    return !this.definition().references.length && !this.scope.pinned();
+});
+
+AST_Symbol.DEFMETHOD("definition", function() {
+    return this.thedef;
+});
+
+AST_Symbol.DEFMETHOD("global", function() {
+    return this.thedef.global;
+});
+
+/**
+ * Format the mangler options (if any) into their appropriate types
+ */
+export function format_mangler_options(options) {
+    options = defaults(options, {
+        eval        : false,
+        nth_identifier : base54,
+        ie8         : false,
+        keep_classnames: false,
+        keep_fnames : false,
+        module      : false,
+        reserved    : [],
+        toplevel    : false,
+    });
+    if (options.module) options.toplevel = true;
+    if (!Array.isArray(options.reserved)
+        && !(options.reserved instanceof Set)
+    ) {
+        options.reserved = [];
+    }
+    options.reserved = new Set(options.reserved);
+    // Never mangle arguments
+    options.reserved.add("arguments");
+    return options;
+}
+
+AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
+    options = format_mangler_options(options);
+    var nth_identifier = options.nth_identifier;
+
+    // We only need to mangle declaration nodes.  Special logic wired
+    // into the code generator will display the mangled name if it's
+    // present (and for AST_SymbolRef-s it'll use the mangled name of
+    // the AST_SymbolDeclaration that it points to).
+    var lname = -1;
+    var to_mangle = [];
+
+    if (options.keep_fnames) {
+        function_defs = new Set();
+    }
+
+    const mangled_names = this.mangled_names = new Set();
+    unmangleable_names = new Set();
+
+    if (options.cache) {
+        this.globals.forEach(collect);
+        if (options.cache.props) {
+            options.cache.props.forEach(function(mangled_name) {
+                mangled_names.add(mangled_name);
+            });
+        }
+    }
+
+    var tw = new TreeWalker(function(node, descend) {
+        if (node instanceof AST_LabeledStatement) {
+            // lname is incremented when we get to the AST_Label
+            var save_nesting = lname;
+            descend();
+            lname = save_nesting;
+            return true;        // don't descend again in TreeWalker
+        }
+        if (
+            node instanceof AST_Defun
+            && !(tw.parent() instanceof AST_Scope)
+        ) {
+            scopes_with_block_defuns = scopes_with_block_defuns || new Set();
+            scopes_with_block_defuns.add(node.parent_scope.get_defun_scope());
+        }
+        if (node instanceof AST_Scope) {
+            node.variables.forEach(collect);
+            return;
+        }
+        if (node.is_block_scope()) {
+            node.block_scope.variables.forEach(collect);
+            return;
+        }
+        if (
+            function_defs
+            && node instanceof AST_VarDef
+            && node.name instanceof AST_Symbol
+            && node.value instanceof AST_Lambda
+            && !node.value.name
+            && keep_name(options.keep_fnames, node.name.name)
+        ) {
+            function_defs.add(node.name.definition().id);
+            return;
+        }
+        if (node instanceof AST_Label) {
+            let name;
+            do {
+                name = nth_identifier.get(++lname);
+            } while (ALL_RESERVED_WORDS.has(name));
+            node.mangled_name = name;
+            return true;
+        }
+        if (!(options.ie8 || options.safari10) && node instanceof AST_SymbolCatch) {
+            to_mangle.push(node.definition());
+            return;
+        }
+    });
+
+    this.walk(tw);
+
+    if (options.keep_fnames || options.keep_classnames) {
+        // Collect a set of short names which are unmangleable,
+        // for use in avoiding collisions in next_mangled.
+        to_mangle.forEach(def => {
+            if (def.name.length < 6 && def.unmangleable(options)) {
+                unmangleable_names.add(def.name);
+            }
+        });
+    }
+
+    to_mangle.forEach(def => { def.mangle(options); });
+
+    function_defs = null;
+    unmangleable_names = null;
+    scopes_with_block_defuns = null;
+
+    function collect(symbol) {
+        if (symbol.export & MASK_EXPORT_DONT_MANGLE) {
+            unmangleable_names.add(symbol.name);
+        } else if (!options.reserved.has(symbol.name)) {
+            to_mangle.push(symbol);
+        }
+    }
+});
+
+AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) {
+    const cache = options.cache && options.cache.props;
+    const avoid = new Set();
+    options.reserved.forEach(to_avoid);
+    this.globals.forEach(add_def);
+    this.walk(new TreeWalker(function(node) {
+        if (node instanceof AST_Scope) node.variables.forEach(add_def);
+        if (node instanceof AST_SymbolCatch) add_def(node.definition());
+    }));
+    return avoid;
+
+    function to_avoid(name) {
+        avoid.add(name);
+    }
+
+    function add_def(def) {
+        var name = def.name;
+        if (def.global && cache && cache.has(name)) name = cache.get(name);
+        else if (!def.unmangleable(options)) return;
+        to_avoid(name);
+    }
+});
+
+AST_Toplevel.DEFMETHOD("expand_names", function(options) {
+    options = format_mangler_options(options);
+    var nth_identifier = options.nth_identifier;
+    if (nth_identifier.reset && nth_identifier.sort) {
+        nth_identifier.reset();
+        nth_identifier.sort();
+    }
+    var avoid = this.find_colliding_names(options);
+    var cname = 0;
+    this.globals.forEach(rename);
+    this.walk(new TreeWalker(function(node) {
+        if (node instanceof AST_Scope) node.variables.forEach(rename);
+        if (node instanceof AST_SymbolCatch) rename(node.definition());
+    }));
+
+    function next_name() {
+        var name;
+        do {
+            name = nth_identifier.get(cname++);
+        } while (avoid.has(name) || ALL_RESERVED_WORDS.has(name));
+        return name;
+    }
+
+    function rename(def) {
+        if (def.global && options.cache) return;
+        if (def.unmangleable(options)) return;
+        if (options.reserved.has(def.name)) return;
+        const redefinition = redefined_catch_def(def);
+        const name = def.name = redefinition ? redefinition.name : next_name();
+        def.orig.forEach(function(sym) {
+            sym.name = name;
+        });
+        def.references.forEach(function(sym) {
+            sym.name = name;
+        });
+    }
+});
+
+AST_Node.DEFMETHOD("tail_node", return_this);
+AST_Sequence.DEFMETHOD("tail_node", function() {
+    return this.expressions[this.expressions.length - 1];
+});
+
+AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) {
+    options = format_mangler_options(options);
+    var nth_identifier = options.nth_identifier;
+    if (!nth_identifier.reset || !nth_identifier.consider || !nth_identifier.sort) {
+        // If the identifier mangler is invariant, skip computing character frequency.
+        return;
+    }
+    nth_identifier.reset();
+
+    try {
+        AST_Node.prototype.print = function(stream, force_parens) {
+            this._print(stream, force_parens);
+            if (this instanceof AST_Symbol && !this.unmangleable(options)) {
+                nth_identifier.consider(this.name, -1);
+            } else if (options.properties) {
+                if (this instanceof AST_DotHash) {
+                    nth_identifier.consider("#" + this.property, -1);
+                } else if (this instanceof AST_Dot) {
+                    nth_identifier.consider(this.property, -1);
+                } else if (this instanceof AST_Sub) {
+                    skip_string(this.property);
+                }
+            }
+        };
+        nth_identifier.consider(this.print_to_string(), 1);
+    } finally {
+        AST_Node.prototype.print = AST_Node.prototype._print;
+    }
+    nth_identifier.sort();
+
+    function skip_string(node) {
+        if (node instanceof AST_String) {
+            nth_identifier.consider(node.value, -1);
+        } else if (node instanceof AST_Conditional) {
+            skip_string(node.consequent);
+            skip_string(node.alternative);
+        } else if (node instanceof AST_Sequence) {
+            skip_string(node.tail_node());
+        }
+    }
+});
+
+const base54 = (() => {
+    const leading = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_".split("");
+    const digits = "0123456789".split("");
+    let chars;
+    let frequency;
+    function reset() {
+        frequency = new Map();
+        leading.forEach(function(ch) {
+            frequency.set(ch, 0);
+        });
+        digits.forEach(function(ch) {
+            frequency.set(ch, 0);
+        });
+    }
+    function consider(str, delta) {
+        for (var i = str.length; --i >= 0;) {
+            frequency.set(str[i], frequency.get(str[i]) + delta);
+        }
+    }
+    function compare(a, b) {
+        return frequency.get(b) - frequency.get(a);
+    }
+    function sort() {
+        chars = mergeSort(leading, compare).concat(mergeSort(digits, compare));
+    }
+    // Ensure this is in a usable initial state.
+    reset();
+    sort();
+    function base54(num) {
+        var ret = "", base = 54;
+        num++;
+        do {
+            num--;
+            ret += chars[num % base];
+            num = Math.floor(num / base);
+            base = 64;
+        } while (num > 0);
+        return ret;
+    }
+
+    return {
+        get: base54,
+        consider,
+        reset,
+        sort
+    };
+})();
+
+export {
+    base54,
+    SymbolDef,
+};
Index: frontend/node_modules/terser/lib/size.js
===================================================================
--- frontend/node_modules/terser/lib/size.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/size.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,511 @@
+import {
+    AST_Accessor,
+    AST_Array,
+    AST_Arrow,
+    AST_Await,
+    AST_BigInt,
+    AST_Binary,
+    AST_Block,
+    AST_Break,
+    AST_Call,
+    AST_Case,
+    AST_Class,
+    AST_ClassStaticBlock,
+    AST_ClassPrivateProperty,
+    AST_ClassProperty,
+    AST_ConciseMethod,
+    AST_Conditional,
+    AST_Const,
+    AST_Continue,
+    AST_Debugger,
+    AST_Default,
+    AST_Defun,
+    AST_Destructuring,
+    AST_Directive,
+    AST_Do,
+    AST_Dot,
+    AST_DotHash,
+    AST_EmptyStatement,
+    AST_Expansion,
+    AST_Export,
+    AST_False,
+    AST_For,
+    AST_ForIn,
+    AST_Function,
+    AST_Hole,
+    AST_If,
+    AST_Import,
+    AST_DynamicImport,
+    AST_ImportMeta,
+    AST_Infinity,
+    AST_LabeledStatement,
+    AST_Let,
+    AST_NameMapping,
+    AST_NaN,
+    AST_New,
+    AST_NewTarget,
+    AST_Node,
+    AST_Null,
+    AST_Number,
+    AST_Object,
+    AST_ObjectKeyVal,
+    AST_ObjectGetter,
+    AST_ObjectSetter,
+    AST_PrivateGetter,
+    AST_PrivateMethod,
+    AST_PrivateSetter,
+    AST_PrivateIn,
+    AST_RegExp,
+    AST_Return,
+    AST_Sequence,
+    AST_String,
+    AST_Sub,
+    AST_Super,
+    AST_Switch,
+    AST_Symbol,
+    AST_SymbolClassProperty,
+    AST_SymbolExportForeign,
+    AST_SymbolImportForeign,
+    AST_SymbolRef,
+    AST_SymbolDeclaration,
+    AST_TemplateSegment,
+    AST_TemplateString,
+    AST_This,
+    AST_Throw,
+    AST_Toplevel,
+    AST_True,
+    AST_Try,
+    AST_Catch,
+    AST_Finally,
+    AST_Unary,
+    AST_Undefined,
+    AST_Using,
+    AST_Var,
+    AST_VarDefLike,
+    AST_While,
+    AST_With,
+    AST_Yield,
+    walk_parent
+} from "./ast.js";
+import { first_in_statement } from "./utils/first_in_statement.js";
+
+let mangle_options = undefined;
+AST_Node.prototype.size = function (compressor, stack) {
+    mangle_options = compressor && compressor._mangle_options;
+
+    let size = 0;
+    walk_parent(this, (node, info) => {
+        size += node._size(info);
+
+        // Braceless arrow functions have fake "return" statements
+        if (node instanceof AST_Arrow && node.is_braceless()) {
+            size += node.body[0].value._size(info);
+            return true;
+        }
+    }, stack || (compressor && compressor.stack));
+
+    // just to save a bit of memory
+    mangle_options = undefined;
+
+    return size;
+};
+
+AST_Node.prototype._size = () => 0;
+
+AST_Debugger.prototype._size = () => 8;
+
+AST_Directive.prototype._size = function () {
+    // TODO string encoding stuff
+    return 2 + this.value.length;
+};
+
+/** Count commas/semicolons necessary to show a list of expressions/statements */
+const list_overhead = (array) => array.length && array.length - 1;
+
+AST_Block.prototype._size = function () {
+    return 2 + list_overhead(this.body);
+};
+
+AST_Toplevel.prototype._size = function() {
+    return list_overhead(this.body);
+};
+
+AST_EmptyStatement.prototype._size = () => 1;
+
+AST_LabeledStatement.prototype._size = () => 2;  // x:
+
+AST_Do.prototype._size = () => 9;
+
+AST_While.prototype._size = () => 7;
+
+AST_For.prototype._size = () => 8;
+
+AST_ForIn.prototype._size = () => 8;
+// AST_ForOf inherits ^
+
+AST_With.prototype._size = () => 6;
+
+AST_Expansion.prototype._size = () => 3;
+
+const lambda_modifiers = func =>
+    (func.is_generator ? 1 : 0) + (func.async ? 6 : 0);
+
+AST_Accessor.prototype._size = function () {
+    return lambda_modifiers(this) + 4 + list_overhead(this.argnames) + list_overhead(this.body);
+};
+
+AST_Function.prototype._size = function (info) {
+    const first = !!first_in_statement(info);
+    return (first * 2) + lambda_modifiers(this) + 12 + list_overhead(this.argnames) + list_overhead(this.body);
+};
+
+AST_Defun.prototype._size = function () {
+    return lambda_modifiers(this) + 13 + list_overhead(this.argnames) + list_overhead(this.body);
+};
+
+AST_Arrow.prototype._size = function () {
+    let args_and_arrow = 2 + list_overhead(this.argnames);
+
+    if (
+        !(
+            this.argnames.length === 1
+            && this.argnames[0] instanceof AST_Symbol
+        )
+    ) {
+        args_and_arrow += 2; // parens around the args
+    }
+
+    const body_overhead = this.is_braceless() ? 0 : list_overhead(this.body) + 2;
+
+    return lambda_modifiers(this) + args_and_arrow + body_overhead;
+};
+
+AST_Destructuring.prototype._size = () => 2;
+
+AST_TemplateString.prototype._size = function () {
+    return 2 + (Math.floor(this.segments.length / 2) * 3);  /* "${}" */
+};
+
+AST_TemplateSegment.prototype._size = function () {
+    return this.value.length;
+};
+
+AST_Return.prototype._size = function () {
+    return this.value ? 7 : 6;
+};
+
+AST_Throw.prototype._size = () => 6;
+
+AST_Break.prototype._size = function () {
+    return this.label ? 6 : 5;
+};
+
+AST_Continue.prototype._size = function () {
+    return this.label ? 9 : 8;
+};
+
+AST_If.prototype._size = () => 4;
+
+AST_Switch.prototype._size = function () {
+    return 8 + list_overhead(this.body);
+};
+
+AST_Case.prototype._size = function () {
+    return 5 + list_overhead(this.body);
+};
+
+AST_Default.prototype._size = function () {
+    return 8 + list_overhead(this.body);
+};
+
+AST_Try.prototype._size = () => 3;
+
+AST_Catch.prototype._size = function () {
+    let size = 7 + list_overhead(this.body);
+    if (this.argname) {
+        size += 2;
+    }
+    return size;
+};
+
+AST_Finally.prototype._size = function () {
+    return 7 + list_overhead(this.body);
+};
+
+AST_Var.prototype._size = function () {
+    return 4 + list_overhead(this.definitions);
+};
+
+AST_Let.prototype._size = function () {
+    return 4 + list_overhead(this.definitions);
+};
+
+AST_Const.prototype._size = function () {
+    return 6 + list_overhead(this.definitions);
+};
+
+AST_Using.prototype._size = function () {
+    const await_size = this.await ? 6 : 0;
+    return await_size + 6 + list_overhead(this.definitions);
+};
+
+AST_VarDefLike.prototype._size = function () {
+    return this.value ? 1 : 0;
+};
+
+AST_NameMapping.prototype._size = function () {
+    // foreign name isn't mangled
+    return this.name ? 4 : 0;
+};
+
+AST_Import.prototype._size = function () {
+    // import
+    let size = 6;
+
+    if (this.imported_name) size += 1;
+
+    // from
+    if (this.imported_name || this.imported_names) size += 5;
+
+    // braces, and the commas
+    if (this.imported_names) {
+        size += 2 + list_overhead(this.imported_names);
+    }
+
+    return size;
+};
+
+AST_ImportMeta.prototype._size = () => 11;
+
+AST_DynamicImport.prototype._size = function () {
+    // `import.` + phase + `()` + arg overhead
+    return 9 + this.phase.length + list_overhead(this.args);
+};
+
+AST_Export.prototype._size = function () {
+    let size = 7 + (this.is_default ? 8 : 0);
+
+    if (this.exported_value) {
+        size += this.exported_value._size();
+    }
+
+    if (this.exported_names) {
+        // Braces and commas
+        size += 2 + list_overhead(this.exported_names);
+    }
+
+    if (this.module_name) {
+        // "from "
+        size += 5;
+    }
+
+    return size;
+};
+
+AST_Call.prototype._size = function () {
+    if (this.optional) {
+        return 4 + list_overhead(this.args);
+    }
+    return 2 + list_overhead(this.args);
+};
+
+AST_New.prototype._size = function () {
+    return 6 + list_overhead(this.args);
+};
+
+AST_Sequence.prototype._size = function () {
+    return list_overhead(this.expressions);
+};
+
+AST_Dot.prototype._size = function () {
+    if (this.optional) {
+        return this.property.length + 2;
+    }
+    return this.property.length + 1;
+};
+
+AST_DotHash.prototype._size = function () {
+    if (this.optional) {
+        return this.property.length + 3;
+    }
+    return this.property.length + 2;
+};
+
+AST_Sub.prototype._size = function () {
+    return this.optional ? 4 : 2;
+};
+
+AST_Unary.prototype._size = function () {
+    if (this.operator === "typeof") return 7;
+    if (this.operator === "void") return 5;
+    return this.operator.length;
+};
+
+AST_Binary.prototype._size = function (info) {
+    if (this.operator === "in") return 4;
+
+    let size = this.operator.length;
+
+    if (
+        (this.operator === "+" || this.operator === "-")
+        && this.right instanceof AST_Unary && this.right.operator === this.operator
+    ) {
+        // 1+ +a > needs space between the +
+        size += 1;
+    }
+
+    if (this.needs_parens(info)) {
+        size += 2;
+    }
+
+    return size;
+};
+
+AST_Conditional.prototype._size = () => 3;
+
+AST_Array.prototype._size = function () {
+    return 2 + list_overhead(this.elements);
+};
+
+AST_Object.prototype._size = function (info) {
+    let base = 2;
+    if (first_in_statement(info)) {
+        base += 2; // parens
+    }
+    return base + list_overhead(this.properties);
+};
+
+/*#__INLINE__*/
+const key_size = key =>
+    typeof key === "string" ? key.length : 0;
+
+AST_ObjectKeyVal.prototype._size = function () {
+    return key_size(this.key) + 1;
+};
+
+/*#__INLINE__*/
+const static_size = is_static => is_static ? 7 : 0;
+
+AST_ObjectGetter.prototype._size = function () {
+    return 5 + static_size(this.static) + key_size(this.key);
+};
+
+AST_ObjectSetter.prototype._size = function () {
+    return 5 + static_size(this.static) + key_size(this.key);
+};
+
+AST_ConciseMethod.prototype._size = function () {
+    return static_size(this.static) + key_size(this.key);
+};
+
+AST_PrivateMethod.prototype._size = function () {
+    return AST_ConciseMethod.prototype._size.call(this) + 1;
+};
+
+AST_PrivateGetter.prototype._size = function () {
+    return AST_ConciseMethod.prototype._size.call(this) + 4;
+};
+
+AST_PrivateSetter.prototype._size = function () {
+    return AST_ConciseMethod.prototype._size.call(this) + 4;
+};
+
+AST_PrivateIn.prototype._size = function () {
+    return 5; // "#", and " in "
+};
+
+AST_Class.prototype._size = function () {
+    return (
+        (this.name ? 8 : 7)
+        + (this.extends ? 8 : 0)
+    );
+};
+
+AST_ClassStaticBlock.prototype._size = function () {
+    // "static{}" + semicolons
+    return 8 + list_overhead(this.body);
+};
+
+AST_ClassProperty.prototype._size = function () {
+    return (
+        static_size(this.static)
+        + (typeof this.key === "string" ? this.key.length + 2 : 0)
+        + (this.value ? 1 : 0)
+    );
+};
+
+AST_ClassPrivateProperty.prototype._size = function () {
+    return AST_ClassProperty.prototype._size.call(this) + 1;
+};
+
+AST_Symbol.prototype._size = function () {
+    if (!(mangle_options && this.thedef && !this.thedef.unmangleable(mangle_options))) {
+        return this.name.length;
+    } else {
+        return 1;
+    }
+};
+
+// TODO take propmangle into account
+AST_SymbolClassProperty.prototype._size = function () {
+    return this.name.length;
+};
+
+AST_SymbolRef.prototype._size = AST_SymbolDeclaration.prototype._size = function () {
+    if (this.name === "arguments") return 9;
+
+    return AST_Symbol.prototype._size.call(this);
+};
+
+AST_NewTarget.prototype._size = () => 10;
+
+AST_SymbolImportForeign.prototype._size = function () {
+    return this.name.length;
+};
+
+AST_SymbolExportForeign.prototype._size = function () {
+    return this.name.length;
+};
+
+AST_This.prototype._size = () => 4;
+
+AST_Super.prototype._size = () => 5;
+
+AST_String.prototype._size = function () {
+    return this.value.length + 2;
+};
+
+AST_Number.prototype._size = function () {
+    const { value } = this;
+    if (value === 0) return 1;
+    if (value > 0 && Math.floor(value) === value) {
+        return Math.floor(Math.log10(value) + 1);
+    }
+    return value.toString().length;
+};
+
+AST_BigInt.prototype._size = function () {
+    return this.value.length;
+};
+
+AST_RegExp.prototype._size = function () {
+    return this.value.toString().length;
+};
+
+AST_Null.prototype._size = () => 4;
+
+AST_NaN.prototype._size = () => 3;
+
+AST_Undefined.prototype._size = () => 6; // "void 0"
+
+AST_Hole.prototype._size = () => 0;  // comma is taken into account by list_overhead()
+
+AST_Infinity.prototype._size = () => 8;
+
+AST_True.prototype._size = () => 4;
+
+AST_False.prototype._size = () => 5;
+
+AST_Await.prototype._size = () => 6;
+
+AST_Yield.prototype._size = () => 6;
Index: frontend/node_modules/terser/lib/sourcemap.js
===================================================================
--- frontend/node_modules/terser/lib/sourcemap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/sourcemap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,148 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+"use strict";
+
+import {SourceMapConsumer, SourceMapGenerator} from "@jridgewell/source-map";
+import {defaults, HOP} from "./utils/index.js";
+
+// a small wrapper around source-map and @jridgewell/source-map
+function* SourceMap(options) {
+    options = defaults(options, {
+        file : null,
+        root : null,
+        orig : null,
+        files: {},
+    });
+
+    var orig_map;
+    var generator = new SourceMapGenerator({
+        file       : options.file,
+        sourceRoot : options.root
+    });
+
+    let sourcesContent = {__proto__: null};
+    let files = options.files;
+    for (var name in files) if (HOP(files, name)) {
+        sourcesContent[name] = files[name];
+    }
+    if (options.orig) {
+        // We support both @jridgewell/source-map (which has a sync
+        // SourceMapConsumer) and source-map (which has an async
+        // SourceMapConsumer).
+        orig_map = yield new SourceMapConsumer(options.orig);
+        if (orig_map.sourcesContent) {
+            orig_map.sources.forEach(function(source, i) {
+                var content = orig_map.sourcesContent[i];
+                if (content) {
+                    sourcesContent[source] = content;
+                }
+            });
+        }
+    }
+
+    function add(source, gen_line, gen_col, orig_line, orig_col, name) {
+        let generatedPos = { line: gen_line, column: gen_col };
+
+        if (orig_map) {
+            var info = orig_map.originalPositionFor({
+                line: orig_line,
+                column: orig_col
+            });
+            if (info.source === null) {
+                generator.addMapping({
+                    generated: generatedPos,
+                    original: null,
+                    source: null,
+                    name: null
+                });
+                return;
+            }
+            source = info.source;
+            orig_line = info.line;
+            orig_col = info.column;
+            name = info.name || name;
+        }
+        generator.addMapping({
+            generated : generatedPos,
+            original  : { line: orig_line, column: orig_col },
+            source    : source,
+            name      : name
+        });
+        generator.setSourceContent(source, sourcesContent[source]);
+    }
+
+    function clean(map) {
+        const allNull = map.sourcesContent && map.sourcesContent.every(c => c == null);
+        if (allNull) delete map.sourcesContent;
+        if (map.file === undefined) delete map.file;
+        if (map.sourceRoot === undefined) delete map.sourceRoot;
+        return map;
+    }
+
+    function getDecoded() {
+        if (!generator.toDecodedMap) return null;
+        return clean(generator.toDecodedMap());
+    }
+
+    function getEncoded() {
+        return clean(generator.toJSON());
+    }
+
+    function destroy() {
+        // @jridgewell/source-map's SourceMapConsumer does not need to be
+        // manually freed.
+        if (orig_map && orig_map.destroy) orig_map.destroy();
+    }
+
+    return {
+        add,
+        getDecoded,
+        getEncoded,
+        destroy,
+    };
+}
+
+export {
+    SourceMap,
+};
Index: frontend/node_modules/terser/lib/transform.js
===================================================================
--- frontend/node_modules/terser/lib/transform.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/transform.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,323 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+"use strict";
+
+import {
+    AST_Array,
+    AST_Await,
+    AST_Binary,
+    AST_PrivateIn,
+    AST_Block,
+    AST_Call,
+    AST_Case,
+    AST_Catch,
+    AST_Chain,
+    AST_Class,
+    AST_ClassStaticBlock,
+    AST_Conditional,
+    AST_DefinitionsLike,
+    AST_Destructuring,
+    AST_Do,
+    AST_Exit,
+    AST_Expansion,
+    AST_Export,
+    AST_For,
+    AST_ForIn,
+    AST_If,
+    AST_Import,
+    AST_LabeledStatement,
+    AST_Lambda,
+    AST_LoopControl,
+    AST_NameMapping,
+    AST_Node,
+    AST_Number,
+    AST_Object,
+    AST_ObjectProperty,
+    AST_PrefixedTemplateString,
+    AST_PropAccess,
+    AST_Sequence,
+    AST_SimpleStatement,
+    AST_Sub,
+    AST_Switch,
+    AST_TemplateString,
+    AST_Try,
+    AST_Unary,
+    AST_VarDefLike,
+    AST_While,
+    AST_With,
+    AST_Yield,
+} from "./ast.js";
+import {
+    MAP as do_list,
+    noop,
+} from "./utils/index.js";
+
+function def_transform(node, descend) {
+    node.DEFMETHOD("transform", function(tw, in_list) {
+        let transformed = undefined;
+        tw.push(this);
+        if (tw.before) transformed = tw.before(this, descend, in_list);
+        if (transformed === undefined) {
+            transformed = this;
+            descend(transformed, tw);
+            if (tw.after) {
+                const after_ret = tw.after(transformed, in_list);
+                if (after_ret !== undefined) transformed = after_ret;
+            }
+        }
+        tw.pop();
+        return transformed;
+    });
+}
+
+def_transform(AST_Node, noop);
+
+def_transform(AST_LabeledStatement, function(self, tw) {
+    self.label = self.label.transform(tw);
+    self.body = self.body.transform(tw);
+});
+
+def_transform(AST_SimpleStatement, function(self, tw) {
+    self.body = self.body.transform(tw);
+});
+
+def_transform(AST_Block, function(self, tw) {
+    self.body = do_list(self.body, tw);
+});
+
+def_transform(AST_Do, function(self, tw) {
+    self.body = self.body.transform(tw);
+    self.condition = self.condition.transform(tw);
+});
+
+def_transform(AST_While, function(self, tw) {
+    self.condition = self.condition.transform(tw);
+    self.body = self.body.transform(tw);
+});
+
+def_transform(AST_For, function(self, tw) {
+    if (self.init) self.init = self.init.transform(tw);
+    if (self.condition) self.condition = self.condition.transform(tw);
+    if (self.step) self.step = self.step.transform(tw);
+    self.body = self.body.transform(tw);
+});
+
+def_transform(AST_ForIn, function(self, tw) {
+    self.init = self.init.transform(tw);
+    self.object = self.object.transform(tw);
+    self.body = self.body.transform(tw);
+});
+
+def_transform(AST_With, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+    self.body = self.body.transform(tw);
+});
+
+def_transform(AST_Exit, function(self, tw) {
+    if (self.value) self.value = self.value.transform(tw);
+});
+
+def_transform(AST_LoopControl, function(self, tw) {
+    if (self.label) self.label = self.label.transform(tw);
+});
+
+def_transform(AST_If, function(self, tw) {
+    self.condition = self.condition.transform(tw);
+    self.body = self.body.transform(tw);
+    if (self.alternative) self.alternative = self.alternative.transform(tw);
+});
+
+def_transform(AST_Switch, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+    self.body = do_list(self.body, tw);
+});
+
+def_transform(AST_Case, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+    self.body = do_list(self.body, tw);
+});
+
+def_transform(AST_Try, function(self, tw) {
+    self.body = self.body.transform(tw);
+    if (self.bcatch) self.bcatch = self.bcatch.transform(tw);
+    if (self.bfinally) self.bfinally = self.bfinally.transform(tw);
+});
+
+def_transform(AST_Catch, function(self, tw) {
+    if (self.argname) self.argname = self.argname.transform(tw);
+    self.body = do_list(self.body, tw);
+});
+
+def_transform(AST_DefinitionsLike, function(self, tw) {
+    self.definitions = do_list(self.definitions, tw);
+});
+
+def_transform(AST_VarDefLike, function(self, tw) {
+    self.name = self.name.transform(tw);
+    if (self.value) self.value = self.value.transform(tw);
+});
+
+def_transform(AST_Destructuring, function(self, tw) {
+    self.names = do_list(self.names, tw);
+});
+
+def_transform(AST_Lambda, function(self, tw) {
+    if (self.name) self.name = self.name.transform(tw);
+    self.argnames = do_list(self.argnames, tw, /* allow_splicing */ false);
+    if (self.body instanceof AST_Node) {
+        self.body = self.body.transform(tw);
+    } else {
+        self.body = do_list(self.body, tw);
+    }
+});
+
+def_transform(AST_Call, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+    self.args = do_list(self.args, tw, /* allow_splicing */ false);
+});
+
+def_transform(AST_Sequence, function(self, tw) {
+    const result = do_list(self.expressions, tw);
+    self.expressions = result.length
+        ? result
+        : [new AST_Number({ value: 0 })];
+});
+
+def_transform(AST_PropAccess, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+});
+
+def_transform(AST_Sub, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+    self.property = self.property.transform(tw);
+});
+
+def_transform(AST_Chain, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+});
+
+def_transform(AST_Yield, function(self, tw) {
+    if (self.expression) self.expression = self.expression.transform(tw);
+});
+
+def_transform(AST_Await, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+});
+
+def_transform(AST_Unary, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+});
+
+def_transform(AST_Binary, function(self, tw) {
+    self.left = self.left.transform(tw);
+    self.right = self.right.transform(tw);
+});
+
+def_transform(AST_PrivateIn, function(self, tw) {
+    self.key = self.key.transform(tw);
+    self.value = self.value.transform(tw);
+});
+
+def_transform(AST_Conditional, function(self, tw) {
+    self.condition = self.condition.transform(tw);
+    self.consequent = self.consequent.transform(tw);
+    self.alternative = self.alternative.transform(tw);
+});
+
+def_transform(AST_Array, function(self, tw) {
+    self.elements = do_list(self.elements, tw);
+});
+
+def_transform(AST_Object, function(self, tw) {
+    self.properties = do_list(self.properties, tw);
+});
+
+def_transform(AST_ObjectProperty, function(self, tw) {
+    if (self.key instanceof AST_Node) {
+        self.key = self.key.transform(tw);
+    }
+    if (self.value) self.value = self.value.transform(tw);
+});
+
+def_transform(AST_Class, function(self, tw) {
+    if (self.name) self.name = self.name.transform(tw);
+    if (self.extends) self.extends = self.extends.transform(tw);
+    self.properties = do_list(self.properties, tw);
+});
+
+def_transform(AST_ClassStaticBlock, function(self, tw) {
+    self.body = do_list(self.body, tw);
+});
+
+def_transform(AST_Expansion, function(self, tw) {
+    self.expression = self.expression.transform(tw);
+});
+
+def_transform(AST_NameMapping, function(self, tw) {
+    self.foreign_name = self.foreign_name.transform(tw);
+    self.name = self.name.transform(tw);
+});
+
+def_transform(AST_Import, function(self, tw) {
+    if (self.imported_name) self.imported_name = self.imported_name.transform(tw);
+    if (self.imported_names) do_list(self.imported_names, tw);
+    self.module_name = self.module_name.transform(tw);
+});
+
+def_transform(AST_Export, function(self, tw) {
+    if (self.exported_definition) self.exported_definition = self.exported_definition.transform(tw);
+    if (self.exported_value) self.exported_value = self.exported_value.transform(tw);
+    if (self.exported_names) do_list(self.exported_names, tw);
+    if (self.module_name) self.module_name = self.module_name.transform(tw);
+});
+
+def_transform(AST_TemplateString, function(self, tw) {
+    self.segments = do_list(self.segments, tw);
+});
+
+def_transform(AST_PrefixedTemplateString, function(self, tw) {
+    self.prefix = self.prefix.transform(tw);
+    self.template_string = self.template_string.transform(tw);
+});
+
Index: frontend/node_modules/terser/lib/utils/first_in_statement.js
===================================================================
--- frontend/node_modules/terser/lib/utils/first_in_statement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/utils/first_in_statement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,53 @@
+import {
+    AST_Binary,
+    AST_Conditional,
+    AST_Chain,
+    AST_Dot,
+    AST_Object,
+    AST_Sequence,
+    AST_Statement,
+    AST_Sub,
+    AST_UnaryPostfix,
+    AST_PrefixedTemplateString
+} from "../ast.js";
+
+// return true if the node at the top of the stack (that means the
+// innermost node in the current output) is lexically the first in
+// a statement.
+function first_in_statement(stack) {
+    let node = stack.parent(-1);
+    for (let i = 0, p; p = stack.parent(i); i++) {
+        if (p instanceof AST_Statement && p.body === node)
+            return true;
+        if ((p instanceof AST_Sequence && p.expressions[0] === node) ||
+            (p.TYPE === "Call" && p.expression === node) ||
+            (p instanceof AST_PrefixedTemplateString && p.prefix === node) ||
+            (p instanceof AST_Dot && p.expression === node) ||
+            (p instanceof AST_Sub && p.expression === node) ||
+            (p instanceof AST_Chain && p.expression === node) ||
+            (p instanceof AST_Conditional && p.condition === node) ||
+            (p instanceof AST_Binary && p.left === node) ||
+            (p instanceof AST_UnaryPostfix && p.expression === node)
+        ) {
+            node = p;
+        } else {
+            return false;
+        }
+    }
+}
+
+// Returns whether the leftmost item in the expression is an object
+function left_is_object(node) {
+    if (node instanceof AST_Object) return true;
+    if (node instanceof AST_Sequence) return left_is_object(node.expressions[0]);
+    if (node.TYPE === "Call") return left_is_object(node.expression);
+    if (node instanceof AST_PrefixedTemplateString) return left_is_object(node.prefix);
+    if (node instanceof AST_Dot || node instanceof AST_Sub) return left_is_object(node.expression);
+    if (node instanceof AST_Chain) return left_is_object(node.expression);
+    if (node instanceof AST_Conditional) return left_is_object(node.condition);
+    if (node instanceof AST_Binary) return left_is_object(node.left);
+    if (node instanceof AST_UnaryPostfix) return left_is_object(node.expression);
+    return false;
+}
+
+export { first_in_statement, left_is_object };
Index: frontend/node_modules/terser/lib/utils/index.js
===================================================================
--- frontend/node_modules/terser/lib/utils/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/terser/lib/utils/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,305 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+  https://github.com/mishoo/UglifyJS2
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+"use strict";
+
+import { AST_Node, AST_Number, AST_UnaryPrefix } from "../ast.js";
+
+function characters(str) {
+    return str.split("");
+}
+
+function member(name, array) {
+    return array.includes(name);
+}
+
+class DefaultsError extends Error {
+    constructor(msg, defs) {
+        super();
+
+        this.name = "DefaultsError";
+        this.message = msg;
+        this.defs = defs;
+    }
+}
+
+function defaults(args, defs, croak) {
+    if (args === true) {
+        args = {};
+    } else if (args != null && typeof args === "object") {
+        args = {...args};
+    }
+
+    const ret = args || {};
+
+    if (croak) for (const i in ret) if (HOP(ret, i) && !HOP(defs, i)) {
+        throw new DefaultsError("`" + i + "` is not a supported option", defs);
+    }
+
+    for (const i in defs) if (HOP(defs, i)) {
+        if (!args || !HOP(args, i)) {
+            ret[i] = defs[i];
+        } else if (i === "ecma" || i === "builtins_ecma") {
+            let ecma = args[i] | 0;
+            if (ecma > 5 && ecma < 2015) ecma += 2009;
+            ret[i] = ecma;
+        } else {
+            ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
+        }
+    }
+
+    return ret;
+}
+
+function noop() {}
+function return_false() { return false; }
+function return_true() { return true; }
+function return_this() { return this; }
+function return_null() { return null; }
+
+var MAP = (function() {
+    function MAP(a, tw, allow_splicing = true) {
+        const new_a = [];
+
+        for (let i = 0; i < a.length; ++i) {
+            let item = a[i];
+            let ret = item.transform(tw, allow_splicing);
+
+            if (ret instanceof AST_Node) {
+                new_a.push(ret);
+            } else if (ret instanceof Splice) {
+                new_a.push(...ret.v);
+            }
+        }
+
+        return new_a;
+    }
+
+    MAP.splice = function(val) { return new Splice(val); };
+    MAP.skip = {};
+    function Splice(val) { this.v = val; }
+    return MAP;
+})();
+
+function make_node(ctor, orig, props) {
+    if (!props) props = {};
+    if (orig) {
+        if (!props.start) props.start = orig.start;
+        if (!props.end) props.end = orig.end;
+    }
+    return new ctor(props);
+}
+
+/** Makes a `void 0` expression. Use instead of AST_Undefined which may conflict
+ * with an existing variable called `undefined` */
+function make_void_0(orig) {
+    return make_node(AST_UnaryPrefix, orig, {
+        operator: "void",
+        expression: make_node(AST_Number, orig, { value: 0 })
+    });
+}
+
+function push_uniq(array, el) {
+    if (!array.includes(el))
+        array.push(el);
+}
+
+function string_template(text, props) {
+    return text.replace(/{(.+?)}/g, function(str, p) {
+        return props && props[p];
+    });
+}
+
+function remove(array, el) {
+    for (var i = array.length; --i >= 0;) {
+        if (array[i] === el) array.splice(i, 1);
+    }
+}
+
+function mergeSort(array, cmp) {
+    if (array.length < 2) return array.slice();
+    function merge(a, b) {
+        var r = [], ai = 0, bi = 0, i = 0;
+        while (ai < a.length && bi < b.length) {
+            cmp(a[ai], b[bi]) <= 0
+                ? r[i++] = a[ai++]
+                : r[i++] = b[bi++];
+        }
+        if (ai < a.length) r.push.apply(r, a.slice(ai));
+        if (bi < b.length) r.push.apply(r, b.slice(bi));
+        return r;
+    }
+    function _ms(a) {
+        if (a.length <= 1)
+            return a;
+        var m = Math.floor(a.length / 2), left = a.slice(0, m), right = a.slice(m);
+        left = _ms(left);
+        right = _ms(right);
+        return merge(left, right);
+    }
+    return _ms(array);
+}
+
+function makePredicate(words) {
+    if (!Array.isArray(words)) words = words.split(" ");
+
+    return new Set(words.sort());
+}
+
+function map_add(map, key, value) {
+    if (map.has(key)) {
+        map.get(key).push(value);
+    } else {
+        map.set(key, [ value ]);
+    }
+}
+
+function map_from_object(obj) {
+    var map = new Map();
+    for (var key in obj) {
+        if (HOP(obj, key) && key.charAt(0) === "$") {
+            map.set(key.substr(1), obj[key]);
+        }
+    }
+    return map;
+}
+
+function map_to_object(map) {
+    var obj = Object.create(null);
+    map.forEach(function (value, key) {
+        obj["$" + key] = value;
+    });
+    return obj;
+}
+
+function HOP(obj, prop) {
+    return Object.prototype.hasOwnProperty.call(obj, prop);
+}
+
+function keep_name(keep_setting, name) {
+    return keep_setting === true
+        || (keep_setting instanceof RegExp && keep_setting.test(name));
+}
+
+var lineTerminatorEscape = {
+    "\0": "0",
+    "\n": "n",
+    "\r": "r",
+    "\u2028": "u2028",
+    "\u2029": "u2029",
+};
+function regexp_source_fix(source) {
+    // V8 does not escape line terminators in regexp patterns in node 12
+    // We'll also remove literal \0
+    return source.replace(/[\0\n\r\u2028\u2029]/g, function (match, offset) {
+        var escaped = source[offset - 1] == "\\"
+            && (source[offset - 2] != "\\"
+            || /(?:^|[^\\])(?:\\{2})*$/.test(source.slice(0, offset - 1)));
+        return (escaped ? "" : "\\") + lineTerminatorEscape[match];
+    });
+}
+
+// Subset of regexps that is not going to cause regexp based DDOS
+// https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
+const re_safe_regexp = /^[\\/|\0\s\w^$.[\]()]*$/;
+
+/** Check if the regexp is safe for Terser to create without risking a RegExp DOS */
+export const regexp_is_safe = (source) => re_safe_regexp.test(source);
+
+const all_flags = "dgimsuyv";
+function sort_regexp_flags(flags) {
+    const existing_flags = new Set(flags.split(""));
+    let out = "";
+    for (const flag of all_flags) {
+        if (existing_flags.has(flag)) {
+            out += flag;
+            existing_flags.delete(flag);
+        }
+    }
+    if (existing_flags.size) {
+        // Flags Terser doesn't know about
+        existing_flags.forEach(flag => { out += flag; });
+    }
+    return out;
+}
+
+function has_annotation(node, annotation) {
+    return node._annotations & annotation;
+}
+
+function set_annotation(node, annotation) {
+    node._annotations |= annotation;
+}
+
+function clear_annotation(node, annotation) {
+    node._annotations &= ~annotation;
+}
+
+export {
+    characters,
+    defaults,
+    HOP,
+    keep_name,
+    make_node,
+    make_void_0,
+    makePredicate,
+    map_add,
+    map_from_object,
+    map_to_object,
+    MAP,
+    member,
+    mergeSort,
+    noop,
+    push_uniq,
+    regexp_source_fix,
+    remove,
+    return_false,
+    return_null,
+    return_this,
+    return_true,
+    sort_regexp_flags,
+    string_template,
+    has_annotation,
+    set_annotation,
+    clear_annotation,
+};
