Skip to content

Commit

Permalink
Documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Nov 22, 2024
1 parent 55d2298 commit 303e42d
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 1 deletion.
4 changes: 4 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/class.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! ES2022: Class Properties
//! Transform of class itself.
use oxc_allocator::Address;
use oxc_ast::{ast::*, NONE};
use oxc_span::SPAN;
Expand Down Expand Up @@ -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;
}
Expand Down
100 changes: 100 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/constructor.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
33 changes: 32 additions & 1 deletion crates/oxc_transformer/src/es2022/class_properties/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
//! * <https://github.com/babel/babel/tree/v7.26.2/packages/babel-plugin-transform-class-properties>
//! * <https://github.com/babel/babel/blob/v7.26.2/packages/babel-helper-create-class-features-plugin/src/index.ts>
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/private.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! ES2022: Class Properties
//! Utility functions.
use oxc_ast::ast::*;
use oxc_span::SPAN;
use oxc_syntax::reference::ReferenceFlags;
Expand Down

0 comments on commit 303e42d

Please sign in to comment.