diff --git a/crates/oxc_transformer/src/es2022/class_properties.rs b/crates/oxc_transformer/src/es2022/class_properties.rs new file mode 100644 index 00000000000000..5087d26aa86c34 --- /dev/null +++ b/crates/oxc_transformer/src/es2022/class_properties.rs @@ -0,0 +1,94 @@ +//! ES2022: Class Properties +//! +//! This plugin transforms class properties to initializers inside class constructor. +//! +//! > This plugin is included in `preset-env`, in ES2022 +//! +//! ## Example +//! +//! Input: +//! ```js +//! class C { +//! foo = 123; +//! #bar = 456; +//! } +//! +//! let x = 123; +//! class D extends S { +//! foo = x; +//! constructor(x) { +//! if (x) { +//! let s = super(x); +//! } else { +//! super(x); +//! } +//! } +//! } +//! ``` +//! +//! Output: +//! ```js +//! var _bar = /*#__PURE__*/ new WeakMap(); +//! class C { +//! constructor() { +//! babelHelpers.defineProperty(this, "foo", 123); +//! babelHelpers.classPrivateFieldInitSpec(this, _bar, 456); +//! } +//! } +//! +//! let x = 123; +//! class D extends S { +//! constructor(_x) { +//! if (_x) { +//! let s = (super(_x), babelHelpers.defineProperty(this, "foo", x)); +//! } else { +//! super(_x); +//! babelHelpers.defineProperty(this, "foo", x); +//! } +//! } +//! } +//! ``` +//! +//! ## Implementation +//! +//! WORK IN PROGRESS. INCOMPLETE. +//! +//! Implementation based on [@babel/plugin-transform-class-properties](https://babel.dev/docs/babel-plugin-transform-class-properties). +//! +//! ## References: +//! * Babel plugin implementation: +//! * +//! * +//! * +//! * Class properties TC39 proposal: + +use serde::Deserialize; + +use oxc_ast::ast::*; +use oxc_traverse::{Traverse, TraverseCtx}; + +use crate::TransformCtx; + +#[derive(Debug, Default, Clone, Copy, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct ClassPropertiesOptions { + #[serde(alias = "loose")] + pub(crate) set_public_class_fields: bool, +} + +pub struct ClassProperties<'a, 'ctx> { + #[expect(dead_code)] + options: ClassPropertiesOptions, + #[expect(dead_code)] + ctx: &'ctx TransformCtx<'a>, +} + +impl<'a, 'ctx> ClassProperties<'a, 'ctx> { + pub fn new(options: ClassPropertiesOptions, ctx: &'ctx TransformCtx<'a>) -> Self { + Self { options, ctx } + } +} + +impl<'a, 'ctx> Traverse<'a> for ClassProperties<'a, 'ctx> { + fn enter_class_body(&mut self, _body: &mut ClassBody<'a>, _ctx: &mut TraverseCtx<'a>) {} +} diff --git a/crates/oxc_transformer/src/es2022/mod.rs b/crates/oxc_transformer/src/es2022/mod.rs index d2feb9f35c7cdb..c0ad06a5fb0838 100644 --- a/crates/oxc_transformer/src/es2022/mod.rs +++ b/crates/oxc_transformer/src/es2022/mod.rs @@ -1,29 +1,44 @@ use oxc_ast::ast::*; use oxc_traverse::{Traverse, TraverseCtx}; +use crate::TransformCtx; + +mod class_properties; mod class_static_block; mod options; +use class_properties::ClassProperties; +pub use class_properties::ClassPropertiesOptions; use class_static_block::ClassStaticBlock; pub use options::ES2022Options; -pub struct ES2022 { +pub struct ES2022<'a, 'ctx> { options: ES2022Options, // Plugins class_static_block: ClassStaticBlock, + class_properties: Option>, } -impl ES2022 { - pub fn new(options: ES2022Options) -> Self { - Self { options, class_static_block: ClassStaticBlock::new() } +impl<'a, 'ctx> ES2022<'a, 'ctx> { + pub fn new(options: ES2022Options, ctx: &'ctx TransformCtx<'a>) -> Self { + Self { + options, + class_static_block: ClassStaticBlock::new(), + class_properties: options + .class_properties + .map(|options| ClassProperties::new(options, ctx)), + } } } -impl<'a> Traverse<'a> for ES2022 { +impl<'a, 'ctx> Traverse<'a> for ES2022<'a, 'ctx> { fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) { if self.options.class_static_block { self.class_static_block.enter_class_body(body, ctx); } + if let Some(class_properties) = &mut self.class_properties { + class_properties.enter_class_body(body, ctx); + } } } diff --git a/crates/oxc_transformer/src/es2022/options.rs b/crates/oxc_transformer/src/es2022/options.rs index 0cd79e5fd099d3..98f1901999ba97 100644 --- a/crates/oxc_transformer/src/es2022/options.rs +++ b/crates/oxc_transformer/src/es2022/options.rs @@ -1,8 +1,11 @@ use serde::Deserialize; -#[derive(Debug, Default, Clone, Deserialize)] +use super::ClassPropertiesOptions; + +#[derive(Debug, Default, Clone, Copy, Deserialize)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)] pub struct ES2022Options { #[serde(skip)] pub class_static_block: bool, + pub class_properties: Option, } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index d6cfd3f7d91824..2e00443b0c4dc4 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -96,7 +96,7 @@ impl<'a> Transformer<'a> { .is_typescript() .then(|| TypeScript::new(&self.options.typescript, &self.ctx)), x1_jsx: Jsx::new(self.options.jsx, ast_builder, &self.ctx), - x2_es2022: ES2022::new(self.options.es2022), + x2_es2022: ES2022::new(self.options.es2022, &self.ctx), x2_es2021: ES2021::new(self.options.es2021, &self.ctx), x2_es2020: ES2020::new(self.options.es2020, &self.ctx), x2_es2019: ES2019::new(self.options.es2019), @@ -117,7 +117,7 @@ struct TransformerImpl<'a, 'ctx> { // NOTE: all callbacks must run in order. x0_typescript: Option>, x1_jsx: Jsx<'a, 'ctx>, - x2_es2022: ES2022, + x2_es2022: ES2022<'a, 'ctx>, x2_es2021: ES2021<'a, 'ctx>, x2_es2020: ES2020<'a, 'ctx>, x2_es2019: ES2019, diff --git a/crates/oxc_transformer/src/options/mod.rs b/crates/oxc_transformer/src/options/mod.rs index 2b9b147a55d692..2208fe7e225ff2 100644 --- a/crates/oxc_transformer/src/options/mod.rs +++ b/crates/oxc_transformer/src/options/mod.rs @@ -14,7 +14,7 @@ use crate::{ es2019::ES2019Options, es2020::ES2020Options, es2021::ES2021Options, - es2022::ES2022Options, + es2022::{ClassPropertiesOptions, ES2022Options}, jsx::JsxOptions, regexp::RegExpOptions, typescript::TypeScriptOptions, @@ -109,7 +109,10 @@ impl TransformOptions { es2019: ES2019Options { optional_catch_binding: true }, es2020: ES2020Options { nullish_coalescing_operator: true }, es2021: ES2021Options { logical_assignment_operators: true }, - es2022: ES2022Options { class_static_block: true }, + es2022: ES2022Options { + class_static_block: true, + class_properties: Some(ClassPropertiesOptions::default()), + }, helper_loader: HelperLoaderOptions { mode: HelperLoaderMode::Runtime, ..Default::default() @@ -165,6 +168,9 @@ impl TryFrom<&EnvOptions> for TransformOptions { }, es2022: ES2022Options { class_static_block: o.can_enable_plugin("transform-class-static-block"), + class_properties: o + .can_enable_plugin("transform-class-properties") + .then(Default::default), }, ..Default::default() }) @@ -352,6 +358,22 @@ impl TryFrom<&BabelOptions> for TransformOptions { let plugin_name = "transform-class-static-block"; options.get_plugin(plugin_name).is_some() || env.es2022.class_static_block }, + class_properties: { + let plugin_name = "transform-class-properties"; + options + .get_plugin(plugin_name) + .map(|o| { + o.and_then(|options| { + serde_json::from_value::(options) + .inspect_err(|err| { + report_error(plugin_name, err, false, &mut errors); + }) + .ok() + }) + .unwrap_or_default() + }) + .or(env.es2022.class_properties) + }, }; if !errors.is_empty() {