mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
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.
This commit is contained in:
parent
af5140fc3d
commit
934cb5e746
13 changed files with 870 additions and 11 deletions
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<A, S>(&self, target: &A, stmts: S)
|
||||
where
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
/// <https://github.com/babel/babel/blob/d20b314c14533ab86351ecf6ca6b7296b66a57b3/packages/babel-plugin-transform-async-generator-functions/src/for-await.ts#L3-L30>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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: <https://babeljs.io/docs/en/babel-plugin-transform-async-generator-functions>
|
||||
//! * Babel implementation: <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-async-generator-functions>
|
||||
//! * Async Iteration TC39 proposal: <https://github.com/tc39/proposal-async-iteration>
|
||||
|
||||
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<bool>,
|
||||
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<Expression<'a>> {
|
||||
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<Expression<'a>> {
|
||||
// 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)))
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,6 @@ use super::ObjectRestSpreadOptions;
|
|||
pub struct ES2018Options {
|
||||
#[serde(skip)]
|
||||
pub object_rest_spread: Option<ObjectRestSpreadOptions>,
|
||||
#[serde(skip)]
|
||||
pub async_generator_functions: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue