refactor(transformer/async-to-generator): move transform methods to AsyncGeneratorExecutor and make it public (#6992)

The `AsyncGeneratorExecutor` contains many transform methods which both can used in `async-to-generator` and `async-generator-functions` plugins, because their implementations are almost the same. I am still not sure where is the best place to place it.
This commit is contained in:
Dunqing 2024-10-29 13:54:21 +00:00
parent d9edef6ae1
commit 562bb9af7f
2 changed files with 86 additions and 57 deletions

View file

@ -63,11 +63,12 @@ use crate::{common::helper_loader::Helper, TransformCtx};
pub struct AsyncToGenerator<'a, 'ctx> { pub struct AsyncToGenerator<'a, 'ctx> {
ctx: &'ctx TransformCtx<'a>, ctx: &'ctx TransformCtx<'a>,
executor: AsyncGeneratorExecutor<'a, 'ctx>,
} }
impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> { impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self { pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
Self { ctx } Self { ctx, executor: AsyncGeneratorExecutor::new(Helper::AsyncToGenerator, ctx) }
} }
} }
@ -77,8 +78,20 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
Expression::AwaitExpression(await_expr) => { Expression::AwaitExpression(await_expr) => {
self.transform_await_expression(await_expr, ctx) self.transform_await_expression(await_expr, ctx)
} }
Expression::FunctionExpression(func) => self.transform_function_expression(func, ctx), Expression::FunctionExpression(func) => {
Expression::ArrowFunctionExpression(arrow) => self.transform_arrow_function(arrow, ctx), if func.r#async && !func.generator && !func.is_typescript_syntax() {
Some(self.executor.transform_function_expression(func, ctx))
} else {
None
}
}
Expression::ArrowFunctionExpression(arrow) => {
if arrow.r#async {
Some(self.executor.transform_arrow_function(arrow, ctx))
} else {
None
}
}
_ => None, _ => None,
}; };
@ -88,20 +101,20 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
} }
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) { fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
let new_statement = match stmt { let function = match stmt {
Statement::FunctionDeclaration(func) => self.transform_function_declaration(func, ctx), Statement::FunctionDeclaration(func) => Some(func),
Statement::ExportDefaultDeclaration(decl) => { Statement::ExportDefaultDeclaration(decl) => {
if let ExportDefaultDeclarationKind::FunctionDeclaration(func) = if let ExportDefaultDeclarationKind::FunctionDeclaration(func) =
&mut decl.declaration &mut decl.declaration
{ {
self.transform_function_declaration(func, ctx) Some(func)
} else { } else {
None None
} }
} }
Statement::ExportNamedDeclaration(decl) => { Statement::ExportNamedDeclaration(decl) => {
if let Some(Declaration::FunctionDeclaration(func)) = &mut decl.declaration { if let Some(Declaration::FunctionDeclaration(func)) = &mut decl.declaration {
self.transform_function_declaration(func, ctx) Some(func)
} else { } else {
None None
} }
@ -109,8 +122,11 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
_ => None, _ => None,
}; };
if let Some(new_statement) = new_statement { if let Some(function) = function {
self.ctx.statement_injector.insert_after(stmt, new_statement); 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);
}
} }
} }
@ -119,7 +135,10 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
node: &mut MethodDefinition<'a>, node: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) { ) {
self.transform_function_for_method_definition(&mut node.value, ctx); let function = &mut node.value;
if function.r#async && !function.generator && !function.is_typescript_syntax() {
self.executor.transform_function_for_method_definition(function, ctx);
}
} }
} }
@ -143,6 +162,17 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
)) ))
} }
} }
}
pub struct AsyncGeneratorExecutor<'a, 'ctx> {
helper: Helper,
ctx: &'ctx TransformCtx<'a>,
}
impl<'a, 'ctx> AsyncGeneratorExecutor<'a, 'ctx> {
pub fn new(helper: Helper, ctx: &'ctx TransformCtx<'a>) -> Self {
Self { helper, ctx }
}
/// Transforms async method definitions to generator functions wrapped in asyncToGenerator. /// Transforms async method definitions to generator functions wrapped in asyncToGenerator.
/// ///
@ -162,15 +192,11 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
/// })(); /// })();
/// } /// }
/// ``` /// ```
fn transform_function_for_method_definition( pub fn transform_function_for_method_definition(
&self, &self,
func: &mut Function<'a>, func: &mut Function<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) { ) {
if !func.r#async {
return;
}
let Some(body) = func.body.take() else { let Some(body) = func.body.take() else {
return; return;
}; };
@ -183,7 +209,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
ctx.scopes_mut().change_parent_id(scope_id, Some(new_scope_id)); ctx.scopes_mut().change_parent_id(scope_id, Some(new_scope_id));
// We need to transform formal parameters change back to the original scope, // We need to transform formal parameters change back to the original scope,
// because we only move out the function body. // because we only move out the function body.
BindingMover::new(new_scope_id, ctx).visit_formal_parameters(&func.params); Self::move_formal_parameters_to_target_scope(new_scope_id, &func.params, ctx);
(scope_id, new_scope_id) (scope_id, new_scope_id)
}; };
@ -196,36 +222,30 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
// Modify the wrapper function // Modify the wrapper function
func.r#async = false; func.r#async = false;
func.generator = false;
func.body = Some(ctx.ast.alloc_function_body(SPAN, ctx.ast.vec(), ctx.ast.vec1(statement))); func.body = Some(ctx.ast.alloc_function_body(SPAN, ctx.ast.vec(), ctx.ast.vec1(statement)));
func.scope_id.set(Some(wrapper_scope_id)); func.scope_id.set(Some(wrapper_scope_id));
} }
/// Transforms [`Function`] whose type is [`FunctionType::FunctionExpression`] to a generator function /// Transforms [`Function`] whose type is [`FunctionType::FunctionExpression`] to a generator function
/// and wraps it in asyncToGenerator helper function. /// and wraps it in asyncToGenerator helper function.
fn transform_function_expression( pub fn transform_function_expression(
&self, &self,
wrapper_function: &mut Function<'a>, wrapper_function: &mut Function<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> { ) -> Expression<'a> {
if !wrapper_function.r#async
|| wrapper_function.generator
|| wrapper_function.is_typescript_syntax()
{
return None;
}
let body = wrapper_function.body.take().unwrap(); let body = wrapper_function.body.take().unwrap();
let params = ctx.alloc(ctx.ast.move_formal_parameters(&mut wrapper_function.params)); let params = ctx.alloc(ctx.ast.move_formal_parameters(&mut wrapper_function.params));
let id = wrapper_function.id.take(); let id = wrapper_function.id.take();
let has_function_id = id.is_some(); let has_function_id = id.is_some();
if !has_function_id && !Self::is_function_length_affected(&params) { if !has_function_id && !Self::is_function_length_affected(&params) {
return Some(self.create_async_to_generator_call( return self.create_async_to_generator_call(
params, params,
body, body,
wrapper_function.scope_id.take().unwrap(), wrapper_function.scope_id.take().unwrap(),
ctx, ctx,
)); );
} }
let (generator_scope_id, wrapper_scope_id) = { let (generator_scope_id, wrapper_scope_id) = {
@ -238,7 +258,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
// and the caller_function is inside the wrapper function. // and the caller_function is inside the wrapper function.
// so we need to move the id to the new scope. // so we need to move the id to the new scope.
if let Some(id) = id.as_ref() { if let Some(id) = id.as_ref() {
BindingMover::new(wrapper_scope_id, ctx).visit_binding_identifier(id); Self::move_binding_identifier_to_target_scope(wrapper_scope_id, id, ctx);
let symbol_id = id.symbol_id.get().unwrap(); let symbol_id = id.symbol_id.get().unwrap();
*ctx.symbols_mut().get_flags_mut(symbol_id) = SymbolFlags::FunctionScopedVariable; *ctx.symbols_mut().get_flags_mut(symbol_id) = SymbolFlags::FunctionScopedVariable;
} }
@ -294,6 +314,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
} }
debug_assert!(wrapper_function.body.is_none()); debug_assert!(wrapper_function.body.is_none());
wrapper_function.r#async = false; wrapper_function.r#async = false;
wrapper_function.generator = false;
wrapper_function.body.replace(ctx.ast.alloc_function_body( wrapper_function.body.replace(ctx.ast.alloc_function_body(
SPAN, SPAN,
ctx.ast.vec(), ctx.ast.vec(),
@ -303,22 +324,15 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
// Construct the IIFE // Construct the IIFE
let callee = ctx.ast.expression_from_function(ctx.ast.move_function(wrapper_function)); let callee = ctx.ast.expression_from_function(ctx.ast.move_function(wrapper_function));
Some(ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec(), false)) ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec(), false)
} }
/// Transforms async function declarations into generator functions wrapped in the asyncToGenerator helper. /// Transforms async function declarations into generator functions wrapped in the asyncToGenerator helper.
fn transform_function_declaration( pub fn transform_function_declaration(
&self, &self,
wrapper_function: &mut Function<'a>, wrapper_function: &mut Function<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Statement<'a>> { ) -> Statement<'a> {
if !wrapper_function.r#async
|| wrapper_function.generator
|| wrapper_function.is_typescript_syntax()
{
return None;
}
let (generator_scope_id, wrapper_scope_id) = { let (generator_scope_id, wrapper_scope_id) = {
let wrapper_scope_id = let wrapper_scope_id =
ctx.create_child_scope(ctx.current_scope_id(), ScopeFlags::Function); ctx.create_child_scope(ctx.current_scope_id(), ScopeFlags::Function);
@ -340,6 +354,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
// Modify the wrapper function // Modify the wrapper function
{ {
wrapper_function.r#async = false; wrapper_function.r#async = false;
wrapper_function.generator = false;
let statements = ctx.ast.vec1(Self::create_apply_call_statement(&bound_ident, ctx)); let statements = ctx.ast.vec1(Self::create_apply_call_statement(&bound_ident, ctx));
debug_assert!(wrapper_function.body.is_none()); debug_assert!(wrapper_function.body.is_none());
wrapper_function.body.replace(ctx.ast.alloc_function_body( wrapper_function.body.replace(ctx.ast.alloc_function_body(
@ -370,20 +385,16 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
let params = Self::create_empty_params(ctx); let params = Self::create_empty_params(ctx);
let id = Some(bound_ident.create_binding_identifier(ctx)); let id = Some(bound_ident.create_binding_identifier(ctx));
let caller_function = Self::create_function(id, params, body, scope_id, ctx); let caller_function = Self::create_function(id, params, body, scope_id, ctx);
Some(Statement::from(ctx.ast.declaration_from_function(caller_function))) Statement::from(ctx.ast.declaration_from_function(caller_function))
} }
} }
/// Transforms async arrow functions into generator functions wrapped in the asyncToGenerator helper. /// Transforms async arrow functions into generator functions wrapped in the asyncToGenerator helper.
fn transform_arrow_function( pub(self) fn transform_arrow_function(
&self, &self,
arrow: &mut ArrowFunctionExpression<'a>, arrow: &mut ArrowFunctionExpression<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> { ) -> Expression<'a> {
if !arrow.r#async {
return None;
}
let mut body = ctx.ast.move_function_body(&mut arrow.body); let mut body = ctx.ast.move_function_body(&mut arrow.body);
// If the arrow's expression is true, we need to wrap the only one expression with return statement. // If the arrow's expression is true, we need to wrap the only one expression with return statement.
@ -401,12 +412,12 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
ctx.scopes_mut().get_flags_mut(generator_function_id).remove(ScopeFlags::Arrow); ctx.scopes_mut().get_flags_mut(generator_function_id).remove(ScopeFlags::Arrow);
if !Self::is_function_length_affected(&params) { if !Self::is_function_length_affected(&params) {
return Some(self.create_async_to_generator_call( return self.create_async_to_generator_call(
params, params,
ctx.ast.alloc(body), ctx.ast.alloc(body),
generator_function_id, generator_function_id,
ctx, ctx,
)); );
} }
let wrapper_scope_id = ctx.create_child_scope(ctx.current_scope_id(), ScopeFlags::Function); let wrapper_scope_id = ctx.create_child_scope(ctx.current_scope_id(), ScopeFlags::Function);
@ -444,7 +455,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
let wrapper_function = Self::create_function(None, params, body, wrapper_scope_id, ctx); let wrapper_function = Self::create_function(None, params, body, wrapper_scope_id, ctx);
// Construct the IIFE // Construct the IIFE
let callee = ctx.ast.expression_from_function(wrapper_function); let callee = ctx.ast.expression_from_function(wrapper_function);
Some(ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec(), false)) ctx.ast.expression_call(SPAN, callee, NONE, ctx.ast.vec(), false)
} }
} }
@ -523,7 +534,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
ctx.ast.statement_return(SPAN, Some(argument)) ctx.ast.statement_return(SPAN, Some(argument))
} }
/// Creates an [`Expression`] that calls the [`Helper::AsyncToGenerator`] helper function. /// Creates an [`Expression`] that calls the [`AsyncGeneratorExecutor::helper`] helper function.
/// ///
/// This function constructs the helper call with arguments derived from the provided /// This function constructs the helper call with arguments derived from the provided
/// parameters, body, and scope_id. /// parameters, body, and scope_id.
@ -546,7 +557,7 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
let function_expression = ctx.ast.expression_from_function(function); let function_expression = ctx.ast.expression_from_function(function);
let argument = ctx.ast.argument_expression(function_expression); let argument = ctx.ast.argument_expression(function_expression);
let arguments = ctx.ast.vec1(argument); let arguments = ctx.ast.vec1(argument);
self.ctx.helper_call_expr(Helper::AsyncToGenerator, arguments, ctx) self.ctx.helper_call_expr(self.helper, arguments, ctx)
} }
/// Creates a helper declaration statement for async-to-generator transformation. /// Creates a helper declaration statement for async-to-generator transformation.
@ -667,6 +678,24 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> {
fn is_function_length_affected(params: &FormalParameters<'_>) -> bool { fn is_function_length_affected(params: &FormalParameters<'_>) -> bool {
params.items.first().is_some_and(|param| !param.pattern.kind.is_assignment_pattern()) params.items.first().is_some_and(|param| !param.pattern.kind.is_assignment_pattern())
} }
#[inline]
fn move_formal_parameters_to_target_scope(
target_scope_id: ScopeId,
params: &FormalParameters<'a>,
ctx: &mut TraverseCtx<'a>,
) {
BindingMover::new(target_scope_id, ctx).visit_formal_parameters(params);
}
#[inline]
fn move_binding_identifier_to_target_scope(
target_scope_id: ScopeId,
ident: &BindingIdentifier<'a>,
ctx: &mut TraverseCtx<'a>,
) {
BindingMover::new(target_scope_id, ctx).visit_binding_identifier(ident);
}
} }
/// Moves the bindings from original scope to target scope. /// Moves the bindings from original scope to target scope.

View file

@ -1,13 +1,13 @@
mod async_to_generator;
pub mod options;
use options::ES2017Options;
use oxc_ast::ast::{Expression, Statement}; use oxc_ast::ast::{Expression, Statement};
use oxc_traverse::{Traverse, TraverseCtx}; use oxc_traverse::{Traverse, TraverseCtx};
use crate::{ use crate::{es2017::async_to_generator::AsyncToGenerator, TransformCtx};
es2017::{async_to_generator::AsyncToGenerator, options::ES2017Options}, #[expect(unused_imports)]
TransformCtx, pub use async_to_generator::AsyncGeneratorExecutor;
};
mod async_to_generator;
pub mod options;
#[allow(dead_code)] #[allow(dead_code)]
pub struct ES2017<'a, 'ctx> { pub struct ES2017<'a, 'ctx> {