Skip to content

Commit

Permalink
w
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Nov 14, 2024
1 parent e2558ed commit b182cf4
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 123 deletions.
19 changes: 18 additions & 1 deletion crates/oxc_transformer/src/es2018/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ mod object_rest_spread;
mod options;

use oxc_ast::ast::{
CatchClause, Expression, ForOfStatement, Function, Statement, VariableDeclaration,
ArrowFunctionExpression, CatchClause, Expression, ForOfStatement, Function, Statement,
VariableDeclaration,
};
use oxc_traverse::{Traverse, TraverseCtx};

Expand Down Expand Up @@ -71,6 +72,22 @@ impl<'a, 'ctx> Traverse<'a> for ES2018<'a, 'ctx> {
}
}

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

fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.object_rest_spread.is_some() {
self.object_rest_spread.enter_function(func, ctx);
}
}

fn exit_function(&mut self, node: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.async_generator_functions {
self.async_generator_functions.exit_function(node, ctx);
Expand Down
214 changes: 119 additions & 95 deletions crates/oxc_transformer/src/es2018/object_rest_spread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use oxc_allocator::{CloneIn, Vec as ArenaVec};
use oxc_ast::{ast::*, NONE};
use oxc_diagnostics::OxcDiagnostic;
use oxc_ecmascript::ToJsString;
use oxc_semantic::{IsGlobalReference, ReferenceFlags, SymbolFlags, SymbolId};
use oxc_semantic::{IsGlobalReference, ScopeId, SymbolFlags};
use oxc_span::{GetSpan, SPAN};
use oxc_traverse::{MaybeBoundIdentifier, Traverse, TraverseCtx};

Expand All @@ -51,8 +51,6 @@ pub struct ObjectRestSpreadOptions {
pub struct ObjectRestSpread<'a, 'ctx> {
ctx: &'ctx TransformCtx<'a>,

options: ObjectRestSpreadOptions,

excluded_variabled_declarators: Vec<VariableDeclarator<'a>>,

Check warning on line 54 in crates/oxc_transformer/src/es2018/object_rest_spread.rs

View workflow job for this annotation

GitHub Actions / Spell Check

"variabled" should be "variables" or "variable".
}

Expand All @@ -78,7 +76,7 @@ impl<'a, 'ctx> ObjectRestSpread<'a, 'ctx> {
"Compiler assumption `ignoreFunctionLength` is not implemented for object-rest-spread.",
));
}
Self { ctx, options, excluded_variabled_declarators: vec![] }
Self { ctx, excluded_variabled_declarators: vec![] }

Check warning on line 79 in crates/oxc_transformer/src/es2018/object_rest_spread.rs

View workflow job for this annotation

GitHub Actions / Spell Check

"variabled" should be "variables" or "variable".
}
}

Expand All @@ -97,6 +95,18 @@ impl<'a, 'ctx> Traverse<'a> for ObjectRestSpread<'a, 'ctx> {
self.transform_object_expression(expr, ctx);
}

fn enter_arrow_function_expression(
&mut self,
arrow: &mut ArrowFunctionExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
Self::transform_arrow(arrow, ctx);
}

fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
Self::transform_function(func, ctx);
}

fn enter_variable_declaration(
&mut self,
decl: &mut VariableDeclaration<'a>,
Expand All @@ -106,98 +116,138 @@ impl<'a, 'ctx> Traverse<'a> for ObjectRestSpread<'a, 'ctx> {
}

fn enter_catch_clause(&mut self, clause: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) {
self.transform_catch_clause(clause, ctx);
Self::transform_catch_clause(clause, ctx);
}
}

// Transform `({ x, ..y })`.
impl<'a, 'ctx> ObjectRestSpread<'a, 'ctx> {
fn transform_object_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
fn transform_object_expression(&self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let Expression::ObjectExpression(obj_expr) = expr else {
return;
};

if obj_expr
.properties
.iter()
.all(|prop| matches!(prop, ObjectPropertyKind::ObjectProperty(..)))
{
if obj_expr.properties.iter().all(|prop| !prop.is_spread()) {
return;
}

let properties = &mut obj_expr.properties;
let mut call_expr: Option<CallExpression<'a>> = None;
let mut props = vec![];

// collect `y` and `z` from `{ ...x, y, z }`
let mut obj_prop_list = ctx.ast.vec();
while properties
.last()
.map_or(false, |prop| matches!(prop, ObjectPropertyKind::ObjectProperty(..)))
{
let prop = properties.pop().unwrap();
obj_prop_list.push(prop);
for prop in obj_expr.properties.drain(..) {
if let ObjectPropertyKind::SpreadProperty(spread_prop) = prop {
self.make_object_spread(&mut call_expr, &mut props, ctx);
let arg = ctx.ast.move_expression(&mut spread_prop.unbox().argument);
call_expr.as_mut().unwrap().arguments.push(Argument::from(arg));
} else {
props.push(prop);
}
}

let Some(ObjectPropertyKind::SpreadProperty(mut spread_prop)) = properties.pop() else {
unreachable!();
};

let mut arguments = ctx.ast.vec();
arguments.push(Argument::from(ctx.ast.move_expression(expr)));
arguments.push(Argument::from(ctx.ast.move_expression(&mut spread_prop.argument)));

let object_id = self.get_object_symbol_id(ctx);
let callee = self.get_extend_object_callee(object_id, ctx);

// ({ ...x }) => _objectSpread({}, x)
*expr = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false);
if !props.is_empty() {
self.make_object_spread(&mut call_expr, &mut props, ctx);
}

// ({ ...x, y, z }) => _objectSpread(_objectSpread({}, x), { y, z });
if !obj_prop_list.is_empty() {
obj_prop_list.reverse();
let mut arguments = ctx.ast.vec();
arguments.push(Argument::from(ctx.ast.move_expression(expr)));
arguments.push(Argument::from(ctx.ast.expression_object(SPAN, obj_prop_list, None)));
*expr = Expression::CallExpression(ctx.ast.alloc(call_expr.unwrap()));
}

let callee = self.get_extend_object_callee(object_id, ctx);
fn make_object_spread(
&self,
expr: &mut Option<CallExpression<'a>>,
props: &mut Vec<ObjectPropertyKind<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
let had_props = !props.is_empty();
let obj = ctx.ast.expression_object(SPAN, ctx.ast.vec_from_iter(props.drain(..)), None);
let new_expr = if let Some(call_expr) = expr.take() {
let callee = self.ctx.helper_load(Helper::ObjectSpread2, ctx);
let arg = Expression::CallExpression(ctx.ast.alloc(call_expr));
let mut arguments = ctx.ast.vec1(Argument::from(arg));
if had_props {
let empty_object = ctx.ast.expression_object(SPAN, ctx.ast.vec(), None);
arguments.push(Argument::from(empty_object));
arguments.push(Argument::from(obj));
}
ctx.ast.call_expression(SPAN, callee, NONE, arguments, false)
} else {
let callee = self.ctx.helper_load(Helper::ObjectSpread2, ctx);
let arguments = ctx.ast.vec1(Argument::from(obj));
ctx.ast.call_expression(SPAN, callee, NONE, arguments, false)
};
expr.replace(new_expr);
}
}

*expr = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false);
impl<'a, 'ctx> ObjectRestSpread<'a, 'ctx> {
// Transform `function foo({...x}) {}`.
fn transform_function(func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
let scope_id = func.scope_id();
let Some(body) = func.body.as_mut() else { return };
for param in func.params.items.iter_mut() {
Self::replace_rest_element(&mut param.pattern, &mut body.statements, scope_id, ctx);
}
}

#[expect(clippy::option_option)]
fn get_object_symbol_id(&self, ctx: &mut TraverseCtx<'a>) -> Option<Option<SymbolId>> {
if self.options.loose {
Some(ctx.scopes().find_binding(ctx.current_scope_id(), "Object"))
} else {
None
// Transform `(...x) => {}`.
fn transform_arrow(arrow: &mut ArrowFunctionExpression<'a>, ctx: &mut TraverseCtx<'a>) {
let scope_id = arrow.scope_id();
let mut replaced = false;
for param in arrow.params.items.iter_mut() {
if Self::replace_rest_element(
&mut param.pattern,
&mut arrow.body.statements,
scope_id,
ctx,
) {
replaced = true;
}
}
if replaced && arrow.expression {
arrow.expression = false;
}
}

#[expect(clippy::option_option)]
fn get_extend_object_callee(
&mut self,
object_id: Option<Option<SymbolId>>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
if let Some(object_id) = object_id {
Self::object_assign(object_id, ctx)
} else {
self.ctx.helper_load(Helper::ObjectSpread2, ctx)
}
// Transform `try {} catch (...x) {}`.
fn transform_catch_clause(clause: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) {
let scope_id = clause.scope_id();
let Some(catch_parameter) = &mut clause.param else { return };
Self::replace_rest_element(
&mut catch_parameter.pattern,
&mut clause.body.body,
scope_id,
ctx,
);
}

fn object_assign(symbol_id: Option<SymbolId>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
let ident =
ctx.create_reference_id(SPAN, Atom::from("Object"), symbol_id, ReferenceFlags::Read);
let object = Expression::Identifier(ctx.alloc(ident));
let property = ctx.ast.identifier_name(SPAN, Atom::from("assign"));
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false))
/// Move the binding to the body if it contains an object rest.
/// The object pattern will be transform by `transform_object_pattern` afterwards.
fn replace_rest_element(
pattern: &mut BindingPattern<'a>,
body: &mut ArenaVec<'a, Statement<'a>>,
scope_id: ScopeId,
ctx: &mut TraverseCtx<'a>,
) -> bool {
if !Self::has_nested_object_rest(pattern) {
return false;
}
let bound_identifier = ctx.generate_uid(
"ref",
scope_id,
SymbolFlags::FunctionScopedVariable | SymbolFlags::CatchVariable,
);
let kind = VariableDeclarationKind::Let;
let id = mem::replace(pattern, bound_identifier.create_binding_pattern(ctx));
let init = bound_identifier.create_read_expression(ctx);
let declarations =
ctx.ast.vec1(ctx.ast.variable_declarator(SPAN, kind, id, Some(init), false));
let declaration = ctx.ast.alloc_variable_declaration(SPAN, kind, declarations, false);
let statement = Statement::VariableDeclaration(declaration);
body.insert(0, statement);
true
}
}

// Transform `let { x, ..y } = foo`.
impl<'a, 'ctx> ObjectRestSpread<'a, 'ctx> {
fn transform_variable_declaration(
&mut self,
Expand Down Expand Up @@ -566,32 +616,6 @@ impl<'a, 'ctx> ObjectRestSpread<'a, 'ctx> {
all_primitives,
})
}

/// Move the catch parameter binding to the catch body if it contains an object rest.
/// It will be transform by `transform_object_pattern` afterwards.
fn transform_catch_clause(&mut self, clause: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) {
let Some(catch_parameter) = &mut clause.param else { return };
if !Self::has_nested_object_rest(&catch_parameter.pattern) {
return;
}
let scope_id = clause.body.scope_id();
let bound_identifier = ctx.generate_uid(
"ref",
scope_id,
SymbolFlags::FunctionScopedVariable | SymbolFlags::CatchVariable,
);
let kind = VariableDeclarationKind::Let;
let id = mem::replace(
&mut catch_parameter.pattern,
bound_identifier.create_binding_pattern(ctx),
);
let init = bound_identifier.create_read_expression(ctx);
let declarations =
ctx.ast.vec1(ctx.ast.variable_declarator(SPAN, kind, id, Some(init), false));
let declaration = ctx.ast.alloc_variable_declaration(SPAN, kind, declarations, false);
let statement = Statement::VariableDeclaration(declaration);
clause.body.body.insert(0, statement);
}
}

struct DeclsBuilder<'a> {
Expand Down Expand Up @@ -637,7 +661,7 @@ impl<'a> Datum<'a> {
}
PropertyKey::PrivateIdentifier(_) => continue, // syntax error
_ => {
let expr = property_key.clone_in(&ctx.ast.allocator).into_expression();
let expr = property_key.clone_in(ctx.ast.allocator).into_expression();
arg = Expression::ComputedMemberExpression(
ctx.ast.alloc_computed_member_expression(span, arg, expr, false),
);
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
if let Some(typescript) = self.x0_typescript.as_mut() {
typescript.enter_arrow_function_expression(arrow, ctx);
}
self.x2_es2018.enter_arrow_function_expression(arrow, ctx);
}

fn enter_variable_declaration(
Expand Down Expand Up @@ -295,6 +296,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {

fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
self.common.enter_function(func, ctx);
self.x2_es2018.enter_function(func, ctx);
}

fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) {
Expand Down
Loading

0 comments on commit b182cf4

Please sign in to comment.