Skip to content

Commit

Permalink
refactor(transformer): use ScopeFlags.is_arrow instead of inside_arro…
Browse files Browse the repository at this point in the history
…w_function_stack
  • Loading branch information
Dunqing authored and Boshen committed Sep 21, 2024
1 parent 4e9e838 commit 7827028
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 121 deletions.
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

0 comments on commit 7827028

Please sign in to comment.