diff --git a/crates/oxc_transformer/src/es2018/mod.rs b/crates/oxc_transformer/src/es2018/mod.rs new file mode 100644 index 0000000000000..6f12be237dcea --- /dev/null +++ b/crates/oxc_transformer/src/es2018/mod.rs @@ -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>, +} + +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); + } + } +} diff --git a/crates/oxc_transformer/src/es2018/object_rest.rs b/crates/oxc_transformer/src/es2018/object_rest.rs new file mode 100644 index 0000000000000..e263d1206b202 --- /dev/null +++ b/crates/oxc_transformer/src/es2018/object_rest.rs @@ -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: +//! * Object rest/spread TC39 proposal: + +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 } + } +} diff --git a/crates/oxc_transformer/src/es2018/object_rest_spread.rs b/crates/oxc_transformer/src/es2018/object_rest_spread.rs new file mode 100644 index 0000000000000..8afae63560a41 --- /dev/null +++ b/crates/oxc_transformer/src/es2018/object_rest_spread.rs @@ -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: +//! * Object rest/spread TC39 proposal: + +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); + } +} diff --git a/crates/oxc_transformer/src/es2018/object_spread.rs b/crates/oxc_transformer/src/es2018/object_spread.rs new file mode 100644 index 0000000000000..a6ac19fcc242e --- /dev/null +++ b/crates/oxc_transformer/src/es2018/object_spread.rs @@ -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: +//! * Object rest/spread TC39 proposal: + +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::, + 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::, + arguments, + false, + ); + } + } +} + +impl<'a> ObjectSpread<'a> { + fn object_assign(symbol_id: Option, 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, + 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, + babel_helpers_id: Option, + 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) + } + } +} diff --git a/crates/oxc_transformer/src/es2018/options.rs b/crates/oxc_transformer/src/es2018/options.rs new file mode 100644 index 0000000000000..26dd5aa442394 --- /dev/null +++ b/crates/oxc_transformer/src/es2018/options.rs @@ -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, +} + +impl ES2018Options { + #[must_use] + pub fn with_object_rest_spread(mut self, option: Option) -> Self { + self.object_rest_spread = option; + self + } +} diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 4d3f76901ac55..7a475bfb4d3b5 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -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); } diff --git a/crates/oxc_transformer/src/options/transformer.rs b/crates/oxc_transformer/src/options/transformer.rs index dc4e3a6ea0845..5f749a991f1da 100644 --- a/crates/oxc_transformer/src/options/transformer.rs +++ b/crates/oxc_transformer/src/options/transformer.rs @@ -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::(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, diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 84883e790e6e8..d78da5bdda98f 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -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 diff --git a/tasks/transform_conformance/babel_exec.snap.md b/tasks/transform_conformance/babel_exec.snap.md index c1bd768254d1d..5d8b3d0bdd0ec 100644 --- a/tasks/transform_conformance/babel_exec.snap.md +++ b/tasks/transform_conformance/babel_exec.snap.md @@ -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 diff --git a/tasks/transform_conformance/src/constants.rs b/tasks/transform_conformance/src/constants.rs index 4dd7fe67dbe10..04660d3f6f9e6 100644 --- a/tasks/transform_conformance/src/constants.rs +++ b/tasks/transform_conformance/src/constants.rs @@ -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",