Skip to content

Commit

Permalink
feat(transformer): enable ArrowFunctionConverter in async-to-generato…
Browse files Browse the repository at this point in the history
…r and async-generator-functions plugins
  • Loading branch information
Dunqing committed Nov 5, 2024
1 parent 0b32951 commit 87fd942
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 298 deletions.
62 changes: 48 additions & 14 deletions crates/oxc_transformer/src/common/arrow_function_converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ use oxc_syntax::{
};
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};

use crate::TransformOptions;

/// Mode for arrow function conversion
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArrowFunctionConverterMode {
Expand All @@ -107,23 +109,27 @@ pub enum ArrowFunctionConverterMode {
Enabled,

/// Only convert async arrow functions
#[expect(unused)]
AsyncOnly,
}

pub struct ArrowFunctionConverterOptions {
pub mode: ArrowFunctionConverterMode,
}

pub struct ArrowFunctionConverter<'a> {
mode: ArrowFunctionConverterMode,
this_var_stack: SparseStack<BoundIdentifier<'a>>,
}

impl<'a> ArrowFunctionConverter<'a> {
pub fn new(options: &ArrowFunctionConverterOptions) -> Self {
pub fn new(options: &TransformOptions) -> Self {
let mode = if options.env.es2015.arrow_function.is_some() {
ArrowFunctionConverterMode::Enabled
} else if options.env.es2017.async_to_generator
|| options.env.es2018.async_generator_functions
{
ArrowFunctionConverterMode::AsyncOnly
} else {
ArrowFunctionConverterMode::Disabled
};
// `SparseStack` is created with 1 empty entry, for `Program`
Self { mode: options.mode, this_var_stack: SparseStack::new() }
Self { mode, this_var_stack: SparseStack::new() }
}
}

Expand Down Expand Up @@ -254,7 +260,11 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
return;
}

if let Expression::ArrowFunctionExpression(_) = expr {
if let Expression::ArrowFunctionExpression(arrow_function_expr) = expr {
if self.is_async_only() && !arrow_function_expr.r#async {
return;
}

let Expression::ArrowFunctionExpression(arrow_function_expr) =
ctx.ast.move_expression(expr)
else {
Expand All @@ -272,13 +282,18 @@ impl<'a> ArrowFunctionConverter<'a> {
self.mode == ArrowFunctionConverterMode::Disabled
}

/// Check if arrow function conversion has enabled for transform async arrow functions
fn is_async_only(&self) -> bool {
self.mode == ArrowFunctionConverterMode::AsyncOnly
}

fn get_this_identifier(
&mut self,
span: Span,
ctx: &mut TraverseCtx<'a>,
) -> Option<ArenaBox<'a, IdentifierReference<'a>>> {
// Find arrow function we are currently in (if we are)
let arrow_scope_id = Self::get_arrow_function_scope(ctx)?;
let arrow_scope_id = self.get_arrow_function_scope(ctx)?;

// TODO(improve-on-babel): We create a new UID for every scope. This is pointless, as only one
// `this` can be in scope at a time. We could create a single `_this` UID and reuse it in each
Expand All @@ -304,7 +319,7 @@ impl<'a> ArrowFunctionConverter<'a> {

/// Find arrow function we are currently in, if it's between current node, and where `this` is bound.
/// Return its `ScopeId`.
fn get_arrow_function_scope(ctx: &mut TraverseCtx<'a>) -> Option<ScopeId> {
fn get_arrow_function_scope(&self, ctx: &mut TraverseCtx<'a>) -> Option<ScopeId> {
// `this` inside a class resolves to `this` *outside* the class in:
// * `extends` clause
// * Computed method key
Expand Down Expand Up @@ -346,13 +361,13 @@ impl<'a> ArrowFunctionConverter<'a> {
// ```
//
// So in this loop, we only exit when we encounter one of the above.
for ancestor in ctx.ancestors() {
let mut ancestors = ctx.ancestors();
while let Some(ancestor) = ancestors.next() {
match ancestor {
// Top level
Ancestor::ProgramBody(_)
// Function (includes class method body)
| Ancestor::FunctionParams(_)
| Ancestor::FunctionBody(_)
// Class property body
| Ancestor::PropertyDefinitionValue(_)
// Class accessor property body
Expand All @@ -361,10 +376,29 @@ impl<'a> ArrowFunctionConverter<'a> {
| Ancestor::StaticBlockBody(_) => return None,
// Arrow function
Ancestor::ArrowFunctionExpressionParams(func) => {
return Some(func.scope_id().get().unwrap())
return if self.is_async_only() && !*func.r#async() {
None
} else {
Some(func.scope_id().get().unwrap())
}
}
Ancestor::ArrowFunctionExpressionBody(func) => {
return Some(func.scope_id().get().unwrap())
return if self.is_async_only() && !*func.r#async() {
None
} else {
Some(func.scope_id().get().unwrap())
}
}
Ancestor::FunctionBody(func) => {
return if self.is_async_only() && *func.r#async()
&& matches!(
ancestors.next().unwrap(),
Ancestor::MethodDefinitionValue(_) | Ancestor::ObjectPropertyValue(_)
) {
Some(func.scope_id().get().unwrap())
} else {
None
}
}
_ => {}
}
Expand Down
17 changes: 2 additions & 15 deletions crates/oxc_transformer/src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Utility transforms which are in common between other transforms.
use arrow_function_converter::{
ArrowFunctionConverter, ArrowFunctionConverterMode, ArrowFunctionConverterOptions,
};
use arrow_function_converter::ArrowFunctionConverter;
use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};
Expand Down Expand Up @@ -31,23 +29,12 @@ pub struct Common<'a, 'ctx> {

impl<'a, 'ctx> Common<'a, 'ctx> {
pub fn new(options: &TransformOptions, ctx: &'ctx TransformCtx<'a>) -> Self {
let arrow_function_converter_options = {
let mode = if options.env.es2015.arrow_function.is_some() {
ArrowFunctionConverterMode::Enabled
} else {
ArrowFunctionConverterMode::Disabled
};
ArrowFunctionConverterOptions { mode }
};

Self {
module_imports: ModuleImports::new(ctx),
var_declarations: VarDeclarations::new(ctx),
statement_injector: StatementInjector::new(ctx),
top_level_statements: TopLevelStatements::new(ctx),
arrow_function_converter: ArrowFunctionConverter::new(
&arrow_function_converter_options,
),
arrow_function_converter: ArrowFunctionConverter::new(options),
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion crates/oxc_transformer/src/es2017/async_to_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,14 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
}

fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
if func.r#async && matches!(ctx.parent(), Ancestor::MethodDefinitionValue(_)) {
if func.r#async
&& !func.is_typescript_syntax()
&& matches!(
ctx.parent(),
// `class A { async foo() {} }` | `({ async foo() {} })`
Ancestor::MethodDefinitionValue(_) | Ancestor::PropertyDefinitionValue(_)
)
{
self.executor.transform_function_for_method_definition(func, ctx);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,11 @@ impl<'a, 'ctx> Traverse<'a> for AsyncGeneratorFunctions<'a, 'ctx> {
if func.r#async
&& func.generator
&& !func.is_typescript_syntax()
&& matches!(ctx.parent(), Ancestor::MethodDefinitionValue(_))
&& matches!(
ctx.parent(),
// `class A { async foo() {} }` | `({ async foo() {} })`
Ancestor::MethodDefinitionValue(_) | Ancestor::ObjectPropertyValue(_)
)
{
self.executor.transform_function_for_method_definition(func, ctx);
}
Expand Down
30 changes: 27 additions & 3 deletions tasks/transform_conformance/snapshots/babel.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -1452,13 +1452,37 @@ x Output mismatch

# babel-plugin-transform-async-generator-functions (15/19)
* async-generators/class-method/input.js
x Output mismatch
Bindings mismatch:
after transform: ScopeId(0): ["C", "_this"]
rebuilt : ScopeId(0): ["C"]
Bindings mismatch:
after transform: ScopeId(3): []
rebuilt : ScopeId(2): ["_this"]
Symbol scope ID mismatch for "_this":
after transform: SymbolId(1): ScopeId(0)
rebuilt : SymbolId(1): ScopeId(2)

* async-generators/object-method/input.js
x Output mismatch
Bindings mismatch:
after transform: ScopeId(0): ["_this"]
rebuilt : ScopeId(0): []
Bindings mismatch:
after transform: ScopeId(2): []
rebuilt : ScopeId(1): ["_this"]
Symbol scope ID mismatch for "_this":
after transform: SymbolId(0): ScopeId(0)
rebuilt : SymbolId(0): ScopeId(1)

* async-generators/static-method/input.js
x Output mismatch
Bindings mismatch:
after transform: ScopeId(0): ["C", "_this"]
rebuilt : ScopeId(0): ["C"]
Bindings mismatch:
after transform: ScopeId(3): []
rebuilt : ScopeId(2): ["_this"]
Symbol scope ID mismatch for "_this":
after transform: SymbolId(1): ScopeId(0)
rebuilt : SymbolId(1): ScopeId(2)

* nested/arrows-in-declaration/input.js
x Output mismatch
Expand Down
Loading

0 comments on commit 87fd942

Please sign in to comment.