Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 89 additions & 98 deletions crates/oxc_transformer/src/es2015/arrow_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@
use oxc_allocator::Vec;
use oxc_ast::{ast::*, NONE};
use oxc_span::SPAN;
use oxc_syntax::{scope::ScopeFlags, symbol::SymbolFlags};
use oxc_syntax::{
scope::{ScopeFlags, ScopeId},
symbol::SymbolFlags,
};
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
use serde::Deserialize;

Expand All @@ -93,18 +96,15 @@ pub struct ArrowFunctions<'a> {
ctx: Ctx<'a>,
_options: ArrowFunctionsOptions,
this_var_stack: std::vec::Vec<Option<BoundIdentifier<'a>>>,
/// Stack to keep track of whether we are inside an arrow function or not.
inside_arrow_function_stack: std::vec::Vec<bool>,
}

impl<'a> ArrowFunctions<'a> {
pub fn new(options: ArrowFunctionsOptions, ctx: Ctx<'a>) -> Self {
Self {
ctx,
_options: options,
// Initial entries for `Program` scope
// Initial entry for `Program` scope
this_var_stack: vec![None],
inside_arrow_function_stack: vec![false],
}
}
}
Expand All @@ -115,8 +115,6 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> {

/// Insert `var _this = this;` for the global scope.
fn exit_program(&mut self, program: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) {
debug_assert!(self.inside_arrow_function_stack.len() == 1);

assert!(self.this_var_stack.len() == 1);
let this_var = self.this_var_stack.pop().unwrap();
if let Some(this_var) = this_var {
Expand All @@ -127,7 +125,6 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> {
fn enter_function(&mut self, func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) {
if func.body.is_some() {
self.this_var_stack.push(None);
self.inside_arrow_function_stack.push(false);
}
}

Expand All @@ -154,38 +151,17 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> {
&this_var,
);
}

self.inside_arrow_function_stack.pop().unwrap();
}

fn enter_arrow_function_expression(
&mut self,
_arrow: &mut ArrowFunctionExpression<'a>,
_ctx: &mut TraverseCtx<'a>,
) {
self.inside_arrow_function_stack.push(true);
}

fn exit_arrow_function_expression(
&mut self,
_arrow: &mut ArrowFunctionExpression<'a>,
_ctx: &mut TraverseCtx<'a>,
) {
self.inside_arrow_function_stack.pop().unwrap();
}

fn enter_static_block(&mut self, _block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) {
self.this_var_stack.push(None);
self.inside_arrow_function_stack.push(false);
}

fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) {
let this_var = self.this_var_stack.pop().unwrap();
if let Some(this_var) = this_var {
self.insert_this_var_statement_at_the_top_of_statements(&mut block.body, &this_var);
}

self.inside_arrow_function_stack.pop().unwrap();
}

fn enter_jsx_element_name(
Expand All @@ -194,12 +170,9 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> {
ctx: &mut TraverseCtx<'a>,
) {
if let JSXElementName::ThisExpression(this) = element_name {
if !self.is_inside_arrow_function() {
return;
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*element_name = self.ctx.ast.jsx_element_name_from_identifier_reference(ident);
}

let ident = self.get_this_identifier(this.span, ctx);
*element_name = self.ctx.ast.jsx_element_name_from_identifier_reference(ident);
};
}

Expand All @@ -209,62 +182,18 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> {
ctx: &mut TraverseCtx<'a>,
) {
if let JSXMemberExpressionObject::ThisExpression(this) = object {
if !self.is_inside_arrow_function() {
return;
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*object =
self.ctx.ast.jsx_member_expression_object_from_identifier_reference(ident);
}

let ident = self.get_this_identifier(this.span, ctx);
*object = self.ctx.ast.jsx_member_expression_object_from_identifier_reference(ident);
}
}

fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
// `this` inside a class resolves to `this` *outside* the class in:
// * `extends` clause
// * Computed method key
// * Computed property key
//
// ```js
// // All these `this` refer to global `this`
// class C extends this {
// [this] = 123;
// static [this] = 123;
// [this]() {}
// static [this]() {}
// }
// ```
//
// `this` resolves to the class / class instance (i.e. `this` defined *within* the class) in:
// * Class method bodies
// * Class property bodies
// * Class static blocks
//
// ```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 }
// static { this }
// }
// ```
//
// Class method bodies are caught by `enter_function`, static blocks caught by `enter_static_block`.
// Handle property bodies here.
if matches!(ctx.parent(), Ancestor::PropertyDefinitionValue(_)) {
self.inside_arrow_function_stack.push(false);
}

if let Expression::ThisExpression(this_expr) = expr {
if !self.is_inside_arrow_function() {
return;
if let Expression::ThisExpression(this) = expr {
if let Some(ident) = self.get_this_identifier(this.span, ctx) {
*expr = self.ctx.ast.expression_from_identifier_reference(ident);
}

let ident = self.get_this_identifier(this_expr.span, ctx);
*expr = self.ctx.ast.expression_from_identifier_reference(ident);
}
}

Expand All @@ -278,24 +207,18 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> {

*expr = self.transform_arrow_function_expression(arrow_function_expr.unbox(), ctx);
}

// See comment in `enter_expression`
if matches!(ctx.parent(), Ancestor::PropertyDefinitionValue(_)) {
self.inside_arrow_function_stack.pop().unwrap();
}
}
}

impl<'a> ArrowFunctions<'a> {
fn is_inside_arrow_function(&self) -> bool {
*self.inside_arrow_function_stack.last().unwrap()
}

fn get_this_identifier(
&mut self,
span: Span,
ctx: &mut TraverseCtx<'a>,
) -> IdentifierReference<'a> {
) -> Option<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.
Expand All @@ -304,9 +227,8 @@ impl<'a> ArrowFunctions<'a> {
if this_var.is_none() {
let target_scope_id = ctx
.scopes()
.ancestors(ctx.current_scope_id())
// We're inside arrow function, so parent scope can't be what we're looking for.
// It's either the arrow function, or a block nested within arrow function.
.ancestors(arrow_scope_id)
// Skip arrow function scope
.skip(1)
.find(|&scope_id| {
let scope_flags = ctx.scopes().get_flags(scope_id);
Expand All @@ -324,7 +246,76 @@ impl<'a> ArrowFunctions<'a> {
));
}
let this_var = this_var.as_ref().unwrap();
this_var.create_spanned_read_reference(span, ctx)
Some(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
//
// ```js
// // All these `this` refer to global `this`
// class C extends this {
// [this] = 123;
// static [this] = 123;
// [this]() {}
// static [this]() {}
// }
// ```
//
// `this` resolves to the class / class instance (i.e. `this` defined *within* the class) in:
// * Class method bodies
// * Class property bodies
// * Class static blocks
//
// ```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 }
// 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::FunctionTypeParameters(_)
| Ancestor::FunctionThisParam(_)
| Ancestor::FunctionParams(_)
| Ancestor::FunctionReturnType(_)
| Ancestor::FunctionBody(_)
// Class property body
| Ancestor::PropertyDefinitionValue(_)
// Class static block
| Ancestor::StaticBlockBody(_) => return None,
Ancestor::ArrowFunctionExpressionTypeParameters(func) => {
return Some(func.scope_id().get().unwrap())
}
Ancestor::ArrowFunctionExpressionParams(func) => {
return Some(func.scope_id().get().unwrap())
}
Ancestor::ArrowFunctionExpressionReturnType(func) => {
return Some(func.scope_id().get().unwrap())
}
Ancestor::ArrowFunctionExpressionBody(func) => {
return Some(func.scope_id().get().unwrap())
}
_ => {}
}
}
unreachable!();
}

fn transform_arrow_function_expression(
Expand Down
20 changes: 0 additions & 20 deletions crates/oxc_transformer/src/es2015/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,26 +51,6 @@ impl<'a> Traverse<'a> for ES2015<'a> {
}
}

fn enter_arrow_function_expression(
&mut self,
arrow: &mut ArrowFunctionExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.arrow_function.is_some() {
self.arrow_functions.enter_arrow_function_expression(arrow, ctx);
}
}

fn exit_arrow_function_expression(
&mut self,
arrow: &mut ArrowFunctionExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.arrow_function.is_some() {
self.arrow_functions.exit_arrow_function_expression(arrow, 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);
Expand Down
3 changes: 0 additions & 3 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ impl<'a> Traverse<'a> for Transformer<'a> {
ctx: &mut TraverseCtx<'a>,
) {
self.x0_typescript.enter_arrow_function_expression(arrow, ctx);
self.x3_es2015.enter_arrow_function_expression(arrow, ctx);
}

fn enter_binding_pattern(&mut self, pat: &mut BindingPattern<'a>, ctx: &mut TraverseCtx<'a>) {
Expand Down Expand Up @@ -306,8 +305,6 @@ impl<'a> Traverse<'a> for Transformer<'a> {
arrow: &mut ArrowFunctionExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x3_es2015.exit_arrow_function_expression(arrow, ctx);

// Some plugins may add new statements to the ArrowFunctionExpression's body,
// which can cause issues with the `() => x;` case, as it only allows a single statement.
// To address this, we wrap the last statement in a return statement and set the expression to false.
Expand Down