refactor(transformer): move implementation of ArrowFunction to common/ArrowFunctionConverter (#7107)

Part of #7074

In order can reuse the ability of the `ArrowFunction` plugin, we moved out the implementation to common, then we can use use in other plugins
This commit is contained in:
Dunqing 2024-11-04 16:32:50 +00:00
parent e04ee97870
commit ff8bd50d38
5 changed files with 533 additions and 365 deletions

View file

@ -0,0 +1,439 @@
//! Arrow Functions Converter
//!
//! This converter transforms arrow functions (`() => {}`) to function expressions (`function () {}`).
//!
//! ## Example
//!
//! Input:
//! ```js
//! var a = () => {};
//! var a = b => b;
//!
//! const double = [1, 2, 3].map(num => num * 2);
//! console.log(double); // [2,4,6]
//!
//! var bob = {
//! name: "Bob",
//! friends: ["Sally", "Tom"],
//! printFriends() {
//! this.friends.forEach(f => console.log(this.name + " knows " + f));
//! },
//! };
//! console.log(bob.printFriends());
//! ```
//!
//! Output:
//! ```js
//! var a = function() {};
//! var a = function(b) { return b; };
//!
//! const double = [1, 2, 3].map(function(num) {
//! return num * 2;
//! });
//! console.log(double); // [2,4,6]
//!
//! var bob = {
//! name: "Bob",
//! friends: ["Sally", "Tom"],
//! printFriends() {
//! var _this = this;
//! this.friends.forEach(function(f) {
//! return console.log(_this.name + " knows " + f);
//! });
//! },
//! };
//! console.log(bob.printFriends());
//! ```
//!
//! #### Example
//!
//! Using spec mode with the above example produces:
//!
//! ```js
//! var _this = this;
//!
//! var a = function a() {
//! babelHelpers.newArrowCheck(this, _this);
//! }.bind(this);
//! var a = function a(b) {
//! babelHelpers.newArrowCheck(this, _this);
//! return b;
//! }.bind(this);
//!
//! const double = [1, 2, 3].map(
//! function(num) {
//! babelHelpers.newArrowCheck(this, _this);
//! return num * 2;
//! }.bind(this)
//! );
//! console.log(double); // [2,4,6]
//!
//! var bob = {
//! name: "Bob",
//! friends: ["Sally", "Tom"],
//! printFriends() {
//! var _this2 = this;
//! this.friends.forEach(
//! function(f) {
//! babelHelpers.newArrowCheck(this, _this2);
//! return console.log(this.name + " knows " + f);
//! }.bind(this)
//! );
//! },
//! };
//! console.log(bob.printFriends());
//! ```
//!
//! The Implementation based on
//! <https://github.com/babel/babel/blob/d20b314c14533ab86351ecf6ca6b7296b66a57b3/packages/babel-traverse/src/path/conversion.ts#L170-L247>
use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec};
use oxc_ast::ast::*;
use oxc_data_structures::stack::SparseStack;
use oxc_span::SPAN;
use oxc_syntax::{
scope::{ScopeFlags, ScopeId},
symbol::SymbolFlags,
};
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};
/// Mode for arrow function conversion
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArrowFunctionConverterMode {
/// Disable arrow function conversion
Disabled,
/// Convert all arrow functions to regular functions
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 {
// `SparseStack` is created with 1 empty entry, for `Program`
Self { mode: options.mode, this_var_stack: SparseStack::new() }
}
}
impl<'a> Traverse<'a> for ArrowFunctionConverter<'a> {
// Note: No visitors for `TSModuleBlock` because `this` is not legal in TS module blocks.
// <https://www.typescriptlang.org/play/?#code/HYQwtgpgzgDiDGEAEAxA9mpBvAsAKCSXjWCgBckANJAXiQAoBKWgPiTIAsBLKAbnwC++fGDQATAK4AbZACEQAJ2z5CxUhWp0mrdtz6D8QA>
/// Insert `var _this = this;` for the global scope.
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
if self.is_disabled() {
return;
}
if let Some(this_var) = self.this_var_stack.take_last() {
self.insert_this_var_statement_at_the_top_of_statements(
&mut program.body,
&this_var,
ctx,
);
}
debug_assert!(self.this_var_stack.len() == 1);
debug_assert!(self.this_var_stack.last().is_none());
}
fn enter_function(&mut self, _func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) {
if self.is_disabled() {
return;
}
self.this_var_stack.push(None);
}
/// ```ts
/// function a(){
/// return () => console.log(this);
/// }
/// // to
/// function a(){
/// var _this = this;
/// return function() { return console.log(_this); };
/// }
/// ```
/// Insert the var _this = this; statement outside the arrow function
fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
if self.is_disabled() {
return;
}
if let Some(this_var) = self.this_var_stack.pop() {
let Some(body) = &mut func.body else { unreachable!() };
self.insert_this_var_statement_at_the_top_of_statements(
&mut body.statements,
&this_var,
ctx,
);
}
}
fn enter_static_block(&mut self, _block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) {
if self.is_disabled() {
return;
}
self.this_var_stack.push(None);
}
fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
if self.is_disabled() {
return;
}
if let Some(this_var) = self.this_var_stack.pop() {
self.insert_this_var_statement_at_the_top_of_statements(
&mut block.body,
&this_var,
ctx,
);
}
}
fn enter_jsx_element_name(
&mut self,
element_name: &mut JSXElementName<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.is_disabled() {
return;
}
if let JSXElementName::ThisExpression(this) = element_name {
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*element_name = JSXElementName::IdentifierReference(ident);
}
};
}
fn enter_jsx_member_expression_object(
&mut self,
object: &mut JSXMemberExpressionObject<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.is_disabled() {
return;
}
if let JSXMemberExpressionObject::ThisExpression(this) = object {
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*object = JSXMemberExpressionObject::IdentifierReference(ident);
}
}
}
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.is_disabled() {
return;
}
if let Expression::ThisExpression(this) = expr {
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*expr = Expression::Identifier(ident);
}
}
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.is_disabled() {
return;
}
if let Expression::ArrowFunctionExpression(_) = expr {
let Expression::ArrowFunctionExpression(arrow_function_expr) =
ctx.ast.move_expression(expr)
else {
unreachable!()
};
*expr = self.transform_arrow_function_expression(arrow_function_expr.unbox(), ctx);
}
}
}
impl<'a> ArrowFunctionConverter<'a> {
/// Check if arrow function conversion is disabled
fn is_disabled(&self) -> bool {
self.mode == ArrowFunctionConverterMode::Disabled
}
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)?;
// 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
// scope. But this does not match output for some of Babel's test cases.
// <https://github.com/oxc-project/oxc/pull/5840>
let this_var = self.this_var_stack.last_or_init(|| {
let target_scope_id = ctx
.scopes()
.ancestors(arrow_scope_id)
// Skip arrow function scope
.skip(1)
.find(|&scope_id| {
let scope_flags = ctx.scopes().get_flags(scope_id);
scope_flags.intersects(
ScopeFlags::Function | ScopeFlags::Top | ScopeFlags::ClassStaticBlock,
) && !scope_flags.contains(ScopeFlags::Arrow)
})
.unwrap();
ctx.generate_uid("this", target_scope_id, SymbolFlags::FunctionScopedVariable)
});
Some(ctx.ast.alloc(this_var.create_spanned_read_reference(span, ctx)))
}
/// 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> {
// `this` inside a class resolves to `this` *outside* the class in:
// * `extends` clause
// * Computed method key
// * Computed property key
// * Computed accessor property key (but `this` in this position is not legal TS)
//
// ```js
// // All these `this` refer to global `this`
// class C extends this {
// [this] = 123;
// static [this] = 123;
// [this]() {}
// static [this]() {}
// accessor [this] = 123;
// static accessor [this] = 123;
// }
// ```
//
// `this` resolves to the class / class instance (i.e. `this` defined *within* the class) in:
// * Method body
// * Method param
// * Property value
// * Static block
//
// ```js
// // All these `this` refer to `this` defined within the class
// class C {
// a = this;
// static b = this;
// #c = this;
// d() { this }
// static e() { this }
// #f() { this }
// g(x = this) {}
// accessor h = this;
// static accessor i = this;
// static { this }
// }
// ```
//
// So in this loop, we only exit when we encounter one of the above.
for ancestor in ctx.ancestors() {
match ancestor {
// Top level
Ancestor::ProgramBody(_)
// Function (includes class method body)
| Ancestor::FunctionParams(_)
| Ancestor::FunctionBody(_)
// Class property body
| Ancestor::PropertyDefinitionValue(_)
// Class accessor property body
| Ancestor::AccessorPropertyValue(_)
// Class static block
| Ancestor::StaticBlockBody(_) => return None,
// Arrow function
Ancestor::ArrowFunctionExpressionParams(func) => {
return Some(func.scope_id().get().unwrap())
}
Ancestor::ArrowFunctionExpressionBody(func) => {
return Some(func.scope_id().get().unwrap())
}
_ => {}
}
}
unreachable!();
}
#[expect(clippy::unused_self)]
fn transform_arrow_function_expression(
&mut self,
arrow_function_expr: ArrowFunctionExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let mut body = arrow_function_expr.body;
if arrow_function_expr.expression {
assert!(body.statements.len() == 1);
let stmt = body.statements.pop().unwrap();
let Statement::ExpressionStatement(stmt) = stmt else { unreachable!() };
let stmt = stmt.unbox();
let return_statement = ctx.ast.statement_return(stmt.span, Some(stmt.expression));
body.statements.push(return_statement);
}
let scope_id = arrow_function_expr.scope_id.get().unwrap();
let flags = ctx.scopes_mut().get_flags_mut(scope_id);
*flags &= !ScopeFlags::Arrow;
Expression::FunctionExpression(ctx.ast.alloc_function_with_scope_id(
FunctionType::FunctionExpression,
arrow_function_expr.span,
None,
false,
arrow_function_expr.r#async,
false,
arrow_function_expr.type_parameters,
None::<TSThisParameter<'a>>,
arrow_function_expr.params,
arrow_function_expr.return_type,
Some(body),
scope_id,
))
}
/// Insert `var _this = this;` at the top of the statements.
#[expect(clippy::unused_self)]
fn insert_this_var_statement_at_the_top_of_statements(
&mut self,
statements: &mut ArenaVec<'a, Statement<'a>>,
this_var: &BoundIdentifier<'a>,
ctx: &TraverseCtx<'a>,
) {
let variable_declarator = ctx.ast.variable_declarator(
SPAN,
VariableDeclarationKind::Var,
this_var.create_binding_pattern(ctx),
Some(ctx.ast.expression_this(SPAN)),
false,
);
let stmt = ctx.ast.alloc_variable_declaration(
SPAN,
VariableDeclarationKind::Var,
ctx.ast.vec1(variable_declarator),
false,
);
let stmt = Statement::VariableDeclaration(stmt);
statements.insert(0, stmt);
}
}

View file

@ -1,11 +1,15 @@
//! Utility transforms which are in common between other transforms.
use arrow_function_converter::{
ArrowFunctionConverter, ArrowFunctionConverterMode, ArrowFunctionConverterOptions,
};
use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};
use crate::TransformCtx;
use crate::{TransformCtx, TransformOptions};
pub mod arrow_function_converter;
pub mod helper_loader;
pub mod module_imports;
pub mod statement_injector;
@ -22,15 +26,28 @@ pub struct Common<'a, 'ctx> {
var_declarations: VarDeclarations<'a, 'ctx>,
statement_injector: StatementInjector<'a, 'ctx>,
top_level_statements: TopLevelStatements<'a, 'ctx>,
arrow_function_converter: ArrowFunctionConverter<'a>,
}
impl<'a, 'ctx> Common<'a, 'ctx> {
pub fn new(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 {
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,
),
}
}
}
@ -40,6 +57,7 @@ impl<'a, 'ctx> Traverse<'a> for Common<'a, 'ctx> {
self.module_imports.exit_program(program, ctx);
self.var_declarations.exit_program(program, ctx);
self.top_level_statements.exit_program(program, ctx);
self.arrow_function_converter.exit_program(program, ctx);
}
fn enter_statements(
@ -58,4 +76,44 @@ impl<'a, 'ctx> Traverse<'a> for Common<'a, 'ctx> {
self.var_declarations.exit_statements(stmts, ctx);
self.statement_injector.exit_statements(stmts, ctx);
}
fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
self.arrow_function_converter.enter_function(func, ctx);
}
fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
self.arrow_function_converter.exit_function(func, ctx);
}
fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
self.arrow_function_converter.enter_static_block(block, ctx);
}
fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
self.arrow_function_converter.exit_static_block(block, ctx);
}
fn enter_jsx_element_name(
&mut self,
element_name: &mut JSXElementName<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.arrow_function_converter.enter_jsx_element_name(element_name, ctx);
}
fn enter_jsx_member_expression_object(
&mut self,
object: &mut JSXMemberExpressionObject<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.arrow_function_converter.enter_jsx_member_expression_object(object, ctx);
}
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.arrow_function_converter.enter_expression(expr, ctx);
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.arrow_function_converter.exit_expression(expr, ctx);
}
}

View file

@ -117,7 +117,8 @@
//!
//! ## Implementation
//!
//! Implementation based on [@babel/plugin-transform-arrow-functions](https://babel.dev/docs/babel-plugin-transform-arrow-functions).
//! The implementation is placed in [`crate::common::arrow_function_converter::ArrowFunctionConverter`],
//! which can be used in other plugins.
//!
//! ## References:
//!
@ -126,15 +127,9 @@
use serde::Deserialize;
use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec};
use oxc_ast::ast::*;
use oxc_data_structures::stack::SparseStack;
use oxc_span::SPAN;
use oxc_syntax::{
scope::{ScopeFlags, ScopeId},
symbol::SymbolFlags,
};
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};
use oxc_traverse::Traverse;
use crate::context::TransformCtx;
#[derive(Debug, Default, Clone, Copy, Deserialize)]
pub struct ArrowFunctionsOptions {
@ -146,284 +141,15 @@ pub struct ArrowFunctionsOptions {
pub spec: bool,
}
pub struct ArrowFunctions<'a> {
pub struct ArrowFunctions<'a, 'ctx> {
_options: ArrowFunctionsOptions,
this_var_stack: SparseStack<BoundIdentifier<'a>>,
_ctx: &'ctx TransformCtx<'a>,
}
impl<'a> ArrowFunctions<'a> {
pub fn new(options: ArrowFunctionsOptions) -> Self {
// `SparseStack` is created with 1 empty entry, for `Program`
Self { _options: options, this_var_stack: SparseStack::new() }
impl<'a, 'ctx> ArrowFunctions<'a, 'ctx> {
pub fn new(options: ArrowFunctionsOptions, ctx: &'ctx TransformCtx<'a>) -> Self {
Self { _options: options, _ctx: ctx }
}
}
impl<'a> Traverse<'a> for ArrowFunctions<'a> {
// Note: No visitors for `TSModuleBlock` because `this` is not legal in TS module blocks.
// <https://www.typescriptlang.org/play/?#code/HYQwtgpgzgDiDGEAEAxA9mpBvAsAKCSXjWCgBckANJAXiQAoBKWgPiTIAsBLKAbnwC++fGDQATAK4AbZACEQAJ2z5CxUhWp0mrdtz6D8QA>
/// Insert `var _this = this;` for the global scope.
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(this_var) = self.this_var_stack.take_last() {
self.insert_this_var_statement_at_the_top_of_statements(
&mut program.body,
&this_var,
ctx,
);
}
debug_assert!(self.this_var_stack.len() == 1);
debug_assert!(self.this_var_stack.last().is_none());
}
fn enter_function(&mut self, _func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) {
self.this_var_stack.push(None);
}
/// ```ts
/// function a(){
/// return () => console.log(this);
/// }
/// // to
/// function a(){
/// var _this = this;
/// return function() { return console.log(_this); };
/// }
/// ```
/// Insert the var _this = this; statement outside the arrow function
fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(this_var) = self.this_var_stack.pop() {
let Some(body) = &mut func.body else { unreachable!() };
self.insert_this_var_statement_at_the_top_of_statements(
&mut body.statements,
&this_var,
ctx,
);
}
}
fn enter_static_block(&mut self, _block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) {
self.this_var_stack.push(None);
}
fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(this_var) = self.this_var_stack.pop() {
self.insert_this_var_statement_at_the_top_of_statements(
&mut block.body,
&this_var,
ctx,
);
}
}
fn enter_jsx_element_name(
&mut self,
element_name: &mut JSXElementName<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if let JSXElementName::ThisExpression(this) = element_name {
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*element_name = JSXElementName::IdentifierReference(ident);
}
};
}
fn enter_jsx_member_expression_object(
&mut self,
object: &mut JSXMemberExpressionObject<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if let JSXMemberExpressionObject::ThisExpression(this) = object {
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*object = JSXMemberExpressionObject::IdentifierReference(ident);
}
}
}
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if let Expression::ThisExpression(this) = expr {
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*expr = Expression::Identifier(ident);
}
}
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if let Expression::ArrowFunctionExpression(_) = expr {
let Expression::ArrowFunctionExpression(arrow_function_expr) =
ctx.ast.move_expression(expr)
else {
unreachable!()
};
*expr = self.transform_arrow_function_expression(arrow_function_expr.unbox(), ctx);
}
}
}
impl<'a> ArrowFunctions<'a> {
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)?;
// 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
// scope. But this does not match output for some of Babel's test cases.
// <https://github.com/oxc-project/oxc/pull/5840>
let this_var = self.this_var_stack.last_or_init(|| {
let target_scope_id = ctx
.scopes()
.ancestors(arrow_scope_id)
// Skip arrow function scope
.skip(1)
.find(|&scope_id| {
let scope_flags = ctx.scopes().get_flags(scope_id);
scope_flags.intersects(
ScopeFlags::Function | ScopeFlags::Top | ScopeFlags::ClassStaticBlock,
) && !scope_flags.contains(ScopeFlags::Arrow)
})
.unwrap();
ctx.generate_uid("this", target_scope_id, SymbolFlags::FunctionScopedVariable)
});
Some(ctx.ast.alloc(this_var.create_spanned_read_reference(span, ctx)))
}
/// 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> {
// `this` inside a class resolves to `this` *outside* the class in:
// * `extends` clause
// * Computed method key
// * Computed property key
// * Computed accessor property key (but `this` in this position is not legal TS)
//
// ```js
// // All these `this` refer to global `this`
// class C extends this {
// [this] = 123;
// static [this] = 123;
// [this]() {}
// static [this]() {}
// accessor [this] = 123;
// static accessor [this] = 123;
// }
// ```
//
// `this` resolves to the class / class instance (i.e. `this` defined *within* the class) in:
// * Method body
// * Method param
// * Property value
// * Static block
//
// ```js
// // All these `this` refer to `this` defined within the class
// class C {
// a = this;
// static b = this;
// #c = this;
// d() { this }
// static e() { this }
// #f() { this }
// g(x = this) {}
// accessor h = this;
// static accessor i = this;
// static { this }
// }
// ```
//
// So in this loop, we only exit when we encounter one of the above.
for ancestor in ctx.ancestors() {
match ancestor {
// Top level
Ancestor::ProgramBody(_)
// Function (includes class method body)
| Ancestor::FunctionParams(_)
| Ancestor::FunctionBody(_)
// Class property body
| Ancestor::PropertyDefinitionValue(_)
// Class accessor property body
| Ancestor::AccessorPropertyValue(_)
// Class static block
| Ancestor::StaticBlockBody(_) => return None,
// Arrow function
Ancestor::ArrowFunctionExpressionParams(func) => {
return Some(func.scope_id().get().unwrap())
}
Ancestor::ArrowFunctionExpressionBody(func) => {
return Some(func.scope_id().get().unwrap())
}
_ => {}
}
}
unreachable!();
}
#[expect(clippy::unused_self)]
fn transform_arrow_function_expression(
&mut self,
arrow_function_expr: ArrowFunctionExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let mut body = arrow_function_expr.body;
if arrow_function_expr.expression {
assert!(body.statements.len() == 1);
let stmt = body.statements.pop().unwrap();
let Statement::ExpressionStatement(stmt) = stmt else { unreachable!() };
let stmt = stmt.unbox();
let return_statement = ctx.ast.statement_return(stmt.span, Some(stmt.expression));
body.statements.push(return_statement);
}
let scope_id = arrow_function_expr.scope_id.get().unwrap();
let flags = ctx.scopes_mut().get_flags_mut(scope_id);
*flags &= !ScopeFlags::Arrow;
Expression::FunctionExpression(ctx.ast.alloc_function_with_scope_id(
FunctionType::FunctionExpression,
arrow_function_expr.span,
None,
false,
arrow_function_expr.r#async,
false,
arrow_function_expr.type_parameters,
None::<TSThisParameter<'a>>,
arrow_function_expr.params,
arrow_function_expr.return_type,
Some(body),
scope_id,
))
}
/// Insert `var _this = this;` at the top of the statements.
#[expect(clippy::unused_self)]
fn insert_this_var_statement_at_the_top_of_statements(
&mut self,
statements: &mut ArenaVec<'a, Statement<'a>>,
this_var: &BoundIdentifier<'a>,
ctx: &TraverseCtx<'a>,
) {
let variable_declarator = ctx.ast.variable_declarator(
SPAN,
VariableDeclarationKind::Var,
this_var.create_binding_pattern(ctx),
Some(ctx.ast.expression_this(SPAN)),
false,
);
let stmt = ctx.ast.alloc_variable_declaration(
SPAN,
VariableDeclarationKind::Var,
ctx.ast.vec1(variable_declarator),
false,
);
let stmt = Statement::VariableDeclaration(stmt);
statements.insert(0, stmt);
}
}
impl<'a, 'ctx> Traverse<'a> for ArrowFunctions<'a, 'ctx> {}

View file

@ -1,5 +1,4 @@
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};
use oxc_traverse::Traverse;
mod arrow_functions;
mod options;
@ -7,78 +6,24 @@ mod options;
pub use arrow_functions::{ArrowFunctions, ArrowFunctionsOptions};
pub use options::ES2015Options;
pub struct ES2015<'a> {
use crate::context::TransformCtx;
pub struct ES2015<'a, 'ctx> {
#[expect(unused)]
options: ES2015Options,
// Plugins
arrow_functions: ArrowFunctions<'a>,
#[expect(unused)]
arrow_functions: ArrowFunctions<'a, 'ctx>,
}
impl<'a> ES2015<'a> {
pub fn new(options: ES2015Options) -> Self {
impl<'a, 'ctx> ES2015<'a, 'ctx> {
pub fn new(options: ES2015Options, ctx: &'ctx TransformCtx<'a>) -> Self {
Self {
arrow_functions: ArrowFunctions::new(options.arrow_function.unwrap_or_default()),
arrow_functions: ArrowFunctions::new(options.arrow_function.unwrap_or_default(), ctx),
options,
}
}
}
impl<'a> Traverse<'a> for ES2015<'a> {
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.exit_program(program, ctx);
}
}
fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_function(func, ctx);
}
}
fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.exit_function(func, ctx);
}
}
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_expression(expr, ctx);
}
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.exit_expression(expr, ctx);
}
}
fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_static_block(block, ctx);
}
}
fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.exit_static_block(block, ctx);
}
}
fn enter_jsx_element_name(&mut self, node: &mut JSXElementName<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_jsx_element_name(node, ctx);
}
}
fn enter_jsx_member_expression_object(
&mut self,
node: &mut JSXMemberExpressionObject<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_jsx_member_expression_object(node, ctx);
}
}
}
impl<'a, 'ctx> Traverse<'a> for ES2015<'a, 'ctx> {}

View file

@ -94,6 +94,7 @@ impl<'a> Transformer<'a> {
jsx::update_options_with_comments(&program.comments, &mut self.options, &self.ctx);
let mut transformer = TransformerImpl {
common: Common::new(&self.options, &self.ctx),
x0_typescript: program
.source_type
.is_typescript()
@ -106,9 +107,8 @@ impl<'a> Transformer<'a> {
x2_es2018: ES2018::new(self.options.env.es2018, &self.ctx),
x2_es2016: ES2016::new(self.options.env.es2016, &self.ctx),
x2_es2017: ES2017::new(self.options.env.es2017, &self.ctx),
x3_es2015: ES2015::new(self.options.env.es2015),
x3_es2015: ES2015::new(self.options.env.es2015, &self.ctx),
x4_regexp: RegExp::new(self.options.env.regexp, &self.ctx),
common: Common::new(&self.ctx),
};
let (symbols, scopes) = traverse_mut(&mut transformer, allocator, program, symbols, scopes);
@ -127,7 +127,8 @@ struct TransformerImpl<'a, 'ctx> {
x2_es2018: ES2018<'a, 'ctx>,
x2_es2017: ES2017<'a, 'ctx>,
x2_es2016: ES2016<'a, 'ctx>,
x3_es2015: ES2015<'a>,
#[expect(unused)]
x3_es2015: ES2015<'a, 'ctx>,
x4_regexp: RegExp<'a, 'ctx>,
common: Common<'a, 'ctx>,
}
@ -145,7 +146,6 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
if let Some(typescript) = self.x0_typescript.as_mut() {
typescript.exit_program(program, ctx);
}
self.x3_es2015.exit_program(program, ctx);
self.common.exit_program(program, ctx);
}
@ -198,11 +198,11 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
}
fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
self.x3_es2015.enter_static_block(block, ctx);
self.common.enter_static_block(block, ctx);
}
fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
self.x3_es2015.exit_static_block(block, ctx);
self.common.exit_static_block(block, ctx);
}
fn enter_ts_module_declaration(
@ -224,15 +224,15 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
self.x2_es2020.enter_expression(expr, ctx);
self.x2_es2018.enter_expression(expr, ctx);
self.x2_es2016.enter_expression(expr, ctx);
self.x3_es2015.enter_expression(expr, ctx);
self.x4_regexp.enter_expression(expr, ctx);
self.common.enter_expression(expr, ctx);
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.x1_jsx.exit_expression(expr, ctx);
self.x2_es2018.exit_expression(expr, ctx);
self.x2_es2017.exit_expression(expr, ctx);
self.x3_es2015.exit_expression(expr, ctx);
self.common.exit_expression(expr, ctx);
}
fn enter_simple_assignment_target(
@ -267,7 +267,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
self.x2_es2018.enter_function(func, ctx);
self.x3_es2015.enter_function(func, ctx);
self.common.enter_function(func, ctx);
}
fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
@ -277,7 +277,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
self.x1_jsx.exit_function(func, ctx);
self.x2_es2018.exit_function(func, ctx);
self.x2_es2017.exit_function(func, ctx);
self.x3_es2015.exit_function(func, ctx);
self.common.exit_function(func, ctx);
}
fn enter_jsx_element(&mut self, node: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) {
@ -287,7 +287,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
}
fn enter_jsx_element_name(&mut self, node: &mut JSXElementName<'a>, ctx: &mut TraverseCtx<'a>) {
self.x3_es2015.enter_jsx_element_name(node, ctx);
self.common.enter_jsx_element_name(node, ctx);
}
fn enter_jsx_member_expression_object(
@ -295,7 +295,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
node: &mut JSXMemberExpressionObject<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x3_es2015.enter_jsx_member_expression_object(node, ctx);
self.common.enter_jsx_member_expression_object(node, ctx);
}
fn enter_jsx_fragment(&mut self, node: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) {