Skip to content

Commit

Permalink
feat(transformer): class properties transform skeleton (#7038)
Browse files Browse the repository at this point in the history
Skeleton of class properties transform. #7011 contains WIP implementation.
  • Loading branch information
overlookmotel committed Oct 31, 2024
1 parent 934cb5e commit 1d906c6
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 10 deletions.
94 changes: 94 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties.rs
Original file line number Diff line number Diff line change
@@ -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:
//! * <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-class-properties>
//! * <https://github.com/babel/babel/blob/main/packages/babel-helper-create-class-features-plugin/src/index.ts>
//! * <https://github.com/babel/babel/blob/main/packages/babel-helper-create-class-features-plugin/src/fields.ts>
//! * Class properties TC39 proposal: <https://github.com/tc39/proposal-class-fields>
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>) {}
}
25 changes: 20 additions & 5 deletions crates/oxc_transformer/src/es2022/mod.rs
Original file line number Diff line number Diff line change
@@ -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<ClassProperties<'a, 'ctx>>,
}

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);
}
}
}
5 changes: 4 additions & 1 deletion crates/oxc_transformer/src/es2022/options.rs
Original file line number Diff line number Diff line change
@@ -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<ClassPropertiesOptions>,
}
4 changes: 2 additions & 2 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -117,7 +117,7 @@ struct TransformerImpl<'a, 'ctx> {
// NOTE: all callbacks must run in order.
x0_typescript: Option<TypeScript<'a, 'ctx>>,
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,
Expand Down
26 changes: 24 additions & 2 deletions crates/oxc_transformer/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
es2019::ES2019Options,
es2020::ES2020Options,
es2021::ES2021Options,
es2022::ES2022Options,
es2022::{ClassPropertiesOptions, ES2022Options},
jsx::JsxOptions,
regexp::RegExpOptions,
typescript::TypeScriptOptions,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
})
Expand Down Expand Up @@ -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::<ClassPropertiesOptions>(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() {
Expand Down

0 comments on commit 1d906c6

Please sign in to comment.