From 303e42da740494422ef311cb1b23d95d9ae5b88a Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Fri, 22 Nov 2024 15:05:26 +0000 Subject: [PATCH] Documentation --- .../src/es2022/class_properties/class.rs | 4 + .../es2022/class_properties/constructor.rs | 100 ++++++++++++++++++ .../src/es2022/class_properties/mod.rs | 33 +++++- .../src/es2022/class_properties/private.rs | 3 + .../src/es2022/class_properties/utils.rs | 3 + 5 files changed, 142 insertions(+), 1 deletion(-) diff --git a/crates/oxc_transformer/src/es2022/class_properties/class.rs b/crates/oxc_transformer/src/es2022/class_properties/class.rs index b03f70be49a38d..b660cf6b225675 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/class.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/class.rs @@ -1,3 +1,6 @@ +//! ES2022: Class Properties +//! Transform of class itself. + use oxc_allocator::Address; use oxc_ast::{ast::*, NONE}; use oxc_span::SPAN; @@ -359,6 +362,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { })); } + // Exit if nothing to transform if instance_prop_count == 0 && !has_static_prop_or_static_block { return; } diff --git a/crates/oxc_transformer/src/es2022/class_properties/constructor.rs b/crates/oxc_transformer/src/es2022/class_properties/constructor.rs index 19794f53425730..91776ef88fbfe2 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/constructor.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/constructor.rs @@ -1,3 +1,103 @@ +//! ES2022: Class Properties +//! Insertion of instance property initializers into constructor. +//! +//! When a class has instance properties / instance private properties, we need to either: +//! 1. Move initialization of these properties into existing constructor, or +//! 2. Add a constructor to the class containing property initializers. +//! +//! Oxc's output uses Babel's helpers (`_defineProperty`, `_classPrivateFieldInitSpec` etc). +//! +//! ## Output vs Babel and ESBuild +//! +//! Oxc's output follows Babel where: +//! 1. the class has no super class, or +//! 2. the class has no constructor, or +//! 3. constructor only contains a single `super()` call at top level of the function. +//! +//! Where a class with superclass has an existing constructor containing 1 or more `super()` calls +//! nested within the constructor, we do more like ESBuild does. We insert a single arrow function +//! `_super` at top of the function and replace all `super()` calls with `_super()`. +//! +//! Input: +//! ```js +//! class C extends S { +//! prop = 1; +//! constructor(yes) { +//! if (yes) { +//! super(2); +//! } else { +//! super(3); +//! } +//! } +//! } +//! ``` +//! +//! Babel output: +//! ```js +//! class C extends S { +//! constructor(yes) { +//! if (yes) { +//! super(2); +//! this.prop = foo(); +//! } else { +//! super(3); +//! this.prop = foo(); +//! } +//! } +//! } +//! ``` +//! [Babel REPL](https://babeljs.io/repl#?code_lz=MYGwhgzhAEDC0FMAeAXBA7AJjAytA3gFDTQAOATgPanQC80AZpZQBQCUA3MdMJehCnIBXYCkrkWATwQQ2BbiQCWDaFJlyiJLdAhDSCCQCZOC6AF9EICAnnaSu_RIDMJ7We7uzQA&presets=&externalPlugins=%40babel%2Fplugin-transform-class-properties%407.25.9&assumptions=%7B%22setPublicClassFields%22%3Atrue%7D) +//! +//! Oxc output: +//! ```js +//! class C extends S { +//! constructor(yes) { +//! var _super = (..._args) => { +//! super(..._args); +//! this.prop = foo(); +//! return this; +//! }; +//! if (yes) { +//! _super(2); +//! } else { +//! _super(3); +//! } +//! } +//! } +//! ``` +//! ESBuild's output: [ESBuild REPL](https://esbuild.github.io/try/#dAAwLjI0LjAALS10YXJnZXQ9ZXMyMDIwAGNsYXNzIEMgZXh0ZW5kcyBTIHsKICBwcm9wID0gZm9vKCk7CiAgY29uc3RydWN0b3IoeWVzKSB7CiAgICBpZiAoeWVzKSB7CiAgICAgIHN1cGVyKDIpOwogICAgfSBlbHNlIHsKICAgICAgc3VwZXIoMyk7CiAgICB9CiAgfQp9) +//! +//! ## `super()` in constructor params +//! +//! Babel handles this case correctly for standard properties, but Babel's approach is problematic for us +//! because Babel outputs the property initializers twice if there are 2 x `super()` calls. +//! We would need to use `CloneIn` and then duplicate all the `ReferenceId`s etc. +//! +//! Instead, we create a `_super` function containing property initializers *outside* the class +//! and convert `super()` calls to `_super(super())`. +//! +//! Input: +//! ```js +//! class C extends S { +//! prop = foo(); +//! constructor(x = super(), y = super()) {} +//! } +//! ``` +//! +//! Oxc output: +//! ```js +//! class C extends S { +//! constructor(x = _super.call(super()), y = _super.call(super())) {} +//! } +//! var _super = function() { +//! this.prop = foo(); +//! return this; +//! }; +//! ``` +//! +//! ESBuild does not `super()` in constructor params correctly: +//! [ESBuild REPL](https://esbuild.github.io/try/#dAAwLjI0LjAALS10YXJnZXQ9ZXMyMDIwAGNsYXNzIEMgZXh0ZW5kcyBTIHsKICBwcm9wID0gZm9vKCk7CiAgY29uc3RydWN0b3IoeCA9IHN1cGVyKCksIHkgPSBzdXBlcigpKSB7fQp9Cg) + use oxc_allocator::Vec as ArenaVec; use oxc_ast::{ast::*, visit::walk_mut, VisitMut, NONE}; use oxc_span::SPAN; diff --git a/crates/oxc_transformer/src/es2022/class_properties/mod.rs b/crates/oxc_transformer/src/es2022/class_properties/mod.rs index 9a42f60cfe36ad..ee102c2f30adc3 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/mod.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/mod.rs @@ -61,9 +61,40 @@ //! //! WORK IN PROGRESS. INCOMPLETE. //! +//! ### Reference implementation +//! //! Implementation based on [@babel/plugin-transform-class-properties](https://babel.dev/docs/babel-plugin-transform-class-properties). //! -//! ## References: +//! I (@overlookmotel) wrote this transform without reference to Babel's internal implementation, +//! but aiming to reproduce Babel's output, guided by Babel's test suite. +//! +//! ### Divergence from Babel +//! +//! In a few places, our implementation diverges from Babel, notably inserting property initializers +//! into constructor of a class with multiple `super()` calls (see comments in [`constructor`] module). +//! +//! ### High level overview +//! +//! Transform happens in 3 phases: +//! +//! 1. Check if class contains properties or static blocks, to determine if any transform is necessary +//! (in [`ClassProperties::transform_class`]). +//! 2. Extract class property declarations and static blocks from class and insert in class constructor +//! (instance properties) or before/after the class (static properties + static blocks) +//! (in [`ClassProperties::transform_class`]). +//! 3. Transform private property usages (`this.#prop`) +//! (in [`ClassProperties::transform_private_field_expression`] and other visitors). +//! +//! Implementation is split into several files: +//! +//! * `mod.rs`: Setup, visitor and ancillary types. +//! * `class.rs`: Transform of class body. +//! * `constructor.rs`: Insertion of property initializers into class constructor. +//! * `private.rs`: Transform of private property usages (`this.#prop`). +//! * `utils.rs`: Utility functions. +//! +//! ## References +//! //! * Babel plugin implementation: //! * //! * diff --git a/crates/oxc_transformer/src/es2022/class_properties/private.rs b/crates/oxc_transformer/src/es2022/class_properties/private.rs index 65ae3ea3675bb8..c75955faddfd36 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/private.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/private.rs @@ -1,3 +1,6 @@ +//! ES2022: Class Properties +//! Transform of private property uses e.g. `this.#prop`. + use std::mem; use oxc_allocator::Box as ArenaBox; diff --git a/crates/oxc_transformer/src/es2022/class_properties/utils.rs b/crates/oxc_transformer/src/es2022/class_properties/utils.rs index 19d81e67ffa467..017acdbc33a9e6 100644 --- a/crates/oxc_transformer/src/es2022/class_properties/utils.rs +++ b/crates/oxc_transformer/src/es2022/class_properties/utils.rs @@ -1,3 +1,6 @@ +//! ES2022: Class Properties +//! Utility functions. + use oxc_ast::ast::*; use oxc_span::SPAN; use oxc_syntax::reference::ReferenceFlags;