Repository: terser/terser

Issue:
Support `using` parsing and minification
https://github.com/tc39/proposal-explicit-resource-management

Node.js@24 and Chrome support this syntax

Discussion:
@alexander-akait: /cc @fabiosantoscode just friendly ping

Solution patch:
diff --git a/lib/ast.js b/lib/ast.js
index 6d1b25a0..096c8d62 100644
--- a/lib/ast.js
+++ b/lib/ast.js
@@ -1355,7 +1355,7 @@ var AST_Finally = DEFNODE("Finally", null, function AST_Finally(props) {
 
 /* -----[ VAR/CONST ]----- */
 
-var AST_Definitions = DEFNODE("Definitions", "definitions", function AST_Definitions(props) {
+var AST_DefinitionsLike = DEFNODE("DefinitionsLike", "definitions", function AST_DefinitionsLike(props) {
     if (props) {
         this.definitions = props.definitions;
         this.start = props.start;
@@ -1364,9 +1364,9 @@ var AST_Definitions = DEFNODE("Definitions", "definitions", function AST_Definit
 
     this.flags = 0;
 }, {
-    $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)",
+    $documentation: "Base class for variable definitions and `using`",
     $propdoc: {
-        definitions: "[AST_VarDef*] array of variable definitions"
+        definitions: "[AST_VarDef*|AST_UsingDef*] array of variable definitions"
     },
     _walk: function(visitor) {
         return visitor._visit(this, function() {
@@ -1382,6 +1382,18 @@ var AST_Definitions = DEFNODE("Definitions", "definitions", function AST_Definit
     },
 }, 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;
@@ -1418,7 +1430,23 @@ var AST_Const = DEFNODE("Const", null, function AST_Const(props) {
     $documentation: "A `const` statement"
 }, AST_Definitions);
 
-var AST_VarDef = DEFNODE("VarDef", "name value", function AST_VarDef(props) {
+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;
@@ -1428,9 +1456,9 @@ var AST_VarDef = DEFNODE("VarDef", "name value", function AST_VarDef(props) {
 
     this.flags = 0;
 }, {
-    $documentation: "A variable declaration; only appears in a AST_Definitions node",
+    $documentation: "A name=value pair in a variable definition statement or `using`",
     $propdoc: {
-        name: "[AST_Destructuring|AST_SymbolConst|AST_SymbolLet|AST_SymbolVar] name of the variable",
+        name: "[AST_Destructuring|AST_SymbolDeclaration] name of the variable",
         value: "[AST_Node?] initializer, or null of there's no initializer"
     },
     _walk: function(visitor) {
@@ -1445,13 +1473,39 @@ var AST_VarDef = DEFNODE("VarDef", "name value", function AST_VarDef(props) {
     },
     declarations_as_names() {
         if (this.name instanceof AST_SymbolDeclaration) {
-            return [this];
+            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;
@@ -2547,6 +2601,21 @@ var AST_SymbolConst = DEFNODE("SymbolConst", null, function AST_SymbolConst(prop
     $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;
@@ -3286,6 +3355,7 @@ export {
     AST_DefaultAssign,
     AST_DefClass,
     AST_Definitions,
+    AST_DefinitionsLike,
     AST_Defun,
     AST_Destructuring,
     AST_Directive,
@@ -3363,6 +3433,7 @@ export {
     AST_SymbolLet,
     AST_SymbolMethod,
     AST_SymbolRef,
+    AST_SymbolUsing,
     AST_SymbolVar,
     AST_TemplateSegment,
     AST_TemplateString,
@@ -3378,8 +3449,11 @@ export {
     AST_UnaryPostfix,
     AST_UnaryPrefix,
     AST_Undefined,
+    AST_Using,
+    AST_UsingDef,
     AST_Var,
     AST_VarDef,
+    AST_VarDefLike,
     AST_While,
     AST_With,
     AST_Yield,
diff --git a/lib/compress/common.js b/lib/compress/common.js
index fab91a46..3e432a64 100644
--- a/lib/compress/common.js
+++ b/lib/compress/common.js
@@ -80,6 +80,7 @@ import {
     AST_True,
     AST_UnaryPrefix,
     AST_Undefined,
+    AST_Using,
 
     TreeWalker,
     walk,
@@ -198,7 +199,7 @@ export function get_simple_key(key) {
     if (key instanceof AST_UnaryPrefix
         && key.operator == "void"
         && key.expression instanceof AST_Constant) {
-        return;
+        return undefined;
     }
     return key;
 }
@@ -308,6 +309,7 @@ export function can_be_evicted_from_block(node) {
         node instanceof AST_Defun ||
         node instanceof AST_Let ||
         node instanceof AST_Const ||
+        node instanceof AST_Using ||
         node instanceof AST_Export ||
         node instanceof AST_Import
     );
diff --git a/lib/compress/index.js b/lib/compress/index.js
index fa29b15f..ce7dc70a 100644
--- a/lib/compress/index.js
+++ b/lib/compress/index.js
@@ -118,6 +118,7 @@ import {
     AST_SymbolLet,
     AST_SymbolMethod,
     AST_SymbolRef,
+    AST_SymbolUsing,
     AST_TemplateString,
     AST_This,
     AST_Toplevel,
@@ -127,6 +128,7 @@ import {
     AST_UnaryPostfix,
     AST_UnaryPrefix,
     AST_Undefined,
+    AST_Using,
     AST_Var,
     AST_VarDef,
     AST_While,
@@ -700,6 +702,7 @@ 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
     );
 }
@@ -878,6 +881,7 @@ AST_Scope.DEFMETHOD("hoist_properties", function(compressor) {
             let def;
             let value;
             if (sym.scope === self
+                && !(sym instanceof AST_SymbolUsing)
                 && (def = sym.definition()).escaped != 1
                 && !def.assignments
                 && !def.direct_access
diff --git a/lib/compress/reduce-vars.js b/lib/compress/reduce-vars.js
index ec1dd60b..fb7a54f6 100644
--- a/lib/compress/reduce-vars.js
+++ b/lib/compress/reduce-vars.js
@@ -88,7 +88,9 @@ import {
     AST_Unary,
     AST_UnaryPrefix,
     AST_Undefined,
+    AST_UsingDef,
     AST_VarDef,
+    AST_VarDefLike,
     AST_While,
     AST_Yield,
 
@@ -235,7 +237,7 @@ function mark_escaped(tw, d, scope, node, value, level = 0, depth = 1) {
         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_VarDef && node === parent.value
+        || 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;
@@ -618,7 +620,7 @@ function handle_defined_after_hoist(parent) {
 
     // Refine `defun_first_read_map` to be as high as possible
     for (const [defun, defun_first_read] of defun_first_read_map) {
-        // Update all depdencies of `defun`
+        // 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);
@@ -847,6 +849,10 @@ def_reduce_vars(AST_VarDef, function(tw, descend) {
     }
 });
 
+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;
diff --git a/lib/compress/tighten-body.js b/lib/compress/tighten-body.js
index e04c2999..9bf80dca 100644
--- a/lib/compress/tighten-body.js
+++ b/lib/compress/tighten-body.js
@@ -55,12 +55,12 @@ import {
     AST_Chain,
     AST_Class,
     AST_Conditional,
-    AST_Const,
     AST_Constant,
     AST_Continue,
     AST_Debugger,
     AST_Default,
     AST_Definitions,
+    AST_DefinitionsLike,
     AST_Defun,
     AST_Destructuring,
     AST_Directive,
@@ -76,7 +76,6 @@ import {
     AST_Import,
     AST_IterationStatement,
     AST_Lambda,
-    AST_Let,
     AST_LoopControl,
     AST_Node,
     AST_Number,
@@ -99,6 +98,7 @@ import {
     AST_SymbolLambda,
     AST_SymbolLet,
     AST_SymbolRef,
+    AST_SymbolUsing,
     AST_SymbolVar,
     AST_This,
     AST_Try,
@@ -107,6 +107,7 @@ import {
     AST_UnaryPostfix,
     AST_UnaryPrefix,
     AST_Undefined,
+    AST_Using,
     AST_Var,
     AST_VarDef,
     AST_With,
@@ -300,6 +301,7 @@ export function tighten_body(statements, compressor) {
             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)
@@ -398,6 +400,7 @@ export function tighten_body(statements, compressor) {
                 && ((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
@@ -812,7 +815,9 @@ export function tighten_body(statements, compressor) {
                     ? expr.left
                     : expr.expression;
                 return !is_ref_of(lhs, AST_SymbolConst)
-                    && !is_ref_of(lhs, AST_SymbolLet) && lhs;
+                    && !is_ref_of(lhs, AST_SymbolLet)
+                    && !is_ref_of(lhs, AST_SymbolUsing)
+                    && lhs;
             }
         }
 
@@ -1147,7 +1152,7 @@ export function tighten_body(statements, compressor) {
                 return false;
             for (var j = i + 1, len = statements.length; j < len; j++) {
                 var stat = statements[j];
-                if (stat instanceof AST_Const || stat instanceof AST_Let)
+                if (stat instanceof AST_DefinitionsLike && !(stat instanceof AST_Var))
                     return false;
             }
             var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
@@ -1287,7 +1292,7 @@ export function tighten_body(statements, compressor) {
             var line = block.body[i];
             if (line instanceof AST_Var && declarations_only(line)) {
                 decls.push(line);
-            } else if (stat || line instanceof AST_Const || line instanceof AST_Let) {
+            } else if (stat || line instanceof AST_DefinitionsLike && !(line instanceof AST_Var)) {
                 return false;
             } else {
                 stat = line;
@@ -1310,7 +1315,7 @@ export function tighten_body(statements, compressor) {
                 if (stat instanceof AST_Exit) {
                     stat.value = cons_seq(stat.value || make_node(AST_Undefined, stat).transform(compressor));
                 } else if (stat instanceof AST_For) {
-                    if (!(stat.init instanceof AST_Definitions)) {
+                    if (!(stat.init instanceof AST_DefinitionsLike)) {
                         const abort = walk(prev.body, node => {
                             if (node instanceof AST_Scope)
                                 return true;
@@ -1330,7 +1335,7 @@ export function tighten_body(statements, compressor) {
                         }
                     }
                 } else if (stat instanceof AST_ForIn) {
-                    if (!(stat.init instanceof AST_Const) && !(stat.init instanceof AST_Let)) {
+                    if (!(stat.init instanceof AST_DefinitionsLike) || stat.init instanceof AST_Var) {
                         stat.object = cons_seq(stat.object);
                     }
                 } else if (stat instanceof AST_If) {
@@ -1447,6 +1452,12 @@ export function tighten_body(statements, compressor) {
                     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) {
diff --git a/lib/equivalent-to.js b/lib/equivalent-to.js
index b2942801..1a858c41 100644
--- a/lib/equivalent-to.js
+++ b/lib/equivalent-to.js
@@ -14,7 +14,7 @@ import {
     AST_ConciseMethod,
     AST_Conditional,
     AST_Debugger,
-    AST_Definitions,
+    AST_DefinitionsLike,
     AST_Destructuring,
     AST_Directive,
     AST_Do,
@@ -61,7 +61,7 @@ import {
     AST_Toplevel,
     AST_Try,
     AST_Unary,
-    AST_VarDef,
+    AST_VarDefLike,
     AST_While,
     AST_With,
     AST_Yield
@@ -184,9 +184,9 @@ AST_Catch.prototype.shallow_cmp = function(other) {
 
 AST_Finally.prototype.shallow_cmp = pass_through;
 
-AST_Definitions.prototype.shallow_cmp = pass_through;
+AST_DefinitionsLike.prototype.shallow_cmp = pass_through;
 
-AST_VarDef.prototype.shallow_cmp = function(other) {
+AST_VarDefLike.prototype.shallow_cmp = function(other) {
     return this.value == null ? other.value == null : this.value === other.value;
 };
 
diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js
index 72dec493..d55c911f 100644
--- a/lib/mozilla-ast.js
+++ b/lib/mozilla-ast.js
@@ -73,7 +73,7 @@ import {
     AST_Default,
     AST_DefaultAssign,
     AST_DefClass,
-    AST_Definitions,
+    AST_DefinitionsLike,
     AST_Defun,
     AST_Destructuring,
     AST_Directive,
@@ -143,6 +143,7 @@ import {
     AST_SymbolMethod,
     AST_SymbolRef,
     AST_SymbolVar,
+    AST_SymbolUsing,
     AST_TemplateSegment,
     AST_TemplateString,
     AST_This,
@@ -155,8 +156,11 @@ import {
     AST_Unary,
     AST_UnaryPostfix,
     AST_UnaryPrefix,
+    AST_Using,
+    AST_UsingDef,
     AST_Var,
     AST_VarDef,
+    AST_VarDefLike,
     AST_While,
     AST_With,
     AST_Yield,
@@ -526,19 +530,30 @@ import { is_basic_identifier_string } from "./parse.js";
 
         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 AST_VarDef({
+                return new defs_type({
                     start: my_start_token(M),
                     end: my_end_token(M),
                     name: from_moz_pattern(M.id, sym_type),
@@ -549,6 +564,7 @@ import { is_basic_identifier_string } from "./parse.js";
                 start       : my_start_token(M),
                 end         : my_end_token(M),
                 definitions : definitions,
+                await       : await_using,
             });
         },
 
@@ -1148,7 +1164,7 @@ import { is_basic_identifier_string } from "./parse.js";
             type: "DebuggerStatement"
         };
     });
-    def_to_moz(AST_VarDef, function To_Moz_VariableDeclarator(M) {
+    def_to_moz(AST_VarDefLike, function To_Moz_VariableDeclarator(M) {
         return {
             type: "VariableDeclarator",
             id: to_moz(M.name),
@@ -1364,12 +1380,14 @@ import { is_basic_identifier_string } from "./parse.js";
         };
     });
 
-    def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(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" : "var",
+                M instanceof AST_Let ? "let" :
+                M instanceof AST_Using ? (M.await ? "await using" : "using") :
+                "var",
             declarations: M.definitions.map(to_moz)
         };
     });
diff --git a/lib/output.js b/lib/output.js
index 66492c0f..ed19a244 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -85,6 +85,7 @@ import {
     AST_Default,
     AST_DefaultAssign,
     AST_Definitions,
+    AST_DefinitionsLike,
     AST_Defun,
     AST_Destructuring,
     AST_Directive,
@@ -147,8 +148,9 @@ import {
     AST_Unary,
     AST_UnaryPostfix,
     AST_UnaryPrefix,
+    AST_Using,
     AST_Var,
-    AST_VarDef,
+    AST_VarDefLike,
     AST_While,
     AST_With,
     AST_Yield,
@@ -1044,7 +1046,7 @@ function OutputStream(options) {
         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_VarDef                        // var a = (1, 2), b = a + a; ==> b == 4
+            || p instanceof AST_VarDefLike                    // var a = (1, 2), b = a + a; ==> b == 4
             || p instanceof AST_PropAccess                    // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
             || p instanceof AST_Array                         // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
             || p instanceof AST_ObjectProperty                // { foo: (1, 2) }.foo ==> 2
@@ -1376,7 +1378,7 @@ function OutputStream(options) {
         output.space();
         output.with_parens(function() {
             if (self.init) {
-                if (self.init instanceof AST_Definitions) {
+                if (self.init instanceof AST_DefinitionsLike) {
                     self.init.print(output);
                 } else {
                     parenthesize_for_noin(self.init, output, true);
@@ -1736,7 +1738,7 @@ function OutputStream(options) {
     });
 
     /* -----[ var/const ]----- */
-    AST_Definitions.DEFMETHOD("_do_print", function(output, kind) {
+    AST_DefinitionsLike.DEFMETHOD("_do_print", function(output, kind) {
         output.print(kind);
         output.space();
         this.definitions.forEach(function(def, i) {
@@ -1758,6 +1760,9 @@ function OutputStream(options) {
     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();
@@ -1925,7 +1930,7 @@ function OutputStream(options) {
         node.print(output, parens);
     }
 
-    DEFPRINT(AST_VarDef, function(self, output) {
+    DEFPRINT(AST_VarDefLike, function(self, output) {
         self.name.print(output);
         if (self.value) {
             output.space();
@@ -2401,7 +2406,7 @@ function OutputStream(options) {
         } else {
             if (!stat || stat instanceof AST_EmptyStatement)
                 output.force_semicolon();
-            else if (stat instanceof AST_Let || stat instanceof AST_Const || stat instanceof AST_Class)
+            else if ((stat instanceof AST_DefinitionsLike && !(stat instanceof AST_Var)) || stat instanceof AST_Class)
                 make_block(stat, output);
             else
                 stat.print(output);
@@ -2481,7 +2486,7 @@ function OutputStream(options) {
         AST_Class,
         AST_Constant,
         AST_Debugger,
-        AST_Definitions,
+        AST_DefinitionsLike,
         AST_Directive,
         AST_Finally,
         AST_Jump,
diff --git a/lib/parse.js b/lib/parse.js
index 1d49da1e..8bdf2820 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -81,6 +81,7 @@ import {
     AST_DefaultAssign,
     AST_DefClass,
     AST_Definitions,
+    AST_DefinitionsLike,
     AST_Defun,
     AST_Destructuring,
     AST_Directive,
@@ -142,6 +143,7 @@ import {
     AST_SymbolMethod,
     AST_SymbolRef,
     AST_SymbolVar,
+    AST_SymbolUsing,
     AST_TemplateSegment,
     AST_TemplateString,
     AST_This,
@@ -154,6 +156,8 @@ import {
     AST_TryBlock,
     AST_UnaryPostfix,
     AST_UnaryPrefix,
+    AST_Using,
+    AST_UsingDef,
     AST_Var,
     AST_VarDef,
     AST_While,
@@ -190,6 +194,8 @@ 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",
@@ -514,6 +520,56 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
             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++))
@@ -756,7 +812,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
             && (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z")
         );
 
-        if (end > start + 1 && ch && ch !== "\\" && !is_identifier_char(ch)) {
+        // 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);
@@ -1017,6 +1074,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
         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;
 
 }
@@ -1238,10 +1298,6 @@ function parse($TEXT, options) {
             return simple_statement();
 
           case "name":
-          case "privatename":
-            if(is("privatename") && !S.in_class)
-                croak("Private field must be used in an enclosing class");
-
             if (S.token.value == "async" && is_token(peek(), "keyword", "function")) {
                 next();
                 next();
@@ -1256,10 +1312,31 @@ function parse($TEXT, options) {
                 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 "{":
@@ -1484,6 +1561,8 @@ function parse($TEXT, options) {
                 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");
@@ -1491,9 +1570,12 @@ function parse($TEXT, options) {
                 token_error(await_tok, for_await_error);
             }
             if (is_in || is_of) {
-                if (init instanceof AST_Definitions) {
+                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");
                 }
@@ -1525,7 +1607,7 @@ function parse($TEXT, options) {
     }
 
     function for_of(init, is_await) {
-        var lhs = init instanceof AST_Definitions ? init.definitions[0].name : null;
+        var lhs = init instanceof AST_DefinitionsLike ? init.definitions[0].name : null;
         var obj = expression(true);
         expect(")");
         return new AST_ForOf({
@@ -2121,23 +2203,26 @@ function parse($TEXT, options) {
             var sym_type =
                 kind === "var" ? AST_SymbolVar :
                 kind === "const" ? AST_SymbolConst :
-                kind === "let" ? AST_SymbolLet : null;
+                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 AST_VarDef({
+                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 AST_VarDef({
+                def = new def_type({
                     start : S.token,
                     name  : as_symbol(sym_type),
                     value : is("operator", "=")
                         ? (next(), expression(false, no_in))
-                        : !no_in && kind === "const"
-                            ? croak("Missing initializer in const declaration") : null,
+                        : !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");
@@ -2174,6 +2259,25 @@ function parse($TEXT, options) {
         });
     };
 
+    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");
@@ -2592,13 +2696,13 @@ function parse($TEXT, options) {
             return name;
         };
 
+        var is_private = prev().type === "privatename";
         const is_not_method_start = () =>
-            !is("punc", "(") && !is("punc", ",") && !is("punc", "}") && !is("punc", ";") && !is("operator", "=");
+            !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 is_private = false;
         var accessor_type = null;
 
         if (is_class && name === "static" && is_not_method_start()) {
@@ -2621,7 +2725,7 @@ function parse($TEXT, options) {
             accessor_type = name;
             name = as_property_name();
         }
-        if (prev().type === "privatename") {
+        if (!is_private && prev().type === "privatename") {
             is_private = true;
         }
 
diff --git a/lib/scope.js b/lib/scope.js
index dc29cccb..9c99302f 100644
--- a/lib/scope.js
+++ b/lib/scope.js
@@ -99,6 +99,7 @@ import {
     AST_SymbolLet,
     AST_SymbolMethod,
     AST_SymbolRef,
+    AST_SymbolUsing,
     AST_SymbolVar,
     AST_Toplevel,
     AST_VarDef,
@@ -322,6 +323,7 @@ AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = undef
             node instanceof AST_SymbolVar
             || node instanceof AST_SymbolLet
             || node instanceof AST_SymbolConst
+            || node instanceof AST_SymbolUsing
             || node instanceof AST_SymbolCatch
         ) {
             var def;
@@ -335,7 +337,7 @@ AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = undef
                 if (node instanceof AST_SymbolBlockDeclaration) {
                     return sym instanceof AST_SymbolLambda;
                 }
-                return !(sym instanceof AST_SymbolLet || sym instanceof AST_SymbolConst);
+                return !(sym instanceof AST_SymbolLet || sym instanceof AST_SymbolConst || sym instanceof AST_SymbolUsing);
             })) {
                 js_error(
                     `"${node.name}" is redeclared`,
diff --git a/lib/size.js b/lib/size.js
index f96435cc..6b047d37 100644
--- a/lib/size.js
+++ b/lib/size.js
@@ -78,8 +78,9 @@ import {
     AST_Finally,
     AST_Unary,
     AST_Undefined,
+    AST_Using,
     AST_Var,
-    AST_VarDef,
+    AST_VarDefLike,
     AST_While,
     AST_With,
     AST_Yield,
@@ -242,7 +243,12 @@ AST_Const.prototype._size = function () {
     return 6 + list_overhead(this.definitions);
 };
 
-AST_VarDef.prototype._size = function () {
+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;
 };
 
diff --git a/lib/transform.js b/lib/transform.js
index 7dd880ad..9c96bd10 100644
--- a/lib/transform.js
+++ b/lib/transform.js
@@ -56,7 +56,7 @@ import {
     AST_Class,
     AST_ClassStaticBlock,
     AST_Conditional,
-    AST_Definitions,
+    AST_DefinitionsLike,
     AST_Destructuring,
     AST_Do,
     AST_Exit,
@@ -83,7 +83,7 @@ import {
     AST_TemplateString,
     AST_Try,
     AST_Unary,
-    AST_VarDef,
+    AST_VarDefLike,
     AST_While,
     AST_With,
     AST_Yield,
@@ -189,11 +189,11 @@ def_transform(AST_Catch, function(self, tw) {
     self.body = do_list(self.body, tw);
 });
 
-def_transform(AST_Definitions, function(self, tw) {
+def_transform(AST_DefinitionsLike, function(self, tw) {
     self.definitions = do_list(self.definitions, tw);
 });
 
-def_transform(AST_VarDef, function(self, tw) {
+def_transform(AST_VarDefLike, function(self, tw) {
     self.name = self.name.transform(tw);
     if (self.value) self.value = self.value.transform(tw);
 });
diff --git a/package-lock.json b/package-lock.json
index 44adface..393ec23b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
       "license": "BSD-2-Clause",
       "dependencies": {
         "@jridgewell/source-map": "^0.3.3",
-        "acorn": "^8.14.0",
+        "acorn": "^8.15.0",
         "commander": "^2.20.0",
         "source-map-support": "~0.5.20"
       },
@@ -267,9 +267,10 @@
       "dev": true
     },
     "node_modules/acorn": {
-      "version": "8.14.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
-      "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+      "version": "8.15.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+      "license": "MIT",
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -2638,9 +2639,9 @@
       "dev": true
     },
     "acorn": {
-      "version": "8.14.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
-      "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="
+      "version": "8.15.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="
     },
     "acorn-jsx": {
       "version": "5.3.2",
diff --git a/package.json b/package.json
index 2410f415..361a497f 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
   ],
   "dependencies": {
     "@jridgewell/source-map": "^0.3.3",
-    "acorn": "^8.14.0",
+    "acorn": "^8.15.0",
     "commander": "^2.20.0",
     "source-map-support": "~0.5.20"
   },



Test patch:
diff --git a/test/compress.js b/test/compress.js
index 0bec5966..886ecf90 100644
--- a/test/compress.js
+++ b/test/compress.js
@@ -2,6 +2,7 @@
 import "source-map-support/register.js";
 import path from "path";
 import fs from "fs";
+import os from "os";
 import child_process from "child_process";
 import assert from "assert";
 import semver from "semver";
@@ -208,6 +209,8 @@ async function run_compress_tests_in_parallel() {
                 if (test_result) {
                     get_task().test_result = test_result;
                     get_task().done = true;
+                    // output might be held up by a task on another worker
+                    proxy_output();
                 }
 
                 current_task = test_files.shift();
@@ -236,7 +239,14 @@ async function run_compress_tests_in_parallel() {
         }
     }
 
-    const n_workers = Math.min(test_files.length, 2);
+    let cpu_count = 1;
+    try {
+        cpu_count = os.availableParallelism
+            ? os.availableParallelism()
+            : os.cpus().length;
+    } catch (_) {}
+
+    const n_workers = Math.min(test_files.length, cpu_count);
     const workers_promises = Promise.all(
         Array.from({ length: n_workers }, start_worker),
     );
diff --git a/test/compress/class-properties.js b/test/compress/class-properties.js
index 7cde95f5..627ac543 100644
--- a/test/compress/class-properties.js
+++ b/test/compress/class-properties.js
@@ -664,3 +664,27 @@ privatein_precedence_bad_2: {
         col: 38,
     })
 }
+
+private_async_function_method_bad: {
+    bad_input: `class C { #async function f() {} }`
+    expect_error: ({
+        name: "SyntaxError",
+        message: "Unexpected token: keyword (function)",
+    })
+}
+
+private_static_block_bad: {
+    bad_input: `class C { #static {} }`
+    expect_error: ({
+        name: "SyntaxError",
+        message: "Unexpected token: punc ({)",
+    })
+}
+
+private_async_function_statement_bad: {
+    bad_input: `class C { static { #async function f() {} } }`
+    expect_error: ({
+        name: "SyntaxError",
+        message: "Unexpected token keyword «function», expected operator «in»",
+    })
+}
diff --git a/test/compress/sequences.js b/test/compress/sequences.js
index 7f962d88..b2a059cc 100644
--- a/test/compress/sequences.js
+++ b/test/compress/sequences.js
@@ -932,6 +932,24 @@ forin: {
     expect_stdout: "PASS"
 }
 
+forin_assignment: {
+    options = {
+        sequences: true,
+    }
+    input: {
+        var o = [], a = {};
+        o.push("PASS");
+        for (a.b in o)
+            console.log(o[a.b]);
+    }
+    expect: {
+        var o = [], a = {};
+        for (a.b in o.push("PASS"), o)
+            console.log(o[a.b]);
+    }
+    expect_stdout: "PASS"
+}
+
 call: {
     options = {
         sequences: true,
diff --git a/test/compress/unicode.js b/test/compress/unicode.js
index 7b6ee1a7..a0dc9ec9 100644
--- a/test/compress/unicode.js
+++ b/test/compress/unicode.js
@@ -104,6 +104,22 @@ ID_continue_with_surrogate_pair: {
     expect_exact: 'var \\u{2f800}\\u{2f800}\\u{2f800}\\u{2f800}="\\u{100000}\\u{100000}\\u{100000}\\u{100000}\\u{100000}";'
 }
 
+ID_continue_with_non_bmp_character: {
+    beautify = {ascii_only: true, ecma: 2015}
+    input: {
+        var in𝓐, instanceof𝓑;
+    }
+    expect_exact: 'var in\\u{1d4d0},instanceof\\u{1d4d1};'
+}
+
+non_id_continue_non_bmp_character: {
+    bad_input: `var in𝅘𝅥`
+    expect_error: ({
+        name: "SyntaxError",
+        message: "Name expected"
+    })
+}
+
 escape_non_escaped_identifier: {
     beautify = {ascii_only: true, ecma: 2015}
     input: {
diff --git a/test/compress/using.js b/test/compress/using.js
new file mode 100644
index 00000000..d4a83b87
--- /dev/null
+++ b/test/compress/using.js
@@ -0,0 +1,532 @@
+using_basic: {
+    options = {}
+    input: {
+        using x = null;
+    }
+    expect: {
+        using x=null;
+    }
+}
+
+using_multiple: {
+    options = {}
+    input: {
+        using x = f(), y = g(), z = null;
+    }
+    expect: {
+        using x=f(),y=g(),z=null;
+    }
+}
+
+using_for: {
+    options = {}
+    input: {
+        for (using x = y; ; ) { };
+    }
+    expect: {
+        for(using x = y;;);
+    }
+}
+
+using_for_in_bad: {
+    options = {}
+    bad_input: `for (using x in y);`
+    expect_error: ({
+        name: "SyntaxError",
+        message: "Invalid using declaration in for..in loop",
+    })
+}
+
+using_for_of: {
+    options = {}
+    input: {
+        for (using x of y);
+    }
+    expect: {
+        for(using x of y);
+    }
+}
+
+using_of_equals_for: {
+    options = {}
+    input: {
+        for (using of = f();;);
+    }
+    expect: {
+        for(using of=f();;);
+    }
+}
+
+using_with_comments: {
+    format = {
+        comments: true
+    }
+    input: {
+        /* 1 */using /* 2 */x /* 3 */= /* 4 */f() /* 5 */;
+    }
+    expect: {
+        /* 1 */using/* 2 */x/* 3 */=/* 4 */f()/* 5 */;
+    }
+}
+
+using_object_pattern_bad: {
+    options = {}
+    bad_input: `using { x } = y;`
+    expect_error: ({
+        name: "SyntaxError",
+        message: "Unexpected token: punc ({)",
+    })
+}
+
+using_as_name_asi: {
+    options = {}
+    input: {
+        using
+        x = f()
+    }
+    expect: {
+        using;x=f();
+    }
+}
+
+using_as_name_subscript_expr: {
+    options = {}
+    input: {
+        function f() {
+            using[x];
+            using.x + using(x) ? using?.x : using`x`;
+        }
+    }
+    expect: {
+        function f(){using[x];using.x+using(x)?using?.x:using`x`};
+    }
+}
+
+using_as_name_in: {
+    options = {}
+    input: {
+        function f() {
+            using in foo;
+        }
+    }
+    expect: {
+        function f() {
+            using in foo;
+        }
+    }
+}
+
+using_as_name_instanceof: {
+    options = {}
+    input: {
+        function f() {
+            using instanceof foo;
+            using in using instanceof using;
+        }
+    }
+    expect: {
+        function f() {
+            using instanceof foo;
+            using in using instanceof using;
+        }
+    }
+}
+
+using_as_name_for_of: {
+    options = {}
+    input: {
+        for (using of f());
+    }
+    expect: {
+        for(using of f());
+    }
+}
+
+using_as_name_for_of_of_member_expr: {
+    options = {}
+    input: {
+        for (using of of[property]);
+    }
+    expect: {
+        for(using of of[property]);
+    }
+}
+
+using_as_name_for_in: {
+    options = {}
+    input: {
+        for (using in f());
+        for (using.foo in []);
+        for (using().foo in []);
+        for (using``.foo in []);
+    }
+    expect: {
+        for(using in f());for(using.foo in []);for(using().foo in []);for(using``.foo in []);
+    }
+}
+
+await_using_basic: {
+    options = {}
+    input: {
+        async () => { await using x = null; }
+    }
+    expect: {
+        async()=>{await using x=null;}
+    }
+}
+
+await_using_multiple: {
+    options = {}
+    input: {
+        async () => { await using x = f(), y = g(), z = null; }
+    }
+    expect: {
+        async()=>{await using x=f(),y=g(),z=null;}
+    }
+}
+
+await_using_escaped_identifier: {
+    options = {}
+    input: {
+        async () => { await using \u0061 = null; }
+    }
+    expect: {
+        async()=>{await using a=null;}
+    }
+}
+
+await_using_identifier_starts_with_in: {
+    options = {}
+    input: {
+        async () => {
+            await using ina = null;
+            await using in\u0062 = null;
+            await using in𝒞 = null;
+        }
+    }
+    expect: {
+        async()=>{await using ina=null;await using inb=null;await using in𝒞=null;}
+    }
+}
+
+await_using_identifier_starts_with_instanceof: {
+    options = {}
+    input: {
+        async () => {
+            await using instanceofa = null;
+            await using instanceof\u0062 = null;
+            await using instanceof𝒞 = null;
+        }
+    }
+    expect: {
+        async()=>{await using instanceofa=null;await using instanceofb=null;await using instanceof𝒞=null;}
+    }
+}
+
+await_using_object_pattern_bad: {
+    options = {}
+    bad_input: `async () => { await using { x } = y; }`
+    expect_error: ({
+        name: "SyntaxError",
+        message: "Unexpected token: punc ({)",
+    })
+}
+
+await_using_for: {
+    options = {}
+    input: {
+        async () => { for (await using x = y; ; ); }
+    }
+    expect: {
+        async()=>{for(await using x=y;;);}
+    }
+}
+
+await_using_for_in_bad: {
+    options = {}
+    bad_input: `async () => { for (await using x in y); }`
+    expect_error: ({
+        name: "SyntaxError",
+        message: "Invalid using declaration in for..in loop",
+    })
+}
+
+await_using_for_of: {
+    options = {}
+    input: {
+        async () => { for (await using x of y); }
+    }
+    expect: {
+        async()=>{for(await using x of y);}
+    }
+}
+
+await_using_for_await_of: {
+    options = {}
+    input: {
+        async () => { for await (await using x of y); }
+    }
+    expect: {
+        async()=>{for await(await using x of y);}
+    }
+}
+
+await_using_with_comments: {
+    format = {
+        comments: true
+    }
+    input: {
+        async () => {/* 1 */await /* 2 */using /* 3 */x /* 4 */= /* 5 */f() /* 6 */}
+    }
+    expect: {
+        async()=>{/* 1 */await/* 2 */using/* 3 */x/* 4 */=/* 5 */f()/* 6 */};
+    }
+}
+
+await_using_as_name_asi: {
+    options = {}
+    input: {
+        async () => {
+            await using
+            x = f()
+        }
+    }
+    expect: {
+        async()=>{await using;x=f();}
+    }
+}
+
+await_using_as_name_asi_line_comment: {
+    options = {}
+    input: {
+        async () => {
+            await using // comment
+            x = f()
+        }
+    }
+    expect: {
+        async()=>{await using;x=f();}
+    }   
+}
+
+await_using_as_name_asi_multiline_comment: {
+    options = {}
+    input: {
+        async () => {
+            await using /* comment 
+            */ x = f()
+        }
+    }
+    expect: {
+        async()=>{await using;x=f();}
+    }   
+}
+
+await_using_as_name_in: {
+    options = {}
+    input: {
+        async function f() {
+            await using in foo;
+        }
+    }
+    expect: {
+        async function f() {
+            await using in foo;
+        }
+    }
+}
+
+await_using_as_name_instanceof: {
+    options = {}
+    input: {
+        async function f() {
+            await using instanceof foo;
+        }
+    }
+    expect: {
+        async function f() {
+            await using instanceof foo;
+        }
+    }
+}
+
+await_using_as_name_subscript_expr: {
+    options = {}
+    input: {
+        async () => {
+            await using[x];
+            await using.x + await using(x) ? await using?.x : await using`x`;
+        }
+    }
+    expect: {
+        async()=>{await using[x];await using.x+await using(x)?await using?.x:await using`x`};
+    }
+}
+
+await_using_and_using_in_same_scope: {
+    options = {}
+    input: {
+        async () => {
+            await
+            z;
+            using x
+                = f();
+            await using y
+                = g();
+        }
+    }
+    expect: {
+        async()=>{await z;using x=f();await using y=g();}
+    }
+}
+
+unused_using_should_be_kept: {
+    options = {
+        unused: true,
+    }
+    input: {
+        using x = f();
+    }
+    expect: {
+        using x=f();
+    }
+}
+
+unused_await_using_should_be_kept: {
+    options = {
+        unused: true,
+    }
+    input: {
+        (async () => { await using x = f(); })();
+    }
+    expect: {
+        (async()=>{await using x = f();})();
+    }
+}
+
+using_is_not_a_simple_statement_with_conditionals: {
+    options = {
+        sequences: true,
+        conditionals: true,
+    }
+    input: {
+        (async (x) => {
+            if (x) {
+                var xx;
+                using x = f();
+            } else {
+                var yy;
+                await using y = g();
+            }
+        })();
+    }
+    expect: {
+        (async(x)=>{if(x){var xx;using x=f()}else{var yy;await using y=g()}})();
+    }
+}
+
+using_definition_transformer: {
+    options = {
+        if_return: true,
+    }
+    input: {
+        using r = ((x) => {
+            if (x) {
+                return;
+            }
+            return null;
+        })();
+    }
+    expect: {
+        using r=(x=>{if(!x)return null})();
+    }
+}
+
+using_should_not_merge_flow_with_if_return: {
+    options = {
+        if_return: true,
+    }
+    input: {
+        (function(x, y) {
+            if (x) {
+                using x = f();
+                return;
+            }
+        })();
+    }
+    expect: {
+        (function(x,y){if(x){using x=f();return}})();
+    }
+}
+
+using_should_not_merge_flow_with_conditionals_if_return: {
+    options = {
+        if_return: true,
+        conditionals: true,
+        evaluate: true
+    }
+    input: {
+        (function(x, y) {
+            if (x) {
+                using x = f();
+                return;
+            }
+            if (y) {
+                using y = g();
+                return y;
+            }
+        })();
+    }
+    expect: {
+        (function(x,y){if(x){using x=f();return}if(y){using y=g();return y}})();
+    }
+}
+
+multiple_using_can_be_joined_with_join_vars: {
+    options = {
+        join_vars: true,
+    }
+    input: {
+        using x = f();
+        using y = g();
+        using z = null;
+    }
+    expect: {
+        using x=f(),y=g(),z=null;
+    }
+}
+
+multiple_await_using_can_be_joined_with_join_vars: {
+    options = {
+        join_vars: true,
+    }
+    input: {
+        async () => {
+            await using x = f();
+            await using y = g();
+            await using z = null;
+        }
+    }
+    expect: {
+        async()=>{await using x=f(),y=g(),z=null;}
+    }
+}
+
+using_should_be_kept_with_defaults_unsafe: {
+    options = {
+        defaults: true,
+        unsafe: true,
+    }
+    input: {
+        const $dispose = Symbol.dispose;
+        const fn = () => { console.log("disposed"); }
+        using x = { one: 1, [$dispose]: fn };
+        console.log(x.one);
+    }
+    expect: {
+        const $dispose = Symbol.dispose,
+              fn = () => { console.log("disposed"); };
+        using x = { one: 1, [$dispose]: fn };
+        console.log(x.one);
+    }
+}
diff --git a/test/input/spidermonkey/input.js b/test/input/spidermonkey/input.js
index 941516f4..512432cb 100644
--- a/test/input/spidermonkey/input.js
+++ b/test/input/spidermonkey/input.js
@@ -299,3 +299,16 @@ function uses_asm() {
     0.0;
 }
 
+// using declarations
+function using() {
+    using x1 = resource(), y1 = resource();
+    for (using x1 = resource(), y1 = resource(); ; );
+    for (using x1 of iterable()) {}
+}
+
+// await using declarations
+async function await_using() {
+    await using x2 = resource(), y2 = resource();
+    for (await using x2 = resource(), y2 = resource(); ; );
+    for (await using x2 of iterable()) {}
+}
diff --git a/test/mocha/size.js b/test/mocha/size.js
index bfce238e..bedabba8 100644
--- a/test/mocha/size.js
+++ b/test/mocha/size.js
@@ -9,4 +9,8 @@ describe("size", () => {
         assert.equal(parse("()=>{return 2}").size(), 5);
         assert.equal(parse("()=>2").size(), 5);
     });
+    it("approximates the size of variable declaration", () => {
+        assert.equal(parse("using x=null").size(), 12);
+        assert.equal(parse("async()=>{await using x=null}").size(), 30);
+    });
 });
diff --git a/test/mocha/spidermonkey.js b/test/mocha/spidermonkey.js
index bb9e6b4e..887d80ba 100644
--- a/test/mocha/spidermonkey.js
+++ b/test/mocha/spidermonkey.js
@@ -134,7 +134,7 @@ describe("spidermonkey export/import sanity test", function() {
     it("should be capable of importing from acorn", async function() {
         var code = fs.readFileSync("test/input/spidermonkey/input.js", "utf-8");
         var terser_ast = parse(code);
-        var moz_ast = acornParse(code, { sourceType: "module", ecmaVersion: 2025 });
+        var moz_ast = acornParse(code, { sourceType: "module", ecmaVersion: 2026 });
         var from_moz_ast = AST.AST_Node.from_mozilla_ast(moz_ast);
         assert.strictEqual(
             from_moz_ast.print_to_string(),
@@ -149,7 +149,7 @@ describe("spidermonkey export/import sanity test", function() {
     it("(temp): should be capable of importing from acorn (astring incompatible bits)", async function() {
         var code = fs.readFileSync("test/input/spidermonkey/input-no-astring.js", "utf-8");
         var terser_ast = parse(code);
-        var moz_ast = acornParse(code, { sourceType: "module", ecmaVersion: 2025 });
+        var moz_ast = acornParse(code, { sourceType: "module", ecmaVersion: 2026 });
         var from_moz_ast = AST.AST_Node.from_mozilla_ast(moz_ast);
         assert.strictEqual(
             from_moz_ast.print_to_string(),
@@ -162,7 +162,7 @@ describe("spidermonkey export/import sanity test", function() {
     })
 
     it("should accept a spidermonkey AST w/ `parse.spidermonkey: true`", async function() {
-        var moz_ast = acornParse("var a = 1 + 2", { sourceType: "module", ecmaVersion: 2025 });
+        var moz_ast = acornParse("var a = 1 + 2", { sourceType: "module", ecmaVersion: 2026 });
         var result = await minify(moz_ast, {ecma: 2015, parse: {spidermonkey: true}});
         assert.deepStrictEqual(result.code, "var a=3;");
     });
@@ -204,7 +204,7 @@ describe("spidermonkey export/import sanity test", function() {
         var terser_ast = parse(code);
         var moz_ast = terser_ast.to_mozilla_ast();
         var generated = astringGenerate(moz_ast);
-        var parsed = acornParse(generated, { sourceType: "module", ecmaVersion: 2025 });
+        var parsed = acornParse(generated, { sourceType: "module", ecmaVersion: 2026 });
         assert.strictEqual(
             AST.AST_Node.from_mozilla_ast(parsed).print_to_string(),
             terser_ast.print_to_string()
@@ -218,7 +218,7 @@ describe("spidermonkey export/import sanity test", function() {
     it("should generate the same AST that acorn does", async function () {
         var code = fs.readFileSync("test/input/spidermonkey/input.js", "utf-8");
         var terser_ast = parse(code).to_mozilla_ast();
-        var acorn_ast = acornParse(code, { sourceType: "module", ecmaVersion: 2025 });
+        var acorn_ast = acornParse(code, { sourceType: "module", ecmaVersion: 2026 });
         compare_estree_asts(terser_ast, acorn_ast)
     });
 });