mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(transformer): enable ArrowFunctionConverter in async-to-generator and async-generator-functions plugins (#7113)
Part of https://github.com/oxc-project/oxc/pull/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
This commit is contained in:
parent
0b32951637
commit
df772411ea
6 changed files with 90 additions and 64 deletions
|
|
@ -97,6 +97,8 @@ use oxc_syntax::{
|
||||||
};
|
};
|
||||||
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};
|
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};
|
||||||
|
|
||||||
|
use crate::TransformOptions;
|
||||||
|
|
||||||
/// Mode for arrow function conversion
|
/// Mode for arrow function conversion
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ArrowFunctionConverterMode {
|
pub enum ArrowFunctionConverterMode {
|
||||||
|
|
@ -107,23 +109,27 @@ pub enum ArrowFunctionConverterMode {
|
||||||
Enabled,
|
Enabled,
|
||||||
|
|
||||||
/// Only convert async arrow functions
|
/// Only convert async arrow functions
|
||||||
#[expect(unused)]
|
|
||||||
AsyncOnly,
|
AsyncOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ArrowFunctionConverterOptions {
|
|
||||||
pub mode: ArrowFunctionConverterMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ArrowFunctionConverter<'a> {
|
pub struct ArrowFunctionConverter<'a> {
|
||||||
mode: ArrowFunctionConverterMode,
|
mode: ArrowFunctionConverterMode,
|
||||||
this_var_stack: SparseStack<BoundIdentifier<'a>>,
|
this_var_stack: SparseStack<BoundIdentifier<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ArrowFunctionConverter<'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`
|
// `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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,7 +260,11 @@ impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
|
||||||
return;
|
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) =
|
let Expression::ArrowFunctionExpression(arrow_function_expr) =
|
||||||
ctx.ast.move_expression(expr)
|
ctx.ast.move_expression(expr)
|
||||||
else {
|
else {
|
||||||
|
|
@ -272,13 +282,18 @@ impl<'a> ArrowFunctionConverter<'a> {
|
||||||
self.mode == ArrowFunctionConverterMode::Disabled
|
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(
|
fn get_this_identifier(
|
||||||
&mut self,
|
&mut self,
|
||||||
span: Span,
|
span: Span,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Option<ArenaBox<'a, IdentifierReference<'a>>> {
|
) -> Option<ArenaBox<'a, IdentifierReference<'a>>> {
|
||||||
// Find arrow function we are currently in (if we are)
|
// 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
|
// 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
|
// `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> {
|
||||||
|
|
||||||
/// Find arrow function we are currently in, if it's between current node, and where `this` is bound.
|
/// Find arrow function we are currently in, if it's between current node, and where `this` is bound.
|
||||||
/// Return its `ScopeId`.
|
/// 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:
|
// `this` inside a class resolves to `this` *outside* the class in:
|
||||||
// * `extends` clause
|
// * `extends` clause
|
||||||
// * Computed method key
|
// * Computed method key
|
||||||
|
|
@ -346,13 +361,13 @@ impl<'a> ArrowFunctionConverter<'a> {
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// So in this loop, we only exit when we encounter one of the above.
|
// 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 {
|
match ancestor {
|
||||||
// Top level
|
// Top level
|
||||||
Ancestor::ProgramBody(_)
|
Ancestor::ProgramBody(_)
|
||||||
// Function (includes class method body)
|
// Function (includes class method body)
|
||||||
| Ancestor::FunctionParams(_)
|
| Ancestor::FunctionParams(_)
|
||||||
| Ancestor::FunctionBody(_)
|
|
||||||
// Class property body
|
// Class property body
|
||||||
| Ancestor::PropertyDefinitionValue(_)
|
| Ancestor::PropertyDefinitionValue(_)
|
||||||
// Class accessor property body
|
// Class accessor property body
|
||||||
|
|
@ -361,10 +376,29 @@ impl<'a> ArrowFunctionConverter<'a> {
|
||||||
| Ancestor::StaticBlockBody(_) => return None,
|
| Ancestor::StaticBlockBody(_) => return None,
|
||||||
// Arrow function
|
// Arrow function
|
||||||
Ancestor::ArrowFunctionExpressionParams(func) => {
|
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) => {
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
//! Utility transforms which are in common between other transforms.
|
//! Utility transforms which are in common between other transforms.
|
||||||
|
|
||||||
use arrow_function_converter::{
|
use arrow_function_converter::ArrowFunctionConverter;
|
||||||
ArrowFunctionConverter, ArrowFunctionConverterMode, ArrowFunctionConverterOptions,
|
|
||||||
};
|
|
||||||
use oxc_allocator::Vec as ArenaVec;
|
use oxc_allocator::Vec as ArenaVec;
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
use oxc_traverse::{Traverse, TraverseCtx};
|
use oxc_traverse::{Traverse, TraverseCtx};
|
||||||
|
|
@ -31,23 +29,12 @@ pub struct Common<'a, 'ctx> {
|
||||||
|
|
||||||
impl<'a, 'ctx> Common<'a, 'ctx> {
|
impl<'a, 'ctx> Common<'a, 'ctx> {
|
||||||
pub fn new(options: &TransformOptions, ctx: &'ctx TransformCtx<'a>) -> Self {
|
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 {
|
Self {
|
||||||
module_imports: ModuleImports::new(ctx),
|
module_imports: ModuleImports::new(ctx),
|
||||||
var_declarations: VarDeclarations::new(ctx),
|
var_declarations: VarDeclarations::new(ctx),
|
||||||
statement_injector: StatementInjector::new(ctx),
|
statement_injector: StatementInjector::new(ctx),
|
||||||
top_level_statements: TopLevelStatements::new(ctx),
|
top_level_statements: TopLevelStatements::new(ctx),
|
||||||
arrow_function_converter: ArrowFunctionConverter::new(
|
arrow_function_converter: ArrowFunctionConverter::new(options),
|
||||||
&arrow_function_converter_options,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>) {
|
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);
|
self.executor.transform_function_for_method_definition(func, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,11 @@ impl<'a, 'ctx> Traverse<'a> for AsyncGeneratorFunctions<'a, 'ctx> {
|
||||||
if func.r#async
|
if func.r#async
|
||||||
&& func.generator
|
&& func.generator
|
||||||
&& !func.is_typescript_syntax()
|
&& !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);
|
self.executor.transform_function_for_method_definition(func, ctx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1452,13 +1452,37 @@ x Output mismatch
|
||||||
|
|
||||||
# babel-plugin-transform-async-generator-functions (15/19)
|
# babel-plugin-transform-async-generator-functions (15/19)
|
||||||
* async-generators/class-method/input.js
|
* 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
|
* 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
|
* 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
|
* nested/arrows-in-declaration/input.js
|
||||||
x Output mismatch
|
x Output mismatch
|
||||||
|
|
|
||||||
|
|
@ -234,33 +234,3 @@ AssertionError: expected false to be true // Object.is equality
|
||||||
|
|
||||||
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/14]⎯
|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/14]⎯
|
||||||
|
|
||||||
|
|
||||||
⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
|
|
||||||
AssertionError: expected [Function Object] to be [Function Bar] // Object.is equality
|
|
||||||
|
|
||||||
- Expected
|
|
||||||
+ Received
|
|
||||||
|
|
||||||
- [Function Bar]
|
|
||||||
+ [Function Object]
|
|
||||||
|
|
||||||
❯ fixtures/babel-preset-env-test-fixtures-plugins-integration-regression-7064-exec.test.js:13:30
|
|
||||||
11| }).call(this);
|
|
||||||
12| _asyncToGenerator(function* () {
|
|
||||||
13| expect(this.constructor).toBe(Bar);
|
|
||||||
| ^
|
|
||||||
14| })();
|
|
||||||
15| _asyncToGenerator(function* () {
|
|
||||||
❯ asyncGeneratorStep ../../node_modules/.pnpm/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:17
|
|
||||||
❯ _next ../../node_modules/.pnpm/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/asyncToGenerator.js:17:9
|
|
||||||
❯ ../../node_modules/.pnpm/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/asyncToGenerator.js:22:7
|
|
||||||
❯ ../../node_modules/.pnpm/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/asyncToGenerator.js:14:12
|
|
||||||
❯ Bar.test fixtures/babel-preset-env-test-fixtures-plugins-integration-regression-7064-exec.test.js:14:6
|
|
||||||
❯ fixtures/babel-preset-env-test-fixtures-plugins-integration-regression-7064-exec.test.js:20:12
|
|
||||||
❯ ../../node_modules/.pnpm/@vitest+runner@2.1.2/node_modules/@vitest/runner/dist/index.js:146:14
|
|
||||||
|
|
||||||
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.
|
|
||||||
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:
|
|
||||||
- The error was thrown, while Vitest was running this test.
|
|
||||||
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue