Skip to content

Commit a9260cf

Browse files
7086cmdDunqing
andauthored
feat(transformer): async-to-generator plugin. (#5590)
Tests are still not passed. A lot need to do yet. --------- Co-authored-by: Dunqing <dengqing0821@gmail.com>
1 parent 3556062 commit a9260cf

File tree

9 files changed

+406
-5
lines changed

9 files changed

+406
-5
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
//! ES2017: Async / Await \[WIP\]
2+
//!
3+
//! This plugin transforms async functions to generator functions.
4+
//!
5+
//! ## Example
6+
//!
7+
//! Input:
8+
//! ```js
9+
//! async function foo() {
10+
//! await bar();
11+
//! }
12+
//! const foo2 = async () => {
13+
//! await bar();
14+
//! };
15+
//! async () => {
16+
//! await bar();
17+
//! }
18+
//! ```
19+
//!
20+
//! Output (Currently):
21+
//! ```js
22+
//! function foo() {
23+
//! return _asyncToGenerator(function* () {
24+
//! yield bar();
25+
//! })
26+
//! }
27+
//! const foo2 = () => _asyncToGenerator(function* () {
28+
//! yield bar();
29+
//! }
30+
//! ```
31+
//!
32+
//! ## Implementation
33+
//!
34+
//! Implementation based on [@babel/plugin-transform-async-to-generator](https://babel.dev/docs/babel-plugin-transform-async-to-generator).
35+
//!
36+
//!
37+
//! Reference:
38+
//! * Babel docs: <https://babeljs.io/docs/en/babel-plugin-transform-async-to-generator>
39+
//! * Esbuild implementation: <https://github.com/evanw/esbuild/blob/main/internal/js_parser/js_parser_lower.go#L392>
40+
//! * Babel implementation: <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-async-to-generator>
41+
//! * Babel helper implementation: <https://github.com/babel/babel/blob/main/packages/babel-helper-remap-async-to-generator>
42+
//! * Async / Await TC39 proposal: <https://github.com/tc39/proposal-async-await>
43+
//!
44+
45+
use oxc_ast::ast::{
46+
ArrowFunctionExpression, Expression, Function, FunctionType, Statement, VariableDeclarationKind,
47+
};
48+
use oxc_ast::NONE;
49+
use oxc_span::{Atom, SPAN};
50+
use oxc_syntax::reference::ReferenceFlags;
51+
use oxc_syntax::symbol::SymbolId;
52+
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
53+
54+
pub struct AsyncToGenerator;
55+
56+
impl AsyncToGenerator {
57+
fn get_helper_callee<'a>(
58+
symbol_id: Option<SymbolId>,
59+
ctx: &mut TraverseCtx<'a>,
60+
) -> Expression<'a> {
61+
let ident = ctx.create_reference_id(
62+
SPAN,
63+
Atom::from("babelHelpers"),
64+
symbol_id,
65+
ReferenceFlags::Read,
66+
);
67+
let object = ctx.ast.expression_from_identifier_reference(ident);
68+
let property = ctx.ast.identifier_name(SPAN, Atom::from("asyncToGenerator"));
69+
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false))
70+
}
71+
72+
fn transform_function<'a>(func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) -> Function<'a> {
73+
let babel_helpers_id = ctx.scopes().find_binding(ctx.current_scope_id(), "babelHelpers");
74+
let callee = Self::get_helper_callee(babel_helpers_id, ctx);
75+
let target = ctx.ast.function(
76+
func.r#type,
77+
SPAN,
78+
None,
79+
true,
80+
false,
81+
false,
82+
func.type_parameters.take(),
83+
func.this_param.take(),
84+
ctx.ast.alloc(ctx.ast.formal_parameters(
85+
SPAN,
86+
func.params.kind,
87+
ctx.ast.move_vec(&mut func.params.items),
88+
func.params.rest.take(),
89+
)),
90+
func.return_type.take(),
91+
func.body.take(),
92+
);
93+
let parameters =
94+
ctx.ast.vec1(ctx.ast.argument_expression(ctx.ast.expression_from_function(target)));
95+
let call = ctx.ast.expression_call(SPAN, callee, NONE, parameters, false);
96+
let returns = ctx.ast.return_statement(SPAN, Some(call));
97+
let body = Statement::ReturnStatement(ctx.ast.alloc(returns));
98+
let body = ctx.ast.function_body(SPAN, ctx.ast.vec(), ctx.ast.vec1(body));
99+
let body = ctx.ast.alloc(body);
100+
let params = ctx.ast.formal_parameters(SPAN, func.params.kind, ctx.ast.vec(), NONE);
101+
ctx.ast.function(
102+
FunctionType::FunctionExpression,
103+
SPAN,
104+
None,
105+
false,
106+
false,
107+
false,
108+
func.type_parameters.take(),
109+
func.this_param.take(),
110+
params,
111+
func.return_type.take(),
112+
Some(body),
113+
)
114+
}
115+
}
116+
117+
impl<'a> Traverse<'a> for AsyncToGenerator {
118+
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
119+
if let Expression::AwaitExpression(await_expr) = expr {
120+
// Do not transform top-level await, or in async generator functions.
121+
let in_async_function = ctx
122+
.ancestry
123+
.ancestors()
124+
.find_map(|ance| {
125+
// We need to check if there's async generator or async function.
126+
// If it is async generator, we should not transform the await expression here.
127+
if let Ancestor::FunctionBody(body) = ance {
128+
if *body.r#async() {
129+
Some(!body.generator())
130+
} else {
131+
None
132+
}
133+
} else if let Ancestor::ArrowFunctionExpressionBody(_) = ance {
134+
// Arrow function is never generator.
135+
Some(true)
136+
} else {
137+
None
138+
}
139+
})
140+
.unwrap_or(false);
141+
if in_async_function {
142+
// Move the expression to yield.
143+
*expr = ctx.ast.expression_yield(
144+
SPAN,
145+
false,
146+
Some(ctx.ast.move_expression(&mut await_expr.argument)),
147+
);
148+
}
149+
} else if let Expression::FunctionExpression(func) = expr {
150+
if !func.r#async || func.generator {
151+
return;
152+
}
153+
let new_function = Self::transform_function(func, ctx);
154+
*expr = ctx.ast.expression_from_function(new_function);
155+
}
156+
}
157+
158+
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
159+
if let Statement::FunctionDeclaration(func) = stmt {
160+
if !func.r#async || func.generator {
161+
return;
162+
}
163+
let new_function = Self::transform_function(func, ctx);
164+
if let Some(id) = func.id.take() {
165+
*stmt = ctx.ast.statement_declaration(ctx.ast.declaration_variable(
166+
SPAN,
167+
VariableDeclarationKind::Const,
168+
ctx.ast.vec1(ctx.ast.variable_declarator(
169+
SPAN,
170+
VariableDeclarationKind::Const,
171+
ctx.ast.binding_pattern(
172+
ctx.ast.binding_pattern_kind_from_binding_identifier(id),
173+
NONE,
174+
false,
175+
),
176+
Some(ctx.ast.expression_from_function(new_function)),
177+
false,
178+
)),
179+
false,
180+
));
181+
} else {
182+
*stmt =
183+
ctx.ast.statement_declaration(ctx.ast.declaration_from_function(new_function));
184+
}
185+
}
186+
}
187+
188+
fn exit_arrow_function_expression(
189+
&mut self,
190+
arrow: &mut ArrowFunctionExpression<'a>,
191+
ctx: &mut TraverseCtx<'a>,
192+
) {
193+
if !arrow.r#async {
194+
return;
195+
}
196+
let babel_helpers_id = ctx.scopes().find_binding(ctx.current_scope_id(), "babelHelpers");
197+
let callee = Self::get_helper_callee(babel_helpers_id, ctx);
198+
let body = ctx.ast.function_body(
199+
SPAN,
200+
ctx.ast.move_vec(&mut arrow.body.directives),
201+
ctx.ast.move_vec(&mut arrow.body.statements),
202+
);
203+
let target = ctx.ast.function(
204+
FunctionType::FunctionExpression,
205+
SPAN,
206+
None,
207+
true,
208+
false,
209+
false,
210+
arrow.type_parameters.take(),
211+
NONE,
212+
ctx.ast.alloc(ctx.ast.formal_parameters(
213+
SPAN,
214+
arrow.params.kind,
215+
ctx.ast.move_vec(&mut arrow.params.items),
216+
arrow.params.rest.take(),
217+
)),
218+
arrow.return_type.take(),
219+
Some(body),
220+
);
221+
let parameters =
222+
ctx.ast.vec1(ctx.ast.argument_expression(ctx.ast.expression_from_function(target)));
223+
let call = ctx.ast.expression_call(SPAN, callee, NONE, parameters, false);
224+
let body = ctx.ast.function_body(
225+
SPAN,
226+
ctx.ast.vec(),
227+
ctx.ast.vec1(ctx.ast.statement_expression(SPAN, call)),
228+
);
229+
arrow.body = ctx.ast.alloc(body);
230+
arrow.r#async = false;
231+
arrow.expression = true;
232+
}
233+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
mod async_to_generator;
2+
pub mod options;
3+
4+
use crate::es2017::async_to_generator::AsyncToGenerator;
5+
use crate::es2017::options::ES2017Options;
6+
use oxc_ast::ast::{ArrowFunctionExpression, Expression, Statement};
7+
use oxc_traverse::{Traverse, TraverseCtx};
8+
9+
#[allow(dead_code)]
10+
pub struct ES2017 {
11+
options: ES2017Options,
12+
13+
// Plugins
14+
async_to_generator: AsyncToGenerator,
15+
}
16+
17+
impl ES2017 {
18+
pub fn new(options: ES2017Options) -> ES2017 {
19+
ES2017 { async_to_generator: AsyncToGenerator, options }
20+
}
21+
}
22+
23+
impl<'a> Traverse<'a> for ES2017 {
24+
fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
25+
if self.options.async_to_generator {
26+
self.async_to_generator.exit_expression(node, ctx);
27+
}
28+
}
29+
30+
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
31+
if self.options.async_to_generator {
32+
self.async_to_generator.exit_statement(stmt, ctx);
33+
}
34+
}
35+
36+
fn exit_arrow_function_expression(
37+
&mut self,
38+
node: &mut ArrowFunctionExpression<'a>,
39+
ctx: &mut TraverseCtx<'a>,
40+
) {
41+
if self.options.async_to_generator {
42+
self.async_to_generator.exit_arrow_function_expression(node, ctx);
43+
}
44+
}
45+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use crate::env::{can_enable_plugin, Versions};
2+
use serde::Deserialize;
3+
4+
#[derive(Debug, Default, Clone, Deserialize)]
5+
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
6+
pub struct ES2017Options {
7+
#[serde(skip)]
8+
pub async_to_generator: bool,
9+
}
10+
11+
impl ES2017Options {
12+
pub fn with_async_to_generator(&mut self, enable: bool) -> &mut Self {
13+
self.async_to_generator = enable;
14+
self
15+
}
16+
17+
#[must_use]
18+
pub fn from_targets_and_bugfixes(targets: Option<&Versions>, bugfixes: bool) -> Self {
19+
Self {
20+
async_to_generator: can_enable_plugin(
21+
"transform-async-to-generator",
22+
targets,
23+
bugfixes,
24+
),
25+
}
26+
}
27+
}

crates/oxc_transformer/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod options;
1919
mod env;
2020
mod es2015;
2121
mod es2016;
22+
mod es2017;
2223
mod es2018;
2324
mod es2019;
2425
mod es2020;
@@ -33,6 +34,7 @@ use std::path::Path;
3334

3435
use common::Common;
3536
use es2016::ES2016;
37+
use es2017::ES2017;
3638
use es2018::ES2018;
3739
use es2019::ES2019;
3840
use es2020::ES2020;
@@ -95,6 +97,7 @@ impl<'a> Transformer<'a> {
9597
x2_es2019: ES2019::new(self.options.es2019),
9698
x2_es2018: ES2018::new(self.options.es2018, &self.ctx),
9799
x2_es2016: ES2016::new(self.options.es2016, &self.ctx),
100+
x2_es2017: ES2017::new(self.options.es2017),
98101
x3_es2015: ES2015::new(self.options.es2015),
99102
x4_regexp: RegExp::new(self.options.regexp, &self.ctx),
100103
common: Common::new(&self.ctx),
@@ -113,6 +116,7 @@ struct TransformerImpl<'a, 'ctx> {
113116
x2_es2020: ES2020<'a, 'ctx>,
114117
x2_es2019: ES2019,
115118
x2_es2018: ES2018<'a, 'ctx>,
119+
x2_es2017: ES2017,
116120
x2_es2016: ES2016<'a, 'ctx>,
117121
x3_es2015: ES2015<'a>,
118122
x4_regexp: RegExp<'a, 'ctx>,
@@ -196,6 +200,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
196200

197201
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
198202
self.x1_react.exit_expression(expr, ctx);
203+
self.x2_es2017.exit_expression(expr, ctx);
199204
self.x3_es2015.exit_expression(expr, ctx);
200205
}
201206

@@ -230,6 +235,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
230235
fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
231236
self.x0_typescript.exit_function(func, ctx);
232237
self.x1_react.exit_function(func, ctx);
238+
self.x2_es2017.exit_function(func, ctx);
233239
self.x3_es2015.exit_function(func, ctx);
234240
}
235241

@@ -326,6 +332,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
326332
.push(ctx.ast.statement_return(SPAN, Some(statement.unbox().expression)));
327333
arrow.expression = false;
328334
}
335+
self.x2_es2017.exit_arrow_function_expression(arrow, ctx);
329336
}
330337

331338
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
@@ -334,6 +341,10 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
334341
self.common.exit_statements(stmts, ctx);
335342
}
336343

344+
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
345+
self.x2_es2017.exit_statement(stmt, ctx);
346+
}
347+
337348
fn enter_tagged_template_expression(
338349
&mut self,
339350
expr: &mut TaggedTemplateExpression<'a>,

0 commit comments

Comments
 (0)