diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs
index 814282a533cc1..7d0b92036d492 100644
--- a/crates/oxc_transformer/src/lib.rs
+++ b/crates/oxc_transformer/src/lib.rs
@@ -122,7 +122,10 @@ impl<'a> Transformer<'a> {
let mut transformer = TransformerImpl {
common: Common::new(&self.env, &self.ctx),
decorator: Decorator::new(self.decorator, &self.ctx),
- explicit_resource_management: ExplicitResourceManagement::new(&self.ctx),
+ explicit_resource_management: ExplicitResourceManagement::new(
+ &self.ctx,
+ self.env.es2017.async_to_generator,
+ ),
x0_typescript: program
.source_type
.is_typescript()
@@ -202,6 +205,7 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> {
ctx: &mut TraverseCtx<'a>,
) {
self.x2_es2018.enter_variable_declaration(decl, ctx);
+ self.explicit_resource_management.enter_variable_declaration(decl, ctx);
}
fn enter_variable_declarator(
@@ -273,12 +277,13 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> {
fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
self.common.enter_static_block(block, ctx);
- self.x2_es2022.enter_static_block(block, ctx);
self.explicit_resource_management.enter_static_block(block, ctx);
+ self.x2_es2022.enter_static_block(block, ctx);
}
fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
self.common.exit_static_block(block, ctx);
+ self.explicit_resource_management.exit_static_block(block, ctx);
self.x2_es2022.exit_static_block(block, ctx);
}
@@ -542,6 +547,7 @@ impl<'a> Traverse<'a> for TransformerImpl<'a, '_> {
if let Some(typescript) = self.x0_typescript.as_mut() {
typescript.exit_statement(stmt, ctx);
}
+ self.explicit_resource_management.exit_statement(stmt, ctx);
self.decorator.enter_statement(stmt, ctx);
self.x2_es2018.exit_statement(stmt, ctx);
self.x2_es2017.exit_statement(stmt, ctx);
diff --git a/crates/oxc_transformer/src/proposals/explicit_resource_management.rs b/crates/oxc_transformer/src/proposals/explicit_resource_management.rs
index dba202bbf8caf..0a9e0ddeaf1a1 100644
--- a/crates/oxc_transformer/src/proposals/explicit_resource_management.rs
+++ b/crates/oxc_transformer/src/proposals/explicit_resource_management.rs
@@ -39,22 +39,44 @@ use rustc_hash::FxHashMap;
use oxc_allocator::{Address, Box as ArenaBox, GetAddress, Vec as ArenaVec};
use oxc_ast::{NONE, ast::*};
+use oxc_data_structures::stack::NonEmptyStack;
use oxc_ecmascript::BoundNames;
use oxc_semantic::{ScopeFlags, ScopeId, SymbolFlags};
use oxc_span::{Atom, SPAN};
-use oxc_traverse::{BoundIdentifier, Traverse, TraverseCtx};
+use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};
use crate::{Helper, TransformCtx};
pub struct ExplicitResourceManagement<'a, 'ctx> {
ctx: &'ctx TransformCtx<'a>,
+ async_to_generator: bool,
+
top_level_using: FxHashMap
,
+
+ /// keeps track of whether the current static block contains a `using` declaration
+ /// so that we can transform it in `exit_static_block`
+ static_blocks_stack: NonEmptyStack,
+
+ /// keeps track of whether the current switch statement contains a `using` declaration
+ /// so that we can transform it in `exit_statement`
+ switch_stmt_stack: NonEmptyStack,
+
+ /// keeps track of whether the current block statement contains a `using` declaration
+ /// so that we can transform it in `exit_statement`
+ block_stmt_stack: NonEmptyStack,
}
impl<'a, 'ctx> ExplicitResourceManagement<'a, 'ctx> {
- pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
- Self { ctx, top_level_using: FxHashMap::default() }
+ pub fn new(ctx: &'ctx TransformCtx<'a>, async_to_generator: bool) -> Self {
+ Self {
+ ctx,
+ async_to_generator,
+ top_level_using: FxHashMap::default(),
+ static_blocks_stack: NonEmptyStack::new(false),
+ switch_stmt_stack: NonEmptyStack::new(false),
+ block_stmt_stack: NonEmptyStack::new(false),
+ }
}
}
@@ -123,6 +145,10 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> {
};
}
+ fn enter_static_block(&mut self, _node: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) {
+ self.static_blocks_stack.push(false);
+ }
+
/// Transform class static block.
///
/// ```js
@@ -143,12 +169,37 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> {
/// }
/// }
/// ```
- fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
- let scope_id = block.scope_id();
- if let Some(replacement) =
- self.transform_statements(&mut block.body, None, scope_id, scope_id, ctx)
+ fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
+ if self.static_blocks_stack.pop() {
+ let scope_id = block.scope_id();
+ if let Some(replacement) =
+ self.transform_statements(&mut block.body, None, scope_id, scope_id, ctx)
+ {
+ block.body = ctx.ast.vec1(replacement);
+ }
+ }
+ }
+
+ fn enter_variable_declaration(
+ &mut self,
+ node: &mut VariableDeclaration<'a>,
+ ctx: &mut TraverseCtx<'a>,
+ ) {
+ if matches!(node.kind, VariableDeclarationKind::Using | VariableDeclarationKind::AwaitUsing)
+ || self.top_level_using.contains_key(&Address::from_ptr(node))
{
- block.body = ctx.ast.vec1(replacement);
+ match ctx.parent() {
+ Ancestor::StaticBlockBody(_) => {
+ *self.static_blocks_stack.last_mut() = true;
+ }
+ Ancestor::SwitchCaseConsequent(_) => {
+ *self.switch_stmt_stack.last_mut() = true;
+ }
+ Ancestor::BlockStatementBody(_) => {
+ *self.block_stmt_stack.last_mut() = true;
+ }
+ _ => {}
+ }
}
}
@@ -193,10 +244,31 @@ impl<'a> Traverse<'a> for ExplicitResourceManagement<'a, '_> {
// or `SwitchStatement`s. We want the common path for "nothing to do here" not to incur the cost of
// a function call.
#[inline]
- fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
+ fn enter_statement(&mut self, stmt: &mut Statement<'a>, _ctx: &mut TraverseCtx<'a>) {
match stmt {
- Statement::BlockStatement(_) => self.transform_block_statement(stmt, ctx),
- Statement::SwitchStatement(_) => self.transform_switch_statement(stmt, ctx),
+ Statement::BlockStatement(_) => {
+ self.block_stmt_stack.push(false);
+ }
+ Statement::SwitchStatement(_) => {
+ self.switch_stmt_stack.push(false);
+ }
+ _ => {}
+ }
+ }
+
+ #[inline]
+ fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
+ match stmt {
+ Statement::BlockStatement(_) => {
+ if self.block_stmt_stack.pop() {
+ self.transform_block_statement(stmt, ctx);
+ }
+ }
+ Statement::SwitchStatement(_) => {
+ if self.switch_stmt_stack.pop() {
+ self.transform_switch_statement(stmt, ctx);
+ }
+ }
_ => {}
}
}
@@ -603,7 +675,7 @@ impl<'a> ExplicitResourceManagement<'a, '_> {
};
let catch = Self::create_catch_clause(&using_ctx, ctx.current_scope_id(), ctx);
- let finally = Self::create_finally_block(&using_ctx, current_scope_id, needs_await, ctx);
+ let finally = self.create_finally_block(&using_ctx, current_scope_id, needs_await, ctx);
*stmt = ctx.ast.statement_try(SPAN, block, Some(catch), Some(finally));
}
@@ -725,7 +797,7 @@ impl<'a> ExplicitResourceManagement<'a, '_> {
}
let catch = Self::create_catch_clause(&using_ctx, parent_scope_id, ctx);
- let finally = Self::create_finally_block(&using_ctx, parent_scope_id, needs_await, ctx);
+ let finally = self.create_finally_block(&using_ctx, parent_scope_id, needs_await, ctx);
Some(ctx.ast.statement_try(SPAN, block, Some(catch), Some(finally)))
}
@@ -779,6 +851,7 @@ impl<'a> ExplicitResourceManagement<'a, '_> {
/// `{ _usingCtx.d(); }`
fn create_finally_block(
+ &self,
using_ctx: &BoundIdentifier<'a>,
parent_scope_id: ScopeId,
needs_await: bool,
@@ -800,7 +873,15 @@ impl<'a> ExplicitResourceManagement<'a, '_> {
false,
);
- let stmt = if needs_await { ctx.ast.expression_await(SPAN, expr) } else { expr };
+ let stmt = if needs_await {
+ if self.async_to_generator {
+ ctx.ast.expression_yield(SPAN, false, Some(expr))
+ } else {
+ ctx.ast.expression_await(SPAN, expr)
+ }
+ } else {
+ expr
+ };
ctx.ast.alloc_block_statement_with_scope_id(
SPAN,
diff --git a/tasks/coverage/snapshots/semantic_typescript.snap b/tasks/coverage/snapshots/semantic_typescript.snap
index eb9a460149df9..f5b1eaf6f6d89 100644
--- a/tasks/coverage/snapshots/semantic_typescript.snap
+++ b/tasks/coverage/snapshots/semantic_typescript.snap
@@ -41524,24 +41524,24 @@ rebuilt : ScopeId(9): []
tasks/coverage/typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/usingDeclarations.1.ts
semantic error: Bindings mismatch:
-after transform: ScopeId(0): ["C1", "C2", "C3", "N", "_af", "_ag", "_asyncToGenerator", "_awaitAsyncGenerator", "_defineProperty", "_usingCtx2", "_usingCtx20", "_usingCtx23", "_usingCtx24", "_usingCtx25", "_usingCtx26", "_usingCtx27", "_usingCtx28", "_usingCtx29", "_usingCtx6", "_wrapAsyncGenerator", "a", "af", "ag", "d1", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "d32", "f", "g"]
-rebuilt : ScopeId(0): ["C1", "C2", "C3", "N", "_af", "_ag", "_asyncToGenerator", "_awaitAsyncGenerator", "_defineProperty", "_usingCtx2", "_usingCtx20", "_usingCtx21", "_usingCtx22", "_usingCtx23", "_usingCtx24", "_usingCtx25", "_usingCtx26", "_usingCtx27", "_usingCtx28", "_usingCtx29", "_usingCtx6", "_wrapAsyncGenerator", "a", "af", "ag", "d1", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "d32", "f", "g"]
+after transform: ScopeId(0): ["C1", "C2", "C3", "N", "_af", "_ag", "_asyncToGenerator", "_awaitAsyncGenerator", "_defineProperty", "_usingCtx19", "_usingCtx2", "_usingCtx22", "_usingCtx23", "_usingCtx24", "_usingCtx25", "_usingCtx26", "_usingCtx27", "_usingCtx28", "_usingCtx29", "_wrapAsyncGenerator", "a", "af", "ag", "d1", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "d32", "f", "g"]
+rebuilt : ScopeId(0): ["C1", "C2", "C3", "N", "_af", "_ag", "_asyncToGenerator", "_awaitAsyncGenerator", "_defineProperty", "_usingCtx19", "_usingCtx2", "_usingCtx20", "_usingCtx21", "_usingCtx22", "_usingCtx23", "_usingCtx24", "_usingCtx25", "_usingCtx26", "_usingCtx27", "_usingCtx28", "_usingCtx29", "_wrapAsyncGenerator", "a", "af", "ag", "d1", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "d32", "f", "g"]
Bindings mismatch:
-after transform: ScopeId(70): ["_usingCtx21", "_usingCtx22"]
+after transform: ScopeId(70): ["_usingCtx20", "_usingCtx21"]
rebuilt : ScopeId(29): []
Symbol span mismatch for "N":
after transform: SymbolId(26): Span { start: 1385, end: 1386 }
rebuilt : SymbolId(66): Span { start: 0, end: 0 }
+Symbol scope ID mismatch for "_usingCtx20":
+after transform: SymbolId(88): ScopeId(70)
+rebuilt : SymbolId(74): ScopeId(0)
Symbol scope ID mismatch for "_usingCtx21":
after transform: SymbolId(90): ScopeId(70)
-rebuilt : SymbolId(74): ScopeId(0)
-Symbol scope ID mismatch for "_usingCtx22":
-after transform: SymbolId(92): ScopeId(70)
rebuilt : SymbolId(78): ScopeId(0)
tasks/coverage/typescript/tests/cases/conformance/statements/VariableStatements/usingDeclarations/usingDeclarationsDeclarationEmit.2.ts
semantic error: Scope children mismatch:
-after transform: ScopeId(0): [ScopeId(2), ScopeId(4), ScopeId(5), ScopeId(6), ScopeId(8)]
+after transform: ScopeId(0): [ScopeId(2), ScopeId(4), ScopeId(5), ScopeId(7), ScopeId(9)]
rebuilt : ScopeId(0): [ScopeId(1), ScopeId(5), ScopeId(7)]
Symbol reference IDs mismatch for "r1":
after transform: SymbolId(0): [ReferenceId(1)]
diff --git a/tasks/transform_conformance/overrides/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-sync/multiple-nested/output.js b/tasks/transform_conformance/overrides/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-sync/multiple-nested/output.js
new file mode 100644
index 0000000000000..b64e874664dc4
--- /dev/null
+++ b/tasks/transform_conformance/overrides/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-sync/multiple-nested/output.js
@@ -0,0 +1,30 @@
+try {
+ var _usingCtx3 = babelHelpers.usingCtx();
+ const x = _usingCtx3.u(obj);
+ try {
+ var _usingCtx2 = babelHelpers.usingCtx();
+ const y = _usingCtx2.u(
+ call(() => {
+ try {
+ var _usingCtx = babelHelpers.usingCtx();
+ const z = _usingCtx.u(obj);
+ return z;
+ } catch (_) {
+ _usingCtx.e = _;
+ } finally {
+ _usingCtx.d();
+ }
+ })
+ );
+ stmt;
+ } catch (_) {
+ _usingCtx2.e = _;
+ } finally {
+ _usingCtx2.d();
+ }
+ stmt;
+} catch (_) {
+ _usingCtx3.e = _;
+} finally {
+ _usingCtx3.d();
+}