feat(transformer): add object-spread plugin (#3133)

This commit is contained in:
magic-akari 2024-08-28 15:57:20 +00:00
parent 5c4c00123d
commit 08dc0adeab
10 changed files with 576 additions and 6 deletions

View file

@ -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);
}
}
}

View file

@ -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 }
}
}

View file

@ -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);
}
}

View file

@ -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)
}
}
}

View file

@ -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
}
}

View file

@ -16,6 +16,7 @@ mod options;
mod env;
mod es2015;
mod es2016;
mod es2018;
mod es2019;
mod es2020;
mod es2021;
@ -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;
@ -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>,
}
@ -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),
}
@ -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);
}

View file

@ -8,6 +8,7 @@ use crate::{
env::{can_enable_plugin, EnvOptions, Versions},
es2015::{ArrowFunctionsOptions, ES2015Options},
es2016::ES2016Options,
es2018::{ES2018Options, ObjectRestSpreadOptions},
es2019::ES2019Options,
es2020::ES2020Options,
es2021::ES2021Options,
@ -41,6 +42,8 @@ pub struct TransformOptions {
pub es2016: ES2016Options,
pub es2018: ES2018Options,
pub es2019: ES2019Options,
pub es2020: ES2020Options,
@ -121,6 +124,16 @@ impl TransformOptions {
enable_plugin(plugin_name, options, &env_options, &targets).is_some()
});
let es2018 = ES2018Options::default().with_object_rest_spread({
let plugin_name = "transform-object-rest-spread";
enable_plugin(plugin_name, options, &env_options, &targets).map(|options| {
from_value::<ObjectRestSpreadOptions>(options).unwrap_or_else(|err| {
report_error(plugin_name, &err, false, &mut errors);
ObjectRestSpreadOptions::default()
})
})
});
let es2019 = ES2019Options::default().with_optional_catch_binding({
let plugin_name = "transform-optional-catch-binding";
enable_plugin(plugin_name, options, &env_options, &targets).is_some()
@ -168,6 +181,7 @@ impl TransformOptions {
react,
es2015,
es2016,
es2018,
es2019,
es2020,
es2021,

View file

@ -1,6 +1,6 @@
commit: 12619ffe
Passed: 287/953
Passed: 292/1015
# All Passed:
* babel-plugin-transform-optional-catch-binding
@ -9,7 +9,7 @@ Passed: 287/953
* babel-plugin-transform-react-jsx-source
# babel-preset-env (100/579)
# babel-preset-env (100/580)
* .plugins-overlapping/chrome-49/input.js
@ -1307,6 +1307,9 @@ Targets: The `esmodules` is not supported
* plugins-integration/regression-2892/input.mjs
* plugins-integration/regression-4855/input.js
* plugins-integration/spread-super-firefox-40/input.js
@ -1878,6 +1881,170 @@ failed to resolve query: failed to parse the rest of input: ...''
# babel-plugin-transform-object-rest-spread (5/59)
* assumption-ignoreFunctionLength/parameters-object-rest-used-in-default/input.js
* assumption-objectRestNoSymbols/rest-assignment-expression/input.js
* assumption-objectRestNoSymbols/rest-computed/input.js
* assumption-objectRestNoSymbols/rest-nested/input.js
* assumption-objectRestNoSymbols/rest-var-declaration/input.js
* assumption-pureGetters/rest-remove-unused-excluded-keys/input.js
* assumption-pureGetters/spread-single-call/input.js
* assumption-setSpreadProperties/assignment/input.js
* assumption-setSpreadProperties/expression/input.js
* assumption-setSpreadProperties/targets-support-object-assign/input.js
* assumption-setSpreadProperties-with-useBuiltIns/assignment/input.js
* assumption-setSpreadProperties-with-useBuiltIns/expression/input.js
* object-rest/assignment-expression/input.js
* object-rest/catch-clause/input.js
* object-rest/duplicate-decl-bug/input.js
* object-rest/export/input.mjs
* object-rest/for-x/input.js
* object-rest/for-x-array-pattern/input.js
* object-rest/for-x-completion-record/input.js
* object-rest/impure-computed/input.js
* object-rest/nested/input.js
* object-rest/nested-2/input.js
* object-rest/nested-array/input.js
* object-rest/nested-array-2/input.js
* object-rest/nested-computed-key/input.js
* object-rest/nested-default-value/input.js
* object-rest/nested-literal-property/input.js
* object-rest/nested-order/input.js
* object-rest/non-string-computed/input.js
* object-rest/null-destructuring/input.js
* object-rest/object-ref-computed/input.js
* object-rest/parameters/input.js
* object-rest/parameters-object-rest-used-in-default/input.js
* object-rest/remove-unused-excluded-keys-loose/input.js
* object-rest/symbol/input.js
* object-rest/template-literal-allLiterals-true-no-hoisting/input.js
* object-rest/template-literal-property-allLiterals-false/input.js
* object-rest/template-literal-property-allLiterals-true/input.js
* object-rest/variable-destructuring/input.js
* object-rest/with-array-rest/input.js
* object-spread/expression/input.js
* object-spread/side-effect/input.js
* object-spread-loose/assignment/input.js
* object-spread-loose/expression/input.js
* object-spread-loose/parameters-object-rest-used-in-default/input.js
* object-spread-loose/side-effect/input.js
* object-spread-loose/variable-declaration/input.js
* object-spread-loose-builtins/expression/input.js
* object-spread-loose-builtins/side-effect/input.js
* regression/gh-4904/input.js
* regression/gh-5151/input.js
* regression/gh-7304/input.mjs
* regression/gh-7388/input.js
* regression/gh-8323/input.js
# babel-plugin-transform-exponentiation-operator (1/4)
* exponentiation-operator/assignment/input.js
x Symbol reference IDs mismatch:
@ -4687,7 +4854,7 @@ failed to resolve query: failed to parse the rest of input: ...''
# babel-plugin-transform-react-jsx (100/142)
# babel-plugin-transform-react-jsx (100/144)
* react/adds-appropriate-newlines-when-using-spread-attribute/input.js
x Unresolved reference IDs mismatch for "Component":
| after transform: [ReferenceId(0), ReferenceId(2)]
@ -4980,6 +5147,20 @@ transform-react-jsx: unknown field `autoImport`, expected one of `runtime`, `dev
| rebuilt : [ReferenceId(1)]
* spread-transform/transform-to-babel-extend/input.js
x Output mismatch
x Unresolved reference IDs mismatch for "Component":
| after transform: [ReferenceId(0), ReferenceId(2)]
| rebuilt : [ReferenceId(1)]
* spread-transform/transform-to-object-assign/input.js
x Output mismatch
x Unresolved reference IDs mismatch for "Component":
| after transform: [ReferenceId(0), ReferenceId(2)]
| rebuilt : [ReferenceId(2)]
# babel-plugin-transform-react-jsx-development (6/10)
* cross-platform/disallow-__self-as-jsx-attribute/input.js

View file

@ -1,6 +1,6 @@
commit: 12619ffe
Passed: 18/23
Passed: 33/54
# All Passed:
* babel-plugin-transform-logical-assignment-operators
@ -21,6 +21,56 @@ exec failed
exec failed
# babel-plugin-transform-object-rest-spread (15/31)
* assumption-objectRestNoSymbols/rest-ignore-symbols/exec.js
exec failed
* assumption-pureGetters/rest-remove-unused-excluded-keys/exec.js
exec failed
* assumption-pureGetters/spread-single-call/exec.js
exec failed
* assumption-setSpreadProperties/expression/exec.js
exec failed
* assumption-setSpreadProperties/no-getOwnPropertyDescriptors/exec.js
exec failed
* assumption-setSpreadProperties/no-object-assign-exec/exec.js
exec failed
* assumption-setSpreadProperties-with-useBuiltIns/expression/exec.js
exec failed
* assumption-setSpreadProperties-with-useBuiltIns/no-getOwnPropertyDescriptors/exec.js
exec failed
* assumption-setSpreadProperties-with-useBuiltIns/no-object-assign-exec/exec.js
exec failed
* object-rest/null-destructuring/exec.js
exec failed
* object-spread/expression/exec.js
exec failed
* object-spread/no-getOwnPropertyDescriptors/exec.js
exec failed
* object-spread/no-object-assign-exec/exec.js
exec failed
* object-spread/side-effect/exec.js
exec failed
* object-spread-loose/side-effect/exec.js
exec failed
* object-spread-loose-builtins/side-effect/exec.js
exec failed
# babel-plugin-transform-react-jsx-source (0/2)
* react-source/basic-sample/exec.js
exec failed

View file

@ -24,7 +24,7 @@ pub(crate) const PLUGINS: &[&str] = &[
// "babel-plugin-transform-json-strings",
// // ES2018
// "babel-plugin-transform-async-generator-functions",
// "babel-plugin-transform-object-rest-spread",
"babel-plugin-transform-object-rest-spread",
// // [Regex] "babel-plugin-transform-unicode-property-regex",
// "babel-plugin-transform-dotall-regex",
// // [Regex] "babel-plugin-transform-named-capturing-groups-regex",
@ -64,7 +64,6 @@ pub(crate) const PLUGINS_NOT_SUPPORTED_YET: &[&str] = &[
"transform-classes",
"transform-destructuring",
"transform-modules-commonjs",
"transform-object-rest-spread",
"transform-optional-chaining",
"transform-parameters",
"transform-private-methods",