Skip to content

Commit

Permalink
feat(transformer): add object-spread plugin (#3133)
Browse files Browse the repository at this point in the history
  • Loading branch information
magic-akari committed Aug 28, 2024
1 parent 5c4c001 commit 08dc0ad
Show file tree
Hide file tree
Showing 10 changed files with 576 additions and 6 deletions.
41 changes: 41 additions & 0 deletions crates/oxc_transformer/src/es2018/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
mod object_rest;
mod object_rest_spread;
mod object_spread;
mod options;

pub use object_rest_spread::{ObjectRestSpread, ObjectRestSpreadOptions};
pub use options::ES2018Options;
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};
use std::rc::Rc;

use crate::context::Ctx;

#[allow(dead_code)]
pub struct ES2018<'a> {
ctx: Ctx<'a>,
options: ES2018Options,

// Plugins
object_rest_spread: Option<ObjectRestSpread<'a>>,
}

impl<'a> ES2018<'a> {
pub fn new(options: ES2018Options, ctx: Ctx<'a>) -> Self {
Self {
object_rest_spread: options
.object_rest_spread
.map(|options| ObjectRestSpread::new(options, Rc::clone(&ctx))),
ctx,
options,
}
}
}

impl<'a> Traverse<'a> for ES2018<'a> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if let Some(object_rest_spread) = &mut self.object_rest_spread {
object_rest_spread.enter_expression(expr, ctx);
}
}
}
39 changes: 39 additions & 0 deletions crates/oxc_transformer/src/es2018/object_rest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! ES2018 object spread transformation.
//!
//! PLACEHOLDER ONLY. NOT IMPLEMENTED YET. TODO.
//!
//! > This plugin is included in `preset-env`, in ES2018
//!
//! ## Example
//!
//! Input:
//! ```js
//! var { a, ...b } = x;
//! ```
//!
//! Output:
//! ```js
//! // TBD
//! ```
//!
//! ## Implementation
//!
//! Implementation based on [@babel/plugin-transform-object-rest-spread](https://babeljs.io/docs/babel-plugin-transform-object-rest-spread).
//!
//! ## References:
//! * Babel plugin implementation: <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-object-rest-spread>
//! * Object rest/spread TC39 proposal: <https://github.com/tc39/proposal-object-rest-spread>
use super::object_rest_spread::ObjectRestSpreadOptions;
use crate::context::Ctx;

pub struct ObjectRest<'a> {
_ctx: Ctx<'a>,
_options: ObjectRestSpreadOptions,
}

impl<'a> ObjectRest<'a> {
pub fn new(options: ObjectRestSpreadOptions, ctx: Ctx<'a>) -> Self {
Self { _ctx: ctx, _options: options }
}
}
71 changes: 71 additions & 0 deletions crates/oxc_transformer/src/es2018/object_rest_spread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! ES2018 object spread transformation.
//!
//! This plugin transforms rest properties for object destructuring assignment and spread properties for object literals.
//!
//! > This plugin is included in `preset-env`, in ES2018
//!
//! ## Example
//!
//! Input:
//! ```js
//! var x = { a: 1, b: 2 };
//! var y = { ...x, c: 3 };
//! ```
//!
//! Output:
//! ```js
//! var x = { a: 1, b: 2 };
//! var y = _objectSpread({}, x, { c: 3 });
//! ```
//!
//! ## Implementation
//!
//! Implementation based on [@babel/plugin-transform-object-rest-spread](https://babeljs.io/docs/babel-plugin-transform-object-rest-spread).
//!
//! ## References:
//! * Babel plugin implementation: <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-object-rest-spread>
//! * Object rest/spread TC39 proposal: <https://github.com/tc39/proposal-object-rest-spread>
use crate::context::Ctx;

use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};
use serde::Deserialize;
use std::rc::Rc;

use super::{object_rest::ObjectRest, object_spread::ObjectSpread};

#[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ObjectRestSpreadOptions {
#[serde(alias = "loose")]
pub(crate) set_spread_properties: bool,
pub(crate) use_built_ins: bool,
}

#[allow(dead_code)]
pub struct ObjectRestSpread<'a> {
ctx: Ctx<'a>,
options: ObjectRestSpreadOptions,

// Plugins
object_spread: ObjectSpread<'a>,
object_rest: ObjectRest<'a>,
}

impl<'a> ObjectRestSpread<'a> {
pub fn new(options: ObjectRestSpreadOptions, ctx: Ctx<'a>) -> Self {
Self {
object_spread: ObjectSpread::new(options, Rc::clone(&ctx)),
object_rest: ObjectRest::new(options, Rc::clone(&ctx)),
ctx,
options,
}
}
}

impl<'a> Traverse<'a> for ObjectRestSpread<'a> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.object_spread.enter_expression(expr, ctx);
}
}
153 changes: 153 additions & 0 deletions crates/oxc_transformer/src/es2018/object_spread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//! ES2018 object spread transformation.
//!
//! This plugin transforms object spread properties (`{ ...x }`) to a series of `_objectSpread` calls.
//!
//! > This plugin is included in `preset-env`, in ES2018
//!
//! ## Example
//!
//! Input:
//! ```js
//! var x = { a: 1, b: 2 };
//! var y = { ...x, c: 3 };
//! ```
//!
//! Output:
//! ```js
//! var x = { a: 1, b: 2 };
//! var y = _objectSpread({}, x, { c: 3 });
//! ```
//!
//! ## Implementation
//!
//! Implementation based on [@babel/plugin-transform-object-rest-spread](https://babeljs.io/docs/babel-plugin-transform-object-rest-spread).
//!
//! ## References:
//! * Babel plugin implementation: <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-object-rest-spread>
//! * Object rest/spread TC39 proposal: <https://github.com/tc39/proposal-object-rest-spread>
use super::object_rest_spread::ObjectRestSpreadOptions;
use crate::context::Ctx;

use oxc_ast::ast::*;
use oxc_semantic::{ReferenceFlags, SymbolId};
use oxc_span::SPAN;
use oxc_traverse::{Traverse, TraverseCtx};

pub struct ObjectSpread<'a> {
_ctx: Ctx<'a>,
options: ObjectRestSpreadOptions,
}

impl<'a> ObjectSpread<'a> {
pub fn new(options: ObjectRestSpreadOptions, ctx: Ctx<'a>) -> Self {
Self { _ctx: ctx, options }
}
}
impl<'a> Traverse<'a> for ObjectSpread<'a> {
fn enter_expression(&mut 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(..)))
{
return;
}

// collect `y` and `z` from `{ ...x, y, z }`
let mut obj_prop_list = ctx.ast.vec();
while obj_expr
.properties
.last()
.map_or(false, |prop| matches!(prop, ObjectPropertyKind::ObjectProperty(..)))
{
let prop = obj_expr.properties.pop().unwrap();
obj_prop_list.push(prop);
}

let Some(ObjectPropertyKind::SpreadProperty(mut spread_prop)) = obj_expr.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 = ctx.scopes().find_binding(ctx.current_scope_id(), "Object");
let babel_helpers_id = ctx.scopes().find_binding(ctx.current_scope_id(), "babelHelpers");

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

// ({ ...x }) => _objectSpread({}, x)
*expr = ctx.ast.expression_call(
SPAN,
callee,
None::<TSTypeParameterInstantiation>,
arguments,
false,
);

// ({ ...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)));

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

*expr = ctx.ast.expression_call(
SPAN,
callee,
None::<TSTypeParameterInstantiation>,
arguments,
false,
);
}
}
}

impl<'a> ObjectSpread<'a> {
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 = ctx.ast.expression_from_identifier_reference(ident);
let property = ctx.ast.identifier_name(SPAN, Atom::from("assign"));

Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false))
}

fn babel_external_helper(
symbol_id: Option<SymbolId>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let ident = ctx.create_reference_id(
SPAN,
Atom::from("babelHelpers"),
symbol_id,
ReferenceFlags::Read,
);
let object = ctx.ast.expression_from_identifier_reference(ident);
let property = ctx.ast.identifier_name(SPAN, Atom::from("objectSpread2"));

Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false))
}

fn get_extend_object_callee(
&mut self,
object_id: Option<SymbolId>,
babel_helpers_id: Option<SymbolId>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
if self.options.set_spread_properties {
Self::object_assign(object_id, ctx)
} else {
Self::babel_external_helper(babel_helpers_id, ctx)
}
}
}
17 changes: 17 additions & 0 deletions crates/oxc_transformer/src/es2018/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use super::object_rest_spread::ObjectRestSpreadOptions;
use serde::Deserialize;

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2018Options {
#[serde(skip)]
pub object_rest_spread: Option<ObjectRestSpreadOptions>,
}

impl ES2018Options {
#[must_use]
pub fn with_object_rest_spread(mut self, option: Option<ObjectRestSpreadOptions>) -> Self {
self.object_rest_spread = option;
self
}
}
5 changes: 5 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod options;
mod env;
mod es2015;
mod es2016;
mod es2018;
mod es2019;
mod es2020;
mod es2021;
Expand All @@ -30,6 +31,7 @@ mod helpers {
use std::{path::Path, rc::Rc};

use es2016::ES2016;
use es2018::ES2018;
use es2019::ES2019;
use es2020::ES2020;
use es2021::ES2021;
Expand Down Expand Up @@ -69,6 +71,7 @@ pub struct Transformer<'a> {
x2_es2021: ES2021<'a>,
x2_es2020: ES2020<'a>,
x2_es2019: ES2019<'a>,
x2_es2018: ES2018<'a>,
x2_es2016: ES2016<'a>,
x3_es2015: ES2015<'a>,
}
Expand Down Expand Up @@ -97,6 +100,7 @@ impl<'a> Transformer<'a> {
x2_es2021: ES2021::new(options.es2021, Rc::clone(&ctx)),
x2_es2020: ES2020::new(options.es2020, Rc::clone(&ctx)),
x2_es2019: ES2019::new(options.es2019, Rc::clone(&ctx)),
x2_es2018: ES2018::new(options.es2018, Rc::clone(&ctx)),
x2_es2016: ES2016::new(options.es2016, Rc::clone(&ctx)),
x3_es2015: ES2015::new(options.es2015, ctx),
}
Expand Down Expand Up @@ -170,6 +174,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
self.x1_react.transform_expression(expr, ctx);
self.x2_es2021.enter_expression(expr, 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);
}
Expand Down
Loading

0 comments on commit 08dc0ad

Please sign in to comment.