From 934cb5e746e33cc1377f1f5c2b416d72727c7f0a Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:14:17 +0000 Subject: [PATCH] feat(transformer): add `async_generator_functions` plugin (#6573) Passed 15/19 tests. The remaining 4 failed tests related to `this` expression, the problem same as I mentioned in #6658. I will fix them in follow-up PRs. --- .../src/common/helper_loader.rs | 8 + .../src/common/statement_injector.rs | 1 - .../src/es2017/async_to_generator.rs | 9 + crates/oxc_transformer/src/es2017/mod.rs | 1 - .../async_generator_functions/for_await.rs | 426 ++++++++++++++++++ .../es2018/async_generator_functions/mod.rs | 256 +++++++++++ crates/oxc_transformer/src/es2018/mod.rs | 59 ++- crates/oxc_transformer/src/es2018/options.rs | 2 + crates/oxc_transformer/src/lib.rs | 7 + crates/oxc_transformer/src/options/mod.rs | 12 +- .../snapshots/babel.snap.md | 16 +- .../snapshots/babel_exec.snap.md | 82 +++- tasks/transform_conformance/src/constants.rs | 2 +- 13 files changed, 870 insertions(+), 11 deletions(-) create mode 100644 crates/oxc_transformer/src/es2018/async_generator_functions/for_await.rs create mode 100644 crates/oxc_transformer/src/es2018/async_generator_functions/mod.rs diff --git a/crates/oxc_transformer/src/common/helper_loader.rs b/crates/oxc_transformer/src/common/helper_loader.rs index 418b32c3f851b..7a96880758bba 100644 --- a/crates/oxc_transformer/src/common/helper_loader.rs +++ b/crates/oxc_transformer/src/common/helper_loader.rs @@ -137,15 +137,23 @@ fn default_as_module_name() -> Cow<'static, str> { /// Available helpers. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum Helper { + AwaitAsyncGenerator, + AsyncGeneratorDelegate, + AsyncIterator, AsyncToGenerator, ObjectSpread2, + WrapAsyncGenerator, } impl Helper { const fn name(self) -> &'static str { match self { + Self::AwaitAsyncGenerator => "awaitAsyncGenerator", + Self::AsyncGeneratorDelegate => "asyncGeneratorDelegate", + Self::AsyncIterator => "asyncIterator", Self::AsyncToGenerator => "asyncToGenerator", Self::ObjectSpread2 => "objectSpread2", + Self::WrapAsyncGenerator => "wrapAsyncGenerator", } } } diff --git a/crates/oxc_transformer/src/common/statement_injector.rs b/crates/oxc_transformer/src/common/statement_injector.rs index 7bb631c479993..eabed43894e2c 100644 --- a/crates/oxc_transformer/src/common/statement_injector.rs +++ b/crates/oxc_transformer/src/common/statement_injector.rs @@ -104,7 +104,6 @@ impl<'a> StatementInjectorStore<'a> { } /// Add multiple statements to be inserted immediately before the target statement. - #[expect(dead_code)] #[inline] pub fn insert_many_before(&self, target: &A, stmts: S) where diff --git a/crates/oxc_transformer/src/es2017/async_to_generator.rs b/crates/oxc_transformer/src/es2017/async_to_generator.rs index d9b0e51c851ad..514dbab1b0ea2 100644 --- a/crates/oxc_transformer/src/es2017/async_to_generator.rs +++ b/crates/oxc_transformer/src/es2017/async_to_generator.rs @@ -696,6 +696,15 @@ impl<'a, 'ctx> AsyncGeneratorExecutor<'a, 'ctx> { ) { BindingMover::new(target_scope_id, ctx).visit_binding_identifier(ident); } + + #[inline] + pub fn move_bindings_to_target_scope_for_statement( + target_scope_id: ScopeId, + stmt: &Statement<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + BindingMover::new(target_scope_id, ctx).visit_statement(stmt); + } } /// Moves the bindings from original scope to target scope. diff --git a/crates/oxc_transformer/src/es2017/mod.rs b/crates/oxc_transformer/src/es2017/mod.rs index 8dbb3a7bd9649..1a02b1793c410 100644 --- a/crates/oxc_transformer/src/es2017/mod.rs +++ b/crates/oxc_transformer/src/es2017/mod.rs @@ -6,7 +6,6 @@ use oxc_ast::ast::{Expression, Statement}; use oxc_traverse::{Traverse, TraverseCtx}; use crate::{es2017::async_to_generator::AsyncToGenerator, TransformCtx}; -#[expect(unused_imports)] pub use async_to_generator::AsyncGeneratorExecutor; #[allow(dead_code)] diff --git a/crates/oxc_transformer/src/es2018/async_generator_functions/for_await.rs b/crates/oxc_transformer/src/es2018/async_generator_functions/for_await.rs new file mode 100644 index 0000000000000..b03639585e495 --- /dev/null +++ b/crates/oxc_transformer/src/es2018/async_generator_functions/for_await.rs @@ -0,0 +1,426 @@ +//! This module is responsible for transforming `for await` to `for` statement + +use oxc_allocator::Vec; +use oxc_ast::{ast::*, NONE}; +use oxc_semantic::{ScopeFlags, ScopeId, SymbolFlags}; +use oxc_span::SPAN; +use oxc_traverse::{BoundIdentifier, TraverseCtx}; + +use super::AsyncGeneratorFunctions; +use crate::{common::helper_loader::Helper, es2017::AsyncGeneratorExecutor}; + +impl<'a, 'ctx> AsyncGeneratorFunctions<'a, 'ctx> { + pub(crate) fn transform_for_of_statement( + &mut self, + stmt: &mut ForOfStatement<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Vec<'a, Statement<'a>> { + let step_key = + ctx.generate_uid("step", ctx.current_scope_id(), SymbolFlags::FunctionScopedVariable); + // step.value + let step_value = ctx.ast.expression_member(ctx.ast.member_expression_static( + SPAN, + step_key.create_read_expression(ctx), + ctx.ast.identifier_name(SPAN, "value"), + false, + )); + + let assignment_statement = match &mut stmt.left { + ForStatementLeft::VariableDeclaration(variable) => { + // for await (let i of test) + let mut declarator = variable.declarations.pop().unwrap(); + declarator.init = Some(step_value); + let variable = ctx.ast.variable_declaration( + SPAN, + declarator.kind, + ctx.ast.vec1(declarator), + false, + ); + let declaration = ctx.ast.declaration_from_variable(variable); + Statement::from(declaration) + } + left @ match_assignment_target!(ForStatementLeft) => { + // for await (i of test), for await ({ i } of test) + let target = ctx.ast.move_assignment_target(left.to_assignment_target_mut()); + let expression = ctx.ast.expression_assignment( + SPAN, + AssignmentOperator::Assign, + target, + step_value, + ); + ctx.ast.statement_expression(SPAN, expression) + } + }; + + let body = { + let mut statements = ctx.ast.vec_with_capacity(2); + statements.push(assignment_statement); + let stmt_body = &mut stmt.body; + if let Statement::BlockStatement(block) = stmt_body { + if block.body.is_empty() { + // If the block is empty, we don’t need to add it to the body; + // instead, we need to remove the useless scope. + ctx.scopes_mut().delete_scope(block.scope_id.get().unwrap()); + } else { + statements.push(ctx.ast.move_statement(stmt_body)); + } + } + statements + }; + + Self::build_for_await( + self.ctx.helper_load(Helper::AsyncIterator, ctx), + ctx.ast.move_expression(&mut stmt.right), + &step_key, + body, + stmt.scope_id.get().unwrap(), + ctx, + ) + } + + /// Build a `for` statement used to replace the `for await` statement. + /// + /// This function builds the following code: + /// + /// ```js + // var ITERATOR_ABRUPT_COMPLETION = false; + // var ITERATOR_HAD_ERROR_KEY = false; + // var ITERATOR_ERROR_KEY; + // try { + // for ( + // var ITERATOR_KEY = GET_ITERATOR(OBJECT), STEP_KEY; + // ITERATOR_ABRUPT_COMPLETION = !(STEP_KEY = await ITERATOR_KEY.next()).done; + // ITERATOR_ABRUPT_COMPLETION = false + // ) { + // } + // } catch (err) { + // ITERATOR_HAD_ERROR_KEY = true; + // ITERATOR_ERROR_KEY = err; + // } finally { + // try { + // if (ITERATOR_ABRUPT_COMPLETION && ITERATOR_KEY.return != null) { + // await ITERATOR_KEY.return(); + // } + // } finally { + // if (ITERATOR_HAD_ERROR_KEY) { + // throw ITERATOR_ERROR_KEY; + // } + // } + // } + /// ``` + /// + /// Based on Babel's implementation: + /// + fn build_for_await( + get_identifier: Expression<'a>, + object: Expression<'a>, + step_key: &BoundIdentifier<'a>, + body: Vec<'a, Statement<'a>>, + for_of_scope_id: ScopeId, + ctx: &mut TraverseCtx<'a>, + ) -> Vec<'a, Statement<'a>> { + let scope_id = ctx.current_scope_id(); + let iterator_had_error_key = + ctx.generate_uid("didIteratorError", scope_id, SymbolFlags::FunctionScopedVariable); + let iterator_abrupt_completion = ctx.generate_uid( + "iteratorAbruptCompletion", + scope_id, + SymbolFlags::FunctionScopedVariable, + ); + let iterator_error_key = + ctx.generate_uid("iteratorError", scope_id, SymbolFlags::FunctionScopedVariable); + + let mut items = ctx.ast.vec_with_capacity(4); + items.push(ctx.ast.statement_declaration(ctx.ast.declaration_variable( + SPAN, + VariableDeclarationKind::Var, + ctx.ast.vec1(ctx.ast.variable_declarator( + SPAN, + VariableDeclarationKind::Var, + iterator_abrupt_completion.create_binding_pattern(ctx), + Some(ctx.ast.expression_boolean_literal(SPAN, false)), + false, + )), + false, + ))); + items.push(ctx.ast.statement_declaration(ctx.ast.declaration_variable( + SPAN, + VariableDeclarationKind::Var, + ctx.ast.vec1(ctx.ast.variable_declarator( + SPAN, + VariableDeclarationKind::Var, + iterator_had_error_key.create_binding_pattern(ctx), + Some(ctx.ast.expression_boolean_literal(SPAN, false)), + false, + )), + false, + ))); + items.push(ctx.ast.statement_declaration(ctx.ast.declaration_variable( + SPAN, + VariableDeclarationKind::Var, + ctx.ast.vec1(ctx.ast.variable_declarator( + SPAN, + VariableDeclarationKind::Var, + iterator_error_key.create_binding_pattern(ctx), + None, + false, + )), + false, + ))); + + let iterator_key = + ctx.generate_uid("iterator", scope_id, SymbolFlags::FunctionScopedVariable); + let block = { + let block_scope_id = ctx.create_child_scope(scope_id, ScopeFlags::empty()); + let for_statement_scope_id = + ctx.create_child_scope(block_scope_id, ScopeFlags::empty()); + ctx.scopes_mut().change_parent_id(for_of_scope_id, Some(block_scope_id)); + + let for_statement = ctx.ast.for_statement_with_scope_id( + SPAN, + Some(ctx.ast.for_statement_init_variable_declaration( + SPAN, + VariableDeclarationKind::Var, + { + let mut items = ctx.ast.vec_with_capacity(2); + items.push(ctx.ast.variable_declarator( + SPAN, + VariableDeclarationKind::Var, + iterator_key.create_binding_pattern(ctx), + Some(ctx.ast.expression_call( + SPAN, + get_identifier, + NONE, + ctx.ast.vec1(ctx.ast.argument_expression(object)), + false, + )), + false, + )); + items.push(ctx.ast.variable_declarator( + SPAN, + VariableDeclarationKind::Var, + step_key.create_binding_pattern(ctx), + None, + false, + )); + items + }, + false, + )), + Some(ctx.ast.expression_assignment( + SPAN, + AssignmentOperator::Assign, + iterator_abrupt_completion.create_read_write_target(ctx), + ctx.ast.expression_unary( + SPAN, + UnaryOperator::LogicalNot, + ctx.ast.expression_member(ctx.ast.member_expression_static( + SPAN, + ctx.ast.expression_parenthesized( + SPAN, + ctx.ast.expression_assignment( + SPAN, + AssignmentOperator::Assign, + step_key.create_read_write_target(ctx), + ctx.ast.expression_await( + SPAN, + ctx.ast.expression_call( + SPAN, + ctx.ast.expression_member( + ctx.ast.member_expression_static( + SPAN, + iterator_key.create_read_expression(ctx), + ctx.ast.identifier_name(SPAN, "next"), + false, + ), + ), + NONE, + ctx.ast.vec(), + false, + ), + ), + ), + ), + ctx.ast.identifier_name(SPAN, "done"), + false, + )), + ), + )), + Some(ctx.ast.expression_assignment( + SPAN, + AssignmentOperator::Assign, + iterator_abrupt_completion.create_read_write_target(ctx), + ctx.ast.expression_boolean_literal(SPAN, false), + )), + { + // Handle the for-of statement move to the body of new for-statement + let for_statement_body_scope_id = for_of_scope_id; + { + ctx.scopes_mut().change_parent_id( + for_statement_body_scope_id, + Some(for_statement_scope_id), + ); + let statement = body.first().unwrap(); + AsyncGeneratorExecutor::move_bindings_to_target_scope_for_statement( + for_statement_body_scope_id, + statement, + ctx, + ); + } + + ctx.ast.statement_from_block(ctx.ast.block_statement_with_scope_id( + SPAN, + body, + for_statement_body_scope_id, + )) + }, + for_statement_scope_id, + ); + let statement = ctx.ast.statement_from_for(for_statement); + ctx.ast.block_statement_with_scope_id(SPAN, ctx.ast.vec1(statement), block_scope_id) + }; + + let catch_clause = { + let catch_scope_id = ctx.create_child_scope(scope_id, ScopeFlags::CatchClause); + let block_scope_id = ctx.create_child_scope(catch_scope_id, ScopeFlags::empty()); + let err_ident = ctx.generate_binding( + Atom::from("err"), + block_scope_id, + SymbolFlags::CatchVariable | SymbolFlags::FunctionScopedVariable, + ); + Some(ctx.ast.catch_clause_with_scope_id( + SPAN, + Some(ctx.ast.catch_parameter(SPAN, err_ident.create_binding_pattern(ctx))), + { + ctx.ast.block_statement_with_scope_id( + SPAN, + { + let mut items = ctx.ast.vec_with_capacity(2); + items.push(ctx.ast.statement_expression( + SPAN, + ctx.ast.expression_assignment( + SPAN, + AssignmentOperator::Assign, + iterator_had_error_key.create_write_target(ctx), + ctx.ast.expression_boolean_literal(SPAN, true), + ), + )); + items.push(ctx.ast.statement_expression( + SPAN, + ctx.ast.expression_assignment( + SPAN, + AssignmentOperator::Assign, + iterator_error_key.create_write_target(ctx), + err_ident.create_read_expression(ctx), + ), + )); + items + }, + block_scope_id, + ) + }, + catch_scope_id, + )) + }; + + let finally = { + let finally_scope_id = ctx.create_child_scope(scope_id, ScopeFlags::empty()); + let try_statement = { + let try_block_scope_id = + ctx.create_child_scope(finally_scope_id, ScopeFlags::empty()); + let if_statement = { + let if_block_scope_id = + ctx.create_child_scope(try_block_scope_id, ScopeFlags::empty()); + ctx.ast.statement_if( + SPAN, + ctx.ast.expression_logical( + SPAN, + iterator_abrupt_completion.create_read_expression(ctx), + LogicalOperator::And, + ctx.ast.expression_binary( + SPAN, + ctx.ast.expression_member(ctx.ast.member_expression_static( + SPAN, + iterator_key.create_read_expression(ctx), + ctx.ast.identifier_name(SPAN, "return"), + false, + )), + BinaryOperator::Inequality, + ctx.ast.expression_null_literal(SPAN), + ), + ), + ctx.ast.statement_from_block(ctx.ast.block_statement_with_scope_id( + SPAN, + ctx.ast.vec1(ctx.ast.statement_expression( + SPAN, + ctx.ast.expression_await( + SPAN, + ctx.ast.expression_call( + SPAN, + ctx.ast.expression_member( + ctx.ast.member_expression_static( + SPAN, + iterator_key.create_read_expression(ctx), + ctx.ast.identifier_name(SPAN, "return"), + false, + ), + ), + NONE, + ctx.ast.vec(), + false, + ), + ), + )), + if_block_scope_id, + )), + None, + ) + }; + let block = ctx.ast.block_statement_with_scope_id( + SPAN, + ctx.ast.vec1(if_statement), + try_block_scope_id, + ); + let finally = { + let finally_scope_id = + ctx.create_child_scope(finally_scope_id, ScopeFlags::empty()); + let if_statement = { + let if_block_scope_id = + ctx.create_child_scope(finally_scope_id, ScopeFlags::empty()); + ctx.ast.statement_if( + SPAN, + iterator_had_error_key.create_read_expression(ctx), + ctx.ast.statement_from_block(ctx.ast.block_statement_with_scope_id( + SPAN, + ctx.ast.vec1(ctx.ast.statement_throw( + SPAN, + iterator_error_key.create_read_expression(ctx), + )), + if_block_scope_id, + )), + None, + ) + }; + ctx.ast.block_statement_with_scope_id( + SPAN, + ctx.ast.vec1(if_statement), + finally_scope_id, + ) + }; + ctx.ast.statement_try(SPAN, block, NONE, Some(finally)) + }; + + let block_statement = ctx.ast.block_statement_with_scope_id( + SPAN, + ctx.ast.vec1(try_statement), + finally_scope_id, + ); + Some(block_statement) + }; + + let try_statement = ctx.ast.statement_try(SPAN, block, catch_clause, finally); + + items.push(try_statement); + items + } +} diff --git a/crates/oxc_transformer/src/es2018/async_generator_functions/mod.rs b/crates/oxc_transformer/src/es2018/async_generator_functions/mod.rs new file mode 100644 index 0000000000000..ed03889d7c267 --- /dev/null +++ b/crates/oxc_transformer/src/es2018/async_generator_functions/mod.rs @@ -0,0 +1,256 @@ +//! ES2018: Async Generator Functions +//! +//! This plugin mainly does the following transformations: +//! +//! 1. transforms async generator functions (async function *name() {}) to generator functions +//! and wraps them with `awaitAsyncGenerator` helper function. +//! 2. transforms `await expr` expression to `yield awaitAsyncGenerator(expr)`. +//! 3. transforms `yield * argument` expression to `yield asyncGeneratorDelegate(asyncIterator(argument))`. +//! 4. transforms `for await` statement to `for` statement, and inserts many code to handle async iteration. +//! +//! ## Example +//! +//! Input: +//! ```js +//! async function f() { +//! for await (let x of y) { +//! g(x); +//! } +//!} +//! ``` +//! +//! Output: +//! ```js +//! function f() { +//! return _f.apply(this, arguments); +//! } +//! function _f() { +//! _f = babelHelpers.asyncToGenerator(function* () { +//! var _iteratorAbruptCompletion = false; +//! var _didIteratorError = false; +//! var _iteratorError; +//! try { +//! for (var _iterator = babelHelpers.asyncIterator(y), _step; _iteratorAbruptCompletion = !(_step = yield _iterator.next()).done; _iteratorAbruptCompletion = false) { +//! let x = _step.value; +//! { +//! g(x); +//! } +//! } +//! } catch (err) { +//! _didIteratorError = true; +//! _iteratorError = err; +//! } finally { +//! try { +//! if (_iteratorAbruptCompletion && _iterator.return != null) { +//! yield _iterator.return(); +//! } +//! } finally { +//! if (_didIteratorError) { +//! throw _iteratorError; +//! } +//! } +//! } +//! }); +//! return _f.apply(this, arguments); +//! } +//! ``` +//! +//! ## Implementation +//! +//! Implementation based on [@babel/plugin-transform-async-generator-functions](https://babel.dev/docs/babel-plugin-transform-async-generator-functions). +//! +//! Reference: +//! * Babel docs: +//! * Babel implementation: +//! * Async Iteration TC39 proposal: + +mod for_await; + +use oxc_allocator::GetAddress; +use oxc_ast::ast::*; +use oxc_data_structures::stack::Stack; +use oxc_span::SPAN; +use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; + +use crate::{common::helper_loader::Helper, context::TransformCtx, es2017::AsyncGeneratorExecutor}; + +pub struct AsyncGeneratorFunctions<'a, 'ctx> { + ctx: &'ctx TransformCtx<'a>, + stack: Stack, + executor: AsyncGeneratorExecutor<'a, 'ctx>, +} + +impl<'a, 'ctx> AsyncGeneratorFunctions<'a, 'ctx> { + pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self { + Self { + ctx, + executor: AsyncGeneratorExecutor::new(Helper::WrapAsyncGenerator, ctx), + stack: Stack::new(), + } + } +} + +impl<'a, 'ctx> Traverse<'a> for AsyncGeneratorFunctions<'a, 'ctx> { + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + let new_expr = match expr { + Expression::AwaitExpression(await_expr) => { + self.transform_await_expression(await_expr, ctx) + } + Expression::YieldExpression(yield_expr) => { + self.transform_yield_expression(yield_expr, ctx) + } + Expression::FunctionExpression(func) => { + if func.r#async && func.generator { + Some(self.executor.transform_function_expression(func, ctx)) + } else { + None + } + } + _ => None, + }; + + if let Some(new_expr) = new_expr { + *expr = new_expr; + } + } + + fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + if let Statement::ForOfStatement(for_of) = stmt { + if !for_of.r#await { + return; + } + + // We need to replace the current statement with new statements, + // but we don't have a such method to do it, so we leverage the statement injector. + // + // Now, we use below steps to workaround it: + // 1. Use the last statement as the new statement. + // 2. insert the rest of the statements before the current statement. + // TODO: Once we have a method to replace the current statement, we can simplify this logic. + let mut statements = self.transform_for_of_statement(for_of, ctx); + let last_statement = statements.pop().unwrap(); + *stmt = last_statement; + self.ctx.statement_injector.insert_many_before(&stmt.address(), statements); + } + } + + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + let function = match stmt { + Statement::FunctionDeclaration(func) => Some(func), + Statement::ExportDefaultDeclaration(decl) => { + if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = + &mut decl.declaration + { + Some(func) + } else { + None + } + } + Statement::ExportNamedDeclaration(decl) => { + if let Some(Declaration::FunctionDeclaration(func)) = &mut decl.declaration { + Some(func) + } else { + None + } + } + _ => None, + }; + + if let Some(function) = function { + if function.r#async && function.generator && !function.is_typescript_syntax() { + let new_statement = self.executor.transform_function_declaration(function, ctx); + self.ctx.statement_injector.insert_after(stmt, new_statement); + } + } + } + + fn exit_method_definition( + &mut self, + node: &mut MethodDefinition<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let function = &mut node.value; + if function.r#async && function.generator && !function.is_typescript_syntax() { + self.executor.transform_function_for_method_definition(function, ctx); + } + } + + fn enter_function(&mut self, func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) { + self.stack.push(func.r#async && func.generator); + } + + fn exit_function(&mut self, _func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) { + self.stack.pop(); + } +} + +impl<'a, 'ctx> AsyncGeneratorFunctions<'a, 'ctx> { + /// Transform `yield * argument` expression to `yield asyncGeneratorDelegate(asyncIterator(argument))`. + #[allow(clippy::unused_self)] + fn transform_yield_expression( + &self, + expr: &mut YieldExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + if !expr.delegate { + return None; + } + + expr.argument.as_mut().map(|argument| { + let argument = Argument::from(ctx.ast.move_expression(argument)); + let arguments = ctx.ast.vec1(argument); + let mut argument = self.ctx.helper_call_expr(Helper::AsyncIterator, arguments, ctx); + let arguments = ctx.ast.vec1(Argument::from(argument)); + argument = self.ctx.helper_call_expr(Helper::AsyncGeneratorDelegate, arguments, ctx); + ctx.ast.expression_yield(SPAN, expr.delegate, Some(argument)) + }) + } + + /// Transforms `await expr` expression to `yield awaitAsyncGenerator(expr)`. + /// Ignores top-level await expression. + #[allow(clippy::unused_self)] + fn transform_await_expression( + &self, + expr: &mut AwaitExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + // We don't need to handle top-level await. + if ctx.parent().is_program() || + // Check the function is async generator function + !self.stack.last().copied().unwrap_or(false) + { + return None; + } + + let mut is_function = false; + let mut function_in_params = false; + for ancestor in ctx.ancestors() { + match ancestor { + Ancestor::FunctionBody(_) if !is_function => { + is_function = true; + } + // x = async function() { await 1 } + Ancestor::AssignmentPatternRight(_) | Ancestor::BindingPatternKind(_) => { + continue; + } + Ancestor::FormalParameterPattern(_) => { + function_in_params = true; + break; + } + _ => { + if is_function { + break; + } + } + } + } + let mut argument = ctx.ast.move_expression(&mut expr.argument); + // When a async function is used as parameter, we don't need to wrap its await expression with awaitAsyncGenerator helper. + // `function example(a = async function b() { await 1 }) {}` + if !function_in_params { + let arguments = ctx.ast.vec1(Argument::from(argument)); + argument = self.ctx.helper_call_expr(Helper::AwaitAsyncGenerator, arguments, ctx); + } + Some(ctx.ast.expression_yield(SPAN, false, Some(argument))) + } +} diff --git a/crates/oxc_transformer/src/es2018/mod.rs b/crates/oxc_transformer/src/es2018/mod.rs index a459d22a3fcd2..a0fce950a4309 100644 --- a/crates/oxc_transformer/src/es2018/mod.rs +++ b/crates/oxc_transformer/src/es2018/mod.rs @@ -1,11 +1,12 @@ -use oxc_ast::ast::*; -use oxc_traverse::{Traverse, TraverseCtx}; - -use crate::TransformCtx; - +mod async_generator_functions; mod object_rest_spread; mod options; +use oxc_ast::ast::{Expression, ForOfStatement, Function, MethodDefinition, Statement}; +use oxc_traverse::{Traverse, TraverseCtx}; + +use crate::context::TransformCtx; +use async_generator_functions::AsyncGeneratorFunctions; pub use object_rest_spread::{ObjectRestSpread, ObjectRestSpreadOptions}; pub use options::ES2018Options; @@ -14,6 +15,7 @@ pub struct ES2018<'a, 'ctx> { // Plugins object_rest_spread: ObjectRestSpread<'a, 'ctx>, + async_generator_functions: AsyncGeneratorFunctions<'a, 'ctx>, } impl<'a, 'ctx> ES2018<'a, 'ctx> { @@ -23,6 +25,7 @@ impl<'a, 'ctx> ES2018<'a, 'ctx> { options.object_rest_spread.unwrap_or_default(), ctx, ), + async_generator_functions: AsyncGeneratorFunctions::new(ctx), options, } } @@ -34,4 +37,50 @@ impl<'a, 'ctx> Traverse<'a> for ES2018<'a, 'ctx> { self.object_rest_spread.enter_expression(expr, ctx); } } + + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.async_generator_functions { + self.async_generator_functions.exit_expression(expr, ctx); + } + } + + fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.async_generator_functions { + self.async_generator_functions.enter_statement(stmt, ctx); + } + } + + fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.async_generator_functions { + self.async_generator_functions.exit_statement(stmt, ctx); + } + } + + fn exit_method_definition( + &mut self, + node: &mut MethodDefinition<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if self.options.async_generator_functions { + self.async_generator_functions.exit_method_definition(node, ctx); + } + } + + fn enter_for_of_statement(&mut self, node: &mut ForOfStatement<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.async_generator_functions { + self.async_generator_functions.enter_for_of_statement(node, ctx); + } + } + + fn enter_function(&mut self, node: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.async_generator_functions { + self.async_generator_functions.enter_function(node, ctx); + } + } + + fn exit_function(&mut self, node: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.async_generator_functions { + self.async_generator_functions.exit_function(node, ctx); + } + } } diff --git a/crates/oxc_transformer/src/es2018/options.rs b/crates/oxc_transformer/src/es2018/options.rs index 57944b881087e..7149646af7163 100644 --- a/crates/oxc_transformer/src/es2018/options.rs +++ b/crates/oxc_transformer/src/es2018/options.rs @@ -7,4 +7,6 @@ use super::ObjectRestSpreadOptions; pub struct ES2018Options { #[serde(skip)] pub object_rest_spread: Option, + #[serde(skip)] + pub async_generator_functions: bool, } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index f3ce6727996df..d6cfd3f7d9182 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -227,6 +227,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { self.x1_jsx.exit_expression(expr, ctx); + self.x2_es2018.exit_expression(expr, ctx); self.x2_es2017.exit_expression(expr, ctx); self.x3_es2015.exit_expression(expr, ctx); } @@ -262,6 +263,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { } fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + self.x2_es2018.enter_function(func, ctx); self.x3_es2015.enter_function(func, ctx); } @@ -270,6 +272,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { typescript.exit_function(func, ctx); } self.x1_jsx.exit_function(func, ctx); + self.x2_es2018.exit_function(func, ctx); self.x3_es2015.exit_function(func, ctx); } @@ -326,6 +329,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.exit_method_definition(def, ctx); } + self.x2_es2018.exit_method_definition(def, ctx); self.x2_es2017.exit_method_definition(def, ctx); } @@ -405,6 +409,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.exit_statement(stmt, ctx); } + self.x2_es2018.exit_statement(stmt, ctx); self.x2_es2017.exit_statement(stmt, ctx); } @@ -422,6 +427,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.enter_statement(stmt, ctx); } + self.x2_es2018.enter_statement(stmt, ctx); } fn enter_declaration(&mut self, decl: &mut Declaration<'a>, ctx: &mut TraverseCtx<'a>) { @@ -462,6 +468,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.enter_for_of_statement(stmt, ctx); } + self.x2_es2018.enter_for_of_statement(stmt, ctx); } fn enter_for_in_statement(&mut self, stmt: &mut ForInStatement<'a>, ctx: &mut TraverseCtx<'a>) { diff --git a/crates/oxc_transformer/src/options/mod.rs b/crates/oxc_transformer/src/options/mod.rs index 5c618152b5045..2b9b147a55d69 100644 --- a/crates/oxc_transformer/src/options/mod.rs +++ b/crates/oxc_transformer/src/options/mod.rs @@ -101,7 +101,11 @@ impl TransformOptions { // Turned off because it is not ready. async_to_generator: false, }, - es2018: ES2018Options { object_rest_spread: Some(ObjectRestSpreadOptions::default()) }, + es2018: ES2018Options { + object_rest_spread: Some(ObjectRestSpreadOptions::default()), + // Turned off because it is not ready. + async_generator_functions: false, + }, es2019: ES2019Options { optional_catch_binding: true }, es2020: ES2020Options { nullish_coalescing_operator: true }, es2021: ES2021Options { logical_assignment_operators: true }, @@ -145,6 +149,8 @@ impl TryFrom<&EnvOptions> for TransformOptions { object_rest_spread: o .can_enable_plugin("transform-object-rest-spread") .then(Default::default), + async_generator_functions: o + .can_enable_plugin("transform-async-generator-functions"), }, es2019: ES2019Options { optional_catch_binding: o.can_enable_plugin("transform-optional-catch-binding"), @@ -314,6 +320,10 @@ impl TryFrom<&BabelOptions> for TransformOptions { }) .or(env.es2018.object_rest_spread) }, + async_generator_functions: { + let plugin_name = "transform-async-generator-functions"; + options.get_plugin(plugin_name).is_some() || env.es2018.async_generator_functions + }, }; let es2019 = ES2019Options { diff --git a/tasks/transform_conformance/snapshots/babel.snap.md b/tasks/transform_conformance/snapshots/babel.snap.md index c0e172b9e43ce..eb4084270dd70 100644 --- a/tasks/transform_conformance/snapshots/babel.snap.md +++ b/tasks/transform_conformance/snapshots/babel.snap.md @@ -1,6 +1,6 @@ commit: d20b314c -Passed: 361/1058 +Passed: 376/1077 # All Passed: * babel-plugin-transform-class-static-block @@ -1450,6 +1450,20 @@ x Output mismatch x Output mismatch +# babel-plugin-transform-async-generator-functions (15/19) +* async-generators/class-method/input.js +x Output mismatch + +* async-generators/object-method/input.js +x Output mismatch + +* async-generators/static-method/input.js +x Output mismatch + +* nested/arrows-in-declaration/input.js +x Output mismatch + + # babel-plugin-transform-object-rest-spread (5/59) * assumption-ignoreFunctionLength/parameters-object-rest-used-in-default/input.js x Output mismatch diff --git a/tasks/transform_conformance/snapshots/babel_exec.snap.md b/tasks/transform_conformance/snapshots/babel_exec.snap.md index 05fa232b70119..b59ee7272d3cc 100644 --- a/tasks/transform_conformance/snapshots/babel_exec.snap.md +++ b/tasks/transform_conformance/snapshots/babel_exec.snap.md @@ -1,6 +1,6 @@ commit: d20b314c -Passed: 45/73 +Passed: 45/99 # All Passed: * babel-plugin-transform-class-static-block @@ -24,6 +24,86 @@ exec failed exec failed +# babel-plugin-transform-async-generator-functions (0/26) +* async-generators/declaration-exec/exec.js +exec failed + +* async-generators/yield-exec/exec.js +exec failed + +* for-await/async-generator-exec/exec.js +exec failed + +* for-await/create-async-from-sync-iterator/exec.js +exec failed + +* for-await/lhs-member-expression/exec.js +exec failed + +* for-await/re-declare-var-in-init-body/exec.js +exec failed + +* for-await/step-single-tick/exec.js +exec failed + +* for-await/step-value-is-promise/exec.js +exec failed + +* for-await/step-value-not-accessed-when-done/exec.js +exec failed + +* regression/test262-fn-length/exec.js +exec failed + +* yield-star/create-async-from-sync-iterator/exec.js +exec failed + +* yield-star/ecma262-pr-2819/exec.js +exec failed + +* yield-star/issue-9905/exec.js +exec failed + +* yield-star/return-method/exec.js +exec failed + +* yield-star/return-method-with-finally/exec.js +exec failed + +* yield-star/return-method-with-finally-multiple-parallel/exec.js +exec failed + +* yield-star/return-method-with-finally-multiple-serial/exec.js +exec failed + +* yield-star/throw-method-with-catch/exec.js +exec failed + +* yield-star/throw-method-with-finally/exec.js +exec failed + +* yield-star-polyfill-corejs3/issue-9905/exec.js +exec failed + +* yield-star-polyfill-corejs3/return-method/exec.js +exec failed + +* yield-star-polyfill-corejs3/return-method-with-finally/exec.js +exec failed + +* yield-star-polyfill-corejs3/return-method-with-finally-multiple-parallel/exec.js +exec failed + +* yield-star-polyfill-corejs3/return-method-with-finally-multiple-serial/exec.js +exec failed + +* yield-star-polyfill-corejs3/throw-method-with-catch/exec.js +exec failed + +* yield-star-polyfill-corejs3/throw-method-with-finally/exec.js +exec failed + + # babel-plugin-transform-object-rest-spread (15/31) * assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js exec failed diff --git a/tasks/transform_conformance/src/constants.rs b/tasks/transform_conformance/src/constants.rs index 3455c0160e922..3248239ad02b1 100644 --- a/tasks/transform_conformance/src/constants.rs +++ b/tasks/transform_conformance/src/constants.rs @@ -23,7 +23,7 @@ pub(crate) const PLUGINS: &[&str] = &[ "babel-plugin-transform-optional-catch-binding", // "babel-plugin-transform-json-strings", // // ES2018 - // "babel-plugin-transform-async-generator-functions", + "babel-plugin-transform-async-generator-functions", "babel-plugin-transform-object-rest-spread", // // [Regex] "babel-plugin-transform-unicode-property-regex", // "babel-plugin-transform-dotall-regex",