|
| 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 | +} |
0 commit comments