Skip to content

Commit df77241

Browse files
committed
feat(transformer): enable ArrowFunctionConverter in async-to-generator and async-generator-functions plugins (#7113)
Part of #7074 In async-to-generator and async-generator-functions plugins, we may need to transform the async arrow function to a regular generator function, now we can reuse the ability of the ArrowFunction plugin by enabling `ArrowFunctionConverter`. I will fix semantic errors in the follow-up PR
1 parent 0b32951 commit df77241

6 files changed

Lines changed: 90 additions & 64 deletions

File tree

crates/oxc_transformer/src/common/arrow_function_converter.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ use oxc_syntax::{
9797
};
9898
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};
9999

100+
use crate::TransformOptions;
101+
100102
/// Mode for arrow function conversion
101103
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102104
pub enum ArrowFunctionConverterMode {
@@ -107,23 +109,27 @@ pub enum ArrowFunctionConverterMode {
107109
Enabled,
108110

109111
/// Only convert async arrow functions
110-
#[expect(unused)]
111112
AsyncOnly,
112113
}
113114

114-
pub struct ArrowFunctionConverterOptions {
115-
pub mode: ArrowFunctionConverterMode,
116-
}
117-
118115
pub struct ArrowFunctionConverter<'a> {
119116
mode: ArrowFunctionConverterMode,
120117
this_var_stack: SparseStack<BoundIdentifier<'a>>,
121118
}
122119

123120
impl<'a> ArrowFunctionConverter<'a> {
124-
pub fn new(options: &ArrowFunctionConverterOptions) -> Self {
121+
pub fn new(options: &TransformOptions) -> Self {
122+
let mode = if options.env.es2015.arrow_function.is_some() {
123+
ArrowFunctionConverterMode::Enabled
124+
} else if options.env.es2017.async_to_generator
125+
|| options.env.es2018.async_generator_functions
126+
{
127+
ArrowFunctionConverterMode::AsyncOnly
128+
} else {
129+
ArrowFunctionConverterMode::Disabled
130+
};
125131
// `SparseStack` is created with 1 empty entry, for `Program`
126-
Self { mode: options.mode, this_var_stack: SparseStack::new() }
132+
Self { mode, this_var_stack: SparseStack::new() }
127133
}
128134
}
129135

@@ -254,7 +260,11 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
254260
return;
255261
}
256262

257-
if let Expression::ArrowFunctionExpression(_) = expr {
263+
if let Expression::ArrowFunctionExpression(arrow_function_expr) = expr {
264+
if self.is_async_only() && !arrow_function_expr.r#async {
265+
return;
266+
}
267+
258268
let Expression::ArrowFunctionExpression(arrow_function_expr) =
259269
ctx.ast.move_expression(expr)
260270
else {
@@ -272,13 +282,18 @@ impl<'a> ArrowFunctionConverter<'a> {
272282
self.mode == ArrowFunctionConverterMode::Disabled
273283
}
274284

285+
/// Check if arrow function conversion has enabled for transform async arrow functions
286+
fn is_async_only(&self) -> bool {
287+
self.mode == ArrowFunctionConverterMode::AsyncOnly
288+
}
289+
275290
fn get_this_identifier(
276291
&mut self,
277292
span: Span,
278293
ctx: &mut TraverseCtx<'a>,
279294
) -> Option<ArenaBox<'a, IdentifierReference<'a>>> {
280295
// Find arrow function we are currently in (if we are)
281-
let arrow_scope_id = Self::get_arrow_function_scope(ctx)?;
296+
let arrow_scope_id = self.get_arrow_function_scope(ctx)?;
282297

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

305320
/// Find arrow function we are currently in, if it's between current node, and where `this` is bound.
306321
/// Return its `ScopeId`.
307-
fn get_arrow_function_scope(ctx: &mut TraverseCtx<'a>) -> Option<ScopeId> {
322+
fn get_arrow_function_scope(&self, ctx: &mut TraverseCtx<'a>) -> Option<ScopeId> {
308323
// `this` inside a class resolves to `this` *outside* the class in:
309324
// * `extends` clause
310325
// * Computed method key
@@ -346,13 +361,13 @@ impl<'a> ArrowFunctionConverter<'a> {
346361
// ```
347362
//
348363
// So in this loop, we only exit when we encounter one of the above.
349-
for ancestor in ctx.ancestors() {
364+
let mut ancestors = ctx.ancestors();
365+
while let Some(ancestor) = ancestors.next() {
350366
match ancestor {
351367
// Top level
352368
Ancestor::ProgramBody(_)
353369
// Function (includes class method body)
354370
| Ancestor::FunctionParams(_)
355-
| Ancestor::FunctionBody(_)
356371
// Class property body
357372
| Ancestor::PropertyDefinitionValue(_)
358373
// Class accessor property body
@@ -361,10 +376,29 @@ impl<'a> ArrowFunctionConverter<'a> {
361376
| Ancestor::StaticBlockBody(_) => return None,
362377
// Arrow function
363378
Ancestor::ArrowFunctionExpressionParams(func) => {
364-
return Some(func.scope_id().get().unwrap())
379+
return if self.is_async_only() && !*func.r#async() {
380+
None
381+
} else {
382+
Some(func.scope_id().get().unwrap())
383+
}
365384
}
366385
Ancestor::ArrowFunctionExpressionBody(func) => {
367-
return Some(func.scope_id().get().unwrap())
386+
return if self.is_async_only() && !*func.r#async() {
387+
None
388+
} else {
389+
Some(func.scope_id().get().unwrap())
390+
}
391+
}
392+
Ancestor::FunctionBody(func) => {
393+
return if self.is_async_only() && *func.r#async()
394+
&& matches!(
395+
ancestors.next().unwrap(),
396+
Ancestor::MethodDefinitionValue(_) | Ancestor::ObjectPropertyValue(_)
397+
) {
398+
Some(func.scope_id().get().unwrap())
399+
} else {
400+
None
401+
}
368402
}
369403
_ => {}
370404
}

crates/oxc_transformer/src/common/mod.rs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
//! Utility transforms which are in common between other transforms.
22
3-
use arrow_function_converter::{
4-
ArrowFunctionConverter, ArrowFunctionConverterMode, ArrowFunctionConverterOptions,
5-
};
3+
use arrow_function_converter::ArrowFunctionConverter;
64
use oxc_allocator::Vec as ArenaVec;
75
use oxc_ast::ast::*;
86
use oxc_traverse::{Traverse, TraverseCtx};
@@ -31,23 +29,12 @@ pub struct Common<'a, 'ctx> {
3129

3230
impl<'a, 'ctx> Common<'a, 'ctx> {
3331
pub fn new(options: &TransformOptions, ctx: &'ctx TransformCtx<'a>) -> Self {
34-
let arrow_function_converter_options = {
35-
let mode = if options.env.es2015.arrow_function.is_some() {
36-
ArrowFunctionConverterMode::Enabled
37-
} else {
38-
ArrowFunctionConverterMode::Disabled
39-
};
40-
ArrowFunctionConverterOptions { mode }
41-
};
42-
4332
Self {
4433
module_imports: ModuleImports::new(ctx),
4534
var_declarations: VarDeclarations::new(ctx),
4635
statement_injector: StatementInjector::new(ctx),
4736
top_level_statements: TopLevelStatements::new(ctx),
48-
arrow_function_converter: ArrowFunctionConverter::new(
49-
&arrow_function_converter_options,
50-
),
37+
arrow_function_converter: ArrowFunctionConverter::new(options),
5138
}
5239
}
5340
}

crates/oxc_transformer/src/es2017/async_to_generator.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,14 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> {
131131
}
132132

133133
fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
134-
if func.r#async && matches!(ctx.parent(), Ancestor::MethodDefinitionValue(_)) {
134+
if func.r#async
135+
&& !func.is_typescript_syntax()
136+
&& matches!(
137+
ctx.parent(),
138+
// `class A { async foo() {} }` | `({ async foo() {} })`
139+
Ancestor::MethodDefinitionValue(_) | Ancestor::PropertyDefinitionValue(_)
140+
)
141+
{
135142
self.executor.transform_function_for_method_definition(func, ctx);
136143
}
137144
}

crates/oxc_transformer/src/es2018/async_generator_functions/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ impl<'a, 'ctx> Traverse<'a> for AsyncGeneratorFunctions<'a, 'ctx> {
172172
if func.r#async
173173
&& func.generator
174174
&& !func.is_typescript_syntax()
175-
&& matches!(ctx.parent(), Ancestor::MethodDefinitionValue(_))
175+
&& matches!(
176+
ctx.parent(),
177+
// `class A { async foo() {} }` | `({ async foo() {} })`
178+
Ancestor::MethodDefinitionValue(_) | Ancestor::ObjectPropertyValue(_)
179+
)
176180
{
177181
self.executor.transform_function_for_method_definition(func, ctx);
178182
}

tasks/transform_conformance/snapshots/babel.snap.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,13 +1452,37 @@ x Output mismatch
14521452

14531453
# babel-plugin-transform-async-generator-functions (15/19)
14541454
* async-generators/class-method/input.js
1455-
x Output mismatch
1455+
Bindings mismatch:
1456+
after transform: ScopeId(0): ["C", "_this"]
1457+
rebuilt : ScopeId(0): ["C"]
1458+
Bindings mismatch:
1459+
after transform: ScopeId(3): []
1460+
rebuilt : ScopeId(2): ["_this"]
1461+
Symbol scope ID mismatch for "_this":
1462+
after transform: SymbolId(1): ScopeId(0)
1463+
rebuilt : SymbolId(1): ScopeId(2)
14561464

14571465
* async-generators/object-method/input.js
1458-
x Output mismatch
1466+
Bindings mismatch:
1467+
after transform: ScopeId(0): ["_this"]
1468+
rebuilt : ScopeId(0): []
1469+
Bindings mismatch:
1470+
after transform: ScopeId(2): []
1471+
rebuilt : ScopeId(1): ["_this"]
1472+
Symbol scope ID mismatch for "_this":
1473+
after transform: SymbolId(0): ScopeId(0)
1474+
rebuilt : SymbolId(0): ScopeId(1)
14591475

14601476
* async-generators/static-method/input.js
1461-
x Output mismatch
1477+
Bindings mismatch:
1478+
after transform: ScopeId(0): ["C", "_this"]
1479+
rebuilt : ScopeId(0): ["C"]
1480+
Bindings mismatch:
1481+
after transform: ScopeId(3): []
1482+
rebuilt : ScopeId(2): ["_this"]
1483+
Symbol scope ID mismatch for "_this":
1484+
after transform: SymbolId(1): ScopeId(0)
1485+
rebuilt : SymbolId(1): ScopeId(2)
14621486

14631487
* nested/arrows-in-declaration/input.js
14641488
x Output mismatch

tasks/transform_conformance/snapshots/babel_exec.snap.md

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -234,33 +234,3 @@ AssertionError: expected false to be true // Object.is equality
234234

235235
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/14]
236236

237-
238-
⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
239-
AssertionError: expected [Function Object] to be [Function Bar] // Object.is equality
240-
241-
- Expected
242-
+ Received
243-
244-
- [Function Bar]
245-
+ [Function Object]
246-
247-
❯ fixtures/babel-preset-env-test-fixtures-plugins-integration-regression-7064-exec.test.js:13:30
248-
11| }).call(this);
249-
12| _asyncToGenerator(function* () {
250-
13| expect(this.constructor).toBe(Bar);
251-
| ^
252-
14| })();
253-
15| _asyncToGenerator(function* () {
254-
❯ asyncGeneratorStep ../../node_modules/.pnpm/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:17
255-
_next ../../node_modules/.pnpm/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/asyncToGenerator.js:17:9
256-
❯ ../../node_modules/.pnpm/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/asyncToGenerator.js:22:7
257-
❯ ../../node_modules/.pnpm/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/asyncToGenerator.js:14:12
258-
❯ Bar.test fixtures/babel-preset-env-test-fixtures-plugins-integration-regression-7064-exec.test.js:14:6
259-
❯ fixtures/babel-preset-env-test-fixtures-plugins-integration-regression-7064-exec.test.js:20:12
260-
❯ ../../node_modules/.pnpm/@vitest+runner@2.1.2/node_modules/@vitest/runner/dist/index.js:146:14
261-
262-
This error originated in "fixtures/babel-preset-env-test-fixtures-plugins-integration-regression-7064-exec.test.js" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
263-
The latest test that might've caused the error is "fixtures/babel-preset-env-test-fixtures-plugins-integration-regression-7064-exec.test.js". It might mean one of the following:
264-
- The error was thrown, while Vitest was running this test.
265-
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.
266-

0 commit comments

Comments
 (0)